前回のおさらい
前回はボード側でマス目の状態を管理できるようになりました。
マスをクリックすると「X」が入るようになりました。
ターンを考える
ここからはいよいよ「X」だけでなく、「O」も入るようにしていきます。
早速ボードにstateを1つ追加します。
誰のターンかを状態として管理する必要があるからです。
今回は「xIsNext」という名前にします。初期値はtrueで、trueかfalseが入ります。
xIsNextがtrueなら、「X」のターン。
xIsNextがfalseなら、「O」のターン。
という設定にします。
export default function Board() {
const [xIsNext, setXIsNext] = useState(true); //追加
const [squares, setSquares] = useState(Array(9).fill(null));
...
イメージはこんな感じです。
- はじめにマスをクリックする => xIsNextがtrueなので「X」がマスに表示される & 「xIsNext」がfalseに切り替わる
- 次にマスをクリック => xIsNextがfalseなので「O」がマスに表示される & 「xIsNext」がtrueに切り替わる
- 次にマスをクリックする => xIsNextがtrueなので「X」がマスに表示される & 「xIsNext」がfalseに切り替わる
- 繰り返す
では、「setXIsNext」で状態を切り替えましょう。
クリックした時に切り替えたいので、handleClick関数の中に書きます。
export default function Board() {
const [xIsNext, setXIsNext] = useState(true);
const [squares, setSquares] = useState(Array(9).fill(null));
function handleClick(i) {
const nextSquares = squares.slice();
if (xIsNext) {
nextSquares[i] = "X";
} else {
nextSquares[i] = "O";
}
setSquares(nextSquares);
setXIsNext(!xIsNext);
}
...以下略
まず「xIsNext」で条件分岐を持たせました。ここで「X」か「O」を判定しています。
handleClick関数の最後にsetXIsNextで、ターンの状態を更新します。
「!xIsnext」で、以前とは逆の状態にすることを意味します。
これで実際の画面を見てみます。
異なるマスをクリックすると、「X」と「O」 が交互に表示されます。
しかし、連続で同じマスをクリックすると切り替わってしまいます。
今回は「1度押したマスは変更不可」にしたいので、切り替わらないようにしたいです。
これはどう実現できるでしょうか?
handleClickに追記します。
function handleClick(i) {
if (squares[i]) {
return
}
const nextSquares = squares.slice();
押されたマスに値がある場合は、 早期にreturnしています。
このように早期にreturnする手法はReactに限らずよく使われます。
これでマス目の値の変更はできなくなりました。
勝敗判定
ここまでで、交互にマス目に値を埋める機能まで完成しました。
残るは勝敗を判定するのみです。
勝敗判定はReactはあまり関係ないです。
export default function Board() {
//...
}
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, b, c] = lines[i];
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a];
}
}
return null;
}
calculateWinnerという関数を定義します。
定数「lines」は縦横斜めの1列に並んだ3マス(三目揃った状態のマス目)を列挙しており、これをforでループをかけて三目揃っているか確認しています。
勝敗がつくと「O」か「X」が返り、そうでなければnullが返される関数になっています。
この関数をhandleClickの中で呼び出します。
function handleClick(i) {
if (squares[i] || calculateWinner(squares)) {
return;
}
勝敗がついたら、それ以上マスを更新できないようにしたいので早期にreturnしています。
これだと誰が勝ったか分からないので、画面上に出力します。
export default function Board() {
const [xIsNext, setXIsNext] = useState(true);
const [squares, setSquares] = useState(Array(9).fill(null));
//ここから追記
const winner = calculateWinner(squares);
let status;
if (winner) {
status = "Winner: " + winner;
} else {
status = "Next player: " + (xIsNext ? "X" : "O");
}
//ここまで
function handleClick(i) {
... 略
return (
<>
<div className="status">{status}</div>
<div className="board-row">
<Square value={squares[0]} onSquareClick={() => handleClick(0)} />
<Square value={squares[1]} onSquareClick={() => handleClick(1)} />
<Square value={squares[2]} onSquareClick={() => handleClick(2)} />
</div>
returnの中にdivタグでstatusを表示します。
これで画面で遊んでみましょう!
ゲームが進行中の場合は次のプレイヤーの番が表示され、
ゲームが終了した場合はステータスに勝者が表示されます。
おめでとうございます!🎉🎉🎉
これで、三目並べゲームが完成しました。また、React の基本も学習しました。
だからあなたはここで本当の勝者です!!(笑)
コードは次のようになります。
import { useState } from "react";
function Square({ value, onSquareClick }) {
return (
<button className="square" onClick={onSquareClick}>
{value}
</button>
);
}
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, b, c] = lines[i];
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a];
}
}
return null;
}
export default function Board() {
const [xIsNext, setXIsNext] = useState(true);
const [squares, setSquares] = useState(Array(9).fill(null));
const winner = calculateWinner(squares);
let status;
if (winner) {
status = "Winner: " + winner;
} else {
status = "Next player: " + (xIsNext ? "X" : "O");
}
function handleClick(i) {
if (squares[i] || calculateWinner(squares)) {
return;
}
const nextSquares = squares.slice();
if (xIsNext) {
nextSquares[i] = "X";
} else {
nextSquares[i] = "O";
}
setSquares(nextSquares);
setXIsNext(!xIsNext);
}
return (
<>
<div className="status">{status}</div>
<div className="board-row">
<Square value={squares[0]} onSquareClick={() => handleClick(0)} />
<Square value={squares[1]} onSquareClick={() => handleClick(1)} />
<Square value={squares[2]} onSquareClick={() => handleClick(2)} />
</div>
<div className="board-row">
<Square value={squares[3]} onSquareClick={() => handleClick(3)} />
<Square value={squares[4]} onSquareClick={() => handleClick(4)} />
<Square value={squares[5]} onSquareClick={() => handleClick(5)} />
</div>
<div className="board-row">
<Square value={squares[6]} onSquareClick={() => handleClick(6)} />
<Square value={squares[7]} onSquareClick={() => handleClick(7)} />
<Square value={squares[8]} onSquareClick={() => handleClick(8)} />
</div>
</>
);
}
次回からは「履歴表示機能」を追加していきます。
(只今作成中です)