友情提示:380元/半年,儿童学编程,就上码丁实验室。
第一章 水果配对
第一节 游戏描述
游戏的用户界面如图1- 1所示,功能描述如下:
- 时间因素:限制游戏时长(如60秒),剩余时间是最后得分的奖励因数;
- 空间因素:用户界面上有16张卡片,排成4×4的方阵,显示背面图案(安卓机器人);16张卡片的正面图案为8种水果,可以两两配对;
- 游戏操作——翻牌:玩家先翻开一张卡片,再翻开另一张卡片,如果两张卡片的正面图案相同,则两张卡片保持翻开状态;如果两张卡片的正面图案不同,两张卡片将闪现片刻,然后反转回去,显示背面图案。
- 计分规则:每翻开一对卡片得10分;如果在规定时间内翻开所有卡片,满分为80分;剩余游戏时间×10作为奖励得分,与翻牌得分一同计入总分;如果在规定时间内没有翻开所有卡片,则不计分;
- 历史记录:首次游戏得分被保存在手机中,在每次游戏完成时,将本次得分与历史记录进行比较,并保存高的得分;玩家可以清除游戏成绩的历史记录;
- 退出游戏:玩家在完成一轮游戏后,可以选择退出游戏。

游戏描述与记叙文的写作有相似之处:记叙文中包含了时间、地点、人物、事件四大关键要素,而游戏描述中通常也会包含时间、空间、角色、事件等基本要素,也要描述角色在特定的时间、空间内的行为(所发生的事件)。
此外,游戏描述又与说明文相像,要求文字简练准确,内容具有条理性、客观性和完整性,不强调修辞方法的使用等等。一篇好的游戏描述为我们后续的应用程序开发提供了一份完整的框架及任务清单,我们的每一个开发步骤都会依据于这份文档,因此,前往不可对此掉以轻心。
有一种说法:需求文档中隐含了程序中的变量和过程,其中的名词有可能成为程序中的全局变量,而动词或动宾词组有可能成为程序中的过程。具体说,在游戏描述的第一条中,游戏时长、剩余时间及奖励因数都有可能成为程序中的全局变量;在第三条中,翻牌、闪现、反转等,有可能成为程序中的过程。如果名词、动词能够与变量、过程一一对应,那么编程的难度会大大降低,但实际上,游戏描述使用的是人类的自然语言,而自然语言存在很大的不确定性,同样的一个游戏,不同的人有可能使用不同的方法来描述它,因此,这种说法可以借鉴,但不能作为绝对的依据,将复杂的问题简单化。
第二节 界面设计
1、界面布局
屏幕被划分为两个部分:在屏幕顶部,使用了水平布局组件,内部放置了显示分数的标签和显示游戏剩余时间的数字滑动条;在屏幕的中央,使用了4X4表格布局组件,共16个单元格,每个单元格中放置一个按钮,来实现点击翻牌功能。如图1- 2所示。其中的组件清单见表1- 1,组件的属性设置见表1- 2。


2、组件属性设置
表1- 2 组件的属性设置
3、上传资源文件
在组件属性设置完成后,上传游戏中将要用到的资源文件。如表1- 3所示,共10张图片,其中用于显示卡片正面图案的水果图片8张、卡片的背面图片1张,用于产品发布的图标图案1张(菠萝的卡通画,ananas.jpg)。上传结果如图1- 3所示。


第三节 编写程序——屏幕初始化
如果把编写软件比喻为烹制一道菜肴,那么用户界面上的元素就相当于制作这道菜肴的全部食材;当材料备齐之后,就可以考虑进入烹制过程了。就软件而言,当用户界面设计完成之后,就可以开始编写代码了。
我们很自然地会问,从哪里开始呢?无论是对于初学者,还是对于有经验的程序员,都无法回避这个问题。通常的做法是,沿着游戏的时间顺序来编写程序。但是对于初学者来说,也可以从最简单的功能做起,例如,先设置按钮的背面图案,然后针对这个按钮,当点击它时,让按钮显示正面图案;然后再考虑第二个按钮,当点击第二个显示背面图案的按钮时,如何处理可能出现的两种情况:两张卡片的图案相同或不同。这里我们采用通常的做法,首先来编写屏幕初始化程序,在这段程序中,最终要将8对(16张)不同的图案分配给16个按钮。
【注:这个功能的实现依赖于两项关键技术——列表操作以及随机数的使用。这里假设你已经了解了App Inventor中关于列表及随机数的知识,如果你还没有学习过相关的技术,推荐你访问http://www.17coding.net/,阅读《App Inventor编程实例及指南》中的“总统测验”及“瓢虫快跑”两章,或访问http://www.17coding.net/reference,阅读参考手册中的相关条目。】
1、创建按钮列表
首先我们引入一个新的概念——组件对象。我们可以在编程视图中,随意点击一个项目中的组件,打开该组件的代码块抽屉,你会发现,在代码块的最后一行,总有一个与该组件同名的代码块,这个代码块代表了这个组件本身,我们称之为组件对象,对于按钮来说,就是按钮对象。如图1- 4所示,红色线条圈住的就是表格布局对象。你可以把组件对象看作是一类特殊的数据(比如由键值对组成的列表),它里面包含了该组件的所有属性值。

