【深圳 IO 攻略】番外篇:巧用 ROM 打表提高運行效率

本文首發(fā)于 B 站《深圳 IO》文集(https://www.bilibili.com/read/readlist/rl569860)。原創(chuàng)不易,轉(zhuǎn)載請注明出處。
ROM 的作用有兩個,一是像第 16 關(guān)《幽靈娃娃》那樣存儲需要連續(xù)讀取的大量數(shù)據(jù),二是建立大量的地址→數(shù)值的映射對(俗稱【打表】)。以往的攻略里,我只在第 30 關(guān)《航空雞尾酒調(diào)酒器》和阿瓦隆城第 8 關(guān)《腦機接口》里使用了 ROM 打表。其實還有很多很多關(guān)卡可以使用打表的方式提高運行效率,這里舉幾例:
示例 1:第 13 關(guān)《古錢幣付款終端》
這一關(guān)在之前的?【深圳 IO 攻略】番外篇:調(diào)整代碼執(zhí)行順序以節(jié)省行數(shù) 里已經(jīng)做了一次優(yōu)化。當時的電路板是這樣的:

現(xiàn)在,我們使用 ROM,去掉三態(tài)判定,電路板變成了下面的樣子:

線路板右邊沒有變化,左邊的變化比較大:

【價格】常數(shù)芯片本應接到 x 口上,但是加了一塊 ROM 后,上方 MC6000 的 x 口不夠用了:ROM 用掉兩個,DX-300 用掉一個,和右側(cè)芯片通訊用掉一個,四個 x 口全用完。所以左下方的 MC4000 只做了一件事:將【價格】常數(shù)芯片轉(zhuǎn)成 p 口信號,供上方的 MC6000 使用(@ mov x1 p1)。
ROM 中存的是 DX-300 的三位數(shù)與金額增量間的映射。當用戶投入 1 元硬幣時,DX-300 = 1,所以 ROM 的 1 號地址里寫的增量值是 1;當用戶投入 5 元硬幣時,DX-300 = 10,所以 ROM 的 10 號地址里寫的增量值是 5;當用戶投入 12 元硬幣時,DX-300 = 100,100 mod 14 = 2,所以 ROM 的 2 號地址里寫的增量值是 12。其余位置的值可以無視。
最后是上方的 MC6000。首先檢查 DX-300 的值是否為 0(tcp x2 0),若為 0,則本輪沒有任何金額增量,關(guān)閉所有 + - 號指令,直接跳到最后睡眠。本輪有金額增量時,將 DX-300 的值置為 ROM 的地址,然后讀取一格 ROM,得到映射的金額增量(+ mov x2 x1, + add x0)。此時判定用戶投入的金額是否是否【不小于】價格(+ tlt acc p0)。若【仍小于】價格,則 + 號激活,跳過所有 - 號前綴的代碼,直奔最后休眠;若【不小于】價格,則將 acc 減去價格,將得到找零金額發(fā)給右邊(- sub p0, - mov acc x3);做完這些后,響鈴 4 秒,然后清空 acc,迎接下一次任務(- gen p1 4 2, - mov 0 acc, slp 1)。
點擊左下角的【模擬】,稍等片刻,便會彈出結(jié)算界面:

上一版方案耗電量為 284,代碼行數(shù)為 21 行;本方案改為 ROM 打表后,耗電量減少到了 255,代碼行數(shù)減少到了 19 行。只是,成本上,多了一塊 ROM,多了一塊用于轉(zhuǎn)換 p/x 信號的 MC4000,共計貴了 5 塊錢。
示例 2:第 15 關(guān)《卡賓槍瞄準照明器》
原版攻略:【深圳 IO 攻略】第 15 關(guān):卡賓槍瞄準照明器


上一版方案里,我們特意從 5 開始計數(shù),同時每秒計 5 個數(shù),就是為了將 1~6 換算成 10~35,然后對十位數(shù)做“三態(tài)判定”,把六種狀態(tài)壓縮成三種狀態(tài),以此來設置最后的點燈狀態(tài)。但如果你使用 ROM 打表的話,直接莽就完事了,根本不需要這些花里胡哨的技巧:


由于 1~6 秒的每種時間間隔都對應著【激光】和【泛光】兩個數(shù)字(也就是每個自變量 x 對應兩個因變量),所以我們在獲得映射值時,要先把 x 的值乘以 2 并設為地址值,然后連續(xù)讀兩格數(shù)據(jù)。我們觀察這個 ROM,不難發(fā)現(xiàn):
地址 2?和 4?開頭的兩格空間存的是時間間隔為 1 或 2 秒時的激光和泛光信號:激光 0,泛光 60,右側(cè)的 DX-300 激活 p0 口,三位數(shù)為 001。所以兩格數(shù)據(jù)依次是 0、1;
地址 6?和 8?開頭的兩格空間存的是時間間隔為 3 或 4 秒時的激光和泛光信號:激光 50,泛光 20,右側(cè)的 DX-300 激活 p2 口,三位數(shù)為 100。所以兩格數(shù)據(jù)依次是 50、100;
地址 10 和 12 開頭的兩格空間存的是時間間隔為 5 或 6 秒時的激光和泛光信號:激光 100,泛光 0,右側(cè)的 DX-300 不激活任何 p 口,三位數(shù)為 000。所以兩格數(shù)據(jù)依次是 100、0。
那么代碼就很容易解讀了:由于讀取 ROM 時,需要把地址設為經(jīng)過的時間 ×2,所以我們在計時的時候,每秒鐘需要計兩個數(shù)(slp 1, add 2)。然后,我們計算【雷達輸入】和【雷達輸出】信號的差值(tcp p0 x3)。差值為負時,【雷達輸出】信號激活,重置計時器(- sub acc);差值為正時,【雷達輸入】信號激活,從 ROM 中讀取根據(jù)當前時間差計算出的【激光】和【泛光】映射值。由于 acc 記錄的是經(jīng)過的時間 ×2 的值,所以我們直接將該值設為 ROM 的地址(+ mov acc x1)。接下來,依次讀取兩格 ROM,將讀到的值依次發(fā)送給【激光】和經(jīng)由?DX-300 轉(zhuǎn)接的【泛光】口(+ mov x0 p1, + mov x0 x2)。
點擊左下角的【模擬】,稍等片刻,便會彈出結(jié)算界面:

上一版方案耗電量為 183,代碼行數(shù)為 13;本方案改為 ROM 打表后,耗電量減少到了 160,代碼行數(shù)減少到了 7?行。ROM:我們的目標是,擯棄一切花里胡哨。
示例 3:第 31 關(guān)《安全網(wǎng)追蹤徽章》
原版攻略:【深圳 IO 攻略】第 31 關(guān):安全網(wǎng)追蹤徽章

上一版方案里,當“脈沖雷達”發(fā)生信號時,左側(cè)的芯片使用了大量的篇幅計算用戶當前所在的區(qū)域代碼。其實與其費勁地找規(guī)律,各種套數(shù)學公式,還不如打表來得爽快。我們打出如下的表:

脈沖雷達是 80 以上時,無需查表,直接短路返回 100。
脈沖雷達是 51~79 時,同步雷達和區(qū)域碼的映射關(guān)系是:0→600,20(mod 14 = 6)→700,40(mod 14 = 12)→700,60(mod 14 = 4)→700,80(mod 14 = 10)→700,100(mod 14 = 2)→700,因此我們在 ROM 的第 0 格里填入 600,在 ROM 的第 6、12、4、10、2 格內(nèi)填入 700。
脈沖雷達是 1~50 時,我們無法在以上格子內(nèi)填入映射值,但是可以填在其后的格子里。即:第 1 格填入 600,第 7 格填入 200,第 13 格填入 201,第 5 格填入 202,第 11 格填入 203,第 3 格填入 204。
還有第 8 和第 9 格沒填,我們可以將這些格子填入滿足第一個條件時短路返回的 100。

右邊的芯片沒有任何變化,重點是這塊加了 ROM 的左邊芯片:

首先等待右側(cè)芯片的喚醒信號(slx x3)。當【脈沖雷達】的值 >79 時,直接將 ROM 的地址指針定位到第 8 格(tgt x3 79, + mov 8 x1)。當【脈沖雷達】的值 ≤79 時,先將 ROM 地址置為同步雷達的值 mod 14(- mov p1 x1),然后檢查脈沖雷達是在 1~50 范圍還是 51~79 范圍。若在 1~50 范圍,則空讀一次數(shù)據(jù)口,令地址指針向后偏移一格(- tcp p0 51, - mov x0 null)。ROM 指針定位完畢后,讀一次數(shù)據(jù)口,將讀到的映射值發(fā)送給右邊的芯片,即完成任務(mov x0 x3)。
點擊左下角的【模擬】,稍等片刻,便會彈出結(jié)算界面:

電量由 366 降低到了 344,代碼行數(shù)也由 28 行減少到了 21 行。