E – Weighted Tic-Tac-Toe 重み付きの三目並べ の続きです。今回はこれをネタにゲームを作ってみることにします。

乱数を生成して 9 個のマスに数字を表示させます。これを見て先手と後手、勝つことができるのかどちらかを判断して選択してもらいます。あとはコンピュータを相手に三目並べをします。勝敗は Weighted Tic-Tac-Toe の問題と同じです。普通の三目並べで決着がつかない場合は得点で勝敗を決めます。

普通の三目並べは双方が最善を尽くせば必ず引き分けになりましたが、このゲームは違います。マスに書かれている数の総和は奇数なので絶対に引き分けにはなりません。どうです!面白いでしょう!(自画自賛w)

HTML部分

まずはHTML部分を示します。

style.css

PositionクラスとBestMoveクラスの定義

最初に位置を表現するためのPositionクラスとコンピュータにとっての最善手を表すためのBestMoveクラスを定義します。

index.js

グローバル変数・定数

グローバル変数と定数を以下のように定義します。

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

ページが読み込まれたときの処理を示します。ページが読み込まれたらセルに相当するボタン要素を2次元配列 cells に格納します。そしてイベントリスナを追加してボリュームの初期設定をおこないます。

getCellElements関数はセルに相当するボタン要素を2次元配列 cells に格納します。

イベントリスナの追加

addEventListeners関数は、マスをクリックしたとき、スタートボタン、先手選択、後手選択のボタンをクリックしたときのイベントリスナを追加します。

initVolume関数はボリューム調整用のスライダーを初期化します。

ゲーム開始時の処理

ゲーム開始ボタンをクリックしたらマスに数字を表示してプレイヤーに先手と後手どちらを選択するかを問うメッセージを表示します。

そのまえに2次元配列 selects の全要素を 0 で初期化することで未選択の状態にしています。同時にこれまでの着手の履歴をクリアしています。

次に問題を生成します。マスに表示する数字は乱数で生成します。ただ引き分けにならないようにするために総和は奇数にしなければなりません。とりあえず -1000 ~ 999 の乱数を生成して総和を求め、偶数であった場合は左上のマスの数を 1 増やすことで総和が偶数になることを回避しています。

そのあとナビゲーションの文字列を初期化して、スタートボタンを非表示にしてユーザーに先手または後手の選択を問うメッセージとボタンを表示するなどの処理をしています。

createNumber関数はマスに表示される数を生成する関数です。

setCellNumbers関数はセルの背景色を元に戻すとともに、createNumber関数が生成して配列に格納した乱数をマスに表示させる処理をおこなっています。

先手と後手の選択

ゲームが開始されてユーザーが先手または後手を選択したらゲームが開始されます。

先手を選択した場合はただちに、後手を選択した場合はCPUが着手した段階で ignoreClick フラグがクリアされ、プレイヤーはマスをクリックすることで着手できるようになります。

また感想戦モードなら感想戦操作用のボタンを表示させます。

プレイヤーによる着手

プレイヤーが着手ときはプレイヤーの手番のときに空いているマスをクリックします。すでに着手されているマスをクリックするなど不正な操作をしたときは効果音を鳴らして正しい操作をするようユーザーに伝えます。

正常な着手がされたときは配列内に着手の履歴を保存します。プレイヤーの着手によって勝敗がつくかもしれないのでチェックします。ゲームセットになった場合は後述するcheckGameSet関数内でゲームの結果を表示したあと、新しいゲームを開始するためのスタートボタンを再表示させますが、感想戦モードでゲームセットになった場合はゲーム開始ボタンは再表示させません。ユーザーが感想戦モードを終了させた段階で次のゲーム開始の処理がおこなわれます。

プレイヤーの着手してゲームセットにならなかった場合、手番をコンピュータに移行します。コンピュータ側の着手が終了していないのにプレイヤーが連続して着手できては困るので、コンピュータ側が着手するまでユーザーがマスをクリックしても無反応状態にします。

コンピュータ側の着手が決まったら着手の処理をおこない、これによってゲームセットとなるかもしれないのでチェックの処理をおこないます。コンピュータ側の着手によって勝敗がつかない場合はプレイヤーに手番を移します。

コンピュータ側の着手と最善手

コンピュータ側の着手を考えるアルゴリズムは E – Weighted Tic-Tac-Toe 重み付きの三目並べ と同じです。

コンピュータ側はつねに最善手を選択します。勝つ方法が存在する場合はそれを選択し、ない場合はできるだけ僅差で負ける手を選択します。

またコンピュータが∞ vs 0 で勝つ手段が複数ある場合(すでに 2 がふたつできている場合)、このままだとすぐに三目並べて勝たずに他のところに着手する場合があります。そこですでにリーチができている場合はその場所へ着手してすぐに終わらせるようにしています。

最善手の探し方は前の記事:E – Weighted Tic-Tac-Toe 重み付きの三目並べ と似たような再帰呼び出しによる処理で探します。

getWinner0関数は、縦横斜めのいずれかで 3 つそろっているかを調べます。

isALLSelected関数はすべてのマスが埋まっているかどうかを調べます。

勝敗がついていない状態で与えられた盤面から次の手の候補を列挙する処理を示します。

コンピュータ側がすでにリーチが掛かっている部分を探す処理を示します。

終了判定

checkGameSet関数はプレイヤーまたはコンピュータ側の着手で勝敗がついてしまったかどうかを調べます。そして勝敗を表示します。また勝った場合は連勝数も更新し負けた場合はリセットします。ただし感想戦モードで勝敗がついた場合は連勝数の更新処理はおこないません。