
计算机里所有的电子元件都可以基于一种叫做“与非门”(NAND gate)的基本元件而实现。在本游戏中,你将会面对一系列挑战,在求解谜题的过程中,走出从基础逻辑门通向算术单元、存储器等复杂元件的道路,并沿着这条道路最终学习如何搭建完整的处理器架构。完成所有主线关卡后,你将对处理器架构、汇编语言和电子元件彼此之间的具体联系产生更加深刻的理解。你也会了解高级编程语言中常见的条件判断、循环、函数等概念是如何在汇编和硬件层面具体实现的。
本游戏是基于一个强大的电路模拟器而开发的。这个电路模拟器允许你自由发挥想象力,以不同的解法通过各个关卡,或以自己喜欢的方式搭建属于自己的计算机。你可以随心所欲地在你的计算机上连接显示屏、计时器、声音元件等部件,也可以接收现实生活中的键盘和网络发送的数据。你甚至可以为你自己的计算机设计一套自己专属的汇编语言。
一个相当不错的游戏,UP还没学过计算机组成原理和数字电路,第一次玩起来还是有相当大的难度的说。想着做一个攻略记录一下学习的过程,于是时隔半年再次打开游戏,然后发现快忘完了喵呜。所以做攻略的时候其实是在考古之前的遗迹喵。有一说一这个玩的真的很上头呐,从与非门到处理器架构,听起来句超级帅好伐。虽然一路遇到很多困难,也想过放弃,最后还是一点一点坚持下来了。看着自己的架构一点点完善,指令越来越丰富,布线慢慢调整优美,真的非常有成就感呢,也希望你能够在这个游戏里面有所收获呐。
最后有点失落那个外星人通关了一点话都没有说,本来还想听个 congratulation 的说。另外攻略基于的版本是 0.1055 Beta 应该影响不大其实。本人的游戏存档可以在这里获得:https://github.com/ETO-QSH/ObsoleteProjectETO/TuringCompleteSaves。存档路径:C:\Users\{YourName}\AppData\Roaming\Godot\app_userdata。以后有时间我应该会在我的架构上进一步进行扩展,写一个小游戏,最终想办法搓一个类似小游戏机的东西出来呢,也算是一个留恋吧。游戏中有什么疑问也可以在评论区贴出来,如果我能够解决我也会去尽可能帮助的呐。
恭喜恭喜,你被绑架了!
我们将对你展开生命体智力测试。
测试规则很简单,造一台电脑就行。我们将会吃掉不具备这种智力的生物。祝你好运。

你在上个测试中发挥得不错!
我们因此初步认定你大概不是一种植物。

正确!你成功解锁了与非门(NAND)。利用这个元件,你就能造出电脑里所有的东西了!
既然你已经解锁了与非门,那么接下来你就可以开始学着搭建一些电路了。
上一关里,你填好了一个与电路的输出相匹配的真值表。这一关里,你的任务是搭建一个与给定的真值表匹配的电路。

眼下,在地球上所有接受测试的对象中,大象的成绩遥遥领先。考虑到你们大脑的尺寸差异,你做得其实也还不错。

地球上大多数生物充满了攻击性,也不知道怎么集中注意力。
要想成功通过我们的测验,你就要学会逻辑思考,不能只想着搞破坏。

高兴点,地球人。能参与我们的实验是你的荣耀。
你可是获得了与银河系中最先进的文明对话的良机。


你也知道,我们进行的这一系列测试,最终的成果将是一台完整的电脑。
按照我们的法律,能完成这一任务的生物会被认定为具有基本的知觉。
这是我们考虑不吃掉你的首要原因。





这关是我最喜欢的消遣小游戏。在规定时间里把十进制数转成二进制数。

我们的科技非常先进,连叠袜子这样的事情都是机器自动完成的。但是不巧,叠袜子机上给袜子配对的检测器刚才坏掉了。


在我们的教育系统中,传统的教学方法是骗学生走错误的路,然后对他们发出大声嘲笑。
我不清楚这对学生有没有好处,但是老师们都很喜欢这么干。


我们用数字丈量宇宙。所以你造的电脑必须学会计数。




