電梯模擬程序--從設計到實現
---結對項目開發:張永&吳盈盈
這是一個大家都很熟悉的題目,很多人也做過類似的題目。最近博客園最近也發表了很多的關於電梯模擬的程序。下面說一下我們小組的基本思想。
題目拿到后,我們先是討論了一下電梯的整體設想。對這個題目進行了粗略的分析。從面向對象的角度對問題剖析:

下面開始電梯的界面設計:

界面設計涉及到的按鈕比較多,所以每個按鈕的命名必須要符合規范,光變量命名就花費了很長時間,舉個例子:每個電梯都有左右門,那么為了見名知意,命名規則采用“elevator_電梯號_左右門”,一號電梯的左門命名為“elevator_ID1_Left”,其他的按鈕也都有相應的命名規則,這里不一一的列舉了。
為了有一個友好的界面,在這引入了一個開關門的特效:先介紹一下怎么實現的,以一個門為例,C#中Timer控件比較好用,每個門對應一個Timer控件(就是每隔一段時間調用一個方法):
/** * @Name: openElevator * @Description: 打開電梯門 * @Version: V1.00 (版本號) * @Create Date: 2014-3-16 (創建日期) * @Parameters:Panel elevator_ID_Left, Panel elevator_ID_Right,電梯的兩個門 * @Return: 電梯的狀態(開和關) */ public int openElevator(Panel elevator_ID_Left, Panel elevator_ID_Right) { elevator_ID_Left.Width = elevator_ID_Left.Width - 1; elevator_ID_Right.Width = elevator_ID_Right.Width - 1; elevator_ID_Right.Location = new System.Drawing.Point(elevator_ID_Right.Location.X + 1, elevator_ID_Right.Location.Y); if (elevator_ID_Left.Width == 10) { flag = Number.OPEN; } return flag; }
在Timer控件中每隔一定的時間就調用openElevator方法,就實現了開門的動作,關門類似。
然后,開始電梯的詳細設計及代碼編寫了。
1.把上面討論的沒一個類都編寫好。在這個程序中,主要是用到了C#中的Timer控件,實現了四部電梯的獨立的運行。初始狀態,四個控件調用對應的函數。當有乘客選擇向上或向下的按鈕時,生成一個People對象,並把這個People對象加到所選擇電梯的peopleList中,同時設置電梯的目標樓層為當前乘客所在的樓層,調用調度函數就會返回相應的電梯,在選擇的同時把所選擇的按鈕置成紅色加以區分,以其中的一個按鈕為例:
private void floor3_Up_CheckedChanged(object sender, EventArgs e) { ElevatorObject elev = null; People people = new People(Number.UP, Number.peopleFloor(floor3_Up)); elev = Number.choseElevator(people, elevatorID1, elevatorID2, elevatorID3, elevatorID4);//這是根據乘客和電梯的狀態選擇合適的電梯,調度函數 elev.goalFloors[3] = 1; if (elev.getCurFloor() == 3 && elev.getElevatorStates() == Number.STOP) { elev.setElevatorStates(Number.UP); } floor3_Up.ForeColor = Color.Red;
2.以4號電梯為例,Timer4控件會一直調用自己對應的方法,當elevatorID4的goalFloor(目標樓層)中有值時,電梯的run方法就會有效(這里其實一直都在調用run方法,只是goalFloor中沒有值是,電梯的當前樓層不會變化),Timer4中的代碼如下(這個方法每個N秒自動調用):
elevatorID4.runElevator(elevator4); setElevator(elevatorID4, elevator_ID4_Curpeople, elevator_ID4_Curweight, elevator_4); if (elevator4.Enabled == false) { openDoor4.Enabled = true; } setElevator(elevatorID4, elevator_ID4_Curpeople, elevator_ID4_Curweight, elevator_4);
調用的runElevator方法如下(這個方法是電梯類中的方法):
public void runElevator(Timer timer) { curFloor = num; if (elevatorStatus == Number.UP || elevatorStatus == Number.DOWN ) { if (elevatorStatus == Number.UP) //當電梯的狀態時上升狀態時,每調用一次這個方法curFloor就加一 { num++; } if (elevatorStatus == Number.DOWN) //當電梯的狀態時下降狀態時,每調用一次這個方法curFloor就減一 if (elevatorStatus == Number.DOWN) { num--; } if (goalFloors[curFloor] == 1) //當電梯當達目標樓層的時候就讓其中目標樓層是當前樓層的乘客下電梯 { //並通過設置Timer的值進行開關門操作 for (int i = 0; i < peopleList.Count; i++) { if (peopleList[i].getGoalFloor() == curFloor) { curPeople = curPeople - 1; curWeight = curWeight - peopleList[i].getWeight(); peopleList[i].setGoalFloor(-2); } } timer.Enabled = false; if (elevatorStatus == Number.UP) { num--; if (peopleList[getPeopleIndex(curFloor)].getPeopleStatus() == Number.DOWN) { elevatorStatus = Number.DOWN_STOP; } else { elevatorStatus = Number.UP_STOP; } } if (elevatorStatus == Number.DOWN) { num++; if (peopleList[getPeopleIndex(curFloor)].getPeopleStatus() == Number.UP) { elevatorStatus = Number.UP_STOP; } else { elevatorStatus = Number.DOWN_STOP; } } goalFloors[num] = 0; } } }
3.當電梯到達目標樓層后,如果有乘客在之前按了上升或下降按鈕,可以選擇要去的樓層,比如按了6層,就會使電梯的當前人數加以,體重加上當前人的體重,設置人的目標樓層和電梯的目標樓層(這兩個值可以判斷,當電梯到達某個目標樓層的時候選出所載的乘客是不是又在這一層下電梯的):
private void ID4_floor6_CheckedChanged(object sender, EventArgs e) { if (ID4_floor6.ForeColor == Color.Azure) { ID4_floor6.ForeColor = Color.Red; } if (ID4_floor6.ForeColor == Color.Red) { elevatorID4.goalFloors[6] = 1; elevatorID4.peopleList[elevatorID4.getPeopleIndex(elevatorID4.getCurFloor())].setGoalFloor(6);//設置乘客的目標樓層 elevatorID4.setCurPeople(elevatorID4.getCurPeople() + 1);//當前乘客人數加1 elevatorID4.setCurWeight(elevatorID4.getCurweight() + elevatorID4.peopleList[elevatorID4.peopleList.Count - 1].getWeight()); } }
4.下面說一下本程序最核心的代碼,就是調度算法:電梯的調度考慮的條件比較多,不能說那個算法對與不對,只能說這個調度算法好還是不好,評判的標准就是能不能合理的利用電梯,是乘客能在最短的時間內到達自己想要到達的樓層。我們的算法是:現根據乘客的選擇的按鈕進行分(是要上樓還是要下樓):只說其中的一個,比如乘客選擇是上樓及UP。 (1).計算出四部電梯的當前樓層和當前乘客所在樓層的差值,當然可能有正有負。
int elevator1_distance=elevator1.getCurFloor()-people.getPeopleFloor();//1號電梯現在的樓層距當前乘客的距離
(2).對這個距離進行排序,對應的電梯號也排序,從大到小。
(3).然后從判斷最小的那個值是不是小於0,亦及小於零的電梯當前樓層在乘客所在樓層的下面。
(4).再結合電梯的狀態選擇,(舉個例子,如果乘客在5樓選擇了向上的按鈕,四部電梯都在0-4樓,離5樓最近的一部電梯在4樓,但是這部電梯的狀態是向下運行的,那么肯定不能選擇這部電梯了。只能在尋找別的電梯;若這部電梯的狀態正好也是往上運行的並且里面的乘客沒有超員,則選擇這部電梯是最合適的)以下是考慮到的各種情況在代碼中的體現,其中(UP:是向上正在運行的意思 UP_STOP:意思是電梯停在了某一層,但是接下來要往上運行 DOWN:電梯處於向下運行的狀態 DOWN_STOP:電梯停在了某一層,接下來要往下運行 STOP:電梯處於停滯狀態)
ElevatorObject chosenElevator=new ElevatorObject(0) ; ElevatorObject testElevator=null; ElevatorObject[] a_Elevator = new ElevatorObject[]{elevator1,elevator2,elevator3,elevator4}; int test = 0; int elevator1_distance=elevator1.getCurFloor()-people.getPeopleFloor();//1號電梯現在的樓層距當前乘客的距離 int elevator2_distance=elevator2.getCurFloor()-people.getPeopleFloor();//2號電梯現在的樓層距當前乘客的距離 int elevator3_distance=elevator3.getCurFloor()-people.getPeopleFloor();//3號電梯現在的樓層距當前乘客的距離 int elevator4_distance=elevator4.getCurFloor()-people.getPeopleFloor();//4號電梯現在的樓層距當前乘客的距離 int[] a = new int[] { elevator1_distance, elevator2_distance, elevator3_distance, elevator4_distance }; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3 - i; j++) { if (a[j] > a[j + 1]) { test = a[j]; testElevator = a_Elevator[j]; a[j] = a[j + 1]; a_Elevator[j] = a_Elevator[j + 1]; a[j + 1] = test; a_Elevator[j + 1] = testElevator; } } } switch(people.getPeopleStatus()) { case Number.UP: if (a[3] < 0) { for (int i = 3; i >= 0; i--) { /*** * 所有的電梯的狀態都是Number.DOWN狀態沒有考慮 * * */ if (a_Elevator[i].getElevatorStates() == Number.UP || a_Elevator[i].getElevatorStates() == Number.UP_STOP || a_Elevator[i].getElevatorStates() == Number.STOP) { chosenElevator = a_Elevator[i]; i = -1;//跳出for循環 } } } else if (a[2] < 0) { if ((a[3] == 0&&a_Elevator[3].getElevatorStates()==Number.UP_STOP)||(a_Elevator[3].getElevatorStates()==Number.STOP&&a[3]<(0-a[2]))) { chosenElevator = a_Elevator[3]; } else { for (int i = 2; i >= 0; i--) { if (a_Elevator[i].getElevatorStates() == Number.UP || a_Elevator[i].getElevatorStates() == Number.UP_STOP || a_Elevator[i].getElevatorStates() == Number.STOP) { chosenElevator = a_Elevator[i]; i = -1;//跳出for循環 } } } } else if (a[1] < 0) { if ((a[2] == 0 && a_Elevator[2].getElevatorStates() == Number.UP_STOP) || (a_Elevator[3].getElevatorStates()==Number.STOP && a[2] < (0 - a[1]))) { chosenElevator = a_Elevator[2]; } else if((a[3] == 0&&a_Elevator[3].getElevatorStates()==Number.UP_STOP)||(a_Elevator[3].getElevatorStates()==Number.STOP&&a[3]<(0-a[2]))) { chosenElevator = a_Elevator[3]; } else { for (int i = 1; i >= 0; i--) { if (a_Elevator[i].getElevatorStates() == Number.UP || a_Elevator[i].getElevatorStates() == Number.UP_STOP || a_Elevator[i].getElevatorStates() == Number.STOP) { chosenElevator = a_Elevator[i]; i = -1;//跳出for循環 } } } } else if (a[0] < 0) { if ((a[1] == 0 && a_Elevator[1].getElevatorStates() == Number.UP_STOP) || (a_Elevator[1].getElevatorStates()==Number.STOP && a[1] < (0 - a[0]))) { chosenElevator = a_Elevator[1]; } else if ((a[2] == 0 && a_Elevator[2].getElevatorStates() == Number.UP_STOP) || (a_Elevator[3].getElevatorStates() == Number.STOP && a[2] < (0 - a[1]))) { chosenElevator = a_Elevator[2]; } else if ((a[3] == 0 && a_Elevator[3].getElevatorStates() == Number.UP_STOP) || (a_Elevator[3].getElevatorStates() == Number.STOP && a[3] < (0 - a[2]))) { chosenElevator = a_Elevator[3]; } else { for (int i = 0; i >= 0; i--) { if (a_Elevator[i].getElevatorStates() == Number.UP || a_Elevator[i].getElevatorStates() == Number.UP_STOP || a_Elevator[i].getElevatorStates() == Number.STOP) { chosenElevator = a_Elevator[i]; i = -1;//跳出for循環 } } } } else { if ((a[0] == 0 && a_Elevator[0].getElevatorStates() == Number.UP_STOP) || a_Elevator[0].getElevatorStates()==Number.STOP ) { chosenElevator = a_Elevator[0]; a_Elevator[0].setElevatorStates(Number.UP); } else if ((a[1] == 0 && a_Elevator[1].getElevatorStates() == Number.UP_STOP) || (a_Elevator[1].getElevatorStates() == Number.STOP && a[1] < (0 - a[0]))) { chosenElevator = a_Elevator[1]; } else if ((a[2] == 0 && a_Elevator[2].getElevatorStates() == Number.UP_STOP) || (a_Elevator[3].getElevatorStates() == Number.STOP && a[2] < (0 - a[1]))) { chosenElevator = a_Elevator[2]; } else if ((a[3] == 0 && a_Elevator[3].getElevatorStates() == Number.UP_STOP) || (a_Elevator[3].getElevatorStates() == Number.STOP && a[3] < (0 - a[2]))) { chosenElevator = a_Elevator[3]; } } break; case DOWN:......省略 } if (chosenElevator.getElevatorStates() == Number.STOP&&chosenElevator.getCurFloor()>people.getPeopleFloor()) { chosenElevator.setElevatorStates(Number.DOWN); } else if (chosenElevator.getElevatorStates() == Number.STOP && chosenElevator.getCurFloor() < people.getPeopleFloor()) { chosenElevator.setElevatorStates(Number.UP); } else if (chosenElevator.getElevatorStates() != Number.STOP) { chosenElevator.setElevatorStates(people.getPeopleStatus()); } chosenElevator.number++; chosenElevator.peopleList.Add(people); return chosenElevator; }
時間管理:
2014年3月15日 14:00--18:00 吳盈盈 界面設計以及變量的命名
2014年3月16日 13:00--20:00 張永 代碼的編寫實現了基本的功能
2014年3月16日 20:00--22:00 張永&吳盈盈 調度算法的討論及完善
2014年3月18日 20:30--22:00 張永&吳盈盈 重量和人數的顯示
2014年3月20日 19::00--22:00 張永&吳盈盈 按鈕顏色的變化
2014年3月21日 8:00--9:30 張永 電梯的整體完善和博客的撰寫
