React Tutorial: Tic-Tac-Toe ②

:::note
[github](https://github.com/mori-tt/react-tic-tac-toe)
:::
:::note
[React Tutorial: Tic-Tac-Toe](https://react.dev/learn/tutorial-tic-tac-toe)
:::

### 1. コード全体

```react
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` を返します。

```javascript
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`関数の実装**:
  - マスがすでに埋まっているか、勝者が決まっている場合には何も行わず、クリック時に次の状態を更新します。これにより、無効なクリックを防ぎます。

```javascript
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` と表示し、次のプレイヤーのターンの場合は次にどちらが手番かを表示します。

```javascript
const winner = calculateWinner(squares);
let status;
if (winner) {
  status = 'Winner: ' + winner;
} else {
  status = 'Next player: ' + (xIsNext ? 'X' : 'O');
}
```

### 5. タイムトラベル機能
- **履歴管理**:
  - ゲームの各状態を保存するために `history` 配列を使用します。ステージごとの `squares` を保存して、以前の状態に戻れるようにします。

```javascript
const [history, setHistory] = useState([Array(9).fill(null)]);
```

### 6. 状態のリフトアップ
- **Gameコンポーネントによる状態管理**:
  - `Game` コンポーネントで歴史と現在のボード状態を管理し、ボードに必要な情報をプロップスとして渡します。これにより、ボードは親コンポーネントから状態を受け取る形で動作します。

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

### 7. コードの整理（冗長性の排除）
- **冗長なstateの排除**:
  - `xIsNext` の状態は `currentMove` に基づいて計算可能なため、独立して持たなくてもよいと判断し、コードを簡潔に保つようにします。

```javascript
const xIsNext = currentMove % 2 === 0;
```

---