上下左右の移動を繰り返して左上のマスから右下のマスまで移動したときの通過地点の値のXORが0になるwalkを探すパズルをつくります。walk とは始点から終点への経路であって、同じ頂点や同じ辺を複数回通っても良いもののことです。

D – XOR Shortest Walk を解く

一応、ヒントになったものがあってそれがこの問題です。

D – XOR Shortest Walk

求めるものは「walk に含まれる辺の重みのビット単位 XOR の最小値」です。walkは同じ辺や頂点を通るため、単純な最短経路問題のように解くことはできません。しかし辺の重みは 0 以上 2^10 未満なので「頂点倍化」と呼ばれるテクニックを用いて解くことができます。

(1,0), (1,1), …, (1,1023), (2,0), (2,1), …, (N,1023) を頂点とする1024 × N 頂点のグラフを考えます。そして「元のグラフにおいて頂点 x から頂点 y への重み w の有向辺があるときかつそのときに限り、1024 × N 頂点のグラフでは頂点 (x, s) から頂点 (y, s XOR w) への有向辺がある」と考えます。

あとは頂点 (1,0) から (N,0), (N,1), …, (N,1023) の各頂点へ到達可能であるかどうかを判定し、到達可能な (N,x)のうち x が最小になるものが答えということになります。

HTML部分

HTML部分を示します。上記の問題では0~1023でしたが、これでは無理ゲーすぎるので0~15にして頂点の数も16個にします。また10進数で表示するのではなく2進数表示にする初心者モードも作りました。途中で操作を間違えたときのリセット機能と出題された問題が難しすぎるときにスキップするためのギブアップ機能も追加します。

style.css

グローバル変数

app.js

QueueStackクラスの定義

幅優先探索をするときに使うqueueとしてQueueStackクラスを定義します。これは 最短以外は不正解!8パズルでゲームをつくる(1) と同じものです。

ページが読み込まれたときの処理

ページが読み込まれたときにおこなわれる処理を示します。イベントリスナを追加して最初の問題を表示し、更新処理を開始します。

イベントリスナの追加

イベントリスナを追加する処理を示します。呼び出されている関数については後述します。

問題の生成と表示

問題を生成して表示する処理を示します。乱数で0~15の整数を生成してこれで問題をつくります。このとき各頂点に遷移するときにどの頂点から遷移するのかも保存しておきます。

問題が生成されたら本当に解が存在するかどうかを調べます。これは頂点倍化された頂点(0, values[0])から(ROW_COUNT * COL_COUNT – 1 ,0)までたどり着くことができるかで判定できます。解が存在する場合は問題と解をセットにして戻り値として返します。解が存在しない場合はnullを返します。

getNextIndexes関数は引数で渡された値に対応するマスから移動できるマスに対応する頂点のindexを取得するためのものです。

問題が生成されたらこれを表示します。生成された問題に解が存在しない場合はcreateQuestion関数がnullを返すのでnullではない値が返されるまでcreateQuestion関数の実行を繰り返します。

問題は0~15の整数の配列ですが、easyモードでプレイするときは二進数表示とします。

getElementFromCellIndex関数は引数として渡された頂点のindexに対応するマスのDOM要素を返します。

引数で渡された値を二進数の文字列に変換する処理を示します。

現在位置とそこから移動可能なマスの色を変える処理を示します。やっていることは、現在位置とその上下左右のマスに色をつけているだけです。

現在のxorの値を表示する処理を示します。

更新処理の開始

更新処理を開始します。更新処理ではtimer_movingがtrueのときは前回更新時と現在時刻の差だけ残り時間を減算してこれを表示するとともに、残り時間が0になったときはゲームオーバーの処理をおこなっています。またHowToPlayの文字列を左方向に流す処理もおこなっています。

ボリューム調整の処理

すでに定番化して久しい処理です。

ゲーム開始以降におこなわれる処理は次回にします。