欢迎光临散文网 会员登陆 & 注册

【深圳 IO 攻略】最终 BOSS 关:水下收割机器人

2022-07-14 17:26 作者:ココアお姉ちゃん  | 我要投稿

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

此文章已过时。请跳转至 【深圳 IO 攻略】最终 BOSS 关《水下收割机器人》的全新解决方案(空间换时间) 继续阅读。

点击主页上的【控制面板】,在【谜题档案】里输入数字 3113,打开隐藏关第 3 关《水下收割机器人》。这也是整个游戏(截至 2022 年 7 月)的最终 BOSS 关卡。

本关是阿瓦隆城第 5 关《海藻收割机器人》的升级关卡,尚未完成阿瓦隆城第 5 关的同学建议先阅读阿瓦隆城第 5 关的攻略,完成后再来挑战本关:【深圳 IO 攻略】阿瓦隆城第 5 关:海藻收割机器人

和阿瓦隆城第 5 关相比,本关虽然少了【收割】这一路输出,但是难度却陡然增大。因为本关的要求是就近收割,而不是按照先来后到的顺序收割。也就是说,每走一步都需要扫描一遍区域里有哪些海藻,然后往最近的海藻方向奔去。因为是就近收割,所以不存在像第 5 关那样往远处跑的过程中“顺手牵羊”的情形,自然也就不需要【收割】这一路输出信号了。

另外,为了简化问题,计算距离时,对角线方向上 1 格的距离也是 1,不是根号 2。类似于国际象棋里的国王,既可以横/竖走一步,也可以斜着走一步。国王在 a 点,到达棋子 b 处至少需要走多少步,在这道题里 a 点和 b 点的距离就是多少。

那么,相比于阿瓦隆城第 5 关来说,本关的思路要做这么几点改变:

  • 因为是就近收割,不是按先来后到的顺序收割,所以我们向 RAM 里添加新的海藻坐标时,要使用第 1 关的模式:遍历 RAM,找到空位后就放进去。如果仍然使用队列模式的话,存在“早期海藻因为距离过远迟迟未被收割,致使新出现的海藻坐标覆盖掉早期海藻”的可能性。

  • 因为每一步都要搜索距离自己最近的海藻在哪,而电机和海藻间的距离是随着电机的移动而变化的,不是一成不变的。所以控制电机的芯片每秒钟都要对外公布自己所在的 (x, y) 坐标,以方便其余芯片计算此时此刻电机和各海藻间的距离。

这道题我花了整整一天时间才做出来。我的设计方案里,一共花了 31 块钱(MC6000×4 + MC4000(X)×3 + RAM×1),写了 74 行代码,且走了背线。我这个方案肯定不是最优秀的,世界排行榜上用 20 多块钱,50 多行代码做出来的大神比比皆是。好在我这个方案的电量只有 6.4K,属于第一梯队。电路图和代码如下:

整块电路板堆得满满的,一股浓浓的窒息感。

以上电路图中,我用黄色字体标注出了 7 块芯片的编号。我来说明一下各个芯片的分工:

  • 1 号芯片负责接收 C2S-RF901 发来的海藻坐标,并放入 RAM 的空位中。

  • 2 号芯片遍历 RAM 中的所有海藻坐标,并委托 3 号芯片依次计算电机与这些海藻的距离。遇到距离为 0 的海藻时,说明当前位置的脚下有海藻,委托 4 号芯片将 RAM 中的相应数据抹除;遇到距离大于 0 的海藻时,找到距离最近的海藻坐标,并发送给 4 号芯片。

  • 3 号芯片在收到 2 号芯片发来的海藻坐标后,计算电机与该海藻的距离并反馈给 2 号芯片。

  • 4 号芯片收到 2 号芯片发来的抹除信号后,抹除数据库中的对应数据;收到海藻坐标信号后,将目标位置发送给 5 号芯片。

  • 5 号芯片将目标位置的 x、y 坐标依次传送给 6、7 号芯片。

  • 6、7 号芯片在收到 5 号芯片发来的目标位置后,判定本轮是否应该移动一格,以及向什么方向移动。移动完毕后,向 3 号芯片报告电机的最新 x、y 坐标,方便它去计算电机与各海藻间的距离。

