AsaDesign

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;
}