首先声明一个全局变量按钮列表,并编写一个创建按钮列表过程,在该过程中,完成列表项的设置,然后在屏幕初始化程序中调用该过程。如图1- 5所示。这个列表的神奇之处稍后你就能有所体会。


2、让按钮显示背面图案
我们可以在设计视图中将每个按钮的图像属性设置为“back.png”,这样当游戏被打开时,16个按钮会默认显示背面图案(安卓机器人)。但你试想一下,当第一轮游戏结束,准备开始下一轮游戏时,如何将16个按钮上的正面图案全部改为背面图案呢?也就是说,如何在程序运行过程中设置每个按钮的图像属性呢?当然,你可以逐个地设置,这需要16行代码,那么有没有更为简便的方法呢?当然,App Inventor提供了一组“任意组件”代码,可以用来动态地读取或改写任何一个组件的属性。如图1- 7所示。

“组件对象列表+循环+任意组件”是解决上述问题的钥匙!代码如图1- 8所示,测试结果如图1- 9所示。



声明一个全局变量图片列表,用来保存所有正面图案的图片文件名,如图1- 11所示。

4、为按钮指定正面图案
首先需要说明一下,这个步骤并不是游戏开发过程中所需要的,这里只是为了让读者了解如何设置按钮的正面图案,因此,这里显示的图片是按照固定顺序排列的。我们设置按钮1和按钮9具有相同的正面图案,同样,按钮2和按钮10具有相同的正面图案,以此类推。与设置背面图案相同的是,这里也要使用“组件对象列表+循环+任意组件”这把钥匙;不同的是,图像属性的值来自于另一个列表变量——图片列表。设置正面图案的代码如图1- 12所示,其测试结果如图1- 13所示。



在图1- 13中,卡片的图案是有规律排列的,如果卡片一直是这样排列,那么游戏将毫无乐趣可言。游戏的乐趣在于其多变性,就像我们玩儿扑克牌游戏,每次手中拿到的牌都是不一样的,这种不可预知的变化才使得游戏充满乐趣和挑战。几乎所有的编程语言,都有生成随机数的功能,App Inventor也不例外。我们来看看如何利用App Inventor的列表及随机数功能来实现类似洗牌的操作。
洗牌原理叙述如下:
- 需要两个列表,A和B,开始时,列表A按顺序放置了8对(16个)图案,列表B为空;
- 从A中随机选出一个列表项X,添加到列表B中,并从A中删除列表项X;
- 从A中剩余的所有列表项中随机选出一个列表项Y添加到B中,再从A中删除Y;
- 重复第3步直到列表A为空,此时列表B中随机排列了8对(16个)图案;
- 分别将这16个图案设置为按钮1~16的图像属性。
根据上述原理,我们首先来设计列表A。由于列表A最终要被删除掉所有的列表项而成为空列表,因此不必使用全局变量来保存它。我们创建一个随机显示图案过程,在该过程中用临时变量图案列表来充当列表A,并用双倍的图片列表来填充图案列表(列表A)。其次考虑列表B。声明一个全局变量随机图案列表来充当列表B,并设置其初始值为空列表。如图1- 15所示。



表1- 4 图1- 16中按钮列表与图案随机列表的对应关系
我们现在已经实现了16个按钮的随机图案设置,并在程序开始运行时,只显示背面图案,下面将针对每个按钮设计它们被点击后程序的行为。
第四节 编写程序——处理按钮点击事件
1、流程图

注意流程图中的三个矩形框:记住第一张卡片、记住第二张卡片、忘记两张卡片,这是编写程序的关键。所谓记住或忘记,就是要用全局变量来记录已经翻开的卡片。这里我们声明两个全局变量:翻牌1及翻牌2,来保存正在翻开等待判断的两个按钮对象。在应用初始化时,设置它们的值为0 ,当第一张牌被翻开时,设
翻牌1 = 第一个被点击的按钮对象
当第二张牌被翻开时,设
翻牌2 = 第二个被点击的按钮对象
并以这两个变量为依据,判断按钮图案的异同。
2、判断两个按钮图案的异同
我们先以按钮1及按钮2为例来编写代码,如图1- 19所示。

- 根据按钮对象在按钮列表中的位置(索引值),从随机图案列表中获取按钮的正面图案,并显示该图案;
- 设置被点击按钮的启用属性值为假(考虑一下为什么,如果不这样,当再次点击该按钮时,会发生什么事情?);
- 判断它是不是第一张被翻开的卡片,如果是,将翻牌1设置为该按钮对象,否则,将翻牌2设置为该按钮对象,并判断已经翻开的两个按钮的正面图案是否相同。这里我们暂时不做进一步的处理,而是利用屏幕的标题属性来显示测试结果,即,如果按钮1与按钮2的图案相同,则屏幕的标题显示“图案相同”,否则显示“图案不同”;
- 如果已经翻开两张卡片,无论它们的正面图案是否相同,都必须重新将翻牌1及翻牌2的值设置为0。
测试结果如图1- 20所示。

按照图1- 18的设计,当图案相同时,记住已经翻开的卡片对数。凡是需要记住的内容,都需要一个全局变量来保存它,已翻开卡片的对数一方面用于计算游戏得分,另一方面用于判断是否所有卡片都已经被翻开(等于8时)。我们将这个变量命名为翻牌对数。
当两张卡片的正面图案相同时,有三件事情需要完成:
- 为全局变量翻牌对数的值+1;
- 计算并显示游戏得分;
- 判断翻牌对数是否等于8,并依据判断结果选择执行两条路径之中的一条:
- 当翻牌对数=8时,显示“游戏结束”;或者
- 当翻牌对数<8时,显示“图案相同”。
假设每翻开一对卡片得10分,因此游戏得分 = 翻牌对数X10,我们用标签得分来显示游戏得分,具体代码如图1-21所示。

当两张被翻开的卡片图案不同时,将它们重新扣上,即,让它们显示背面图案。为了让已经翻开的图片能够显示一定的时间,这里需要用到计时器组件,一旦判断出两个卡片图案不同,则启动计时器,经过一个计时间隔的时长后,计时器发生计时事件,在计时事件的处理程序中,将两张卡片同时扣上。我们用闪现计时器来实现这一功能。这里闪现计时器的计时间隔为500毫秒,如果需要加大游戏的难度,可以将计时间隔设置的更短。
我们在“图案不同”的分支里添加一个语句——启动闪现计时器,并编写了闪现计时器的计时事件处理程序,如图1- 22所示。

5、代码的复用——改进按钮点击事件处理程序
到目前为止,我们已经能够处理两个按钮的点击事件,我们需要将按钮1点击事件处理程序中的代码复制到其他14个按钮的点击事件处理程序中。这种说法听起来很可怕,试想,如果我们开发过程中需要修改其中的部分代码(这种事情经常会发生),那么我们要完成额外15倍的工作量,同时也增加了程序出错的风险。即便是我们能够一丝不苟地完成这些代码,但如何编写闪现计时器的计时事件处理程序呢?因此我们需要寻找一个更为简洁的代码编写方法。让我们先来观察一下已有的两个按钮的点击事件处理程序,如图1- 23所示。

创建一个带参数的过程,过程名为处理点击事件,参数名为某按钮,将按钮1的代码拖拽到新建的过程中,然后对代码进行改造,如图1- 24所示:
- 添加一个局部变量图案索引值:求某按钮在按钮列表中的位置,前面我们讲过,按钮列表与随机图案列表中的列表项是一一对应的,因此某按钮在按钮列表中的位置也是它的正面图案在随机图案列表中的位置;
- 使用“任意组件”类代码来取代原来的前两行代码——设置某按钮的图像属性为正面图案,设置某按钮的启用属性为假;
- 当被点击的两张卡片图案不同时,添加一个局部变量翻牌1图案索引值,求出第一张翻开的卡片其正面图案在随机图案列表中的位置,并使用两个局部变量(翻牌1图案索引值及图案索引值)来求得两个卡片的正面图案;
- 在按钮1及按钮2的点击事件处理程序中调用该过程,并为参数指定具体按钮。