上一关里,你已经知道了我们不允许电路中存在循环依赖。现在你需要掌握一种例外情况。


我们正在试验背景图像如何影响地球生物的认知功能。

也许你会觉得把无法通过测试的所有地球生物都吃掉是不道德的?
但事实上这不构成什么伦理问题,因为你们都是很好的野生动物,对我们来说珍惜野味是种美德。

我们先前的数学模型使用大脑体积来作为智力高低的主要判据。现在看来,这个模型还是过于简单了。
制造和使用工具的能力在智力的早期发展过程中起着决定性的作用。
所以很显然,胳膊的数量才应该是智力的主要判据。你知道吗?地球上有种海洋生物有八条胳膊,它们在我们的测试里表现得更好。


要知道事物之间的差异,你需要减法。而要得到减法,首先你需要负数。

虽然他没能通过我们的测试,但我们最终决定留下他的狗。和其他的地球生物不同,狗狗毛茸茸的,还能遵守简单的命令。
我们也许会要你们俩组队,因为你们的优缺点正好彼此互补。



延迟线允许我们将一个输入值暂存1刻。不过一个能将输入值保存更长时间的元件会更有用。
我们希望你能搭建这么一个元件。




我们让实习生给这个元件添加了一个“禁用”位。我们受不了他总是四处转悠、恳求我们给他一点无聊乏味的活儿来做。

你能在这有限的空间里塞下4个字节的寄存器吗?

在我们的先进文明中,强迫囚犯做极其琐碎的工作是奴役行径,技术上讲这是不合法的。所以我们让我们的实习生为你的组件创建了一个256字节的版本。
计数是一项昆虫也具备的基本能力。有了计数能力,生物就有了进行比较和算术的能力。然后在不知不觉间,猴子们也就能学会制造电脑了。
搭建一个元件,使其每一刻输出的数值都会自动增长。


是时候开启你的主要项目了,从现在开始,一步步搭建『OVERTURE』计算机架构吧。它实实在在地是一台图灵完备的机器,无论从什么角度讲都称得上是一台真正的计算机!
我把这一关中红色元件的位置锁定了,因为你的布线总是乱糟糟,没有留下足够的空间。从现在开始,你设计的这些乱七八糟的电路会在各关卡之间同步,这意味着你不再需要每关都从零开始。

本关并不是一个挑战关卡,而是一个工具性关卡。你随时可以选择回到关卡选择界面,继续完成后续任务。

你之前在“寄存器之间”一关中建立的电路可以在寄存器之间复制数值,而在“算术引擎”一关中建立的电路可以对2个输入做不同的算术运算。但在后续的关卡中你将需要在同一个电路中同时进行这两种操作。要做到这一点,你需要建立一个“解码器”。它能根据指令中我们尚未使用的那两位数值,来决定我们的计算机处于何种模式。

这一关里,所有的寄存器都拥有一个额外的输出引脚。这个输出引脚会无视读取引脚的输入,始终输出寄存器中存储的数值。




到目前为止,我们所有的程序都只能逐字节地按顺序执行。
在之前的关卡中,只有代码能影响数据,现在也该是时候让数据来影响代码运行了。添加了条件跳转的能力后,我们的电脑就能运行任何算法,完成一切可行的计算了。

你居然成功了,地球人!我本来以为你不过是看起来很奇怪的某种无毛猿类,但你确实造出一台真正的电脑来了,挺厉害!
你已经搭建好第一台计算机的硬件架构了,不过要想通过我们的测试,你还需要借助它完成走迷宫的测验。
只是鉴于你还不知道怎么在这台计算机上编程,目前你还没法完成这种挑战。
因此我们从我们的飞船上替你找了些差事,供你练手。



