『Replicube』実況:Luaのコードでボクセル立体を描くパズルを18問解く

YouTubeで開く ↗
概要

『Replicube』は、指定された3Dオブジェクトを再現するステージクリア型のパズルゲームだ。ボクセルを手で置くのではなく、プログラムを書いて配置する。この動画では、実況者が最初のチュートリアル問題集11問と、次に届く追加問題集7問を解いていく。全問クリアを目指すのではなく、2つの問題集を終えたところで区切るという方針だ。実況者は全体を通して、スコアを詰めることよりも読みやすいコードを書くことを優先している。

11分で読めます
0:23

ゲームの仕組み:座標を受け取って色を返す

チュートリアルによると、このゲームはマインクラフトのようなボクセル空間に、全16色のボクセルを配置していく。ボクセルとは「体積のある画素」という意味の造語である。配置にはコードを使い、常に白を返すコードを書けば空間全体が白で埋まり、紫を返せば紫、青を返せば青になる。つまり各ボクセルの座標を受け取り、そこに置く色の値を返すコードを書く、という形式だ。実況者は「早くも頭が痛くなって参りました」と言いつつ、最近は戦うゲームばかりだったので、たまにはこういう平和なゲームもいいと話している。

ゲーム内ではCubenetという場所の管理人からメールが届き、そこに添付された基礎問題集を解く流れになっている。画面の上に正解の形状、下に現在のコードが描く形状が表示される。採点にはコード量や実行速度も含まれるようだが、実況者は最適化を「後のデザート」に回し、まずは読みやすさを重視すると決める。理由として「コードなんて基本、人間用に書くもの」と述べている。

1:47

1-1〜1-3:色の書き換えと条件分岐

1問目は、すでにあるコードのコメントを読めば、返している色が少し違うだけだと分かる。PURPLEをREDに書き換えてクリアした。

2問目では、位置に応じて2色に塗り分ける。座標を表す変数x、y、zが与えられているので、yが1以上ならDARKBLUE、そうでなければORANGEを返すif文を書いて解いた。

3問目のテーマは比較演算子である。実況者は、この言語では不等価を「~=」で書くことに注目し、近いうちに使うことになりそうだと予想している。問題自体は2問目とほぼ同じパターンで、「yが○○以上」という条件で解ける。比較演算子を使わない別解として return GREEN - (y + 4) // 3 * 2 のような書き方もできると挙げたが、この種のコードは読み手を困らせやすいとして採用しなかった。

3:55

1-4・1-5:透明にする方法

4問目から、y座標に加えてx座標とz座標も使い始める。初期コードには虹色の配色がすでにできている。y座標の値をそのまま色コードとして流用しているため、1段上がるごとに次の色に変わる仕組みだ。ただし、この問題で必要なのはオブジェクトの一部を透明にすることだけである。コメントによると、何も返さないか、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:自由課題「sushi?」をチェス盤距離とテーブルで解く

ところが8問目は、ゼロから書かせる問題だった。問題名は「sushi?」で、実況者は「とんでもない感性」を感じている。形状を自由に作ってよさそうなので、中央からの距離に応じて赤・白・緑に塗り分ける方針を立てた。

ここで使う距離はチェス盤距離と呼ばれるものだ。8方向に動ける駒が何歩でたどり着けるか、という距離であり、実況者は『風来のシレン』などのローグライクでもたまに世話になる概念だと紹介している。この距離はmax関数があれば求めやすいため、Documentationタブを開いて調べた。ドキュメントは4章構成で、1章が概要、2章が描画の詳細、3章に求めていた情報がありそうだという。そこで、変数を定義できること、+や-などの演算子が使えること、そしてmax関数があることを確認した。

赤・白・緑への変換について、実況者は便利な機能がなければif文で愚直に分岐するつもりだった。「面白実装」の案としては、7・1・11を16進数で並べた0xb17を距離に応じて右シフトする方法も挙げている。

ここで実況者は余談として、普段の動画収録に使っているOBSのプラグインはLuaでも書けると話す。YouTubeのタイムスタンプを自動生成する簡単なプラグインを書いたことがあり、Luaに触れたのは人生でそのときだけかもしれないという。

実装では、まずxの絶対値とyの絶対値の最大値をとって、原点からのチェス盤距離を求めた。今回は0、1、2のいずれかになるので、これを0→7(赤)、1→1(白)、2→11(緑)に変換する仕組みが必要になる。ドキュメントに「table」という文字がちらっと見えたので、配列のような構造が使えると推測した。多くの言語で使える角括弧の配列リテラルを書いたところ、Luaでは使えなかった。そこで「Table」という名称から中括弧型だろうと考えて書き換えると、ほぼうまくいった。ただし色が1つずれており、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)を2回計算しているのはやや無駄だが、遅い処理ではないので気にしないことにしている。

17:00

2-4〜2-6:剰余と加算で模様を作る

4問目では、割り算の余りを返す%演算子が導入される。初期コードはyを3で割った余りで、0・1・2に順番に振り分けている。正解の形状を見ると、xの値を0か1に変換しているように見えたので、2で割った余りを使えばよいと推測した。1マス分ずれていたため、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 を2回書いており、「やっぱりWHITEに変えてくれない?」と言われたら二度手間になる。そこで形状だけを扱う関数を作り、色は1か所で決める形に書き直した。

具体的には、その座標が天板かどうかを返すisTopPlate関数と、脚かどうかを返すisLeg関数を定義し、「天板または脚であればBROWNを返す」と書いた。Luaの関数名はたぶんsnake_caseが一般的だが、今回は画面幅が極端に狭いので念のためcamelCaseにしたという。関数呼び出しには一定のコストがかかるものの、静止物体の描画ではそれほど性能を求められないので問題ないと実況者は判断している。一方、本作ではアニメーションも作れるようなので、そういう場面では多少気を払ったほうがいいかもしれないとも述べている。

22:52

実況者の評価

2つの問題集を終えて、実況者は『Replicube』を次のように評価している。最初は取っ付きにくい印象があったが、UIも導入も非常に丁寧で、遊んでみるととても楽しい。いろいろな観点や解法があるパズルゲームとして優秀で、プログラミングに興味を持つきっかけとしても楽しめる。数学的な分野とのつながりもあり、多方面で楽しめる作品だと感じた、と締めくくっている。