电玩圈游戏网 搜一搜

安卓手游去频道 >

角色扮演 射击枪战 棋牌卡牌 体育运动 角色扮演 赛车竞速 休闲益智 音乐舞蹈 模拟经营 战棋塔防 推理解谜 策略战争

安卓应用去频道 >

社交通讯 系统工具 影音视听 拍摄美化 效率办公 学习教育 生活服务 旅游出行 资讯阅读 金融理财 网络购物 游戏助手

游戏视频去频道 >

动作冒险 射击枪战 棋牌卡牌 体育运动 角色扮演 赛车竞速 音乐舞蹈 模拟经营 战棋塔防 推理解谜 策略战争 休闲益智

资讯攻略去频道 >

手游资讯 手游攻略 手游问答 游戏资讯 游戏杂谈 游戏攻略 软件教程 软件资讯

专题合集去频道 >

游戏专题 应用专题

排行榜单去频道 >

游戏排行 应用排行
首页 游戏 应用 视频 资讯 专题 榜单

首页>资讯>手游攻略>深圳SHENZHENIO阿瓦隆城第5关海藻收割机器人攻略

深圳SHENZHENIO阿瓦隆城第5关海藻收割机器人攻略

作者:佚名来源:百度2022/07/04

深圳IO是一款硬核的编程游戏,有着严谨的游戏内容,那么一起来看看阿瓦隆城第5关海藻收割机器人的攻略吧。

关卡展示

本关的 C2S-RF901 会不定期地发送一些长度为 2 的数据包:两个数字分别记为 x 和 y,表示在 (x, y) 位置出现了新的海藻。题目保证不会在 (0, 0) 点出现海藻。

当队列中有等待收割的海藻时,命令电机按照先来后到的顺序依次收割所有的海藻。这次的电机变成了在二维平面上运动,控制规则如下:

【电机 x】和【电机 y】信号初值均为 50。

给【电机 x】发送 a 秒的 0 信号,表示令电机左移 a 格;给【电机 x】发送 b 秒的 100 信号,表示令电机右移 b 格。

给【电机 y】发送 c 秒的 0 信号,表示令电机下移 a 格;给【电机 y】发送 d 秒的 100 信号,表示令电机上移 d 格。

可以同时给【电机 x】和【电机 y】两路输出发送信号,此时电机将按斜线方向移动:x = 100, y = 100 时,向右上方移动;x = 100, y = 0 时,向右下方移动;x = 0, y = 0 时,向左下方移动;x = 0, y = 100 时,向左上方移动。

移动完毕后,需要将【电机 x】和【电机 y】都还原成 50。

当电机移动到有海藻的位置时,给【收割】端口发送 1 秒钟的 100 信号,即可完成一次收割。

为了以最快速度到达海藻的目标位置,我们需要让电机优先按斜线方向移动。另外,原则上,我们需要按照先来后到的顺序收割海藻。但如果前往目标点的过程中,遇到了后出现的海藻,也顺带收割掉。

例如,当电机停在 (0, 0),需要前往 (2, 3) 收割海藻时,需要依次移动到 (0, 0)→(1, 1)→(2, 2)→(2, 3) 点。如果在走到 (1, 1) 点时,发现了后出现的海藻,那么即使它是先来后到里的后者,也顺带收割掉。

本关的难点在于维护一个【任务队列】。和第 1 关不同,第 1 关从冷库中拿食物的规则是【随用随取】,需要用了就拿出来,你需要随时告诉系统,我现在要拿编号多少的食物。而本关收割海藻的要求是按照先来后到的顺序收割,系统不会告诉你,现在要往哪跑,收割哪里的海藻。

队列是一种动态存储大量数据的数据结构,就跟生活中随处可见的“排队”一样,排在前面的人最先享受服务。在队列这种数据结构里,添加操作只能在队列的末端进行,读取和删除操作只能在队列的首端进行。如下图所示。

我们收到数据包的时候,将要到达的目标位置放在【队尾】。当任务队列中还有数据时,我们需要令电机到达【队头】所指示的位置,完成收割后,将【队头】元素删除。如此,便能实现“按先来后到的顺序收割海藻”的要求。