我们的飞船通常使用激光来摧毁接近的小行星。
为了校准激光炮,我们需要你借助复杂的数学公式来计算出小行星的周长。
2×π×r
半径 r 是输入值。
你可以将 π 近似为 3 。
计算完成后,请将结果发送到输出设备上。
你现在可以使用汇编代码编写你的程序了。汇编代码允许你在编辑器里为指令取一个别名,例如你可以用“add”代替“68”来表示加法。
汇编别名:
add --> 0b01000100 (68)
copy --> 0b10000000 (128)
io --> 0b00000110 (7)
程序代码:
# 1号寄存器 --> R
copy|io*8|1 # 0b10110001 (177) # 2号寄存器 --> R copy|1*8|2 # 0b10001010 (138) # 3号寄存器 --> 2R add # 0b01000100 (68) # 1号寄存器 --> 2R copy|3*8|1 # 0b01011001 (89) # 3号寄存器 --> 3R add # 0b01000100 (68) # 1号寄存器 --> 3R copy|3*8|1 # 0b01011001 (89) # 2号寄存器 --> 3R copy|3*8|2 # 0b01011010 (90) # 3号寄存器 --> 6R add # 0b01000100 (68) # 输出 6R 结果 copy|3*8|io # 0b01011110 (94)
我们飞船的货仓里了进了几只太空老鼠。
我们已将你的计算机连接到了我们的高科技机器人上,你的任务是给机器人编程,让它发射激光来清除太空老鼠。
注意,上一束激光仍在空中飞行时,机器人是无法发射新的激光的。
如果你想知道如何为机器人编程,请参阅机器人操作指南。代码编辑器里也有指向该手册词条的链接。
汇编别名:
eq_zero --> 0b11000001 (193)
程序代码:
# 机器人操作常量定义
const left 0
const step 1
const right 2
const sleep 3
const enter 4
const tap 5
# 来到就绪位置
tap copy|io step copy|io
# 停留循环
label sleep_loop
sleep copy|io # 等待
# 观察节点
label tap_loop
copy|io*8|3 # 输入至reg3
sleep_loop eq_zero # 观察到0继续停留
tap copy|io # 发射
copy|2*8|3 # 清空reg3
tap_loop eq_zero # 跳回进行观察
储藏室的保险门坏了,我们的老清洁工被锁在了里面。
这扇门总是无缘无故地改变密码,因此我们需要一个可以随时恢复密码的程序。
找到密码的最简单方法是尝试所有的组合,直到你找到正确的密码。
另外,当你猜测的数值过高时,这个坏掉的锁会发出奇怪的哗哗声。利用这一点,你或许可以找到更高效的解法。


在我们星球上每个星期有 4 天,分别是星期零,星期壹,星期贰和星期叁。我听说有的地球人会把星期贰的日子算错?
我对此并不感到奇怪。
不管怎么说,新年就要到了。我们需要你来算一下每个人的生日都在一星期里的哪一天。我会给你日期,你只需要在8个时钟刻内给我报上那是星期几就行。


传说中的迷宫。如果你解决了这个难题,你就通过了测试!
程序代码:
# 左手法则
label go
step copy|io # 前进
left copy|io # 左转
# 观察环境
label see
copy|io*8|3 # 观察前方
go eq_zero # 没有障碍物就跳回前进
right copy|io # 否则右转回去
enter copy|io # 有门自然会开
see always # 无条件跳转
汇编别名:
or --> 0b01000000 (64)
nand --> 0b01000001 (65)
程序代码:
# 读取输入,进行按位或
copy|io*8|1 copy|io*8|2 or
# 进行按位与非
copy|3*8|4 nand
# 进行按位与
copy|3*8| 1 copy|4*8| 2 and
# 输出(原理见:异或门 (XOR))
copy|3*8|io






终于是时候让你构建『LEG』体系了!







恭喜,你已经实现了『LEG』计算机的全部功能!
我将逐步向你展示一些可能的升级操作,直到你实现函数调用功能为止。但从今往后,一切实现细节由你自己决定,我不会再要求你应该使用什么操作码,也不会给你施加其他诸如此类的限制。
这关是我第二喜欢的消遣小游戏。在规定时间里把十进制数转成十六进制数。


除了左移元件以外,我们还找了个实习生,让他帮你解锁了右移元件。他的工作很简单,把你的电路图镜像翻转一下就行了。
我推荐把左移和右移功能添加到你的计算机硬件里,以便在后续的关卡里使用。


