はじめに
私自身の復習兼、備忘録的な意味もあり、複数回の記事に渡って React を用いてマルバツゲーム(三目並べ)を開発していきたいと思います。
シリーズの一覧
- React入門1: 環境構築 [オンライン版]
- React入門2: 盤面の作成
- React入門3: インタラクションの実装
- React入門4: リファクタリング [リフトアップ編]
- React入門5: リファクタリング [インタラクション編]
- React入門6: 手番の実装
- React入門7: ゲームの勝利判定 (今回)
- React入門8: テキストの実装
- React入門9: タイムトラベル(1)
- React入門10: タイムトラベル(2)
- React入門11: タイムトラベル(3)
目的について
全体の目的
React公式のチュートリアルで公開されているマルバツゲームを 3x3 のマスで実装していきます。
今回の目的
ゲームの勝利を判定するプログラムを実装していきます。今回は、マルバツゲームの仕様をどのようにプログラムで表現するかを中心に考えます。少し複雑なアルゴリズムなので、図と疑似コード (日本語 + プログラム) を使いながら説明していきます。そのため、「 React のチュートリアルだけをしたい!」という方は、ソースコードのコピーをするだけして、次回の記事に進んでください。
ゲームの勝利判定
次のページで、前回のソースファイルを確認できます。
- 前回の内容はコチラから!
アルゴリズムの導出
勝利パターン
コーディングをする前に、マルバツゲームの勝利パターン数を考えてみましょう。
9つのマスの状態は Board
コンポーネントで定義した state
型の squares
配列によって管理されています。その要素を Square
コンポーネントの value
プロパティに渡すことで、マスの状態を表示しています。squares
配列の要素が示すマスは次のように対応しています。
マルバツゲームでの勝利条件は、同一の印がある方向に 3マス分 並んでいることです。従って、盤面に対して横向きの行方向、縦向きの列方向、斜めの対角方向について考える必要があります。各方向で考えられる勝利パターンを挙げてみます。
上の図から確認できるように、勝利判定が成立するのは合計 8パターンとなります。各勝利パターンは、3マス分の位置を示すことで、プログラムの表現ができそうですね。
const 勝利パターン = [マスの位置a, マスの位置b, マスの位置c];
プログラムの仕様
手番が交代するたびに、勝者判定を計算するプログラムとして、calculateWinner()
関数を実装していきます。この関数は次の処理を実行させることが目的です。
- 盤面の状況として、全マスの状態を引数として受け取る
- ゲームに決着がついたか否かを判定する
- 決着がついた場合は、勝者の印(「X」もしくは「O」)を返す
- 決着がついていない場合は、
null
を返す
では、上記の仕様を満たす疑似コードを考えます。記事の冒頭で簡単に記しましたが、疑似コードとは自然言語(私たち人間が日常的に使う言葉)とプログラムを混ぜたものです。
function calculateWinner( 全マスの状態 ) {
const 全勝利パターン = [
[ 勝利パターン0 ],
[ 勝利パターン1 ],
[ 勝利パターン2 ],
...,
[ 勝利パターン7 ],
];
for( 全勝利パターンを調べる ) {
if( 勝者条件が満たされた ) {
勝者の印を返す;
}
}
全勝利パターンが満たされなかったので null を返す;
}
勝利の判定方法
勝利判定を成立させるマスの並び方を確認したところで、盤面がそれを満たしているか否かを判定するアルゴリズムを考えてみます。
ある勝利パターンが成立しているか否かを判定するには次の条件を全て満たす必要があります。なお、配列のインデックスと対応させるために、先頭要素を「 0
マス目」という表記をしています。
- 勝利パターンの 0マス目の状態が空白ではない (「X」か「O」の印がある)
- 勝利パターンの 1マス目は、0マス目と同じ印である
- 勝利パターンの 2マス目も、0マス目と同じ印である
これを疑似コードで表してみます。
const 勝利パターン = [0マス目の位置, 1マス目の位置, 2マス目の位置];
if (0マス目に印がある && 0マス目と1マス目は同じ印である && 0マス目と2マス目は同じ印である) {
その印が勝者である;
}
関数の作成
疑似コードの完成
ここまで 3つの疑似コードを提示しましたが、これらを組み合わせて calculateWinner()
関数を明確にしていきます。複雑な疑似コードとなりましたが、これはあくまでイメージを掴むためのものですので、読むのを飛ばしても問題ありません。
function calculateWinner( 全マスの状態 ) {
const 全勝利パターン = [
勝利パターン0,
勝利パターン1,
勝利パターン2,
...,
勝利パターン7,
];
for( let i = 0; i < 全勝利パターンの数; i++ ) {
const 0マス目の位置 = i番目の勝利パターンにおける0マス目の位置;
const 1マス目の位置 = i番目の勝利パターンにおける1マス目の位置;
const 2マス目の位置 = i番目の勝利パターンにおける2マス目の位置;
if (0マス目に印がある && 0マス目と1マス目は同じ印である && 0マス目と2マス目は同じ印である)
その印が勝者である;
}
}
全勝利パターンが満たされなかったので null を返す;
}
プログラムでの表現
calculateWinner()
関数を App.js に定義していきます。先ほどの疑似コードと照らし合わせて頂けると処理内容を理解しやすいと思います。
function calculateWinner(squares) {
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6]
];
for (let i = 0; i < lines.length; i++) {
const a = lines[i][0];
const b = lines[i][1];
const c = lines[i][2];
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a];
}
}
return null;
}
コードの整形
calculateWinner()
関数を作成していきました。for
文内の冒頭で次のコードがありますね。
const a = lines[i][0];
const b = lines[i][1];
const c = lines[i][2];
このままでも問題ありませんが、次のようにコードを整形して、1行で表現します。
const [a, b, c] = lines[i];
これで勝利を判定する関数を作成することができました。
おわりに
今回は、ゲームの勝利を判定するプログラムとして calculateWinner()
関数を作成しました。関数を定義しただけなので、前回と同じ動作となります。次のページに現段階のソースファイルを示します。
次回は、この関数を Board
コンポーネントに組み込んで、勝利判定を表示できるようにします。ゲームが完成するまでもう少しです!