【深圳 IO 攻略】第 5 关:游戏积分器

本文首发于 B 站《深圳 IO》文集(https://www.bilibili.com/read/readlist/rl569860)。原创不易,转载请注明出处。
关卡展示

每当得分信号出现时就 +1 分,犯规信号出现时就 -2 分,但分数不能低于 0 分。(得分和犯规信号不会同时出现)按照上述规则随时更新显示屏上的分数。
根据以上规则,我们很容易设计出如下的算法:
得分信号出现时,令 acc +1;
犯规信号出现时,令 acc -2;
当 acc < 0 时,将 acc 置为 0;
执行完以上操作后,将 acc 的值发送给显示屏,然后休眠一秒进入下一个时钟周期。
代码如下:

点击左下角的【模拟】,稍等片刻,便会弹出结算界面:

优化电量
我们可以看到,这个初版方案的耗电量惨不忍睹。这是因为我们的代码中有很多冗余操作:
得分和犯规信号不会同时为 100,所以 teq p0 100 和 teq p1 100 这两条判断至少有一条是不生效的,效率上有浪费。完全可以改为比较 p0 和 p1 的差值。
仅当触发了犯规信号时,分数才有可能低于 0。没必要每个时钟周期都判断分数是否低于 0。
仅当分数产生变化时才有必要将 acc 的值传给显示器,平时分数没有变化的时候没必要反复传同样的信号。
针对以上几点,我们重新设计一套更优的算法:
计算犯规信号(p0)和得分信号(p1)的差值;
当差值为 0 时,当前时钟周期内不做任何操作;
当差值为 -100 时,说明得分信号激活,令 acc +1,然后将 acc 的值发送到显示器;
当差值为 +100 时,说明犯规信号激活,令 acc -2。与此同时,立刻判断 acc 是否小于 0。若是,则将 acc 更新为 0。做完以上操作后,将 acc 的值发送到显示器;
休眠一秒,进入下一个时钟周期,如此循环。
改进后的代码如下:

我在第 3 关的时候说过,测试指令前也是可以带上 + - 号的。这段代码里就出现了这样的条件嵌套。这道题里,每一秒钟可能出现 4 种不同的情况,依次如下:
p0 - p1 = 0;
p0 - p1 = -100(得分);
p0 - p1 = +100, acc - 2 >= 0(犯规,但是没有扣到 0 分以下);
p0 - p1 = +100, acc - 2 < 0(犯规且扣到了 0 分以下,强制还原成 0 分)。
我们现在对所有的情况依次讨论:
p0 - p1 = 0,此时在执行了 tcp p0 p1 指令后,所有带 + - 前缀的指令都会被关闭,直接跳到最后一行【slp 1】(当前时钟周期内不做任何操作)。此时的代码看起来是这样的:
p0 - p1 = -100,此时在执行了 tcp p0 p1 指令后,带 - 前缀的指令会被激活,接下来会执行第 2 行的【- add 1】、第 7 行的【- mov acc x1】和第 8 行的【slp 1】指令(令 acc +1,然后将 acc 的值发送到显示器)。此时的代码看起来是这样的:
p0 - p1 = +100 且 acc - 2 >= 0,此时在执行了 tcp p0 p1 指令后,带 + 前缀的指令会被激活。然后会执行第 3 行的【+ sub 2】和第 4 行的【+ tlt acc 0】指令。接下来,由于 acc - 2 >= 0,所以执行完 tlt 测试指令后,带 + 前缀的指令会变为关闭状态,带 - 前缀的指令会变成激活状态。接下来直接执行第 7 行的【- mov acc x1】和第 8 行的【slp 1】指令(令 acc -2,然后将 acc 的值发送到显示器)。此时的代码看起来是这样的:
p0 - p1 = +100 且 acc - 2 < 0,直到第 4 行前都和上一条无异。但是此时,因为 acc - 2 < 0,所以 tlt 测试指令执行完后的结果是 + 前缀指令保持激活,- 前缀指令保持关闭。因此接下来执行的是第 5 行的【+ mov 0 acc】、第 6 行的【+ mov acc x1】和第 8 行的【slp 1】(acc -2 后发现小于 0,将 acc 强制置零,然后将 acc 的值发送到显示器)。此时的代码看起来是这样的:

耗电大幅减少到 170,可喜可贺。
附:一些简单的逻辑嵌套
下面我给出一些简单的逻辑嵌套定式,读者可以尝试自行推理证明。
所以以上代码中:
两条测试指令间构成了【与】关系,“仅当 p0 - p1 > 0 且 acc - 2 < 0 时,才将 acc 置 0 并发送给 x1”,“只要以上有一条不满足,那么就执行 - 前缀的部分,只要将 acc 发送给 x1 就可以了,不需要将 acc 置零。其中若 p0 - p1 < 0 时还要额外执行一条 add 1 指令”。