汇编别名:
i --> 0b10000000 (128)
j --> 0b01000000 (64)
add --> 0b00000000 (0)
sub --> 0b00000001 (1)
read --> 0b00110000 (48)
write --> 0b00110001 (49)
eq --> 0b00100000 (32)
neq --> 0b00100001 (33)
xq --> 0b00100010 (34)
dq --> 0b00100100 (36)
deq --> 0b00100101 (37)
pop --> 0b00110010 (50)
push --> 0b00110011 (51)
io --> 0b00000111 (7)
程序代码:
# 初始化数组长度
i|j|add 32 0 2
# 循环读取数据至内存
label input
i|add 0 io 5
write 5 3 0
i|add 1 3 3
j|sub 2 1 2
dq 2 0 input
# 重新初始化寄存器
i|j|add 32 0 2
i|j|add 0 0 3
# 循环读取内存以输出
label output
read 0 3 5
i|add 0 5 io
i|add 1 3 3
j|sub 2 1 2
dq 2 0 output
所有元件都有一个延迟量。一个电路中,总的延迟量是由延迟最严重(速度最慢)的那条路径决定的。为了减小延迟量,你可以把逻辑门并行排列,使它们能同时进行运算。在本关里,你要向我证明你能够理解这些概念。

将两个 4 位数字相乘,便可以得到最多 8 位的乘积。设计一个电路,实现此乘法功能。

你刚才已经设计出了两路 4 位输入的乘法装置。我们让我们的实习生免费把它扩展成了 8 位乘法器。
为了削减开支,我们决定改变服务窗口的排队制度,以减少来访的人数。我们将用“先来后服务”的模式代替原先的“先来先服务”模式。试想一摞叠得高高的文件,公民们只能把提交的文件放到最上面(称压栈,即PUSH),而办事的员工也从最上面拿走文件开始处理(称弹栈,即POP)。这种数据结构的名称叫栈(stack),我们希望你能够在硬件层面实现它。

欢迎来到实验室。和元件工坊一样,这不是一个主线关卡,而是我们为你提供的工具性关卡。
在出错的硬件上编程会很麻烦。一边编程,一边还要排查电路中的错误,这种体验非常痛苦。你会忍不住去随手修复一下硬件上的问题,以便继续回头写代码。但这些硬件层面的改动很可能又会弄坏别的功能。所以在开始编程前,尽量确保你的硬件能做到100%可靠吧!
另外,因为你已经完成了『LEG』架构,我替你解锁了沙盒中的 16 位、 32 位和 64 位元件。

程序代码:
# 读取被除数和除数
i|add 0 io 2
i|add 0 io 3
label divmod
# 如果被除数小于除数跳出进行输出
xq 2 3 24
# 用被除数减去除数
sub 2 3 2
j|add 4 1 4
# 否则重复减去除数
deq 2 3 divmod
# 输出商和余数
add 4 0 io
add 2 0 io
程序代码:
label main
j|add io 0 2 # 读取输入
eq 1 2 16 # 为 0 出栈否则入栈
# 入栈分支
push 2 0 0
eq 0 0 main
# 出栈分支
pop 0 0 io
eq 0 0 main
我们最近的经费预算有点紧张,只能给大家降薪,结果现在实验室的助手们都罢工了。所以,这一关卡里你需要亲自评估设计的成果。当然,这也能让我们测试你的可信度和成熟度。

讲道理这个题写不写都不影响后面的挑战
call和ret完全用不上的说,我第一次通关也是直接跳过的喵
但是如果想搞我也给出我的设计方案呐,就是一套下来东西怪多的说
因为没有官方的测试,我还编写了一段递归斐波那契数列方便测试
以及搞了一个时尚小垃圾方便可视化结果呢