我们依次来分析。首先是 1 号芯片:

C2S-RF901 元件有一个隐藏特性:第一秒钟一定没有数据。因此和 C2S-RF901 相接的 1 号芯片可以在第 1 秒时直接休眠,避免做无用功(slp 1)。这个特性在以往的关卡里也一样存在,但提升不明显。可本关不一样,本关里,每秒钟例行的扫描数据库任务是一个高耗能操作,如果第一秒休眠的话,相比于不休眠,能节省几百格电量。

从第 2 秒起,1 号芯片就要从 C2S-RF901 的 rx 口读入数据了(mov x2 acc)。如果读入的是 -999(tcp acc -999),那么关闭一切 + - 号指令,直接跳到最后唤醒 2 号芯片。读入的不是 -999 时,我们需要将新的海藻坐标存入 RAM。我们刚才读到的首数字是 x 坐标,同阿瓦隆城第 5 关一样,现在我们再读入 y 坐标并放在十位数上,形成一个由 y 和 x 构成的两位数(+ dst 1 x2)。此时开始寻找 RAM 中的空位,即值为 0 的格子(+ mov x1 dat, + tgt x0 0, + jmp 5)。找到后,我们定位到该空位,并将记录海藻坐标的 acc 的值存入该格(- mov dat x1, - mov acc x0)。做完这些后,给 2 号芯片发送 999 唤醒它(mov 999 x3),此时我们就可以安心睡眠了(slp 1)。

注意一下 1 号芯片的 x3 口。我们再按住 TAB 键,观察一下透视图:

发现 1 号芯片的 x3 口和其余芯片的很多端口都有连接。首先是和 C2S-RF901 的 tx 口相连接,但本关并不需要向 tx 口发送数据,tx 口一直处于高阻状态,所以数据并不会流到 tx 中。这里的 tx 口仅仅起到了一个“借过”的作用。

除 tx 外,1 号芯片的 x3 口还和 2 号芯片的 x3 口、3 号芯片的 x0 口和 4 号芯片的 x2 口相连接。后面我们会说到,2、3 号芯片通过各自的 x2 和 x1 口通讯,3 号芯片的 x0 口是处于高阻态,不接收数据的,因此 3 号芯片的 x0 口也只起到了一个“借过”的作用。

因此,这根导线上的数据实际只会在【1 号芯片的 x3 口】、【2 号芯片的 x3 口】和【4 号芯片的 x2 口】间流动。1 号芯片给 x3 发送 999 时,会同步唤醒 2 号和 4 号芯片。时序上要确保 2 号芯片“快人一步”收掉这个 999,4 号芯片需要接收的只是将来 2 号芯片发来的数据。因为 1 号芯片发完 999 后,这一秒就睡过去了,因此 2 号芯片在本秒内发送的数据都只会流向 4 号芯片,不会回流到 1 号芯片。

为什么要接这么复杂的线,还要各种借过?实在是因为电路板面积有限啊,君不见本关的电路板已经塞得满满的了,不借过的话就无路可走了。

现在我们来看 2 号芯片:

2 号芯片的第 1 秒钟里跟 1 号芯片一样睡眠(slp 1),从第 2 秒钟开始工作。首先趁着 4 号芯片没醒,赶紧把 1 号芯片发来的 999 给收了(mov x3 dat)。至于这个 999 有什么用,我们接下来会说。

