电梯模拟程序--从设计到实现
---结对项目开发:张永&吴盈盈
这是一个大家都很熟悉的题目,很多人也做过类似的题目。最近博客园最近也发表了很多的关于电梯模拟的程序。下面说一下我们小组的基本思想。
题目拿到后,我们先是讨论了一下电梯的整体设想。对这个题目进行了粗略的分析。从面向对象的角度对问题剖析:
下面开始电梯的界面设计:
界面设计涉及到的按钮比较多,所以每个按钮的命名必须要符合规范,光变量命名就花费了很长时间,举个例子:每个电梯都有左右门,那么为了见名知意,命名规则采用“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 张永 电梯的整体完善和博客的撰写