汇编别名:
call --> 0b00110100 (52)
ret --> 0b00110101 (53)
程序代码:
label main
i|j|add 0 6 1 # 索引计数
call fib 0 0 # 函数调用
i|add 0 2 io # 结果输出
label fib
i|add 0 1 3
j|dq 3 2 else
i|j|add 0 1 2
ret 0 0 0
label else
push 3 0 0
push 4 0 0
push 5 0 0
# 计算fib(n-1)
j|sub 3 1 3
i|add 0 3 1
call fib 0 0
pop 0 0 5
pop 0 0 4
pop 0 0 3
i|add 0 2 4
push 3 0 0
push 4 0 0
push 5 0 0
# 计算fib(n-2)
j|sub 3 2 3
i|add 0 3 1
call fib 0 0
pop 0 0 5
pop 0 0 4
pop 0 0 3
i|add 0 2 5
# 返回结果
add 4 5 2
ret 0 0 0
NAK 02 是我们的智能工程机器人。它很聪明,但有些时候会耍流氓,甚至在飞船上煽动叛乱。
这一回它又占据了飞船主控室,还劫持了船长。
它唯一的弱点是好赌。我们已经吸引了它的注意力,让它跟你来打牌。它跟我们保证,如果你赢了,它就投降。
一定要赢,你是我们唯一的希望了!

机器人竞赛是飞船上最受欢迎的运动。不同程序控制的机器人需要完成同一组障碍赛。在完赛的程序中,谁的代码量最少,谁就是赢家。
这次你控制的机器人叫Fastbot,他看不到面前的东西,但它可以同时完成转身和前进的动作。另外,它还有双帅气的红色跑鞋。

程序代码:
# 如果开10kHz,会出现“量子隧串效应”
# 最后6个码可以不用写,从64优化到58喵
3 0 1 0 0 3 2 3 0 3 2 2 1 2 3 3
0 3 2 3 3 0 1 0 3 0 1 1 2 1 0 0
0 3 2 3 3 0 1 0 3 0 1 1 2 1 0 1
1 2 3 2 2 1 0 1 2 1 1 0 0 3 0 1
你们星球上最令人难忘的是水果,它们确实很美味。
因此我们要在食堂举办一个水果尝鲜活动。
但我们要确保不会两次送上同样的水果供人品尝,不然场面会很尴尬。
程序代码:
# 走到操作台(是不是有点太远了)
i|j|add 0 0 io
i|j|add 0 1 io
i|j|add 0 0 io
i|j|add 0 1 io
i|j|add 0 1 io
i|j|add 0 1 io
i|j|add 0 1 io
i|j|add 0 0 io
i|j|add 0 1 io
i|j|add 0 2 io
i|j|add 0 1 io
# 轨道常数 92
i|j|add 92 0 5
# 思路是将出现过的数字在RAM对应位置记录
# 如果记录过则说明出现过
# 这个方法可以在内存充足的时候高效排重
label main
j|add io 0 1 # 观察前方
eq 5 1 wait # 如果是轨道就等到
read 0 1 2 # 否则读取内存地址
eq 5 2 wait # 如果值为轨道等待
neq 2 4 break # 同时不为 0 则说明水果出现过
write 1 1 0 # 否则将水果数值写入对应地址
# 等待等待
label wait
i|j|add 0 3 io
eq 0 0 main
# 跑去按按钮
label break
i|j|add 0 2 io
i|j|add 0 4 io

我们正在为银河系的食物百科全书编写有关人类食物的条目。我们的语言中没有字母表,因此百科全书中的条目是按美味程度排序的。
程序代码:
# 方法一:
# 经典的冒泡排序算法,时间复杂度:O(n^2)
# 同时使用了n(15)个RAM位置存储数组
# 现将输入全部读取在内存中
label input
i|add 0 io 1
write 1 3 0
i|add 1 3 3
i|dq 15 3 input
# 记录排序指针
i|j|add 0 15 1
# 重置指针以及缩短边界
label wait
j|add 0 1 0
i|j|sub 0 1 2
i|j|add 0 0 3
j|sub 1 1 1
i|eq 0 1 output
# 向右读取两个数字
label main
i|add 1 2 2
i|add 1 3 3
read 0 2 4
read 0 3 5
eq 1 2 wait # 如果指针到达边界
dq 4 5 rec # 如果左边大于右边
eq 0 0 main # 否则指针向前推
# 交互两个元素的位置
label rec
write 4 3 0
write 5 2 0
eq 0 0 main
# 将答案逐个输出
label output
read 0 1 io
i|add 1 1 1
eq 0 0 output
# 方法二:
# 思路是用RAM开集合
# 优点是时间复杂度只有O(n)且代码实现简单
# 缺点是输出的时候要遍历整个RAM
# 数据较少的时候利用率低
label input
i|add 0 io 1
# 对应地址位数值加加
read 0 1 0
i|add 1 0 0
write 0 1 0
# 循环读取输入
i|add 1 2 2
i|dq 15 2 input
add 0 0 1
# 遍历RAM进行输出
label output
read 0 1 2
i|add 1 1 1
i|eq 0 2 output
# 按照出现的次数(RAM数值)多次输出
label while
j|sub 1 1 io
j|sub 2 1 2
j|dq 2 0 while
eq 0 0 output
我们都喜欢机器人在舞池中的动作。因此我们希望他能带领我们的舞蹈队。
唯一的问题是如何让他想出原创的舞蹈序列。
你或许会问,如何从决定性的逻辑中诞生出创造性的力量?
答案是通过伪随机数生成器。

