《Replicube》實況:用 Lua 程式碼繪製體素立體物件,挑戰 18 道謎題

翻譯自 日本語

在 YouTube 開啟 ↗
概要

《Replicube》是一款關卡制的解謎遊戲,玩家要重現指定的 3D 物件。體素不是用手擺放,而是透過撰寫程式來配置。在這部影片中,實況主依序解完最初的教學題組 11 題,以及接著收到的追加題組 7 題。實況主的方針不是破完全部關卡,而是在完成這兩個題組後告一段落。整部影片中,實況主都把撰寫易讀的程式碼放在比壓低分數更優先的位置。

10分鐘閱讀
0:23

遊戲機制:接收座標、回傳顏色

根據教學說明,這款遊戲是在類似 Minecraft 的體素空間中,配置共 16 種顏色的體素。體素是意指「具有體積的像素」的造詞。配置時要使用程式碼:寫出永遠回傳白色的程式碼,整個空間就會被白色填滿;回傳紫色就是紫色,回傳藍色就是藍色。換句話說,就是撰寫一段接收每個體素的座標、並回傳該處要放置的顏色值的程式碼。實況主一邊說「已經開始頭痛起來了」,一邊表示最近玩的都是戰鬥類遊戲,偶爾玩玩這種和平的遊戲也不錯。

在遊戲中,會收到一個名為 Cubenet 的地方的管理員寄來的郵件,玩家要解開附件中的基礎題組。畫面上方顯示正確答案的形狀,下方則顯示目前程式碼所繪製的形狀。評分似乎也包含程式碼量和執行速度,但實況主把最佳化留作「之後的甜點」,決定先重視易讀性。理由是「程式碼基本上就是寫給人看的」。

1:47

1-1〜1-3:改寫顏色與條件分支

第 1 題只要讀一下現有程式碼的註解,就會知道只是回傳的顏色稍有不同。把 PURPLE 改成 RED 就過關了。

第 2 題要依位置塗成兩種顏色。由於已提供代表座標的變數 x、y、z,實況主寫了一個 if 判斷式:y 大於等於 1 時回傳 DARKBLUE,否則回傳 ORANGE,就此解開。

第 3 題的主題是比較運算子。實況主注意到在這個語言中,不等於要寫成「~=」,並預測近期應該會用到。題目本身和第 2 題幾乎是同一種模式,用「y 大於等於○○」這樣的條件就能解開。實況主也舉出不使用比較運算子的另一種解法,例如 return GREEN - (y + 4) // 3 * 2 這種寫法,但認為這類程式碼容易讓讀者困擾,因此沒有採用。

3:55

1-4・1-5:變成透明的方法

從第 4 題開始,除了 y 座標,也開始使用 x 座標和 z 座標。初始程式碼已經做好了彩虹配色。它直接把 y 座標的值拿來當作顏色代碼,所以每往上一層就會變成下一個顏色。不過這一題需要做的只是讓物件的一部分變透明。根據註解,什麼都不回傳,或是回傳 0 或 EMPTY 就會變透明。實況主把初始程式碼中「x 和 z 大於等於 2 時」的條件改成「大於等於 0 時」,就此過關。

第 5 題的結構也幾乎相同,寫出「x、y、z 全都大於等於 1 時」的條件就解開了。

5:42

1-6:用絕對值繪製線對稱的形狀

第 6 題登場的是 abs 函式。註解寫著這是回傳絕對值的函式,在繪製線對稱的物件時很好用。這次的目標形狀對任何一軸都是線對稱的。

實況主先著眼於 z 軸,讓 z 的絕對值大於等於 2 時回傳空,藉此決定厚度。雖然也可以寫成「大於等於 2 或小於等於 -2」,但實況主認為運用絕對值的概念會讓程式碼更容易理解。剩下的井字形也同樣用絕對值做出直條和橫條,再用邏輯或 or 連接起來完成。由於線對稱的結構今後似乎也會頻繁出現,實況主預料會有很多用到絕對值的場合。

6:49

1-7:閱讀錯誤訊息並修正

第 7 題一開始就出現錯誤。實況主首先閱讀錯誤訊息,從「LUA_ERRSYNTAX」的顯示推測這個語言是 Lua。第 10 行的 if 預期要有對應的 end 來結束,但第 13 行卻寫了 if。也就是說,忘了寫 end。

