第十三章 为3号组块编程

我们回顾一下第七章中关于组块旋转的内容,在这一章中,我们编制了一个包含19种组块的表格,并为每种组块编号。为方便起见,我们将表格复制到本章,并在接下来的章节里逐一编写每种组块的绘制及擦除过程,并修改与组块编号相关的所有程序。为了便于代码的统一管理,我们放弃原有过程的命名方式(绘制红色水平组块、绘制红色垂直组块等),而一律使用与组块编号相关的过程名称,如绘制组块_1=绘制红色水平组块,擦除组块_1=擦除红色水平组块,以此类推。

表13-1 游戏中所有形态的编码及编号

在为3号组块编写程序之前,我们先来修改已有的1、2号组块的绘制与擦除过程的名称。在定义过程的代码中来修改过程名称,一旦修改完成,曾经调用过该过程的代码将自动更新,如图13-1。

图13-1 修改组块绘制及擦除过程的名称

第一节 编写3号组块的绘制及擦除过程

为了编写与3号组块相关的程序,首先需要确定基准行、基准列在组块中的位置,如图13-2所示,我们将组块右下角的方块设定为基准行、基准列的位置,组块的初始值为基准行=1,基准列=7,前一刻基准行=0,前一刻基准列=7。这些初始值的设定与创建新组块过程中使用的初始值相吻合,这样我们就可以使用同一的创建新组块过程来处理所有类型的组块。

图13-2 组块3基准行、列位置

根据基准行、基准列的设定,我们定义”绘制组块_3”及“擦除组块_3”过程,如图13-3所示。

图13-3 组块3的绘制与擦除过程的定义

第二节 修改与组块编号相关的程序

接下来要修改若干个与组块编号相关的程序,在第十一章中,我们曾经罗列出这些程序:

  1. 需要修改条件判断语句,扩展分支语句的过程:
    • a. 组块下落
    • b. 求触底组块覆盖的行
    • c. 已经触块
    • d. 已经触顶
    • e. 求重绘起始行
    • f. 更新色块列表
  2. 需要扩展数值范围的过程:
    • a. 创建新组块:将随机数范围从1~2扩展到1~19
    • b. 重绘画布:根据色块列表中记录的颜色码来重绘画布
  3. 事件处理程序:
    • a. 左移程序
    • b. 右移程序
    • c. 快落程序
    • d. 旋转程序

由于3号组块的几何形状具有水平及垂直方向上的对称性,旋转操作不会改变组块的形态,因而无需修改旋转程序,这样,共有11个程序需要修改。现在让我们来逐一修改。

1.a 组块下落

为条件语句添加两个“否则, 如果”分支,依据组块编号决定所要执行的操作,如图13-4所示。

图13-4 为组块下落过程添加条件判断分支

1.b 求触底组块覆盖的行

在原过程的否则分支中,添加一个“如果…则…否则”分支,以便针对不同组块返回不同结果,代码如图13-5所示。

图13-5 为求触底组块覆盖的行过程添加条件分支语句

1.c 已经触块

在原过程中添加两个“否则…如果”块,来替换原来的“否则”块,代码如图13-6所示。

图13-6 为已经触块过程添加条件语句分支

1.d 已经触顶

如图13-7所示,为已经触顶过程添加一个“如果…则…否则”分支。

图13-7 为已经触顶过程添加条件语句分支

1.e 求重绘起始行

如图13-8所示,对应三种不同的组块,局部变量组块顶行具有三个可能的值,因此在原来的否则分支中添加一个“如果…则…否则”分支。

图13-8 为求重绘起始行过程添加条件语句分支

1.f 更新色块列表

如图13-9所示,为更新色块列表过程添加两个“否则…如果”分支,替换原来的“否则”分支。

图13-9 为更新色块列表过程添加条件语句分支

2.a 创建新组块

为了生成三种组块,将生成随机数的最大值由原来的2改为3,代码如图13-10所示。

图13-10 扩大取随机整数的范围,从1~2改为1~3

2.b 重绘画布

