Steamなどで販売されていた「ヒューマン・リソース・マシーン」のNIntendo Switch版が先日発売されました。プログラミングパズルという独特のジャンルで人々を引きつけているようですが、その難しさに困惑する人もあとをたちません。
この記事では、ヒューマン・リソース・マシーンを攻略するにあたっての要点を紹介したいと思います。
ヒューマン・リソース・マシーン
ヒューマン・リソース・マシーンはプログラミングパズルです。シンプルな命令を組み合わせキャラクターを操作し、課題の解決に取り組みます。
クソ難しい
「これプログラミング風パズルじゃなくて、本当のプログラミングパズルじゃない……?」
プレイしているうちに、あなたは真実に気づいてしまいます。そう、これはプログラミング風パズルというよりも、本当のプログラミングです。なんて酷いことを!
プログラミング言語は人間の言語に近い「高級言語」と機械語に近い「低級言語」に分けられますが、このゲームで要求されるのは低級言語です。しかもかなり難しい方です。私が行っていた大学の単位殺しで有名な「アセンブラ演習」の2倍ぐらい難しいです。
ここまで読んで「このゲーム、プログラマにしかできないのでは?」と思ったそこのあなた、おそらく間違いです。これ、プログラマにとってすら難しいと思います。
知識を身につければわりとなんとかなる
このゲームはガッチガチのプログラミングなので、プログラミングの知識があると有利に働きます。むしろプログラミングの知識がないとやってられないかもしれません。それぐらい本格的なゲームです。
ですが、逆を言えばコツさえ掴んでしまえばわりとあっさり解けたりするゲームでもあります。知識を身につけて、頑張って倒しましょう。
基本的なコツ
最適化しない
最も重要なコツは、最適化(プログラムを短くする)しないことです。
「いやいや、そんなこと言ってもゲーム中の目標に最短行数と最短手数とかあるし……」
それでも最適化しないでください。最適化は、一般的なプログラミングにおいてさえも悪者扱いされることがあります。それは可読性(読みやすさ)が犠牲になるからです。可読性が失われると、人間(あなた)にとってプログラムが読みにくくなり、プログラムの流れがぐちゃぐちゃになって、破滅してしまいます。最適なプログラムを作るつもりが、そもそも完成にまで至らないという事態になってしまいます。
最適化は難易度を著しく上昇させる原因のひとつとなります。まず最初は素直に解いて、効率目標をクリアしたければ後からやるといいでしょう。
入力、処理、出力!
プログラムは整然とした流れがあると人間にとって把握しやすくなります。一般的に使われる流れとしては、入力→処理→出力というものがあります。このゲームで言えば、inbox→処理→outbox、ですね。
プログラムの頭の方にinboxを、後ろの方にoutboxを、そしてその間に処理を書くとわかりやすいでしょう。
大枠の処理から先に書く
プログラムを上から一行ずつ丁寧に書いていくというのもありですが、基本的には大きな枠となる部分を組んでから、あとで間を埋めていく方がやりやすいと思います。
例えば、先述の通り、多くのプログラムは「inbox→処理→outbox」というのが基本の流れになります。このとき、始めのinboxと最後のoutboxはほぼ固定の処理となります。こういった枠となる部分を先に書くといいでしょう。
また最後のoutboxのあとに初めのinboxに戻るというのもよくある処理です。これも先に書いてしまいましょう。
このあたりは最初にまとめて書いてしまって、それから間を埋めていくという方法が楽です。
名前をつける
名前をつけるという行為は、プログラムを組む上で最も重要な要素とも言えます。結局のところプログラムは人間が読むものなので、人間にとってわかりやすいようにすれば、それだけ組む効率も上がるのです。
そしてヒューマン・リソース・マシーンにも名前をつけるための機能があります。パネルを置く場所に名前をつけられます。
もちろん名前をつけずに攻略することもできます。「14」をコピーするとか、「8」に置くとか、そんな感じです。でも「ゼロ」からコピーする、とか、「カウンタ」に置く、とかのほうがわかりやすいでしょう?
一時的に使う場所には「一時的」と書いたり、文字列の長さを置く場所には「長さ」と書いてみたり、いろいろと名前をつけてみましょう。
場所を使いまわさない
これは「最適化しない」と「名前をつける」に関係するコツです。パネルを置く場所を使いまわさないようにするというものです。使いまわさないようにするというのは、「ひとつの場所には、ひとつの名前、ひとつのパネル」を意識するということです。
例えば「一時的」の名前をつけた場所が既に使われてるとします。そして文字数のカウントをしたいとします。このとき「他の場所を使うのは勿体無い!効率よくやろう!」と考えて、「一時的」の場所に「文字数」を置きたくなります。でも、それはやめましょう。そういうときは、新しい場所を使って、「文字数」の名前をつけるべきです。
ひとつの名前に対して複数の使い道を用意すると、人間は大きく混乱します。場所が足りない時は仕方ないですが、幸い、後半の問題では場所は大きく余るようになっています。有効に活用しましょう。
コメントを書く
しばらくゲームを進めると、プログラム中にコメントが書けるようになります。
命令の羅列を見ても、人間は「このプログラムが何をやっているのか」を理解できません。そこでコメントを入れることによって、「ここは比較をしているんだな」とか、「ここは結果を出力しているんだな」ということを把握しやすくなります。コメントを見て把握しやすくなれば、次にどこをいじればいいのかわかりやすくなり、結果として目標を達成するための近道となります。
ググる
わからなくなったらググりましょう!
いやふざけてるわけではないです。例えばYear41の「並べ替えよ」では、並べ替えが要求されます。並べ替えのことをソートと言いますが、ソートにはあるアルゴリズム(特定の手順)が必要になります。そしてそのアルゴリズムはおそらく何日考えても思いつかないものだと思います。
でも例えば「バブルソート」などでググれば簡単なアルゴリズムの実装方法が出てきます。しかしアルゴリズム自体は簡単でも、このゲームでそれを組み上げるのはなかなか骨が折れます。ゲームとしての面白みは損なわないでしょう。
実際のプログラミングでもググった情報を元にああでもないこうでもないと頭をひねるものなので、このゲームでも無理して自分一人だけで考えない方がいいと思います。
攻略実践
コツだけ伝えてあとは投げっぱなしというのは少々無責任です。なのでゲーム中の問題中からいくつか選んで、攻略の手引きをしたいと思います。
Year 4 : 逆にして運んで
課題:左側のコンベアのパネルがなくなるまで、右側に運んでください。運ぶ時はパネルが2つごとに逆になるようにしてください。
序盤からなかなかに難しい問題を出してきます。これは「A」「B」という文字の組があったら「B」「A」の順番で運ぶというものです。床の好きな場所にパネルを置くcopyto命令が使えるようになるので、それの練習ですね。
こういうときは、とりあえず初めは骨組みから組んでみます。骨組みというのは「inbox→outbox→inbox→outbox→…」という入力と出力の繰り返しです。
これで最低限のプログラムができます。もちろん課題に即していないので、解けたことにはなりませんが……。こうやって初めは骨組みだけ組んで、後からすり合わせを行なっていうというやり方が楽だと思います。
次は入力(inbox)と出力(outbox)の数を合わせてみましょう。ふたつのパネルの組なので、ふたつずつ必要になってくるはずです。
これで「ふたつずつ入力、ふたつずつ出力」ができました。そして実際に動かしてみてください。1つめのパネルをoutboxする前に2つめのパネルをinboxしているので、先に2つめのパネルがoutboxへと運ばれます。これで課題はもうすぐ達成できそうです。しかし1つめのパネルはどこかへ消えてしまい、outboxできません。
このゲームではパネルは一度にひとつしか持てず、他のパネルを持つと、前に持っていたパネルは破棄してしまいます。そのため、保存しておきたいパネルは、床の空いている場所に置く必要があります。これを実現するのがcopyto命令です。
2つめのパネルをinboxする前に、1つめのパネルを床に置いて保存しましょう。copytoを使って0番の床に置きます。そして2つめのパネルをoutboxした後に、copyfromで床の1つめのパネルを取り、outboxします。
「A」「B」と運ばれてきたら「A」を床に置き、「B」をinboxしてそのままoutboxし、床に置いた「A」をコピーしてoutboxします。これでできました!
Year 13:同じかどうか
課題:左側の数値2つごとに、同値か否かを判断してください。同値である場合、その値を1つ右側に運んでください。同値でない場合は、捨てちゃってください。
やることがたくさんある課題です。まずはどこから手をつけていけばいいでしょうか。
最初はやはり、プログラムの骨組みから作っちゃいましょう。今回は、数値2つごとに比較して、同じなら1つoutboxします。つまり、入力が2つで、出力が1つです。
次に数値の比較を行う必要があります。ここで、数値同士を引き算して、0になったら同じ数値、0にならなかったら違う数値となる性質を利用します。
しかし2つのパネルを同時には持てないので、1つは床に置きます。1つめのパネルを床に置いて、2つめのパネルを取ってきて、あとは引き算です。
これで2つのinboxが同じ数字になれば、手に持っているのは0になります。0にならなかった場合は違う数字です。
次に「条件分岐」をします。条件分岐として、「0ならば元の数字をoutboxする」「異なるなら破棄して次の組にうつる」を実現する必要があります。これはjump if zeroを使えばできます。引き算の後に「0ならば出力パートにjumpする」という処理を入れればいいでしょう。
また、求められている出力は0ではなく「元の数字」なので、出力の直前に、床に置いた数値をcopyfromしておきます。
そろそろプログラムも大きくなってきたので、コメントを入れておきましょう。比較するところには「比較」、出力するところには「出力」と書いています。
これでほぼ完成ですが、このままだと異なる数字であっても出力してしまいます。異なる数字だった場合の処理を入れましょう。jump if zeroは「0の場合、ジャンプする。そうでない場合、次の行を実行する」命令です。つまり、比較して異なる数字だった場合、jump if zeroを無視して次の行が実行されます。
パネルを破棄するには、ただ単に次のinboxを取ればいいだけです。そしてまた比較します。つまり、破棄する場合は最初に戻ればいいだけです。jump if zeroの次の行に、プログラムの最初へ飛ぶjump命令を入れます。
これで完成です!
Year 21:ゼロが区切り
課題:左側にいくつかの「0で終わる数値グループ」が置いてあります。各グループ内の数値の合計値を算出し、右側に運んでください。
このあたりになってくると、どんどん難しくなってきます。めげずに頑張りましょう。
まずは今までと同様に骨組みから作ってみましょう。
今回の課題は入力の長さが不定です。入力が5個かもしれませんし、10個かもしれません。こういうときはjump命令でループを作って、無限に入力を取り出すようにしましょう。
しかしこのままだと入力の終わりまで取り出し続けるだけで、何も起きません。ここで問題文をもう一度読んでみましょう。「0で終わる数値グループ」が入力となっています。そしてグループごとに合計値を算出し、出力するのが課題です。
これは「0が出てきたら結果を出力して、次のグループへ移る」と取ることができそうです。ループの中に、「0だったらループから抜け出して出力する」を付け加えてみましょう。
ここまでできたら、一度動かしてみると、それっぽい動きをしてくれるようになっているのがわかります。これで「0が出るまで入力を取り出して、0が出てきたら出力する」が完成しました。
次に取り出した入力値を合計するプログラムを組みます。数値の総和を求めるのには定石があります。まず暫定合計値として0を床に置きます。次にinboxで取り出した入力値を暫定合計値に足し、足した結果を新しい暫定合計値として床に置きます。これを繰り返し、終わりが来たら暫定合計値が総和になっているので、その値を出力します。
これを作ってみましょう。まず、床の0番目に「合計」と名前をつけておきます。次に、0は床の5番目に置いてあるのでそれをループの前にcopyfromしましょう。コピーした0を「合計」にcopytoします。
加算の処理は後回しにしましょう。まずは出力の直前に「合計」からcopyfromします。これで合計が出力されます。
これで合計を出力する仕組みができました。
最後に加算処理を追加しましょう。ループの中で、inboxから取り出した後に「合計」と加算して、結果を「合計」に置くという処理です。
これで「合計値を0にし、0が出てくるまで合計値を足し続け、0が出てきたら合計値を出力する」プログラムが完成しました!