与点击事件相关联的还有闪现计时器的计时事件,在图1- 23中,我们直接改写了按钮1及按钮2的图像及启用属性,现在需要将这段程序加以修改,以适用于所有的按钮。
还记得全局变量翻牌1和翻牌2中保存的是什么吗?是的,保存的正是已经被翻开的两个按钮。我们正好可以利用这两个变量,对计时程序中的前四行代码进行改写,如图1- 28所示。

上述代码需要经过测试才能进入下一步开发。测试过程记录如下:
- 程序启动之后,16个按钮显示背面图案;✓
- 点击按钮1,按钮1显示正面图案;✓
- 点击按钮2,按钮2显示正面图案,两按钮的图案不同,但并没有闪现之后扣上;✓
- 在开发环境的编程视图中,弹出错误提示,如图1- 29所示。✘

问题有可能出在全局变量翻牌1和翻牌2的设置上。我们来分析一下程序的执行顺序,如图1- 30所示。

翻牌1 = 按钮1 翻牌2 = 按钮2
由于图案不相同,闪现计时器被启动,从这一时刻起开始计时,500毫秒之后,开始执行计时程序。而此时的按钮点击程序并没有停止,在屏幕标题显示“图案不同”之后,立即执行最后两条命令——设翻牌1及翻牌2的值为0。

为了解决这个问题,我们调整程序的流程,如图1- 31所示。与新流程对应的代码如图1- 32所示,经过测试,程序运行正常,运行结果如图1- 33所示。


我们的程序开发到这里,游戏已经具备了基本的功能,但是显然这样的游戏是毫无乐趣的,因为任何人最终都能将所有卡片翻开,而且无论如何也只能得到80分,因此我们要增加游戏的难度,并让那些记忆力超强的玩儿家能得到更高的分数。我们的方法是限制游戏时间,并用剩余时间来奖励那些高手。
第五节 编写程序——控制游戏时长
我们用计时器组件来控制游戏时长,用数字滑动条组件来显示游戏的剩余时间,组件属性的具体设置参见表1- 2。
1、控制游戏时长
我们用游戏计时器来实现控制游戏时长的功能。游戏计时器的计时间隔为1秒钟(1000毫秒),即每隔1秒钟会触发一次计时事件,如果我们希望游戏时长为60秒,那么当计时次数达到60次时,游戏结束。为了便于计算成绩,我们利用剩余时间来判断游戏是否结束。声明一个全局变量剩余时间,在每次计时事件中让它的值减1,当剩余时间等于0时,游戏结束,游戏计时器停止计时。具体代码如图1- 34所示。

通过设置数字滑动条组件的滑块位置,可以表示游戏的剩余时间。需要说明一点,滑动条的宽度属性只代表它的几何尺寸,而滑块的位置属性仅仅与最大值、最小值以及当前值有关,与滑动条的宽度无关。例如,如果滑动条宽度为120像素,则没过1秒钟滑块向左移动2个像素,是滑动条宽度的1/60;如果滑动条为180像素,则每个1秒滑块向左移动3个像素,也是宽度的1/60。因此只要在游戏计时器的计时事件中,让滑块位置=剩余时间即可,代码如图1- 35所示。



为了鼓励玩家在更短的时间内翻开所有卡片,我们将剩余时间的10倍作为奖励,添加到游戏的最后得分中,这样,每次的游戏得分将有所不同,增加了游戏的趣味性。代码如图1- 38所示。

第六节 编写程序——设计游戏结尾
到目前为止,我们只是用屏幕的标题来显示游戏结束的状态,我们需要为游戏设计一个正式的结尾,并实现一些重要的功能。这些功能包括:
- 显示游戏得分;
- 显示历史最高得分;
- 清除历史记录;
- 返回游戏;
- 退出游戏。
上述功能的实现主要依赖于对话框组件及微数据库组件。我们需要创建一个名为游戏结束的过程,并在适当的位置调用该过程。
1、显示游戏得分
有两种情况会导致游戏结束:①剩余时间=0;②翻牌对数=8。这两种情况需要分别加以考虑,其中关键条件是剩余时间是否>0。如果剩余时间>0,则计算总分,否则将没有成绩。
对话框组件提供了很多内置过程(紫色的代码块),在调用这些过程时,屏幕上会弹出一个对话框:有些对话框只显示简单的信息,信息停留片刻后,就会慢慢隐去;有些则可以显示多项信息,并提供若干按钮供用户选择。在用户选择了某个按钮之后,将触发“选择完成”事件,开发者可以从事件携带的消息中,获得用户的选择,并针对不同选择,执行不同的程序分支。在游戏结束过程中,我们先使用一个简单的只带一个按钮的内置过程,如图1- 39所示。