在重绘画布过程中,为画块过程的颜色参数提供更多的选择,即,由原来的两种颜色增加为三种颜色,代码如图13-11所示。

图13-11 修改后的重绘画布过程

3.a 左移程序

在左移按钮的点击事件程序中,新增两个“否则…如果”分支,替换原来的“否则”分支,来处理与3号组块相关的操作,代码如图13-12所示。

图13-12 为左移程序添加条件语句分支

3.b 右移程序

同左移程序一样,在右移程序中新增两个“否则…如果”分支,替换原来的“否则”分支,来处理与3号组块相关的操作,代码如图13-13所示。

图13-13 为右移程序添加条件语句分支

3.c 快落程序

在快落程序中新增两个“否则…如果”分支,来替代原有的“否则”分支,以便处理与3号组块相关的操作,代码如图13-14所示。

图13-14 为快落程序添加条件语句分支

经过测试,3号组块会随机出现,一切如我们所期待的那样,程序运行正常,如图13-15所示。

图13-15 出现3号组块

第三节 回顾与展望

我们来回顾一下为3号组块添加或修改的代码:

  1. 新建过程:组块的绘制及擦除过程(2个);
  2. 修改过程:组块下落、求触底组块覆盖的行、已经触块、已经触顶、求重绘起始行、色块列表更新(6个);以上过程的修改方法类似,即,在现有程序基础上,添加一个条件分支语句,最后的句式为:如果 组块编号=1 则...否则,如果 组块编号=2 则...否则...(...表示将要执行的代码);
  3. 修改过程:创建新组块,将随机数范围改为1~3(1个);
  4. 左右移动事件处理程序:这部分需要在两处添加条件分支语句,一是组块的触边条件,二是组块的触块条件(2个);
  5. 快落事件处理程序;此处要添加一个条件分支语句,虽然不同的组块触底的条件相同,但触块的条件各不相同(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 用坐标来表示组块的旋转禁区

如图13-16中所示的方块①及方块②:

  1. 2号组块图中的方块①:
    • 绝对坐标:(1,2),画布上的第1行、第2列;
    • 相对2号组块基准行列的相对坐标是(-3,-2),从基准位置向上数3行,向左数2列;
  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-17A 旋转禁区相对坐标列表

为了便于在开发屏幕上查看代码,我们通常采用内嵌的方式显示列表项,如图13-17B所示,这两种方式是等价的。为了在有限的页面中显示列表内容,我们剪切掉了变量初始化块中的大面积无内容的橙色区域。

图13-17B 旋转禁区相对坐标列表

三、多层列表的访问技术

列表数据类型的伟大之处,就在于我们可以访问到其中的任何一个值,这是因为列表中的数据是有结构、有顺序的。针对前面提到的1、2号组块旋转禁区列表,我们来尝试访问其中的值。

我们给自己出一个题,比如,我们要判断1号组块旋转禁区内第1行、第2列方块的颜色码是否为0,我们应该如何实现呢?如图13-18所示。我们有两种方式来访问列表项。第一种方式如图A,通过设置临时变量来逐层访问列表,访问顺序是从外向里;第二种方式是直接访问指定的列表项,访问顺序是从里向外。对于初学者来说,第一种方式比较容易理解,对于有一定经验的程序员来说,第二种方式更为简洁。

图13-18A 访问多层列表中的列表项
图13-18B 访问多层列表中的列表项

这里需要强调一点,在我们创建禁区列表时,禁区内多个方块在列表中是按照自上而下、自左向右的顺序排列的,因此上面例子中1号组块旋转禁区中第1行、第2列的方块在禁区列表中的索引值=2;在我们以后创建的所有坐标列表中,多个方块的坐标都将按照这样的顺序进行排列。

四、列表的其他访问方式

在接下来的程序编写中,要用到列表的一个功能:判断列表中是否包含某个列表项。

例如有一个列表(1,2,3,4,5),要判断列表中是否包含1或6,就可以使用这一功能,具体使用方式如图13-19所示,左图的返回值为真,右图的返回值为假。

图13-19 判断列表中是否包含某个列表项