汇编别名:
and --> 0b00000010 (2)
rol --> 0b00000110 (6)
ror --> 0b00000111 (7)
程序代码:
# 经典的RPNG随机数算法
# 我这里使用rol和ror,而不是shl,shr
# 主要原因的我的ALU被塞满了果咩(虽然好多都用不上)
# 前者实现后者的效果只需要AND与一下进行掩膜
# 而后者会完全丢失数据,所以我的ALU采用的是前者
# 如果你不想怎么麻烦可以直接把ALU里面对应的元件进行替换即可
i|add 0 io 1 # 随机数种子
label prng
j|ror 1 1 2
j|and 2 127 2
xor 1 2 3 # step 1
j|rol 3 1 4
j|and 4 254 4
xor 3 4 5 # step 2
j|ror 5 2 2
j|and 2 63 2
xor 5 2 1 # step 3
j|mod 1 4 3
i|add 0 3 io # step 4
eq 0 0 prng
我们需要你帮忙清理一下地下室。
我们想让你从旧的反应堆之中取出具有放射性的碟片,并将它们按顺序叠好。请不要将较大的碟片摞在较小的碟片之上,否则反应堆将会爆炸。
方法一(不使用函数,而是压缩指令集):




汇编别名:
shift --> 0b00110110 (54)
程序代码:
# 读取输入建立映射表
i|add 0 io 3
i|add 0 io 0
i|add 0 io 1
i|add 0 io 2
shift 1 # 切换至二位码模式
# 先进后出数据栈
push 1 push 0
push 1 push 2 push 0 push 2
push 1 push 0 push 2 push 1
push 2 push 0 push 1 push 0
push 1 push 2 push 0 push 2
push 0 push 1 push 2 push 1
push 0 push 2 push 1 push 0
push 1 push 2 push 0 push 2
push 1 push 0 push 2 push 1
push 2 push 0 push 1 push 0
push 2 push 1 push 0 push 2
push 0 push 1 push 2 push 1
push 2 push 0 push 1 push 0
push 1 push 2 push 0 push 2
push 1 push 0 push 2 push 1
push 2 push 0 push 1 push 0
shift 0 # 切换至四位码模式
# 数据循环出栈
label for
pop 0 0 io
i|j|add 0 5 io
eq 0 0 for
方法二(使用函数,进行递归操作):
程序代码:
# 具体代码可以查看这个视频
# 网页链接
# 对应思路抄过来的说不想努力了喵
# 讲道理还是我上面那个写法比较帅喵
const catch 5
j|add io 0 1
j|add io 0 2
j|add io 0 3
j|add io 0 4
label main
call move 0 0
eq 0 0 main
label move
j|eq 1 0 zero
push 1 0 0
call move_1 0 0
pop 0 0 1
call move_0 0 0
push 1 0 0
call move_2 0 0
pop 0 0 1
label end
ret 0 0 0
label zero
call move_0 0 0
eq 0 0 end
pop 0 0 1
ret 0 0 0
label move_0
j|add 2 0 io
i|j|add catch 0 io
j|add 3 0 io
i|j|add catch 0 io
ret 0 0 0
label move_1
j|sub 1 1 1
j|add 3 0 5
j|add 4 0 3
j|add 5 0 4
call move 0 0
j|add 3 0 5
j|add 4 0 3
j|add 5 0 4
ret 0 0 0
label move_2
j|sub 1 1 1
j|add 2 0 5
j|add 4 0 2
j|add 5 0 4
call move 0 0
j|add 2 0 5
j|add 4 0 2
j|add 5 0 4
ret 0 0 0
我们让实习生用人类的文字来输入星球名称,可惜他忘记将每个名称的首字母大写了。
程序代码:
# 初始化变量
i|j|add 32 0 2
i|j|sub 0 1 3
# 读取字符
label main
i|add 0 io 1
j|add 3 1 3
# 判断字符类型
eq 1 2 jump
eq 3 4 first
# 非首字母小写
i|add 0 1 io
eq 0 0 main
# 非法字符跳过
label jump
i|j|add 32 0 io
i|add 0 io 1
sub 1 2 io
eq 0 0 main
# 首字母大写
label first
sub 1 2 io
eq 0 0 main
好消息,我们要把地球变成一个异星的水上乐园!
我们需要你帮助我们为“海盗瀑布”水滑梯找到一个合适的地点。具体地说,我们正在寻找一个能容纳大量水的地方。
程序代码:
# 算法:双指针接雨水
# 这个算法采用双指针法,通过一次遍历数组
# 利用两个指针分别从数组两端向中间移动
# 同时维护两个变量分别记录当前指针左侧和右侧的最大高度
# 根据最大高度的大小关系,动态结算当前指针位置的雨水量
# 并相应移动指针,直到两个指针相遇
# 这种方法将时间复杂度优化到 O(n),空间复杂度优化到 O(1)
# 循环读取数组 - h
label input
i|add 0 io 2 # 读取输入到寄存器
write 2 1 0 # 寄存器写入到RAM
i|add 1 1 1 # 读取计数,地址加加
i|dq 16 1 input # 读取16个退出
# 初始化变量
i|j|add 0 0 1 # result
i|j|add 0 0 2 # left
i|j|sub 16 1 3 # right
# do-while主循环
label while
read 0 2 4 # h[left]
read 0 3 5 # h[right]
deq 4 5 else # h[left] < h[right]
j|read 0 16 5 # leftMax
xq 4 5 else_1 # h[left] < leftMax
j|write 4 16 0 # leftMax = h[left]
eq 0 0 out_1 # 无条件跳出
label else_1
add 1 5 1 # res += leftMax
sub 1 4 1 # res -= h[left]
label out_1
j|add 2 1 2 # left ++
eq 0 0 out_3 # 无条件跳出
label else
j|read 0 16 4 # rightMax
xq 5 4 else_2 # h[right] < rightMax
j|write 5 16 0 # rightMax = h[right]
eq 0 0 out_2 # 无条件跳出
label else_2
add 1 4 1 # res += rightMax
sub 1 5 1 # res -= h[right]
label out_2
j|sub 3 1 3 # right --
label out_3
xq 2 3 while # left < right
# 输出结果
i|add 0 1 io
C语言:
#include <stdio.h>
int main() { int heightSize = 16; int height[] = {4, 6, 1, 4, 6, 5, 1, 4, 1, 2, 6, 5, 6, 1, 4, 2}; int leftMax = 0, rightMax = 0; // 分别记录左侧和右侧的最大高度 int left = 0, right = heightSize - 1; // 左指针和右指针 int result = 0; // 用于存储结果 do { if (height[left] < height[right]) { // 如果左侧的高度小于右侧的高度 if (height[left] >= leftMax) { // 更新左侧的最大高度 leftMax = height[left]; } else { // 左侧结算 result += leftMax - height[left]; } left++; // 左指针右移 } else { // 如果左侧的高度大于等于右侧的高度 if (height[right] >= rightMax) { // 更新右侧的最大高度 rightMax = height[right]; } else { // 右侧结算 result += rightMax - height[right]; } right--; // 右指针左移 } } while (left < right); // 当左指针小于等于右指针时,继续循环 printf("Trapped water: %d\n", result); return 0; }