2 号芯片每秒钟都需要扫描整个 RAM,检查有哪些待收割的海藻,以及最近的海藻在哪。首先我们从 RAM 中读一格数字(mov x0 acc),检查它是否大于 0(tcp acc 0)。如果读到了 0 的格子,说明这个格子是空的,直接关闭所有 + - 号指令,跳到第 12 行的循环末尾,准备读下一格(tcp x1 0, + jmp 3)。如果读到了非 0 的格子,我们将从该格读到的坐标值发送给 3 号芯片两次(+ mov acc x2, + mov acc x2),并从 3 号芯片接收算好的距离值,将它放到百位上(+ dst 2 x2)。此时判断带上了距离值的 acc 是否小于 100(+ tlt acc 100)。小于 100 时,百位为 0,说明刚才读到的这个海藻和我们电机的距离为 0,说明脚下的这个格子是有海藻的,说明我们需要把这个海藻收割掉。此时我们将 RAM 地址(当前海藻所在的位置 +1)发给上方的 4 号芯片,委托它将地址 -1 处的格子置零(+ mov x1 x3)。而如果 acc 大于 100,那说明我们找到了一个等待收割的海藻。但是怎么判断它是不是所有海藻中最近的那一个呢?

我们在上面说了,原先记录海藻 (x, y) 坐标值的 acc,在经过 3 号芯片的计算后,会变成一个三位数,它的百位用于记录电机和这个海藻的距离。例如,当电机在 (5, 5),海藻在 (3, 2) 时,acc 初始值为 23。然后由于两者距离为 3,acc 在经过 3 号芯片的计算后会变成 323。我们在比较两个数的大小的时候,是从高位开始比的,这样一来,问题就变成了“找最小值问题”。我们需要将 RAM 中记录的所有【两位数】都变成【三位数】,并找出【最小的三位数】。

这里的 dat 记录的是“迄今为止找到的最小三位数”,初值为本游戏能容纳的最大值 999。如果当前的 acc 比 dat 中记录的三位数小(- tcp acc dat),那么就说明 acc 的百位是小于等于 dat 的百位的。题目又明确说明了不会出现多个等距的海藻,也就是说各个三位数间,百位一定互不相等。那就只可能 acc 的百位比 dat 的百位小,说明找到了比之前最小的三位数还要小的三位数。此时我们需要把当前的 acc 覆盖到 dat 里(- mov acc dat),这样一圈下来,我们的 dat 里存储的就是最小的三位数了

循环的末尾,我们检查 RAM 的地址口是否绕了一圈回到了 0(tcp x1 0)。尚未回到 0 时,说明还没遍历完整个 RAM,跳回到第 3 行继续遍历(+ jmp 3),直到遍历完所有的格子为止,将 dat 中存储的【最小的三位数】发给 4 号芯片(mov dat x3)。

现在我们来看专门计算电机和海藻的距离的 3 号芯片:

该芯片可以从 p1 口获得电机当前的 x 坐标(由 6 号芯片每秒刷新),从 p0 口获得当前电机的 y 坐标(由 7 号芯片每秒刷新)。首先等待 2 号芯片发来记录着海藻坐标的两位数(slx x1)。我们首先取出这个两位数的个位(x 坐标),然后计算和电机 x 坐标的横向距离(dst 0 x1, sub p1, dst 1 0),计算完毕后放入 dat 中暂存(mov acc dat)。

值得注意的是第 4 行的置位指令(dst 1 0),它的作用是【取绝对值】。我在龙腾第 15 关《卡宾枪瞄准照明器》的攻略里提到过:

置位指令:dst I1/R1/P1 I2/R2/P2,将 acc 寄存器中的某一位置为特定的值。位数由第一个操作数决定,0~2 分别表示个位/十位/百位,若在此范围外,则不执行任何操作。具体设置的值由第二个操作数决定,会只看最低位,忽略最高位,同时会将数字的符号设置为和该数一致。 作者:ココアお姉ちゃん https://www.bilibili.com/read/cv16919367 出处:bilibili

注意这句【同时会将数字的符号设置为和该数一致】。在本游戏里,0 是视为正数的。因此当我们执行了 dst 1 0,将十位强行置为 0 这样的操作后,会强行将 acc 置为正数。10×10 的网格里,任意两点间的距离最多为 9,所以计算出距离后,十位、百位是肯定为 0 的。这里的置位指令并不会修改十位数字,唯一的作用就是取绝对值