本关需要 6 块芯片分工合作完成。1 号芯片是数据库管理员,和 C2S-RF901 直接连接,用于不断向任务队列里添加新的收割任务。2 号芯片是监察员,不断监测当前的队头任务是否完成。队头任务完成后,需要向后寻找新的任务,并将队头指向新的任务,同时给 3 号芯片发送新的任务指令。3 号芯片是调度员,用于给后面的 4~6 号工人芯片发送新的任务指令。4~6 号芯片是勤劳的工人,分别控制电机 x、电机 y 和收割信号。其中 6 号芯片也兼顾数据库管理员这一职,每完成一次收割,就将任务队列里的对应任务清除,以便 2 号监察员能够监视到。

电路图如下所示:

本关的接线非常复杂。我先解释一下为什么要连这么多线。

左上角的芯片是 1 号数据库管理员芯片,需要完成【从 C2S-RF901 中接收数据包】、【将新的数据包写入任务队列】、【唤醒右侧的总监芯片】3 件事情。所以它的 x0 口和 C2S-RF901 的 rx 口连接,x1 口和 RAM 的 d0 口连接、x3 口和右侧的总监芯片连接。

正中央的芯片是 2 号监视员芯片。虽然是每秒钟都需要监视数据库,但由于 1 号芯片向任务队列中写入新任务是一个耗时操作,如果过快启动,会导致本秒监测不到需要启动的新任务。为了同步的需要,这里的监视员还是选择每秒钟都等 1 号芯片唤醒自己后再工作。这块芯片需要完成【监视数据库】,以及【向上方的 3 号芯片告知当前任务】的这两项任务,看似只需要使用 3 个 x 口,分别接 RAM 的 d1、a1,以及上方芯片的一个 x 口即可。为什么这么简单的任务却接了 4X1P 共五个口,我们后面会详细说。

上方居中的芯片是 3 号调度员芯片,x0 口和 p1 口都是用于和下方芯片通讯的,而 x2 口连接着右边控制电机移动的 4、5 号芯片,用于给它们发送电机要到达的目标位置。

然后是右边的 3 块工人芯片。4、5 号芯片的 x0 口用于接收 3 号芯片发来的任务,p1 口连接着【电机 x/y】的输出端信号。而 x1 口是和 6 号芯片的 x3 口相连的,作用是:每到达一个新位置,就唤醒 6 号芯片,让它检查现在脚下有没有待收割的海藻。

6 号收割芯片的 x3 口用于和上方负责移动电机的芯片通讯,p1 口连接着【收割】的输出端信号。x0、x1 口连接着 RAM 的 d1 和 a1,以便当自己成功收割一个海藻后,能够抹除数据库里的相应任务记录。

这一关里,芯片间的信息传递非常频繁,虽说看起来导线连接得异常错综复杂,但真的没有一根导线是废线。我们先从 1 号数据库管理员芯片开始写起。代码如下:

首先将本秒内 C2S-RF901 中的首数字读入 acc(mov x0 acc),检查它是不是 -999(tcp acc -999)。若不是,说明是由两个一位数组成的数据包,首数字代表新海藻所在的 x 坐标,第二个数字代表新海藻所在的 y 坐标。此时 x 坐标已经放在了个位,我们只需要读入第二个 y 坐标的数字,并把它放在十位上,就可以得到一个由两位数组成的【海藻位置】信息(+ dst 1 x0)。处理完成后,我们就可以将这个新的坐标加入任务队列了(+ mov acc x1)。不管本秒内是否往任务队列里加入了新的任务,我们都需要在临睡前唤醒 2 号监视员芯片(mov 0 x3),待唤醒 2 号芯片后,方可睡去(slp 1)。

接下来是位于线路板最中央的 2 号监视员芯片:

上方的 3 号芯片我预写了两行代码。因为左侧的芯片向 x3 口发 0 时,是会同时唤醒 2、3 两块芯片的(纯属无奈,电路板没有足够的空间布置成更细致的样子)。3 号芯片需要在 2 号芯片做好准备工作后才能开始工作,现在被唤醒纯属副作用,唯一能做的事就是把 1 号芯片发来的 0 给丢弃掉,防止线程阻塞(slx x0, mov x0 null)。1 号芯片之前发来的 0 是没有任何含义的,只是为了唤醒而随意发送的一个数字,这个数字换成其他任何数字都可以。