修正之後也還沒完成,仍有應該是紅色的箱子變成了 GREY 的地方,實況主把那裡也改寫後才過關。實況主在此提到,其實從零開始寫程式碼的機會很少,實際上閱讀別人程式碼的能力往往更為重要。

7:45

1-8:用棋盤距離和 table 解開自由課題「sushi?」

然而第 8 題卻是要從零開始寫的題目。題目名稱是「sushi?」,實況主覺得這是「驚人的品味」。由於形狀似乎可以自由製作,實況主訂下方針:依照與中心的距離分別塗上紅、白、綠。

這裡使用的距離稱為棋盤距離。這是指可以朝 8 個方向移動的棋子要走幾步才能抵達的距離,實況主介紹說,在《風來的希煉》等 Roguelike 遊戲中偶爾也會用到這個概念。由於有 max 函式就很容易求出這個距離,實況主打開 Documentation 分頁查詢。文件由 4 章構成,第 1 章是概要,第 2 章是繪製的細節,要找的資訊似乎在第 3 章。實況主在那裡確認了可以定義變數、可以使用 + 和 - 等運算子,以及有 max 函式。

關於轉換成紅、白、綠,實況主原本打算若沒有方便的功能,就用 if 判斷式老老實實地分支。作為「有趣實作」的方案,實況主也提出了把 7、1、11 用十六進位排列成 0xb17,再依距離向右位移的方法。

實況主在此順帶一提,平常錄製影片所用的 OBS 外掛也可以用 Lua 撰寫。實況主曾寫過一個自動產生 YouTube 時間戳記的簡單外掛,說不定人生中接觸 Lua 就只有那一次。

實作時,實況主先取 x 的絕對值與 y 的絕對值的最大值,求出與原點的棋盤距離。這次的值會是 0、1、2 其中之一,因此需要一套機制把它轉換成 0→7(紅)、1→1(白)、2→11(綠)。實況主在文件中瞥見「table」這個字,推測可以使用類似陣列的結構。寫了許多語言都能用的方括號陣列字面值後,發現 Lua 不支援。於是實況主從「Table」這個名稱推想應該是大括號形式,改寫後幾乎就成功了。不過顏色錯開了一格,這才發現 Lua 的索引是從 1 開始。照此修正後,壽司就完成了。

實況主說,雖然用到的元素有點艱深,但看著程式碼,意外地顯得很簡潔。不過實況主也自己補充,會這麼想的通常只有寫的人本人。

11:25

1-9・1-10:「不塗的地方」與巢狀 if

第 9 題比較接近操作教學,要玩家用滑鼠移動視角好好確認形狀。形狀是上方開口的箱子,只要在 x 或 z 為 ±3 時,或 y 為 -3 時回傳 BROWN 即可。排除 y 為 +3 的情況,就會變成打開上蓋的形狀。這裡也用到了絕對值。註解似乎希望玩家不是用「塗哪裡」而是用「不塗哪裡」來表達,但實況主判斷哪一種都可以。由於前一題的壽司太過投入,這題讓實況主覺得有點撲空。

第 10 題據實況主所說是類似 Google Chrome 的配色,但顏色的排列看不出有什麼特別的規律。註解似乎也期待使用巢狀 if,所以實況主就老實地寫了條件分支。y 大於等於 1 時,x 大於等於 1 就回傳 GREEN,否則回傳 ORANGE。y 不大於等於 1 時,z 大於等於 1 就回傳 BLUE,否則回傳 RED。實況主一邊旋轉視角讓軸向更容易看清楚,一邊寫下去。

13:34

1-11:雙重拼字錯誤與 X 字的原理

教學最後的第 11 題,一開始也帶有問題。根據錯誤訊息,第 31 行處理了一個名為「colo」的未知變數。實況主推測這是「color」的打字錯誤並加以修正。但光是這樣,階梯只會畫出一部分。進一步閱讀程式碼後,發現有地方寫成了英式英語拼法的「colour」,修正後才完成。

關於這段程式碼為何能畫出 X 字,實況主解讀是因為使用了 x==y 和 x==-y 這樣的式子。也就是說,把一次函數當作等式使用,就能畫出這樣的形狀。實況主在此說了一句:「嘛,這大概是跟我無緣的知識吧。」

14:40

2-1〜2-3:用等式繪製斜線

完成教學後,會收到社群寄來的歡迎郵件,以及共 7 題的追加題組。