针对剩余时间>0的情况,我们用一张流程图来理清解决问题的思路,如图1- 43所示。



在对话框组件提供的“完成选择”事件里,我们可以通过事件程序中的“选择值”来侦测用户的选择。事件处理程序如图1- 46所示。这里我们暂时用屏幕的标题栏来显示程序的执行结果,稍后我们将写一个游戏初始化过程:当用户点击“Cancel”或“返回游戏”按钮时,将开始新一轮的游戏。

4、创建游戏初始化过程
如图1- 47所示,游戏初始化过程将实现以下功能:
- 生成新的随机图案列表
- 让所有卡片显示背面图案
- 让全局变量翻牌对数=0
- 让全局变量剩余时间 = 60
- 让滑块回到起始点
- 得分显示为0
- 启动游戏计时器,开始新的一轮游戏。


第七节 程序的测试与修正
1、开发工具提示错误
(1)错误描述:无论是闯关成功还是失败,当一轮游戏结束,点击“Cancel”或“重新开始”按钮后,在开发工具的编程视图中,出现以下错误提示,如图1- 49所示。同时,测试手机上显示上一轮游戏结束时的画面,如图1- 50。


从错误提示上看,错误与列表操作有关。我们来查看一下,在对话框组件中,当选择了“重新开始”按钮之后,都发生了哪些事情——在对话框1的“完成选择”事件中,调用了游戏初始化过程,我们来查看该过程。如图1- 47所示,该过程调用了两个过程(随机分配图案、初始化背面图案),并执行了五条指令(设置全局变量值、设置组件属性),其中两个过程都涉及到列表操作,那么问题在那个过程里呢?我们发现当开发环境提示错误信息时,测试手机上仍然显示上一轮游戏结束时的画面(显示已经翻开的水果图案),也就是说,初始化背面图案的过程没有起作用。我们尝试调换两个过程的顺序,让初始化背面图案过程优先执行,而随机分配图案过程随后执行。如图1- 51所示,测试结果发现所有卡片都显示了背面图案。这说明初始化背面图案过程中没有错误,由此看来,问题就出在随机分配图案过程中。


在图1- 52中,当我们从局部变量图案列表中逐项删除其中的元素时,全局变量图片列表中的列表项也被同时删除,我们可以通过实验来证明这一点。我们在用户界面中添加一个标签组件,命名为图片列表,并在随机显示图案过程中监控图片列表的内容。
先把显示图片列表的代码放在过程的第一行,如图1- 53所示。



(3)程序的修正
找到问题的原因就等于解决了问题的一大半,下面我们来修补程序,完成这个重新开始游戏的功能。
解决方法一:创建一个图片初始化的过程,在每次重新开始游戏时,调用该过程,如图1- 56所示。


我们继续进行测试。第一轮游戏运行正常,当开始第二轮游戏时,开发环境中不再出现错误提示,却发现点击按钮时没有任何反应。
2、重新开始游戏时点击按钮无响应
(1)问题分析:
这也许是一个最容易解决的问题:按钮对于点击行为没有响应,说明按钮处于禁用状态(启用属性值为假)。回想一下我们的程序,每翻开一对卡片,都会设置按钮的启用属性值为假,在一轮游戏结束,并开始下一轮游戏时,执行了游戏初始化过程,该过程并没有更改按钮的启用属性,按钮实际上仍然处于禁用状态,因此点击按钮才会没有反应。
(2)修改程序
在游戏初始化过程里,添加针对按钮列表的循环语句,将每个按钮的启用属性设置为真。修改后的代码如图1- 58所示。

3、重新开始游戏时图案排列不变
(1)问题分析
图案随机排列的功能由随机分配图案过程负责,因此我们来检查这个过程。为了查看程序的执行效果,我们添加了一个标签,用于显示随机图案列表的内容,并在随机分配图案过程里设置它的显示文本属性。如图1- 59所示。测试结果如图1- 60所示,随机图案列表的列表项多出一倍。问题的原因在于:每次调用随机分配图案过程时,都会在原有列表的末尾添加16个列表项,因此每一轮游戏都会显示前面的16个图案,而新生成的16个图案永远都不可能被显示。