然后我们看 2 号芯片的代码。2 号芯片的 acc 寄存器用于存储队头地址,即【当前正在执行的任务编号】。当任务队列为空(即没有新任务)时,acc 表示的是【上一个已完成的任务编号】。前一个任务完成后,这个芯片不会立刻将这个已完成任务从队列里清除,而是扫描到新任务后,再去清除。这样可以保证队列里“至少有一个任务”(即使这个任务是已完成的任务),降低逻辑的复杂度。初始上电状态下,我们既没有【正在执行的任务】,也没有【上一个已完成的任务】。队列是真正意义上的空队列。 所以此时,我们需要人为地把队头往前移一格,将 13 号任务“视为上一个已完成的任务”,将队头地址初始化为 13(@ mov 13 acc)。

初始化完毕后,我们进入等待唤醒状态(slx x0)。唤醒后,发来的数字会被上方的 3 号芯片吸收掉,所以我们不需要在本芯片里再吸收一次了,这也就是后续代码里不再有任何读 x0 的操作的原因。唤醒后,首先我们把 RAM 的 a1 指针重置到队头,以便检查队头的任务有没有执行完毕(mov acc x3)。如果队头处的数字仍然大于 0,说明队头任务还在执行中,那么什么改变都不用做,直接跳到最后唤醒 3 号芯片就行了(tgt x2 0, + jmp c)。读一次 RAM 后,地址指针会自动 +1。我们知道 1 号芯片操作的是 d0 数据口,这就导致 a0 地址口始终指向队尾。如果队头地址 +1 正好等于队尾地址,就说明队列中仅有一个任务。这个任务要么正在进行,要么已完成。正在进行的话刚才已经说过了,直接跳到结尾。而如果唯一的一个任务是已完成状态的话,那么任务队列事实上相当于是空的。此时我们根本没有新任务可以安排,也只能什么都不做,直接跳到最后唤醒 3 号芯片(- teq x3 x1, + jmp c)。

队头已经变成了 0,且队列大小不为 1 时,就说明队头任务已经执行完毕,同时 RAM 里还有其他的等待执行的任务。此时我们需要向后查找第一个非 0 的格子,将队头地址更新为该格子的地址(mov x3 acc, tgt x2 0, - jmp 7),后面的时间里改为监测这个格子是否变成 0。找到新的队头后,我们将新的目标位置同步到 p 口上,并唤醒上方的 3 号芯片(mov acc x3, mov x2 p1, mov 0 x0)。

为什么要将目标位置同步到 p 口上,而不使用 x 口来传输呢?有两点原因:①上方的 3 号芯片需要将这个两位数拆解,每个周期都要读两次原数,将信号传到 p 口上可以最大化利用 p 口可以【反复读取】的优点,节约数据传输次数;②同样因为 p 口可以反复读,所以 2 号芯片只需要在任务更新时才刷新 p 口的值,队头的任务仍然在进行中时就不需要反复传同样的任务了。使用 p 口可以大大减少发送方的压力。

我们最近的 3 关:喂猫机、打靶练习机,以及本关的海藻收割机,都有传输【不频繁变化,且需要被多块芯片读取/被同一块芯片读取多次】这类数据的需求。这类数据相比于传统的 x 口传输法,改用 p 口传输对于发送方和接收方来说都能大大简化逻辑复杂度和电量消耗。我们在这 3 关里也都使用了 p 口传输这类数据。

接下来是 3 号调度员芯片:

调度员执行到第 3 条指令时,由于需要读入 x0,所以其实是处于【等待唤醒】的状态的。但必须要在本秒内唤醒,否则会导致线程阻塞。2 号芯片在最后会将一个 0 传给 3 号芯片,所以 3 号芯片的第三行指令(dst x0 p1)相当于 dst 0 p1,将 acc 的个位置为 p1。我们知道,3 号芯片被唤醒前,p1 口会被 2 号芯片修改为【当前任务的 x/y 坐标】的值,所以 p1 口的值会是个两位数。与此同时,我在第 15 关卡宾枪的攻略里说过:

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

dst 指令的第二个操作数是多位数时,会只看个位,忽略最高位。所以 dst 0 p1 这条指令相当于下面两条指令:

mov p1 acc

dgt 0

