【TIS-100 攻略】TIS-NET 第 8~9 关:第二大数字选择器、十进制数位分解器

本文首发于 B 站《TIS-100》文集(https://www.bilibili.com/read/readlist/rl626023)。原创不易,转载请注明出处。
TIS-NET 第 8 关《第二大数字选择器》(Submaximum Selector)关卡展示

TIS-NET 系列的前 7 关还是比较容易的,这一关遇到的是第一个比较难的挑战。本关你需要将 IN.A ~ IN.D 这四路输入里第二大的数字给找出来并输出。
我们设找到的第一大数字为 X,第二大数字为 Y。那么我们需要做的事情就是计算出 Y 是多少,并输出。C 语言代码如下:
将以上 C 语言代码转换成 TIS-100 代码,如下:

1 号节点将 a 向右发送两遍,以便 b 和 a 能够比较大小(mov up acc, mov acc right, mov acc right)。2 号节点用来比较 a 和 b 的大小,然后令 x = 较大者,y = 较小者。逻辑如下:
2 号节点拿到 b 后(mov up acc),
首先将其复制一份到 bak 中(sav),
然后计算 b-a 的值(sub left)。
b-a <= 0 时按顺序执行,b-a > 0 时跳到第 8 行执行(jgz 8)。
这里我们选择在 bak 里放入 x,在 acc 里放入 y。当 b-a <= 0 时,x=a,y=b,此时从左边读入 a(mov left acc)
然后发现,acc = a, bak = b。这时候我们要将原先放在 bak 里的 b 换到 acc 里(swp)
(jmp 9)
当 b-a > 0 时,x=b,y=a,此时正常将 a 放入 acc 中,bak 里的数不变(mov left acc)。
完成后,我们先将 acc 里的 y 向右发送两遍(mov acc right)
(mov acc right)
再将 bak 里的 x 向右发送一遍(swp)
(mov acc right)。
接下来是 3 号节点:
3 号节点拿到 c 后(mov up acc),
首先计算 c-y 的值(sub left)。
c-y <= 0 时按顺序执行,c-y > 0 时跳到第 6 行执行(jgz 6)。
c-y <= 0 时,说明 c <= y,c 没有对前两名构成威胁,此时直接将 acc 覆盖为左边二次提供的原始 y(mov left acc)
(jmp 7)
c-y > 0 时,说明 c > y,c 至少是前两名之一,原始 y 出局,此时令 acc 加回一个原始 y,将 c-y 变回成 c(add left)。
3 号节点里至此已经写不下 c 继续跟 x 比大小的逻辑了,但我们知道 x 一定是前两名之一,所以我们将原始的 x 向右发一遍(mov left right),
将 y 的值向右发两遍(mov acc right)
(mov acc right)让右边的 4 号节点来完成这个【给前两名排序】的工作。
4 号节点首先将收到的 d 值向下传,接下来的 2~9 行代码跟 2 号节点大同小异,就是比较 3 号节点发来的两个数的大小,确定最新的 x 和 y 的值,其中第二个数会发两遍(mov left acc, sav, sub left, jgz 9, mov left acc, swp, jmp a, mov left acc)。我就不再解释了。第 10~14 行,我们将得到的最新的 y 值和 x 值各向下发两遍(mov acc down, mov acc down, swp, mov acc down, mov acc down)。
5 号和 6 号节点纯粹给 7 号节点传话(mov up left, mov right down)。
7 号节点会依次收到一次 d、两次 y 和两次 x。这个节点要在 x、y 和 d 中找到第二大的数,逻辑如下:
首先计算 d-y 的值(mov up acc)
(sub up)
然后判定:d-y <= 0 时按顺序执行,d-y > 0 时跳到第 6 行执行(jgz 6)。
d-y <= 0 时,说明 d <= y,没有对前两名构成威胁,输出的值是原始 y(mov up acc)。
(jmp 7)这里要注意:由于上方会固定发送一次 d、两次 y、两次 x,所以即使我们已经提前得出结论了,也仍然要和接下来的 x 白白比较一次,以便把最后两次 x 消耗掉。这就是为什么这里写的是 mov up acc, jmp 7 而不是 mov up down, jmp 1 的原因。
d-y > 0 时,说明 d > y,d 至少是前两名之一,原始 y 出局,此时令 acc 加回一个原始 y,将 d-y 变回成 d,作为新的 y(add up)。
这时候这个新的 y(或者原始的 y)还要再跟 x 比一次,计算 y-x 的值(sub up)。
若 y-x > 0,跳到第 12 行执行(jgz c)
若 y-x <= 0,说明这个新的 y 是第二名,我们令 acc 加回一个 x(add up),
将 y-x 变回成 y 后输出(mov acc down)。
(jmp 1)
若 y-x > 0,说明这个新的 y 是第一名,而原始的 x 是第二名,我们要输出 x 的值(mov up down)。
点击左下角的【RUN】,稍等片刻,便会弹出结算界面:


TIS-NET 第 9 关《十进制数位分解器》(Decimal Decomposer)关卡展示

本关要求分解 IN 中的数字,将读入数字的百位、十位、个位分别写入 OUT.X、OUT.Y 和 OUT.Z。当读入的数字不满三位数时,没有数字的高位视为 0。
本关的本质是除法计算:首先将得到的数字除以 100,得到的商就是百位数,然后将剩下的余数再除以 10,第二次得到的商就是十位数,余数就是个位数。
本关的除法最多做 10 次运算就能得出结论,线性查找和二分查找的效率相当,因此我就不提供二分查找的做法了。代码如下:

上方两个节点纯传话(mov up down, mov up down)。
IN 输入在第二纵列,正对着 OUT.Y 这个出口。所以 OUT.Y 上方的节点收到 IN 后,先将这个数字传给左边的节点,让它计算出百位数后,将剩下的余数再发给自己。现在我们将目光暂时移动到左边:
左边的节点收到 IN 后(mov right acc),开始做除以 100 的运算。
每当 acc 减去一个 100(sub 100),就判断是否减到了负数。
减到了负数时,跳到第 8 行执行(jlz 8);
尚未减到负数时,令 bak 加上 1(swp)
(add 1)
(swp)
然后跳回第 2 行继续减(jmp 2),
直到减到了负数为止,将这个【多减了 100 的余数】传给右边(mov acc right)
并清除(sub acc)。
然后我们自己将 bak 里的商,即百位(swp)
发给下方的 OUT.X(mov acc down)。
然后回头来看中间的节点:
中间的节点在把 IN 传给左边后(mov up left),就一直在等待左边传来【多减了 100 的余数】。
收到左边传的【多减了 100 的余数】后,需要加上 100 才能开始做除以 10 的运算。由于第一次运算一定要减去 10,所以我们改为加上 90(mov 90 acc)
(add left)
这时候开始判断 acc 是否减到了负数。减到了负数时,跳到第 10 行执行(jlz a);
尚未减到负数时,令 bak 加上 1(swp)
(add 1)
(swp)
令 acc 继续减 10(sub 10),
然后跳回第 4 行继续判断(jmp 4),
直到减到了负数为止,我们将【多减了 10 的余数】加回一个 10(add 10),
传给右边并清除(mov acc right)
并清除(sub acc),
然后我们自己将 bak 里的商,即十位(swp)
发给下方的 OUT.Y(mov acc down)。
右边的节点收到左边传的余数后,直接将这个余数(个位)发给下方的 OUT.Z(mov left down)。
点击左下角的【RUN】,稍等片刻,便会弹出结算界面:
