Shenzhen I/O 过关流程
《Shenzhen I/O》是一个汇编模拟器,跟《人力资源机器》是同一个类型。只不过《Shenzhen I/O》非常硬核,如果你没有编程基础,建议不要买,如果一定要买,那大概率会变成一个纸牌游戏。
前两天被steam推荐到这个游戏,原价48块,但是刚刚好在特价促销,打骨折只要16块,我就剁手了。
游戏一上来就直接丢给你一本40多页的说明书,比较类似于芯片的datasheet,而且还推荐打印出来翻阅。感觉在这里就会劝退很多咸鱼了啊~
这篇文章就是记录我的解题过程了,大概率不是最优解,但是平均水平应该还是有的吧。偶尔有些不太好说明的地方,我会直接写伪代码。
Lv.01 - 模拟安全摄像头
1 | * *活动状态*和*网络*是连接 LED 灯的简单输出。 |
这个感觉没啥好说的,观察给定的信号输出图,就能写出来。。
这里大概解释一下slp指令。我的理解就是类似于“结束回合”。控制器在一个时间周期内应该是可以执行无限量的指令,所以一般来说都至少需要一个slp 1
来进入下一个时间周期。当然在这一关,由于电平信号较长时间不变,所以可以直接slp跳过多个时间周期。
- 成本:6
- 用电量:60
- 代码行数:12
Lv.02 - 控制信号放大器
1 | * *控制输入*是连接工厂设备的简单输入。 |
这关要求做一个信号放大器,基本上就是检查有没有看过手册了,因为这里需要用到acc(通用累加器)
一开始想复杂了,以为要手动输入信号,后来发现是自动输入的,那就简单了。。
- 成本:3
- 用电量:300
- 代码行数:5
Lv.03 - 诊断脉冲生成器
1 | * *按钮*是连接按钮的简单输入。 |
这关要求生成脉冲信号,要用到条件分支了。当然,游戏里是一个大大简化模型,跟直接写if else差不多了。
伪代码差不多是这样。
1 | if (p0 == 100) { |
- 成本:3
- 用电量:281
- 代码行数:6
Lv.04 - 动画 ESPORTS 标志
1 | * *点击0*和*点击1*是连接显示组件的简单输出,分别与点击鼠标的两幅动画对应。 |
这关要求控制一个应援灯牌的闪烁,开始有点意思了。总体来说不难。最右边那个控制器不知道有没有办法优化进9行代码,如果可以那就只需要MC4000了。
- 成本:11
- 用电量:341
- 代码行数:23
左边的控制器用了条件,我稍微写点伪代码好了。核心思想就是把acc当成flag用。顺便说一下,这个if else是支持多行的。
1 | if (acc == 0) { |
Lv.05 - 饮酒游戏积分器
1 | * *得分*和*犯规*是连接按钮的简单输入。 |
这关开始要用到XBus来发送数据了,但是却出乎意料的简单。。。
顺带说一句,acc是可以为负数的,所以可以先进行加减操作,再检查是否小于0。
- 成本:3
- 用电量:323
- 代码行数:8
Lv.06 - 调谐最优化引擎
1 | * *音频输入*是连接音频源的简单输入。 |
这关要求处理音频,并且在开关开启的时候进行音质优化。说明书给了下面这个公式:
1 | AUDIO_OUT = (AUDIO_IN - 50) x 4 + 50 |
这里需要做到“开关”的效果,但是无论MC4000还是MC6000都只有两个I/O引脚,所以这里直接用XBus引脚控制状态即可。需要注意的是,XBus要求读写同步,所以需要用slx指令等待接收XBus数据包。
开始我用了三个控制器,后来发现原来两个就足够了。而且这个公式也是一个优化点,可以用数学方法化简,变成下面这样,从而节省一行代码。
1 | AUDIO_OUT = 4 x AUDIO_IN - 150 |
- 成本:6
- 用电量:550
- 代码行数:11
Lv.07 - 被动红外感应器
1 | * *时间*是连接 DT2415 时钟的简单输入,能提供当前时间。 |
这关要制作一个红外报警器,个人感觉挺难的,因为新增了不少模块,得一个个去摸索作用。中文版也有点小问题,关卡描述里的“报警器”,和电路板上的“闹钟”应该是同一个东西,我切英文版看了,都是alarm。。。因此这个电路板上的“闹钟”大概就是翻译错误了。
首先是DT2415累加式时钟,输出值范围是0-95,累积到最大值后,下一次就回到0。输出引脚在右下,输出简单I/O信号。
然后是时间点输入模块,这俩都输出XBus数据包。
那么要求我直接用伪代码描述一下。
1 | // 注意:“flag_激活报警器”的初始值是0 |
注意:这里有个坑,MCxxxx系列的控制器,是没有>=
的。这里只能用>
(tgt),那么相应的临界值就变成了19,而不是题目描述给的20。
- 成本:8
- 用电量:503
- 代码行数:11
Lv.08 - 仿真蜂音器
1 | * *无线rx*是连接无线接收器的非阻塞式 XBus 输入。 |
这个题要写出来不难,但是我这肯定不是最优解。总感觉可以只用一个MC6000,但是却想不到如何保存多个状态。由于XBus在一个时间内只能读一次,但是有-999(什么都不做),0(关闭),1(开启)三个状态,所以肯定需要一个临时的变量来保存获取的值。然后又需要一个变量表示开关。最后还需要一个变量来控制蜂鸣器的状态。
TODO:看看之后能不能想出更好的解法
Lv.09 - 无线游戏控制器
1 | * *x*和*y*是连接游戏杆的简单输入。 |
这关难度不小,光是理级题目要求就要费点功夫,我在这里简要概括下。
我们需要获取x,y,a,b,tx,最后输出到rx。其中tx是传输开始的信号,值为-1时传输,-999则不传输。x和y是直接使用原始值,a和b经过处理之后生成一个值(这里我们称之为acc),然后我们需要把x,y,acc按照顺序依次输出到rx。
其它地方都不难,重点是这个c的计算过程。题目给出下面的规则:
1 | if (a == 0) { |
乍一看,这是要搞嵌套循环啊,但是MC系列的汇编语法不允许这样嵌套。与其说是嵌套(nesting),实际上是链式(chaining)。我们来看下面的例子。
1 | teq a 0 |
发现问题了吧,这种写法,用伪代码写出来就是下面这样。
1 | if (a == 0 && b == 0) acc = 0; |
这种问题可以通过jmp来解决,但是强如MC6000也只能写14行汇编,所以这里我们转向数学方法。仔细观察,会发现下面的关系。
1 | a == 1 -> acc += 1 |
所以这么写就可以了
1 | teq a 1 |
- 成本:13
- 用电量:375
- 代码行数:21
Lv.10 - 激光束游戏设备
1 | * *击中*是一个简单输入,连接着背心上的激光探测器列阵。 |
这关要求特别多,略显麻烦。不过我看我的三项排名都比较靠前,估计是最优解了。
由于简单I/O接口特别多,这关很明显需要用到DX300。这个模块不仅便宜(只要1块),而且还带有3个简单I/O引脚。不过DX300需要配合MC系列的控制器一起使用,因为DX300本身无法进行编程,只能用作数据接口。
这里简单说下DX300的用法。可以用任意XBus接口输入一个二进制的三位数来控制3个简单I/O引脚,也可以读取当前状态下的简单I/O引脚情况(获取二进制的三位数)。其中p0是个位,p1是十位,p2是百位。(例子:XBus=101 -> p0=1, p1=0, p2=1)
1 | // 处理“击中”,“复活”,“活着” |
- 成本:7
- 用电量:483
- 代码行数:14
Lv.11 - 变色电子烟笔
1 | * *无线rx* 是连接无线接收器的非阻塞式 XBus 输入。 |
这题需要控制一个RGB灯,给出答案不难,但是想要一次就写出最优解还是没那么容易的。
一开始我给出的解是这样的:
- 成本:13
- 用电量:828
- 代码行数:20
这种方实比较模块化,思路也很清晰,但是可以看出有很多引脚并没有用上,尤其是相对珍贵的p引脚。所以,可以考虑直接从主控用p引脚连接RGB灯,就像下面这样。
- 成本:8
- 用电量:588
- 代码行数:15
从电量使用的分布来看,似乎还有更省电的答案,但是不清楚是否要用到其它模块,或者是牺牲成本和代码行数。不管了,就先这样吧。
Lv.12 - 不明优化设备
1 | * *x*和*y*是连接未知设备的简单输入,该设备可能是某种未知传感器。 |
这关好难啊,思路不对的话,死磕是解不出来的。
首先我们需要研究下说明书里头的这个图。
稍微用伪代码写一下,大概就是下面这样的:
1 | if (x < 20) power = 30; |
要是平时,我就直接用上面的代码了,反正现在电脑性能高得很,优化什么的当然是不存在的。但是现在只能用汇编,而且一个控制器最多写14行汇编代码。所以,上面那段伪代码是没有办法实现的。
那么,有没有简化的方法呢?我后来就尝试将整个地图切割成下面这样的15份。
赋予地图一个坐标系之后,看起来比较可行,因为绝大部分区域都是0和30,这些可以批量去判断。关于两个特殊的格子(50和80),可以单独拿出来判断。最后一共用了五个控制器,然后还差一行写不下,太尴尬了。。。而且塞满了整个电路板,说明答案肯定不对了。
后来实在没办法了,上网翻到一个讨论贴,看了眼提示,然后恍然大悟。这里其实还是用数学方法来简化,很关键的一点就是:
The value inside the inner square is the outer value + 50
所以我当时咋就没看出来呢。。。有了这个信息,那就简单很多了。首先通过x的区间,决定是0或30。然后再通过x和y共同决定是否+50(也就是0或50),最后把两个数值加一起就行了。
- 成本:10
- 用电量:1037
- 代码行数:19
感谢评论区Wof3YldPjOvDh9xe提出了另外两种更好的方案,在此一并列出。
另外,优化后的设计,比博主目前的版本稍好些,希望博主能更新文章,方便大家😋
- 成本:8
- 用电量:740
- 代码行数:16
一个QQ群友,告诉我一个更牛逼的,人外有人,天外有天呀🤡
- 成本:6
- 用电量:715
- 代码行数:14
秘密指令!
这个不是关卡,但是这段剧情提到了两个可能有用的新指令,而且不在说明书上。
gen P R/I R/I
说明:它能方便地在所有简单 I/O 引脚上生成脉冲!该信号会在开(100)和关(0)之间跳转,你想要持续多少时间单位都行,取决于最后两个操作数的数值。
1 | 用法:gen P X Y |
@ 条件语句
说明:在指令前加 @ 符号能让该指令只执行一次
Lv.13 - 古钱币付款终端
1 | * *投入口1*、*投入口5*和*投入口12*是连接到一台机器上的简单输入,该装置能检测并接收 1 銖、5 銖和 12 銖钱币。 |
这关出乎意料的简单,一次就写出答案了。这里用了两块控制器,一块收钱响铃,另一块找钱。
不过在写的时候有个坑。控制器只支持下面这些指令。
1 | teq a b -> if (a == b) exec +; else exec -; |
那么要实现>=
的效果怎么办呢?其实很简单,走false分支就可以了,像下面这样。
1 | tlt acc x0 |
- 成本:11
- 用电量:389
- 代码行数:25
Lv.14 - 个人三明治制作机
1 | * *小键盘*是连接小键盘的 XBus 输入。 |
这关写出来不难,但是要把所有代码塞进一个控制器,还是有点难度的。
首先,很容易想到,用一个DX300控制肉、奶酪、芥末,然后用MC6000的两个p引脚控制面包和旗子,基本上写出下面的代码不难。
1 | slx x1 |
但是问题来了,这里一共有15行,超出了1行,所以得想办法变成14行。接下来要说的,会推翻前面对“条件语句”的认知。
根据说明书上提供的条件表格,“+”和“-”指令在不同的条件下会“允许执行”或者“禁止执行”。也就是说,“+”和“-”指令不需要紧跟着条件语句,而是应当看作“是否被激活”的一个状态。
关于tcp
指令,说明书是这么写的:
条件 | 对‘+’ 指令的作用 | 对‘-’ 指令的作用 |
---|---|---|
A 大于 B | 允许执行 | 禁止执行(屏蔽) |
A 等于 B | 禁止执行(屏蔽) | 禁止执行(屏蔽) |
A 小于 B | 禁止执行(屏蔽) | 允许执行 |
因此这里可以直接用tcp来标记状态(是否允许执行特定的代码),从而省去把状态保存到acc这一步。
1 | slx x1 |
这个应该是最优解了,而且使用一个控制器的,肯定也只有这个唯一解。这一关看成绩分布的时候,发现很多人都用了两个控制器,好像还有用三个的。。。
- 成本:6
- 用电量:84
- 代码行数:14
Lv.15 - 卡宾枪瞄准照明器
1 | * *雷达输出*是连接微型雷达单元的简单输出,该设备在发射雷达“ping”时会发送信号。 |
这关只要善用jmp
,写出来还是挺容易的。收到“雷达输出”的时候开始用acc计时,收到“雷达输入”的时候停止计时(并且别忘了acc + 1)。然后就判断acc的范围,一个控制器写不下就通过xBus接口转发。
我这个不是最优解,也懒得优化了,差不多就行。
- 成本:11
- 用电量:262
- 代码行数:26
Lv.16 - 幽灵娃娃
1 | * *随机*是连接随机数发生器的简单输入。 |
这关如果用teq
的话,又写不下,所以还是要利用强大的tcp
。而且如果写不下的话,也会大大增加布线难度。这里就给出我两个解,可以体会下差距有多大。
首先是使用teq
的版本,一共使用了三个控制器,而且布局比较乱。这里倒是用到了两个小技巧。
第一,线路可以从芯片下方穿过。这里就是从随机数生成器那里开始,穿过200P-14(只读储存器),最后到达左下角MC6000控制器的p0引脚。
第二,可以把多个线路联通,从而实现一对多的关系。这里就是两个MC6000控制器的x3引脚,同时连接到了右边MC4000控制器的x0引脚。
- 成本:17
- 用电量:490
- 代码行数:20
接下来是tcp
的版本。由于代码恰好能塞进一个MC6000控制器,所以面板非常简洁,而且控制器的六个引脚全部用上。
- 成本:9
- 用电量:238
- 代码行数:14
最后上一张资源消耗对比图。
现在用高级语言写代码可以随心所欲,还得感谢硬件的发展啊。要是回到过去,那可得绞尽脑汁去进行优化的。
Lv.17 - 共生环境维护机器人
1 | * *无线rx*是连接无线接收器的非阻塞式 XBus 输入。 |
这关的难点在于,如何利用gen输出0和100之外的数值。开始想了半天,后来才发现,只要在slp之前覆盖简单输出的值,就可以偷梁换柱了。具体实现就是下面这样。
1 | tcp dat acc |
至于分区处理,还是比较好想到的。电机单独控制,然后清洁工具和加液工具一起控制。两个控制器之间使用slx来通信,机制比较类似于callback。电机当前的位置用acc记录,目标位置从x0获取之后存放在dat,因为只有acc可以进行加减操作,dat只能存放数据。
- 成本:10
- 用电量:217
- 代码行数:23
Lv.18 - 远程退出开关
1 | * *无线rx*是连接无线接收器的非阻塞式 XBus 输入。 |
这关需要提供三个p引脚输出,很明显要用到DX300。DX300接收的是一个三位数,这里就要利用dst指令来给指定数位赋值。然而dst指令是只能作用于acc的,这关还额外要求我们设计一个倒计时功能。注意dat是无法进行运算操作的,只是一个储存器,因此很明显我们需要两个acc。
这里我采用的方案是,用一个MC4000当倒计时器,MC6000当作主控。答案似乎不是最优解,耗电量超过了平均值,但是懒得优化了。
- 成本:9
- 用电量:798
- 代码行数:20
Lv.XX - 未完待续。。。
Lv.19有点难,所以本文好像要烂尾了
最后更新:2021/2/28