在随机分配图案过程里添加一行代码,在每次调用该过程时,先清空原有列表,如图1- 61所示。


(1)问题分析
问题的出现一定与闪现计时器的延迟有关。从闪现计时器开始计时,到第一次计时事件发生,这之间有500毫秒的时间,此时,全局变量翻牌1与翻牌2都不等于0,如果这期间玩家点击了第三个按钮,那么翻牌2将等于第三个按钮,而第二个按钮将失去翻牌2的“身份”,像一个孤儿一样被翻开在屏幕上,不能被再次点击(启用属性值为假),也没有机会被重新设置其图像及启用属性。这就是问题出现的原因。
(2)程序修正
为了防止发生这样的问题,我们采用一个极端的行为,在两张不同的卡片被翻开后,让所有的按钮都处于未启用状态,直到两张不同的卡片扣过去之后,再启用那些没有被翻开的按钮。这项功能需要对按钮的翻开状态进行判断。对处理点击事件过程进行修改,改过的代码如图1- 63及图1- 64所示。


测试环节告一段落,随着更多的人开始使用这个软件,还有可能发现新的bug。
第八节 代码整理

这里再推荐一种要素关系图,图中包含了项目中的各类要素:组件(属性)、变量、过程及事件处理程序,同时给出了各个要素之间的调用或设置关系,其中的黑色箭头表示对过程的调用,红色箭头表示对变量的改写,而绿色箭头表示对组件属性的设置。它不仅可以帮助我们从整体的角度去认识程序,还能够对程序的优化提供思路,如图1- 66所示。

此外,这个图也可以帮助开发者做代码的优化。例如,在屏幕初始化程序中有四行代码,其中除了调用创建按钮列表过程之外,其余代码都包含在游戏初始化过程之中,可以在屏幕初始化程序中,直接调用游戏初始化过程,这样优化了程序的结构,也提高了代码的复用性。
注意到图1- 66中有一个空闲的全局变量——图片列表,没有任何箭头指向它。这很容易理解,在程序运行过程中,它的值只是被读取,而不曾被改写。在一般的编程语言中,有一种语言要素被称为常量,与变量不同的是,它的值在程序运行过程中保持不变,像图片列表这样的数据就可以保存在常量中。
我们可以用“优雅”这个词来形容一组好的程序,好程序其实没有特定的标准,以下几点是笔者个人的经验,与大家共享:
- 关注代码的可读性:可读性的关键在于组件、变量及过程的命名。好的命名让代码读起来像一篇文章,易于理解。像本游戏中对计时器的命名,在笔者自己开发这个程序时,用的名称是计时器1和计时器2,这就不是一种好的命名,在开发到结尾阶段时,连我自己都会发懵。因此在撰写这篇文章时,将计时器1命名为闪现计时器,将计时器2命名为游戏计时器。
- 关注程序的结构:从图1- 66中我们可以直观地体会什么是结构。像这样在事件处理程序中直接改写变量值或组件属性的做法,当程序足够庞大时,会给代码的维护带来很大的麻烦。就App Inventor开发的程序而言,比较好的做法是,让事件处理程序调用某个过程,让过程来改写变量或属性的值。
- 小心对待写操作:对组件属性和变量的值有两种操作——读和写。这两种操作中,写操作是不安全的。如果一组程序中有多处代码对同一个变量进行写操作,那么这个变量会像一颗潜伏的炸弹,随时有引爆的危险。好的办法是,减少写操作入口,必要时可以绘制变量的状态图,标出所有的写操作,以便纠错或优化程序。
我们对现有程序作如下两项改进:
- 改造屏幕初始化事件处理程序:只调用创建按钮列表及游戏初始化两个过程;
- 去除重复调用:在要素关系图中,游戏计时器组件汇聚了三个箭头,应该减为两个箭头,因为对计时器的设置只有两种可能:①启用计时,②终止计时。启用计时在游戏初始化过程中执行,终止计时在游戏计时程序以及游戏结束过程中执行,而游戏计时程序又调用了游戏结束过程,这相当于终止计时被执行了两次。因此可以删除前者对终止计时的设置,这样指向游戏计时的箭头就剩下两个了。
我们重新绘制改进之后的要素关系图,如图1- 67所示。
