React:三目並べ(ロジック編)
React公式のチュートリアルです。
手番の処理 以降から進めていきます。
現状Xしか表示できていないので、Oを追加します。
前回のコードの復習
– メインコンポーネントにstate変数がある=子コンポーネント同士の値を管理している
– handleClick内のsetSquaresで値を更新している
– 子コンポーネントのそれぞれの要素には管理番号が振られている
– 子コンポーネントにアロー関数が渡されている=表示時にhandleClick()が実行されるのを防いでいる
import { useState } from "react";
function Square({value, onSquareClick}) {
return (
<button className="square" onClick={onSquareClick}>
{value}
</button>
);
}
export default function Board() {
const [squares, setSquares] = useState(Array(9).fill(null));
function handleClick(i) {
const nextSquares = squares.slice();
console.log('nextSquares', nextSquares);
nextSquares[i] = "X";
setSquares(nextSquares);
}
return (
<>
<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>
</>
);
}
交互にXとOを切り替える
今XなのかOなのか管理するためのstateを、親コンポーネントに追加します。
xIsNext:次Xを表示するかどうか
useState:初期値。true
最初のクリックでは初期値trueでXが表示されます。
const [xIsNext, setXIsNext] = useState(true);
handleClick関数も変更します。
最初のクリックの後処理で、xIsNextはfalseに変わります。
そのため、次のクリックではfalse=Oが表示されます。
function handleClick(i) {
const nextSquares = squares.slice();
console.log('nextSquares', nextSquares);
if (xIsNext) {
nextSquares[i] = "X";
} else {
nextSquares[i] = "O";
}
setSquares(nextSquares);
setXIsNext(!xIsNext);
}
XとOが交互に表示されるようになりました。
ただ、今の状態では埋めたマスにも上書きできてしまいます。
上書き防止のため、マスが埋まっているかどうかチェックする
handleClick関数の序盤でマスの管理番号に基づき値の有無チェックをして、値があれば早期リターンします。これだけでOKです。
function handleClick(i) {
if (squares[i]) {
return;
}
...
勝ち判定のロジックを追加する
判定用のオリジナルの関数を用意します。
引数で渡されてきた配列を元に、勝ち確定の並びパターンと照らし合わせて全て同じ値かどうかを見ています。
calculateWinner関数の詳細コード
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;
}
先ほどのhandleClick関数の序盤に、calculateWinnerの結果を追加します。
XかOどちらかが勝っていれば早期リターンします。
function handleClick(i) {
if (squares[i] || calculateWinner(squares)) {
return;
}
handleClick関数の処理が終わったら、勝敗は誰か、または次は誰の番か、という状況を表示します。
const winner = calculateWinner(squares);
let status;
if (winner) {
status = "Winner" + winner;
} else {
status = "Next player" + (xIsNext ? "X" : "O");
}
return (
<>
<div className="status">{status}</div>
...
実況が表示されました!これで最低限必要な機能は揃いました。
全体のコード
import { useState } from "react";
function Square({value, onSquareClick}) {
return (
<button className="square" onClick={onSquareClick}>
{value}
</button>
);
}
export default function Board() {
const [xIsNext, setXIsNext] = useState(true);
const [squares, setSquares] = useState(Array(9).fill(null));
function handleClick(i) {
if (squares[i] || calculateWinner(squares)) {
return;
}
const nextSquares = squares.slice();
console.log('nextSquares', nextSquares);
if (xIsNext) {
nextSquares[i] = "X";
} else {
nextSquares[i] = "O";
}
setSquares(nextSquares);
setXIsNext(!xIsNext);
}
const winner = calculateWinner(squares);
let status;
if (winner) {
status = "Winner: " + winner;
} else {
status = "Next player: " + (xIsNext ? "X" : "O");
}
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>
</>
);
}
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;
}