0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

React Tutorial: Tic-Tac-Toe ②

Last updated at Posted at 2024-11-27

1. コード全体

import { useState } from "react";

function Square({
  value,
  onSquareClick,
}: {
  value: string | null;
  onSquareClick: () => void;
}) {
  return (
    <button className="square" onClick={onSquareClick}>
      {value}
    </button>
  );
}

function Board({
  xIsNext,
  squares,
  onPlay,
}: {
  xIsNext: boolean;
  squares: (string | null)[];
  onPlay: (nextSquares: (string | null)[]) => void;
}) {
  function handleClick(i: number) {
    if (calculateWinner(squares) || squares[i]) {
      return;
    }
    const nextSquares = squares.slice();
    if (xIsNext) {
      nextSquares[i] = "X";
    } else {
      nextSquares[i] = "O";
    }
    onPlay(nextSquares);
  }

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

export default function Game() {
  const [history, setHistory] = useState([Array(9).fill(null)]);
  const [currentMove, setCurrentMove] = useState(0);
  const xIsNext = currentMove % 2 === 0;
  const currentSquares = history[currentMove];

  function handlePlay(nextSquares: (string | null)[]) {
    const nextHistory = [...history.slice(0, currentMove + 1), nextSquares];
    setHistory(nextHistory);
    setCurrentMove(nextHistory.length - 1);
  }

  function jumpTo(nextMove: number) {
    setCurrentMove(nextMove);
  }

  const moves = history.map((squares, move) => {
    let description;
    if (move > 0) {
      description = "Go to move #" + move;
    } else {
      description = "Go to game start";
    }
    return (
      <li key={move}>
        <button onClick={() => jumpTo(move)}>{description}</button>
      </li>
    );
  });

  return (
    <div className="game">
      <div className="game-board">
        <Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} />
      </div>
      <div className="game-info">
        <ol>{moves}</ol>
      </div>
    </div>
  );
}

function calculateWinner(squares: (string | null)[]) {
  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;
}


2. ゲームの基本構造

  • コンポーネントの定義:
    • 三目並べのボードはBoard コンポーネントで構成され、各マスは Square コンポーネントで表現されます。
    • プレイヤーのターンを管理するために xIsNext を使用し、どちらのプレイヤー(XまたはO)が次に手を打つかを判定します。

3. 勝者を判定する関数

  • calculateWinner関数の作成:
    • 3つの同じマーク(XまたはO)が直線的に並んでいるかを判定するために、勝利条件を配列で定義します。
    • 条件を全て検査し、勝者がいればそのマーキングを返します。全ての条件を試行して勝者がいない場合は null を返します。
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;
}

4. ゲームの進行管理

  • handleClick関数の実装:
    • マスがすでに埋まっているか、勝者が決まっている場合には何も行わず、クリック時に次の状態を更新します。これにより、無効なクリックを防ぎます。
function handleClick(i) {
  if (calculateWinner(squares) || squares[i]) {
    return;
  }
  const nextSquares = squares.slice();
  nextSquares[i] = xIsNext ? 'X' : 'O'; // XまたはOを配置
  setSquares(nextSquares);
  setXIsNext(!xIsNext); // 次のプレイヤーに切り替え
}

5. ゲームの状態表示

  • 状態の表示:
    • 勝者が決定した場合は Winner: X または Winner: O と表示し、次のプレイヤーのターンの場合は次にどちらが手番かを表示します。
const winner = calculateWinner(squares);
let status;
if (winner) {
  status = 'Winner: ' + winner;
} else {
  status = 'Next player: ' + (xIsNext ? 'X' : 'O');
}

5. タイムトラベル機能

  • 履歴管理:
    • ゲームの各状態を保存するために history 配列を使用します。ステージごとの squares を保存して、以前の状態に戻れるようにします。
const [history, setHistory] = useState([Array(9).fill(null)]);

6. 状態のリフトアップ

  • Gameコンポーネントによる状態管理:
    • Game コンポーネントで歴史と現在のボード状態を管理し、ボードに必要な情報をプロップスとして渡します。これにより、ボードは親コンポーネントから状態を受け取る形で動作します。
function handlePlay(nextSquares) {
  const nextHistory = [...history.slice(0, currentMove + 1), nextSquares];
  setHistory(nextHistory);
  setCurrentMove(nextHistory.length - 1);
}

7. コードの整理(冗長性の排除)

  • 冗長なstateの排除:
    • xIsNext の状態は currentMove に基づいて計算可能なため、独立して持たなくてもよいと判断し、コードを簡潔に保つようにします。
const xIsNext = currentMove % 2 === 0;

0
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?