第十三章 为3号组块编程
我们回顾一下第七章中关于组块旋转的内容,在这一章中,我们编制了一个包含19种组块的表格,并为每种组块编号。为方便起见,我们将表格复制到本章,并在接下来的章节里逐一编写每种组块的绘制及擦除过程,并修改与组块编号相关的所有程序。为了便于代码的统一管理,我们放弃原有过程的命名方式(绘制红色水平组块、绘制红色垂直组块等),而一律使用与组块编号相关的过程名称,如绘制组块_1=绘制红色水平组块,擦除组块_1=擦除红色水平组块,以此类推。
表13-1 游戏中所有形态的编码及编号
在为3号组块编写程序之前,我们先来修改已有的1、2号组块的绘制与擦除过程的名称。在定义过程的代码中来修改过程名称,一旦修改完成,曾经调用过该过程的代码将自动更新,如图13-1。
第一节 编写3号组块的绘制及擦除过程
为了编写与3号组块相关的程序,首先需要确定基准行、基准列在组块中的位置,如图13-2所示,我们将组块右下角的方块设定为基准行、基准列的位置,组块的初始值为基准行=1,基准列=7,前一刻基准行=0,前一刻基准列=7。这些初始值的设定与创建新组块过程中使用的初始值相吻合,这样我们就可以使用同一的创建新组块过程来处理所有类型的组块。
根据基准行、基准列的设定,我们定义”绘制组块_3”及“擦除组块_3”过程,如图13-3所示。
第二节 修改与组块编号相关的程序
接下来要修改若干个与组块编号相关的程序,在第十一章中,我们曾经罗列出这些程序:
- 需要修改条件判断语句,扩展分支语句的过程:
- a. 组块下落
- b. 求触底组块覆盖的行
- c. 已经触块
- d. 已经触顶
- e. 求重绘起始行
- f. 更新色块列表
- 需要扩展数值范围的过程:
- a. 创建新组块:将随机数范围从1~2扩展到1~19
- b. 重绘画布:根据色块列表中记录的颜色码来重绘画布
- 事件处理程序:
- a. 左移程序
- b. 右移程序
- c. 快落程序
- d. 旋转程序
由于3号组块的几何形状具有水平及垂直方向上的对称性,旋转操作不会改变组块的形态,因而无需修改旋转程序,这样,共有11个程序需要修改。现在让我们来逐一修改。
1.a 组块下落
为条件语句添加两个“否则, 如果”分支,依据组块编号决定所要执行的操作,如图13-4所示。
1.b 求触底组块覆盖的行
在原过程的否则分支中,添加一个“如果…则…否则”分支,以便针对不同组块返回不同结果,代码如图13-5所示。
1.c 已经触块
在原过程中添加两个“否则…如果”块,来替换原来的“否则”块,代码如图13-6所示。
1.d 已经触顶
如图13-7所示,为已经触顶过程添加一个“如果…则…否则”分支。
1.e 求重绘起始行
如图13-8所示,对应三种不同的组块,局部变量组块顶行具有三个可能的值,因此在原来的否则分支中添加一个“如果…则…否则”分支。
1.f 更新色块列表
如图13-9所示,为更新色块列表过程添加两个“否则…如果”分支,替换原来的“否则”分支。
2.a 创建新组块
为了生成三种组块,将生成随机数的最大值由原来的2改为3,代码如图13-10所示。
2.b 重绘画布
在重绘画布过程中,为画块过程的颜色参数提供更多的选择,即,由原来的两种颜色增加为三种颜色,代码如图13-11所示。
3.a 左移程序
在左移按钮的点击事件程序中,新增两个“否则…如果”分支,替换原来的“否则”分支,来处理与3号组块相关的操作,代码如图13-12所示。
3.b 右移程序
同左移程序一样,在右移程序中新增两个“否则…如果”分支,替换原来的“否则”分支,来处理与3号组块相关的操作,代码如图13-13所示。
3.c 快落程序
在快落程序中新增两个“否则…如果”分支,来替代原有的“否则”分支,以便处理与3号组块相关的操作,代码如图13-14所示。
经过测试,3号组块会随机出现,一切如我们所期待的那样,程序运行正常,如图13-15所示。
第三节 回顾与展望
我们来回顾一下为3号组块添加或修改的代码:
- 新建过程:组块的绘制及擦除过程(2个);
- 修改过程:组块下落、求触底组块覆盖的行、已经触块、已经触顶、求重绘起始行、色块列表更新(6个);以上过程的修改方法类似,即,在现有程序基础上,添加一个条件分支语句,最后的句式为:如果 组块编号=1 则...否则,如果 组块编号=2 则...否则...(...表示将要执行的代码);
- 修改过程:创建新组块,将随机数范围改为1~3(1个);
- 左右移动事件处理程序:这部分需要在两处添加条件分支语句,一是组块的触边条件,二是组块的触块条件(2个);
- 快落事件处理程序;此处要添加一个条件分支语句,虽然不同的组块触底的条件相同,但触块的条件各不相同(1个)。
总结起来,我们为三号组块新建了2个过程,修改了9个过程及3个事件处理程序,一共涉及到14段代码,可见我们的工作量之大。这还是最简单的3号组块,不必考虑旋转程序,对于其余的16个组块而言,每个组块与15段代码相关,一共要修改程序240次。你是不是有一种绝望的感觉呢?修改代码的次数越多,带来错误的机会也越大,我们是否会陷入错误的泥潭而不能自拔呢?再或许,我们是否会就此止步,半途而废呢?
这些困惑正是我曾经有过的,我用App Inventor开发的第一个版本的俄罗斯方块就只有这三种组块,我的手机上有将近半年的时间里,安装了这个只有三种组块的俄罗斯方块,每当出门需要坐地铁时,我便拿出手机游戏一番,倒也还兴致盎然。
当我决定要写这个开发笔记后,我开始重新编写代码,这个过程并不是对第一个版本的重复,就像之前提到过,第一次开发中并没有遇到组块填满一行时不能落底的错误,但这次却遇到了。第一次开发算是一种探险,想试试看App Inventor是否真的可以用来开发正式的产品;而这一次的开发过程带有更多的理性和设想,并希望在开发过程中实现这些设想。与正式的编程语言不同的是,在其他编程语言中有数组这种数据类型,虽然App Inventor有列表类型与之对应,但动辄一个列表就占满全屏的代码块,还是让人感觉到无奈,并怀疑它的运行效率能否满足游戏的要求。
值得庆幸的是,我克服了自己的恐惧心理,并利用列表数据实现了一个完整的俄罗斯方块,这得益于多重列表功能,即,列表元素本身也可以是列表,可以逐层地访问列表元素,直到找到所需要的值。
在下一章,我们将具体针对每一种组块,编写相关的数据列表,并通过对列表元素的操作,实现19种组块在游戏中的完整功能,从而摆脱“浩如烟海”的代码修改工作。在本章,我们将利用剩下的篇幅,来熟悉一下App Inventor中与多层列表相关的功能。
第四节 使用多层列表存储并访问数据
一、组块数据的坐标表示法
在第12章中,我们讨论了1、2号组块在旋转时所覆盖的区域,我们称之为旋转禁区,如图13-16中阴影的部分,对这些区域的描述,要依赖于数学方法——坐标法。每一个方块都对应于一个画布上的绝对坐标(绝对行,绝对列),同时又存在一个相对于基准行列的相对坐标(相对行,相对列)。
如图13-16中所示的方块①及方块②:
- 2号组块图中的方块①:
- 绝对坐标:(1,2),画布上的第1行、第2列;
- 相对2号组块基准行列的相对坐标是(-3,-2),从基准位置向上数3行,向左数2列;
- 1号组块中的方块②:
- 绝对坐标:(1,8),画布上的第1行、第8列;
- 相对1号组块基准行列的相对坐标是(-2,-2),从基准位置向上数2行,向左数2列。
在程序运行过程中,我们需要利用绝对坐标值来判断某个方块的颜色码,决定组块是否可以左右移动、快落、旋转,或是否已经触块,并以此为依据决定程序的走向。方块①、②的绝对坐标值会随着组块的移动而变化,但它们相对于基准行列的相对坐标是固定不变的,因此我们可以利用变化的基准行、列值和不变的相对坐标值,来计算出禁区内的每个方块的绝对坐标值。第12章中对旋转禁区的判断就利用了禁区内方块的相对坐标值。如1号组块旋转禁区内的第一行的绝对坐标为:
(基准行-2, 基准列-2),(基准行-2, 基准列-1),(基准行-2, 基准列)
而相对坐标为:
(-2, -2),(-2,-1),(-2, 0)
从形式上看,将绝对坐标中的基准行、基准列去掉,就变成了相对坐标;从实际意义上看,负号表示方块位于基准行的上方、基准列的左侧,正号表示方块位于基准行的下方、基准列的右侧,0表示方块与基准行或列在同一行或同一列,坐标的绝对值表示偏离的行、列数。
有了相对坐标这个工具,我们就可以将空间上的问题转化为数字的问题,进而就可以通过计算来获得我们需要的结果。
二、组块数据的列表化
在第12章中,我们建立了一个1、2号组块旋转禁区的坐标列表,如图13-16所示。为了便于理解多层列表的结构,我们采用外挂式显示列表项,如13-17A所示。这是一个三层列表,第一层包含2个列表项,对应于1、2号组块;第二层各包含8个列表项,代表旋转禁区内的8个方块;第三层各包含两个列表项,是每个方块具体的相对坐标值(行,列)。
为了便于在开发屏幕上查看代码,我们通常采用内嵌的方式显示列表项,如图13-17B所示,这两种方式是等价的。为了在有限的页面中显示列表内容,我们剪切掉了变量初始化块中的大面积无内容的橙色区域。
三、多层列表的访问技术
列表数据类型的伟大之处,就在于我们可以访问到其中的任何一个值,这是因为列表中的数据是有结构、有顺序的。针对前面提到的1、2号组块旋转禁区列表,我们来尝试访问其中的值。
我们给自己出一个题,比如,我们要判断1号组块旋转禁区内第1行、第2列方块的颜色码是否为0,我们应该如何实现呢?如图13-18所示。我们有两种方式来访问列表项。第一种方式如图A,通过设置临时变量来逐层访问列表,访问顺序是从外向里;第二种方式是直接访问指定的列表项,访问顺序是从里向外。对于初学者来说,第一种方式比较容易理解,对于有一定经验的程序员来说,第二种方式更为简洁。
这里需要强调一点,在我们创建禁区列表时,禁区内多个方块在列表中是按照自上而下、自左向右的顺序排列的,因此上面例子中1号组块旋转禁区中第1行、第2列的方块在禁区列表中的索引值=2;在我们以后创建的所有坐标列表中,多个方块的坐标都将按照这样的顺序进行排列。
四、列表的其他访问方式
在接下来的程序编写中,要用到列表的一个功能:判断列表中是否包含某个列表项。
例如有一个列表(1,2,3,4,5),要判断列表中是否包含1或6,就可以使用这一功能,具体使用方式如图13-19所示,左图的返回值为真,右图的返回值为假。