相当于将 acc 置为 p1 的个位。1 号芯片收到一个新的海藻坐标时,会将个位置成 x 坐标,十位置成 y 坐标。我们现在先提取出目的地址的个位数(即 x 坐标),发送给右边的芯片后(mov acc x1),再提取出目的地址的十位数(即 y 坐标),发送给右边的芯片(mov p1 acc, dgt 1, mov acc x1)。3 号芯片的 x1 口同时连接着右边控制 x、y 的两块芯片,发送的时候没有具体指定是哪个芯片接收,所以 4、5 号芯片需要自我调整时序,确保控制 x 坐标的芯片接收到的是第一个数字,而控制 y 坐标的芯片接收到的是第二个数字。

接下来是 4、5 号控制电机 x/y 位置的芯片:

两块芯片仅仅是第 2、3 行代码换了一下位置,其余的部分一模一样。下方芯片用于控制 x 坐标,需要接收第一个数字,所以在第二行就开始读 x0;而上方芯片用于控制 y 坐标,需要接收第二个数字,所以等到第三行,下方芯片读过一次 x0 后,才开始读自己这里的 x0。

下方芯片的 acc 寄存器用于记录电机当前所在的 x 坐标,上方芯片的 acc 寄存器用于记录电机当前所在的 y 坐标。首先都是等待左边 3 号芯片的唤醒信号(slx x0)。唤醒后,从左边的芯片接收目标 x/y 的值,将目标的 x/y 和当前的 x/y 做三态判定(tcp x0 acc)。首先假设是中间状态,即目标 x/y 和当前 x/y 是一致的,将电机信号置为 50(mov 50 p1),如果的确是端点状态,再撤销之前的设置。如果目标 x/y 小于当前 x/y,需要令电机向左/下移动一格,同时 acc 里记录的实时 x/y 值也要 -1(- sub 1, - mov 0 p1);如果目标 x/y 大于当前 x/y,需要令电机向右/上移动一格,同时 acc 里记录的实时 x/y 值也要 +1(+ add 1, + mov 100 p1)。做完这些事情后,我们将新的 x/y 值发给下方的 6 号芯片,由它判断脚下是否有等待收割的海藻(mov acc x1)。最后,跳回到第 1 行(slx x0),等待下一秒钟再次被 3 号芯片唤醒。

由于 4、5 号芯片每秒钟都会被唤醒,所以这里不需要写成循环结构,每次唤醒时都做同样的事情时,就相当于在循环了。

最后看 6 号用于控制收割的芯片:

首先等待被 4、5 号芯片唤醒(slx x3)。由于控制 x 的芯片收到第一个数字开始干事时,控制 y 的芯片还在等待接收第二个数字,因此控制 x 的芯片总是“快人一步”。观察 3 号芯片里的指令可以发现,y 坐标比 x 坐标至少迟了 3 个机器周期才发出来。因此,即使在此期间 y 坐标没改变但 x 坐标改变了(多执行了 add/sub 以及 mov 共两条指令),控制 x 的芯片仍然会快人一步,先将 x 坐标发出来。6 号芯片的收到的第一个数字一定是 x 坐标,所以先设置的一定是个位(dst 0 x3)。y 坐标总是会“慢人一步”到来,所以我们后设置的是十位(dst 1 x3)。如此,便得到了用两位数表示的“当前电机所在坐标”。如果计算出来是 0,就说明电机还停在原点 (0, 0)。而题目明确说了不会在 (0, 0) 处出现海藻,所以当电机还停在原点时,跳回开头继续睡觉,无需检查这个位置是否需要收割(teq acc 0, + jmp 1)。当我们计算出来的数字不是 0 时,就必须要遍历整个 RAM 检查一下是否有海藻停在这个位置。首先将 RAM 指针归零(mov 0 x1),每读一格数据,就检查读到的数据是否和 acc 一致(mov x1 dat, teq x0 acc)。读到一致的数据时,说明当前这一格脚下有等待收割的海藻,我们将 RAM 中这一格的数据写成 0,表示“这个位置的海藻已被收割”(+ mov dat x1, + mov 0 x0)。改写完 RAM 后,向【收割】端口发送 1 秒钟的 100 信号(+ gen p1 1 0)。如果尚未读到一致的数据,则不停地读,直到读遍整个 RAM,地址自然归零为止(- teq x1 0, - jmp 7)。做完这些后,跳回到第一行(slx x3),等待下一秒钟再次被 4、5 号芯片唤醒。

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

评论 (0)

相关阅读
网友评论0条评论

上拉或点击查看更多