接下来的 6~9 行代码,我们再从 2 号芯片处获得一份一模一样的两位数(mov x1 acc),取出它的十位(y 坐标),然后计算和电机 y 坐标的纵向距离(dgt 1, sub p0, dst 1 0)。至此,dat 中记录的是横向距离,acc 中记录的是纵向距离,两者中的较大值即为电机和当前海藻的距离。我们将两者中的较大值反馈给 2 号芯片(tgt acc dat, + mov acc x1, - mov dat x1)。

现在是 4 号芯片,它有两个任务:收到地址值时,将 RAM 中对应地址处的海藻坐标抹除;收到三位数时,将其中记录的【最近的海藻坐标】告知接下来的芯片。我们来看它的代码:

首先等待 2 号芯片的唤醒信号(slx x2),并将它发来的数存入 acc(mov x2 acc)。此时检查这个数是不是三位数(tlt acc 100)。不是三位数时,说明是一个地址值。我们按照 2 号芯片里的嘱托,将 RAM 中地址值 -1 处的格子清零(+ sub 1, + mov acc x0, + mov 0 x1, + jmp 1)。而如果发来的是三位数的话,需要分情况讨论:如果不是 999(- tlt acc 999),那皆大欢喜,直接把这个三位数发给 5 号芯片发两份就完事了(+ mov acc x3, + mov acc x3)。而如果传来的是 999,则有两种可能性:①区域里没有待收割的海藻,三位数一直保持着最开始的 999;②区域里只有唯一一个位于 (9, 9) 点的,距离为 9 的海藻。收到 999 时,我们需要遍历一遍 RAM,检查有没有位于 (9, 9) 位置的海藻。仅当 RAM 中有位于 (9, 9) 位置的海藻时,我们才能将这个三位数发给 5 号芯片(- mov x0 dat, - teq x1 99, + mov acc x3, + mov acc x3, - teq x0 dat, - jmp a)。如果把 RAM 遍历了一圈都没有发现位于 (9, 9) 位置的海藻,那么就什么都不做,直接跳回第 1 行等待下一次任务。

最后来看控制电机的 5~7 号芯片的代码:

5 号芯片做的事比较简单。5 号芯片会从 4 号芯片处收到两份同样的三位数,将它的个位(目标点的 x 坐标)提取出来通过 x2 发给 6 号芯片(slx x0, dst 0 x0, mov acc x2),再将它的十位(目标点的 y 坐标)提取出来通过 x1 发给 7 号芯片(mov x0 acc, dgt 1, mov acc x1),即完成任务。

6、7 号芯片的 acc 寄存器用来记录电机的实时 (x, y) 坐标。初始状态下,两块芯片都给各自的输出端口赋上 50 的电平值(mov 50 p1),然后等待 5 号芯片发来的目标 x/y 信号(slx x1/x0)。发来后,将目标位置和当前位置做差值运算,检查差值的正负号(tcp x1/x0 acc)。差值为 0 时,让电平信号保持为 50 即可,同样也不需要修改 acc 的值。差值为负时,说明目标点在当前点的左/下方,需要令 acc -1,并给输出口赋上 0 的电平值(- sub 1, - mov 0 p1);差值为负时,说明目标点在当前点的右/上方,需要令 acc +1,并给输出口赋上 100 的电平值(+ add 1, + mov 100 p1)。做完后,休眠一秒令电平生效(slp 1)。一秒过后,立刻将新的电机 (x, y) 坐标发往 p0 口,供 3 号芯片使用(mov acc p0)。

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

至此,深圳 IO 的所有主线攻略就全部写完了。如果这份攻略里出现了笔误,亦或读者在某些关卡里有比我更好的设计方案的话,欢迎和我探讨。也希望各位读者能喜欢这个游戏作品。

【深圳 IO 攻略】最终 BOSS 关:水下收割机器人的评论 (共 条)

分享到微博请遵守国家法律