追加題組的第 1 題,馬上就出現了剛剛才說「無緣」的一次函數問題。這是 y = -x 的直線,在 Lua 中可以寫成 x == -y。用 == 比較會回傳 true 或 false,而在 Replicube 中這會被繪製成白色和空,所以只要直接回傳比較結果就過關了。

第 2 題同樣是斜的直線,但需要再用 z 座標進一步篩選。實況主先用 x == y 畫出平面,再用邏輯且 and 接上 x == z 的條件,完成形狀。為了讓回傳值不是 true/false 而是 GREEN 或空,實況主在末尾加上 and GREEN,利用了短路求值。

第 3 題是把第 2 題做成線對稱的形狀,只要把式子改成絕對值版本就解開了。雖然計算了兩次 abs(x) 有點浪費,但因為不是很慢的處理,實況主決定不去在意。

17:00

2-4〜2-6:用餘數和加法製作花紋

第 4 題導入了回傳除法餘數的 % 運算子。初始程式碼用 y 除以 3 的餘數,依序分配成 0、1、2。看了正確答案的形狀後,實況主覺得像是把 x 的值轉換成 0 或 1,因此推測用除以 2 的餘數就可以。由於錯開了一格,實況主把 x 減 1 加以調整後過關。

第 5 題是西洋棋盤格。實況主先為了做成平面,打算寫一段不為 0 就回傳空的處理,一開始卻用 z 寫了條件,後來改成 y。雖然 RGB 箭頭分別對應 XYZ,但實況主說有時會忘記而搞混。至於花紋,實況主「偷偷瞄了一眼」第 2 題初始程式碼中的 return x+y,用加法會形成斜條紋。只要用 % 加以收斂,就會變成棋盤格。一開始變成了粉紅色,於是改成回傳黑色後過關。實況主也示範,若想避免條件分支,也可以用一個把餘數 0 和 1 轉換成黑白的 1 和 3 的式子來實現,但評論說這樣轉換成顏色的部分會變得非常難懂。

第 6 題是斜向的彩虹色花紋。實況主利用加法可以畫出斜條紋這一點,先把 x 和 y 相加。這樣外觀就大致相似了。不過,實況主的程式碼在原點會回傳 0,而正確答案在該處是 LIGHTGREEN,所以就加上 LIGHTGREEN 來對齊。最後讓 z 不為 0 時回傳空以調整厚度,就此完成。

19:55

2-7:四腳桌與用函式整理

最終題是典型的四腳桌,材質全部是 BROWN。由於只需考慮形狀,實況主仿照現實中的組裝流程,把桌面和桌腳分開處理。發現可以用 -- 寫註解後,實況主也加上了註解。桌面只是一個在 y 為 1 時回傳 BROWN 的平面。桌腳方面,先把繪製範圍限制在 y 小於等於 0 以決定高度。位置是線對稱的,所以照例使用絕對值,只在 x 和 z 的絕對值都為 2 時才繪製。把這些條件用 and 連接起來,全部滿足時回傳 BROWN,就此完成。

不過,實況主有點意猶未盡,決定再多下點功夫。根據文件,應該可以定義自己的函式,實況主想試著用用看。目前的程式碼明明材質全是 BROWN,卻寫了兩次 return BROWN,要是被要求「還是改成 WHITE 好不好?」就得改兩次。於是實況主做了一個只處理形狀的函式,改寫成在一個地方決定顏色的形式。

具體來說,實況主定義了回傳該座標是否為桌面的 isTopPlate 函式,以及回傳是否為桌腳的 isLeg 函式,並寫成「若是桌面或桌腳就回傳 BROWN」。實況主說 Lua 的函式名稱大概一般是用 snake_case,但這次畫面寬度極窄,保險起見改用 camelCase。雖然呼叫函式會有一定的成本,但繪製靜止物體時並不太講求效能,所以實況主判斷沒有問題。另一方面,本作似乎也能製作動畫,實況主也提到在那種場合或許多少要留意一下。

22:52

實況主的評價

完成兩個題組後,實況主對《Replicube》做出如下評價:一開始給人難以入門的印象,但 UI 和導入都非常細心,實際玩起來非常有趣。它是一款有著多種觀點與解法的優秀解謎遊戲,也能作為對程式設計產生興趣的契機來享受。它也與數學領域有所連結,實況主最後總結,覺得這是一部能從多方面享受的作品。