上一篇文章介绍了加州大学洛杉矶分校(UCLA)的一门C#语言课的教学内容。今天就来讲解一下其中的一个作业题,这个作业题占它这门课程总成绩的20%。以下是我按照原文写的,以翻译为主,删掉了一点非核心的内容,并稍加了一些说明,原文见这里。
概述
这个作业题的内容,是请你模拟一座建筑物中的电梯。你的程序需要控制电梯的运行以及乘坐电梯的乘客流。从教学的角度出发,这个作业可以分为三个部分,但你在实现的时候,不一定非要按照下面描述的顺序。你要为一座多层的建筑物中的几部电梯建模,这个模拟过程发生在一座建筑物中,需要跟踪乘客乘坐电梯在不同楼层之间运行。为了简单起见,我们设定一共有5层(标记为0-4),以及两部电梯(标记为0和1)。
这个模拟是基于“节拍”的,每一拍发生如下三件事:
1)电梯根据当前的状态进行移动;
2)乘客移动;
3)电梯的控制器更新电梯的状态。
模拟乘客流
一个乘客的状态一定处于两种状态之一:“空闲”(Idle)或“去向(n)”(GoingTo(n)),各自含义是:
去向(n):这位乘客要去向第n层楼。
空闲:乘客在电梯外休息。
乘客的状态变迁如图所示:
当整个模拟开始时,每个乘客的起始状态都是“空闲”。
乘客的位置
每个乘客有一个当前的位置,有以下7种可能: { 电梯0, 电梯1, 楼层0, 楼层1, 楼层2, 楼层3, 楼层4}。初始情况是每个乘客都在“楼层1”(楼层0表示地下室)。
当一个乘客正在乘坐“电梯i”的时候,我们就说他的位置是“电梯i”。当一个乘客正在“楼层f”的时候,我们就说他的位置是“楼层f”。
乘客的指令
我们一种简单的格式定义某个乘客的行动计划,例如
(t1,f1); (t2,f2); (t3,f3); …
其中的一个括号中有两个数字,t表示的时某一个时刻,f表示此时该乘客要取得楼层。这样的一对数字,我们称为一个“指令”。用一系列的指令就可以描述一个乘客的一系列行动的计划。注意,在实际模拟时,严格按照序列顺序,即使后面的指令中的时间早于前面指令的时间。然后,用一个简单的文本文件,可以构造出多个乘客的行动序列,每个乘客一行,如下所示:
(t11, f11); (t12, f12); (t13, f13) …
(t21, f21); (t22, f22) …
(t31, f31); (t32, f32); (t33, f33) …
在模拟程序开始的时候,读入这个文本文件,然后程序根据这个文件设定的所有指令模拟整个运行过程。
对乘客建模
乘客的移动按照下面的规则进行:假设一个乘客S,当前的状态是state,当前位置是p,当前的指令I,并假设当前的时刻为T。
* 如果 state 为 Idle,那么
o 如果 I 为空,那么什么也不做
o 如果 I 的形式为 (t0,f0);I′, 那么
+ 如果 T < t0 那么什么也不做。
+ 如果 T ≥ t0 那么将 state 设置为 GoingTo(f0) 并且将 I 设置为 I′。
* 如果 state 为 GoingTo(f), 那么
o 如果 p = "楼层f",那么将 state 设置为 Idle。
o 如果 S 正在 电梯e中,并且电梯e位于“楼层f”,电梯状态是 Open,那么将 p 设置为“楼层f”。
o 如果 p =“楼层f0” ( 不等于“楼层f" ),那么
+ 如果 f < f0,那么将指示灯 d 设置为 Down,否则将 d 设置为 Up.
+ 如果电梯e位于“楼层f0”,且状态为 Open,指示灯为d,电梯内少于5人, 那么将p设置为电梯e。
注意,如果点运行的速度慢,可能导致乘客获取到一个过期的指令,以上的规则可以保证,所有的指令都会按照顺序被执行。换句话说,乘客可能在某些步骤,比指令计划的有所延迟,但是都会根据指令完成确定的旅程计划。
对电梯建模
模拟的电梯承载着乘客,在楼层之间移动。我们需要使用一些状态来对电梯建模。
电梯高度
每一部电梯都有一个“当前高度”,假设每层楼高10英尺,电梯的高度用从0到40的整数表示。当电梯的高度是10的整数倍的时候,我们就说该电梯唯一第“高度/10”层。比如当前的高度是30,就说明它在第3层。在模拟开始时,两部电梯都在第0层。
电梯动作
一个电梯一共有4种可能的状态:“打开”(Open)、“静止”(Stopped)、“上移”(GoingUp)和“下移”(GoingDown),描述如下。任何操作都不会使电梯的高度小于0或大于40,即高度永远在 [0,40] 这个区间内。
“打开”:乘客可以进入或离开。
“上移”:电梯按照5英尺/拍的速度上升。
“下移”:电梯按照5英尺/拍的速度下降。
“静止”:没有什么动作。
只有特定的电梯动作序列是有效的。一个电梯一旦开始运动,就不能开门。各种状态之间变迁需要符合下图。即从状态s到状态s’,只有符合下面图中存在从s到s’的箭头时才是允许的。例如。从“下移”状态,可以变为“静止”状态,但是不能变为“打开”或“上移”状态。
电梯的初始状态是“静止”。一部电梯内同时最多容纳5个乘客。
指示灯
每一部电梯有一个指示灯,分别有“上”、“下”和“无”三种显示状态。
电梯调度
由电梯本身来决定自身的行动,也未尝不可。不过更好的方式,还是为电梯的运行提供集中的控制。这个模拟程序,将使用单一的方法来为整个电梯系统的运行提供计划。这个方法称为 PlanStep,由以下接口定义:
interface IElevatorPlanner
{
//定义方法 PlanStep(controls, upFloors, downFloors)
//用于对电梯进行调度
//其中的参数:
//controls[e] 对应于电梯e的控制
//upFloors[i]为 true ,当且仅当一名乘客在“楼层i”,且状态为GoingTo(j),且 j > i
//downFloors[i]为 true ,当且仅当一名乘客在“楼层i”,且状态为GoingTo(j),且 j < i
void PlanStep(IElevatorControl[] controls,
bool [] upFloors,
bool [] downFloors);
}
PlanStep方法将会使用comtrols数组中的对象对外界作出响应。PlanStep可以使用内部的状态来辅助作出决策,但是必须做到仅通过这个接口与电梯和乘客交互。显然,upFloors 和 downFloors 数组对应的就是对每个楼层的电梯按钮,又等候电梯的乘客按下。这里做了一点简化,使用真实的电梯时,乘客会先把电梯呼叫到他所在的楼层,进入以后,再按目的楼层的按钮。而这里则化简为在电梯外就确定目的楼层。
IElevatorPlanner 接口依赖于一个控制电梯的类(声明为接口)以及两个相关的枚举类型。分别定义如下:
enum ElevAction { Open, Stopped, GoingUp, GoingDown };
enum ElevLight { Up, Down, None };
interface IElevatorControl{
//StopRequested(i) 返回 true,当且仅电梯内乘客的状态是GoingTo(i)。
//需要对i做参数检查,确保在 [0..4]范围内。
bool StopRequested(int i);
//只读属性,返回电梯的当前状态。
ElevAction ActionState { get; }
//设置电梯的当前状态。
//需要对参数作检查,确保参数是一个有效的状态变迁。
void SetAction(ElevAction s);
//只读属性,返回电梯所电梯指示灯的状态。
Light Indicator { get; set; }
//只读属性,返回电梯所处的高度。
int Height { get; }
}
作业内容
编写一个图形界面,显示模拟的当前状态。提示:System.Windows.Forms.PictureBox 提供了显示图像的方法。你的图形界面应该包括以下功能:
1)用户可以把鼠标移动到一个电梯或乘客上,查看他当时的状态。可以使用:System.Windows.Forms.ToolTip。
2)用户可可以控制电梯运行的步数,每次跳过一定的步数(比如10步、50步),显示电梯的状态。
3)用户可以通过读入文本文件设定乘客的行程计划。
挑战部分:动画演示
使用动画方式演示电梯的运行过程。
总结
这是一个很有意思的题目,通过这个题目,读者可以注意到几个问题:
1)现实世界中的系统,和信息世界中的系统,是如何对应的。在题目中多次提到了一个词“建模”,这非常本质地说明了,软件开发很重的一个性质。
2)系统地分析,在这题目的描述中,可以发现他非常清晰对系统地进行了划分,而且每个部分之间的耦合度非常低,这是值得学习程序设计的读者仔细领会的要点。
希望我的文章对您有所帮助。
您可以到新浪微博联系我: http://t.sina.com.cn/1906984307 。