箱入り娘とはブロックを動かして「娘」を外に出すのが目的のパズルゲームです。一般的な初期配置のものは動画の通りなのですが、これはやや難しい配置で最短手数は81手(ただし1回の移動で2マス移動を含めた場合(直線ではなくL字移動も含む)。L字移動も含まない場合は90手、1マス移動に限定した場合は116手)です。

HTML部分

HTML部分を示します。

style.css

グローバル変数と定数

2次元配列 array2x2 にブロックの情報を格納します。以下は格納例です。

‘単’は1×1のブロック、’上’は上下に2個つながったブロックの上側、’下’はその下側、’左’は左右に2個つながったブロックの左側、’右’はその右側、’娘’は「娘」の左上、’娘1’は「娘」の右上、’娘2’は「娘」の左下、’娘3’は「娘」の右下、’空’はブロックが存在しない部分です。

Positionクラス

位置を表現するためにPositionクラスを定義します。

Rectangleクラスの定義

canvasに描画されるブロックを操作できるようにRectangleクラスを定義します。

コンストラクタ

コンストラクタを示します。引数は左上にくる部分の位置(上から何番目、左から何番目)、横と縦に何個つながっているか? 表示色、ブロックに書かれている文字列です。引数から各ブロックを描画する座標を算出し、メンバー変数 X と Y に格納します。

ブロックを移動可能であるかどうか調べる処理の準備としてブロックの上下左右にある隣の位置を取得する処理を示します。

たとえばRectangle.RowとRectangle.Colから各ブロックの右隣りの位置を算出する場合、ブロックが横に何個つながっているかで変わってきます。1個ならRectangle.Colに1足した値になりますが、2個つながっている場合は2をたさなければなりません。また右隣りの位置の個数は縦に何個つながっているかで変わってきます。

ブロックを移動可能であるかどうか調べる処理を示します。

引数で指定された方向の隣が存在し、それがすべて’空’であれば移動可能です。それ以外は移動不可です。

移動

実際に移動させる処理を示します。

まずCanMove関数を呼び出し移動できるか確認します。移動可能であるなら移動先の座標を求め、その方向に移動させます。アニメーションさせたいので16回にわけ少しずつ移動させ、移動のたびにcanvasを再描画しています。

移動が終わったらupdateArray2x2関数を呼び出してarray2x2に変更を反映させています。

ブロックの位置が変更されたら二次元配列に変更を反映させる updateArray2x2関数とcanvas全体を再描画する draw関数は以下のように定義されています。

描画

各ブロックを描画する処理を示します。

Rectangle.Colorでブロックを描画し、白で縁取りをして内部に文字列Rectangle.Nameを書き込んでいるだけです。

Exist関数は指定された位置にブロックが存在するかどうかを返します。

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

ページが読み込まれたときにおこなわれる処理を示します。

背景に使う画像の読み込み、canvasの初期化、レンジスライダーによるボリューム設定の有効化、問題の作成、ボタンクリックのイベントリスナの追加、エンドレスでBGMを再生するための処理、描画処理をおこなっています。

canvasの初期化

canvas初期化の処理を示します。

canvasのサイズ調整をしたあと親要素の中に追加し、5行4列のセルを生成します。これによってcanvasのブロックが描画される部分に該当するセルがクリックされたら移動処理ができるようになります。

レンジスライダーによるボリューム設定

レンジスライダーでボリュームを調節できるようにする処理を示します。

問題の生成

問題を生成する処理を示します。問題は3パターン用意しました。

カンタンな問題

これはまあカンタンな問題です。

代表的な問題ですが、難しめの問題です。

以下はブロックのオブジェクトを生成して配列に格納するとともに二次元配列に文字列を格納する処理です。

以下は空白部分に該当する文字列を二次元配列に格納する処理です。

ゲーム開始の処理

ゲーム開始の処理を示します。

手数をリセットし、クリックされた問題に応じて問題を生成して表示させます。同時にゲーム開始ボタンやギブアップボタンの表示や非表示、タイマーのカウントアップの開始処理をおこないます。

手数と経過時間を表示する処理を示します。

ブロックの移動

canvas上に存在する見えないセルをクリックしたらブロックの移動処理をおこなうイベントリスナを追加する処理を示します。ignoreClickフラグがセットされているときはクリックしてもなにもしません。

移動対象のブロックを選択する

移動対象のブロックを選択する処理を示します。

セルの位置から対応するRectangleオブジェクトがあるか調べます。オブジェクトが存在し、それが移動可能であるならRectangle.RowとRectangle.ColをfromRowとfromColに格納します。またナビゲーションに◯◯の移動先を指定せよと表示させます。

(追記)

ユーザーに移動元を選択させて、さらにそのあと移動先を選択するというのは操作性が悪すぎるのではないかという指摘があったので、移動元を選択したときに移動先がひとつしかない場合はただちに移動させます。移動先がふたつある場合は選択されているブロックと移動先のセルに色をつけてどこへ移動可能なのかが視覚的にわかりやすくします。

セルの位置から対応するオブジェクトを取得する処理を示します。

移動対象を選択しようとしたけれどもできなかった場合の処理を示します。

移動対象を移動させる

移動対象を移動させる処理を示します。

移動対象が選択されている場合、クリックされた場所が移動対象の隣であるか調べます。ひとつ隣またはふたつ隣の場合は移動可能であれば移動させます。

移動できた場合はonMoveFinished関数を呼び出し、移動できなかった場合はonMoveFailured関数を呼び出します。また移動できた場合はゲームクリア判定もおこないます。

移動を完了したときの処理を示します。

移動を完了したので移動対象のブロックに関する情報はリセットします(fromRowとfromColを-1で初期化)。そのあと表示されている手数を1増やします。

移動できなかったときの処理を示します。この場合は移動対象を再指定するところからやり直しとなります。

ゲームクリア時の処理

ゲームクリア時の処理を示します。

二次元配列 array2x2 を更新した結果、array2x2[3][1] == ‘娘’ となれば娘を出口まで移動させたことになります。この場合はゲームクリア処理をおこないます。娘をcanvas下部から退場させ、ゲームクリアを示す文字列とボタン類の表示非表示の処理をおこないます。

娘を退場させる処理を示します。count×3回少しずつ下に移動させてcanvasの外に移動させます。

ふぎゃあ。クソ長い記事になってしまった。次回はギブアップ時に解を表示する処理の解説をおこないます。