JavaScriptの学習のため、とある記事を参考に(ほぼ流用ですが…)ハノイの塔が遊べるサイトを作ってみました。
作ったサイトはこちら
流用とは言っていますが、それでは学習したうちには入りません。そこで今回は、コーディングの棚卸しを行いたいと思います。
まずは聞きなれない「ハノイの塔」について説明します。私の場合、ゲームの内容とゲームの名称が一致していなかっただけで、ゲーム自体は遊んだことがありました。皆さんもそうじゃないでしょうか?
ハノイの塔とは?
3つの棒があり、その内の一つに1~n[n:任意の数]の数が割り当てられた円盤が上から順に1、2、3、…というように収まっています。この3つの棒を用いて最終的にもう一方の支柱に同様の形で移し替えることができたらクリアになります。
しかし、一つ制約があります。移し替える先に円盤がある場合、必ず移し替える先の円盤の数字よりも小さくなる必要があります。例えば、「3の円盤の上に1の円盤は置けますが、2の円盤の上に4の円盤は置けない」という要領です。
実際に文字で見るより、手を動かしてみたほうが理解が早いと思います。
イメージとしては下の図のようなものです。
コーディングの説明
ゲームのルール説明はこの辺りにして、早速本題に入ります。JavaScriptの勉強のためということもあり、ほぼJavaScriptで書かれています。HTMLやCSSも使われていますが、気持ち程度です。
あくまでもざっくりとした説明です。詳細について気になったなら、ブラウザ上で右クリック→「ページのソースを表示」で確認してみてください。
JavaScript
大きく分けて3つの機能があります。
- 定義・キャンバスの取得・初期化・図形の描画
- ユーザー操作
- 解答作成・表示
まずは1の定義・初期化・図形の描画について説明します。
定義・キャンバスの取得・初期化・図形の描画
定義
「var」を用いて様々な変数を宣言しています。中には初期値を設定しているものもあります。
キャンバスや、円盤の数、円盤を動かす手数など本当に様々なものが宣言されています。
キャンバスの取得
キャンバス、つまり図形が表示される領域を取得しています。具体的にどのようなものが表示されるように設定するのかは後述で出てきますが、ここではあらかじめフォントや文字の基準位置について設定しています。中でも注目してほしいのが以下3つのコードです。
初期化
手数やゲームクリア時に表示されるメッセージ、タイマー、円盤の位置などを初期化しています。まずは、手数やメッセージについての説明です。上述のgetElementByIdの後に以下のようにコーディングされています。
どうやら、getElementByIdと併用することで取得したドキュメント要素を書き換えているようです。
次はタイマーについてです。
あとでどのように使われているか説明しますが、とりあえずここではsetInterval()とclearInterval()が対になっていることを覚えておきましょう。
次は円盤の位置についてです。これはいくつかの機能を組み合わせて実装させています。
- 配列
- for文
- push()
空の要素を3つ持つ二次元配列を作成しています。
numは円盤の数として設定しています。プレイしたい円盤の数を取得して、その数から1までをfor文としてループさせています。以下、後述部分について。
このようにすることで、左端の支柱(bar[0])に指定した枚数の円盤をセットする(push(i))ことを可能にしています。
図形の描画
円盤(静止時)と円盤(移動時)・棒について、分けてコーディングされています。まずは円盤(静止時)について説明します。
円盤の横幅を変数を用いて設定しています。また、各メソッドを用いて円盤の枠線の色や、円盤の塗りつぶし色など設定しています。以下、それらのメソッドの説明です。
次に円盤(移動時)・棒についての説明です。基本的に行っていることは円盤(静止時)と同じですが、上述の配列やfor文、if文を駆使して描画されています。説明すると長くなりそうなので、割愛させていただきます。
ユーザー操作
ユーザーのマウス操作によって、円盤がどのように動くかがコーディングされています。内容は大きく分けて以下の3つです。
- 円盤の移動開始
- 円盤の移動中
- 円盤の移動完了
マウスカーソルの座標を取得し、その座標によって現在のカーソルがどの棒に合わされているか判断します。ドラッグ開始時には、円盤の座標がカーソルの座標と一致するようにしています。ドロップ終了時には、上述の制約に基づいて適当な棒の上でドロップすれば円盤は別の棒の上に移動し、手数が加算されます。
ここでも詳細は割愛させていただきます。
解答作成・表示
ここから更に、コーディングが難しく感じて自分も把握しきれていませんが、ゲーム攻略の考え方としては以下の通りです。※以下、円盤の数を4枚として説明します。
まず、最初の目標は「4の円盤をGOALの棒に移動させる」ことです。しかし、これは同時に「別の棒に1、2、3の順に円盤を移動させる」ことも意味します。
そして、「別の棒に1、2、3の順に円盤を移動させる」ことは「3の円盤を別の棒に移動させる」こととも言えますよね。そのためには、「GOALの棒に1、2の順に円盤を移動させる」必要があります。
察しのいい方なら気付いたかと思いますが、同じ動作を繰り返していることが分かると思います。そして、この考え方は円盤の数が増えても変わりません。また、このように同じ動作を繰り返すには再帰呼び出しが向いています。
円盤の動かし方を記録し、その方法を配列に入れて、アニメーションで解答を表示させるというステップを踏んでいます。アニメーションを表示させる際に表示間隔をsetInterval()で設定しています。
再帰呼び出しとは?
聞きなれない言葉かもしれません。要するに、「とある関数:fの中に自身の関数:fを再度呼び出す」ことを差します。繰り返し処理としてfor文やwhile文などがあり実装も可能ですが、for文やwhile文などと比べて自然且つ簡潔に記述できることがあるようです。
今回の場合は、再帰呼び出しのほうが向いているようですが、どのような処理の時に再帰呼び出しのほうが向いているのかについて今後理解を深めていく必要があるなと感じました。
HTML
特に注目すべきコーディングは以下の3つです。
- body要素にonload=”機能名”
- input要素でボタンや数字選択エリアの作成
- キャンバス作成
body要素にonload=”機能名”
以下のように記述することで、htmlファイルが読み込まれたときに”機能名”が発火するようになります。
このファイルでは、キャンバスの取得、初期化、円盤(移動時)・棒が発火します。
input要素でボタンや数字選択エリアの作成
以下のように記述することで、ボタンや数字選択エリアを作成することができます。
このファイルでは、解答の表示・円盤のリセットボタン、遊ぶ円盤の数の選択エリアを実装しています。
キャンバス作成
領域の広さをwidthやheightで指定しています。また、領域内で発火する機能についてコーディングされています。
CSS
円盤の数の選択エリアの横幅やクリア時に表示されるメッセージの文字色についてコーディングされています。
まとめ・今後の課題
以上、現在作成中のハノイの塔のサイトについてでした。自分自身もあまり深く理解しきれていないこともあり、自分の理解が追い付いていないと人に分かりやすく伝えるのは難しいのだと改めて思いました。
まだこちらのサイトはマウス操作にしか対応していませんし、スマホからだとスクロールが必要だったりと、マルチデバイスには対応できていません。今後はマルチデバイス対応のために改良を重ねていきたいと思います。