reactで立体四目並べを作る

reactのチュートリアルで三目並べを作ったので、その応用として立体四目並べを作ってみました。
reactに慣れるという目的で作ったのでリファクタリングしてません。汚いコードです。
もし立体四目作ろうと思っているという方の参考になれば幸いです。


reactの三目並べチュートリアル
https://ja.react.dev/learn/tutorial-tic-tac-toe
これに変更を加えて行きます。

この記事では↓ こういうものを作ります
![スクリーンショット 2024-09-30 1.38.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747635/47e4f3df-a606-bf51-ccfe-9e4774c47a60.png)


# 立体四目並べの説明
![スクリーンショット 2024-09-30 0.57.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747635/c86afc90-b59d-d73f-e7f8-5e7092f71016.png)

https://www.amazon.co.jp/%E5%B9%BB%E5%86%AC%E8%88%8E-Gentosha-こういうの。%E7%AB%8B%E4%BD%93%E5%9B%9B%E7%9B%AE-%E7%AB%8B%E4%BD%934%E7%9B%AE/dp/B01LY0OX7B

こういうの。平面(x,y軸)の四目並べではなく、立体（x,y,z軸）の四目並べ。

# 完成したもの
Xと○を交互に置いて行って、先に四目並べた方を判定して、勝ちとする。
判定は主に以下の４つ
![スクリーンショット 2024-09-30 1.33.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747635/91257f3a-f787-949c-5ed0-42b825cbfb56.png)

左から３つ目の立体✖️斜めに関しては、見づらいのでイメージ図貼ります
![スクリーンショット 2024-09-30 1.20.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747635/79a01e0c-6bda-6efb-1a32-2e3087439fc9.png)

# 手順
[1.一般的な四目並べを作る](#一般的な（平面の）四目並べを作る)
[2.それぞれで独立したデータを持つボードを4枚用意する](#それぞれで独立したデータを持つボードを4枚用意する)
[3.前の層にマークがあれば、現在の層への操作を許可する](#前の層にマークがあれば、現在の層への操作を許可する)
[4.4枚目の層で、立体的に判定を行う](#4枚目の層で、立体的に判定を行う)
[5.４つのboardのうち、いずれかで勝者が決まった場合、その後の操作を無効にする](#４つのboardのうち、いずれかで勝者が決まった場合、その後の操作を無効にする)
[6.Winnerが決まっていれば、操作をさせないようにする](#Winnerが決まっていれば、操作をさせないようにする)

# 一般的な四目並べを作る
立体四目並べを作るために、まず1枚のボード上で「平面の四目並べ」をつくる。
チュートリアルのコードを元に以下２点を修正していく。
・マスを3✖️3から4✖️4に増やす
・判定するマスを調整する

![スクリーンショット 2024-09-29 21.48.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747635/94e3987a-ac1a-f37d-57ea-066ef7674ca9.png)

<details><summary>App.jsのコード</summary>

```react:App.js
import { useState } from "react";

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

function Board({ xIsNext, squares, onPlay }) {
  function handleClick(i) {
    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)} />
        <Square value={squares[3]} onSquareClick={() => handleClick(3)} />
      </div>
      <div className="board-row">
        <Square value={squares[4]} onSquareClick={() => handleClick(4)} />
        <Square value={squares[5]} onSquareClick={() => handleClick(5)} />
        <Square value={squares[6]} onSquareClick={() => handleClick(6)} />
        <Square value={squares[7]} onSquareClick={() => handleClick(7)} />
      </div>
      <div className="board-row">
        <Square value={squares[8]} onSquareClick={() => handleClick(8)} />
        <Square value={squares[9]} onSquareClick={() => handleClick(9)} />
        <Square value={squares[10]} onSquareClick={() => handleClick(10)} />
        <Square value={squares[11]} onSquareClick={() => handleClick(11)} />
      </div>
      <div className="board-row">
        <Square value={squares[12]} onSquareClick={() => handleClick(12)} />
        <Square value={squares[13]} onSquareClick={() => handleClick(13)} />
        <Square value={squares[14]} onSquareClick={() => handleClick(14)} />
        <Square value={squares[15]} onSquareClick={() => handleClick(15)} />
      </div>
    </>
  );
}

export default function Game() {
  const [xIsNext, setXIsNext] = useState(true);
  const [history, setHistory] = useState([Array(9).fill(null)]);
  const currentSquares = history[history.length - 1];

  function handlePlay(nextSquares) {
    setHistory([...history, nextSquares]);
    setXIsNext(!xIsNext);
  }

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

// 揃ったかの判定を行う
function calculateWinner(squares) {
  const lines = [
    // 横に揃う
    [0, 1, 2, 3],
    [4, 5, 6, 7],
    [8, 9, 10, 11],
    [12, 13, 14, 15],
    // 縦に揃う
    [0, 4, 8, 12],
    [1, 5, 9, 13],
    [2, 6, 10, 14],
    [3, 7, 11, 15],
    // 斜めに揃う
    [0, 5, 10, 15],
    [3, 6, 9, 12],
  ];
  for (let i = 0; i < lines.length; i++) {
    const [a, b, c, d] = lines[i];
    if (
      squares[a] &&
      squares[a] === squares[b] &&
      squares[a] === squares[c] &&
      squares[a] === squares[d]
    ) {
      return squares[a];
    }
  }
  return null;
}
```
</details>


# それぞれで独立したデータを持つボードを4枚用意する
立体四目には４つの層があるため、4枚のボードを用意する必要がある。
![スクリーンショット 2024-09-29 22.27.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747635/968c3afd-19eb-8881-9133-e2924302bc68.png)

チュートリアルのコードを元に以下２点を修正していく。
・ボードを1枚から4枚に増やす
・各ボードに独自のデータを持たせる

![スクリーンショット 2024-09-29 22.23.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747635/fbe4f7e6-b792-836f-a34d-d851f7f3723f.png)

<details><summary>App.jsのコード</summary>

```react:App.js
import { useState } from "react";

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

function Board({ xIsNext, squares, onPlay }) {
  function handleClick(i) {
    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)} />
        <Square value={squares[3]} onSquareClick={() => handleClick(3)} />
      </div>
      <div className="board-row">
        <Square value={squares[4]} onSquareClick={() => handleClick(4)} />
        <Square value={squares[5]} onSquareClick={() => handleClick(5)} />
        <Square value={squares[6]} onSquareClick={() => handleClick(6)} />
        <Square value={squares[7]} onSquareClick={() => handleClick(7)} />
      </div>
      <div className="board-row">
        <Square value={squares[8]} onSquareClick={() => handleClick(8)} />
        <Square value={squares[9]} onSquareClick={() => handleClick(9)} />
        <Square value={squares[10]} onSquareClick={() => handleClick(10)} />
        <Square value={squares[11]} onSquareClick={() => handleClick(11)} />
      </div>
      <div className="board-row">
        <Square value={squares[12]} onSquareClick={() => handleClick(12)} />
        <Square value={squares[13]} onSquareClick={() => handleClick(13)} />
        <Square value={squares[14]} onSquareClick={() => handleClick(14)} />
        <Square value={squares[15]} onSquareClick={() => handleClick(15)} />
      </div>
    </>
  );
}

export default function Game() {
  const [xIsNext, setXIsNext] = useState(true);

  const [history1, setHistory1] = useState([Array(9).fill(null)]); // 1枚目のボード用
  const [history2, setHistory2] = useState([Array(9).fill(null)]); // 2枚目のボード用
  const [history3, setHistory3] = useState([Array(9).fill(null)]); // 3枚目のボード用
  const [history4, setHistory4] = useState([Array(9).fill(null)]); // 4枚目のボード用
  const currentSquares1 = history1[history1.length - 1]; // 1枚目のボード用
  const currentSquares2 = history2[history2.length - 1]; // 2枚目のボード用
  const currentSquares3 = history3[history3.length - 1]; // 3枚目のボード用
  const currentSquares4 = history4[history4.length - 1]; // 4枚目のボード用

  // 1枚目のボード用
  function handlePlay1(nextSquares) {
    setHistory1([...history1, nextSquares]);
    setXIsNext(!xIsNext);
  }
  // 2枚目のボード用
  function handlePlay2(nextSquares) {
    setHistory2([...history2, nextSquares]);
    setXIsNext(!xIsNext);
  }
  // 3枚目のボード用
  function handlePlay3(nextSquares) {
    setHistory3([...history3, nextSquares]);
    setXIsNext(!xIsNext);
  }
  // 3枚目のボード用
  function handlePlay4(nextSquares) {
    setHistory4([...history4, nextSquares]);
    setXIsNext(!xIsNext);
  }

  return (
    <div className="game">
      <div className="game-board">
        <div>1枚目</div>
        <Board
          xIsNext={xIsNext}
          squares={currentSquares1}
          onPlay={handlePlay1}
        />
        <div>2枚目</div>
        <Board
          xIsNext={xIsNext}
          squares={currentSquares2}
          onPlay={handlePlay2}
        />
        <div>3枚目</div>
        <Board
          xIsNext={xIsNext}
          squares={currentSquares3}
          onPlay={handlePlay3}
        />
        <div>4枚目</div>
        <Board
          xIsNext={xIsNext}
          squares={currentSquares4}
          onPlay={handlePlay4}
        />
      </div>
      <div className="game-info">
        <ol>{/*TODO*/}</ol>
      </div>
    </div>
  );
}

// 揃ったかの判定を行う
function calculateWinner(squares) {
  const lines = [
    // 横に揃う
    [0, 1, 2, 3],
    [4, 5, 6, 7],
    [8, 9, 10, 11],
    [12, 13, 14, 15],
    // 縦に揃う
    [0, 4, 8, 12],
    [1, 5, 9, 13],
    [2, 6, 10, 14],
    [3, 7, 11, 15],
    // 斜めに揃う
    [0, 5, 10, 15],
    [3, 6, 9, 12],
  ];
  for (let i = 0; i < lines.length; i++) {
    const [a, b, c, d] = lines[i];
    if (
      squares[a] &&
      squares[a] === squares[b] &&
      squares[a] === squares[c] &&
      squares[a] === squares[d]
    ) {
      return squares[a];
    }
  }
  return null;
}

```
</details>


# 前の層にマークがあれば、現在の層への操作を許可する
![スクリーンショット 2024-09-29 22.49.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747635/1c9918ba-8f8f-91e2-f1a0-02424dabb440.png)

![スクリーンショット 2024-09-29 22.53.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747635/9032dd6f-f1c5-ad19-1175-49d4e82612dc.png)


以下の処理を追加します
・Boadに、１つ下の層のデータを渡す
・Boad内の処理で、iがクリックされたとき、一つ下の層のiがなければ、操作を無効とする

```
        <div>1枚目</div>
        <Board
          xIsNext={xIsNext}
          squares={currentSquares1}
          onPlay={handlePlay1}
          // １つ目のボードに関しては最下層であるため、
        />
        <div>2枚目</div>
        <Board
          xIsNext={xIsNext}
          squares={currentSquares2}
          onPlay={handlePlay2}
          prevSquares={currentSquares1} // 1つ下の層の状態を渡す
        />
        <div>3枚目</div>
        <Board
          xIsNext={xIsNext}
          squares={currentSquares3}
          onPlay={handlePlay3}
          prevSquares={currentSquares2} // 1つ下の層の状態を渡す
        />
        <div>4枚目</div>
        <Board
          xIsNext={xIsNext}
          squares={currentSquares4}
          onPlay={handlePlay4}
          prevSquares={currentSquares3} // 1つ下の層の状態を渡す
        />
```

Boad内の処理

```react:App.js
  function handleClick(i) {
    if (calculateWinner(squares) || squares[i]) {
      return;
    }

    // ========ここから追加========
    if (prevSquares && !prevSquares[i]) {
      // クリックされたマスの１つ前の層にマークがなければ無効
      return;
    }
    // ========ここまで追加========

    const nextSquares = squares.slice();
    if (xIsNext) {
      nextSquares[i] = "X";
    } else {
      nextSquares[i] = "O";
    }
    onPlay(nextSquares);
  }

```

<details><summary>App.jsの全体のコード</summary>

```react:App.js
import { useState } from "react";

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

function Board({ xIsNext, squares, onPlay, prevSquares }) {
  function handleClick(i) {
    if (calculateWinner(squares) || squares[i]) {
      return;
    }

    if (prevSquares && !prevSquares[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)} />
        <Square value={squares[3]} onSquareClick={() => handleClick(3)} />
      </div>
      <div className="board-row">
        <Square value={squares[4]} onSquareClick={() => handleClick(4)} />
        <Square value={squares[5]} onSquareClick={() => handleClick(5)} />
        <Square value={squares[6]} onSquareClick={() => handleClick(6)} />
        <Square value={squares[7]} onSquareClick={() => handleClick(7)} />
      </div>
      <div className="board-row">
        <Square value={squares[8]} onSquareClick={() => handleClick(8)} />
        <Square value={squares[9]} onSquareClick={() => handleClick(9)} />
        <Square value={squares[10]} onSquareClick={() => handleClick(10)} />
        <Square value={squares[11]} onSquareClick={() => handleClick(11)} />
      </div>
      <div className="board-row">
        <Square value={squares[12]} onSquareClick={() => handleClick(12)} />
        <Square value={squares[13]} onSquareClick={() => handleClick(13)} />
        <Square value={squares[14]} onSquareClick={() => handleClick(14)} />
        <Square value={squares[15]} onSquareClick={() => handleClick(15)} />
      </div>
    </>
  );
}

export default function Game() {
  const [xIsNext, setXIsNext] = useState(true);

  const [history1, setHistory1] = useState([Array(9).fill(null)]); // 1枚目のボード用
  const [history2, setHistory2] = useState([Array(9).fill(null)]); // 2枚目のボード用
  const [history3, setHistory3] = useState([Array(9).fill(null)]); // 3枚目のボード用
  const [history4, setHistory4] = useState([Array(9).fill(null)]); // 4枚目のボード用
  const currentSquares1 = history1[history1.length - 1]; // 1枚目のボード用
  const currentSquares2 = history2[history2.length - 1]; // 2枚目のボード用
  const currentSquares3 = history3[history3.length - 1]; // 3枚目のボード用
  const currentSquares4 = history4[history4.length - 1]; // 4枚目のボード用

  // 1枚目のボード用
  function handlePlay1(nextSquares) {
    setHistory1([...history1, nextSquares]);
    setXIsNext(!xIsNext);
  }
  // 2枚目のボード用
  function handlePlay2(nextSquares) {
    setHistory2([...history2, nextSquares]);
    setXIsNext(!xIsNext);
  }
  // 3枚目のボード用
  function handlePlay3(nextSquares) {
    setHistory3([...history3, nextSquares]);
    setXIsNext(!xIsNext);
  }
  // 3枚目のボード用
  function handlePlay4(nextSquares) {
    setHistory4([...history4, nextSquares]);
    setXIsNext(!xIsNext);
  }

  return (
    <div className="game">
      <div className="game-board">
        <div>1枚目</div>
        <Board
          xIsNext={xIsNext}
          squares={currentSquares1}
          onPlay={handlePlay1}
        />
        <div>2枚目</div>
        <Board
          xIsNext={xIsNext}
          squares={currentSquares2}
          onPlay={handlePlay2}
          prevSquares={currentSquares1} // 1枚目の状態を渡す
        />
        <div>3枚目</div>
        <Board
          xIsNext={xIsNext}
          squares={currentSquares3}
          onPlay={handlePlay3}
          prevSquares={currentSquares2} // 2枚目の状態を渡す
        />
        <div>4枚目</div>
        <Board
          xIsNext={xIsNext}
          squares={currentSquares4}
          onPlay={handlePlay4}
          prevSquares={currentSquares3} // 3枚目の状態を渡す
        />
      </div>
      <div className="game-info">
        <ol>{/*TODO*/}</ol>
      </div>
    </div>
  );
}

// 揃ったかの判定を行う
function calculateWinner(squares) {
  const lines = [
    // 横に揃う
    [0, 1, 2, 3],
    [4, 5, 6, 7],
    [8, 9, 10, 11],
    [12, 13, 14, 15],
    // 縦に揃う
    [0, 4, 8, 12],
    [1, 5, 9, 13],
    [2, 6, 10, 14],
    [3, 7, 11, 15],
    // 斜めに揃う
    [0, 5, 10, 15],
    [3, 6, 9, 12],
  ];
  for (let i = 0; i < lines.length; i++) {
    const [a, b, c, d] = lines[i];
    if (
      squares[a] &&
      squares[a] === squares[b] &&
      squares[a] === squares[c] &&
      squares[a] === squares[d]
    ) {
      return squares[a];
    }
  }
  return null;
}

```
</details>


# 4枚目の層で、立体的に判定を行う
![スクリーンショット 2024-09-30 0.02.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747635/b050ce69-4c6c-8c46-3690-0eeb0e223933.png)

![スクリーンショット 2024-09-30 0.06.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747635/db6a103a-0427-b56a-eecd-132aaa43cf6b.png)

以下の処理を追加
・4層目にマークされたあと、立体的に４つ並んでいるかの判定を行う関数を追加
・board内で、追加した関数を呼び出す

```react:App.js
//　立体的な判定を行う
function calculateWinner3D(squares, otherSquares) {
  const lines = [
    // 各層で横に揃うライン
    [0, 1, 2, 3],
    [4, 5, 6, 7],
    [8, 9, 10, 11],
    [12, 13, 14, 15],
    // 各層で横に揃うライン(逆方向)
    [3, 2, 1, 0],
    [7, 6, 5, 4],
    [11, 10, 9, 8],
    [15, 14, 13, 12],

    // 各層で縦に揃うライン
    [0, 4, 8, 12],
    [1, 5, 9, 13],
    [2, 6, 10, 14],
    [3, 7, 11, 15],
    // 各層で縦に揃うライン(逆方向)
    [12, 8, 4, 0],
    [13, 9, 5, 1],
    [14, 10, 6, 2],
    [15, 11, 7, 3],

    // 各層で斜めに揃うライン
    [0, 5, 10, 15],
    [3, 6, 9, 12],
    // 各層で斜めに揃うライン(逆方向)
    [15, 10, 5, 0],
    [12, 9, 6, 3],
  ];
  for (let i = 0; i < lines.length; i++) {
    const [a, b, c, d] = lines[i];
    // console.log("===確かめ===");
    // console.log("4層目：squares[a]:", squares[a]);
    // console.log("4層目：otherSquares[2][b]:", otherSquares[2][b]);
    // console.log("4層目：otherSquares[1][c]:", otherSquares[1][c]);
    // console.log("4層目：otherSquares[0][d]:", otherSquares[0][d]);
    if (
      squares[a] && // 4層目の現在のボード
      squares[a] === otherSquares[2][b] && // 3層目
      squares[a] === otherSquares[1][c] && // 2層目
      squares[a] === otherSquares[0][d] // 1層目
    ) {
      return squares[a];
    }
  }

  // 各層の同じマスにマークがある判定
  for (let i = 0; i < squares.length; i++) {
    if (
      squares[i] && // 4層目
      squares[i] === otherSquares[0][i] && // 1層目
      squares[i] === otherSquares[1][i] && // 2層目
      squares[i] === otherSquares[2][i] // 3層目
    ) {
      return squares[i];
    }
  }
  return null;
}

```

<details><summary>App.js前コード</summary>

```react:App.js
import { useState } from "react";

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

function Board({ xIsNext, squares, onPlay, prevSquares, otherSquares }) {
  function handleClick(i) {
    if (calculateWinner(squares) || squares[i]) {
      return;
    }

    if (prevSquares && !prevSquares[i]) {
      // クリックされたマスの１つ前の層にマークがなければ無効
      return;
    }

    const nextSquares = squares.slice();
    if (xIsNext) {
      nextSquares[i] = "X";
    } else {
      nextSquares[i] = "O";
    }
    onPlay(nextSquares);
  }

  let winner = calculateWinner(squares);
  if (!winner && otherSquares) {
    // 平面の判定で勝者が決まらず、４層目の場合は立体の判定を行う
    winner = calculateWinner3D(squares, otherSquares);
  }
  let status;
  if (winner) {
    status = "Winner: " + winner;
  }

  return (
    <>
      {status && <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)} />
        <Square value={squares[3]} onSquareClick={() => handleClick(3)} />
      </div>
      <div className="board-row">
        <Square value={squares[4]} onSquareClick={() => handleClick(4)} />
        <Square value={squares[5]} onSquareClick={() => handleClick(5)} />
        <Square value={squares[6]} onSquareClick={() => handleClick(6)} />
        <Square value={squares[7]} onSquareClick={() => handleClick(7)} />
      </div>
      <div className="board-row">
        <Square value={squares[8]} onSquareClick={() => handleClick(8)} />
        <Square value={squares[9]} onSquareClick={() => handleClick(9)} />
        <Square value={squares[10]} onSquareClick={() => handleClick(10)} />
        <Square value={squares[11]} onSquareClick={() => handleClick(11)} />
      </div>
      <div className="board-row">
        <Square value={squares[12]} onSquareClick={() => handleClick(12)} />
        <Square value={squares[13]} onSquareClick={() => handleClick(13)} />
        <Square value={squares[14]} onSquareClick={() => handleClick(14)} />
        <Square value={squares[15]} onSquareClick={() => handleClick(15)} />
      </div>
    </>
  );
}

export default function Game() {
  const [xIsNext, setXIsNext] = useState(true);

  const [history1, setHistory1] = useState([Array(9).fill(null)]); // 1枚目のボード用
  const [history2, setHistory2] = useState([Array(9).fill(null)]); // 2枚目のボード用
  const [history3, setHistory3] = useState([Array(9).fill(null)]); // 3枚目のボード用
  const [history4, setHistory4] = useState([Array(9).fill(null)]); // 4枚目のボード用
  const currentSquares1 = history1[history1.length - 1]; // 1枚目のボード用
  const currentSquares2 = history2[history2.length - 1]; // 2枚目のボード用
  const currentSquares3 = history3[history3.length - 1]; // 3枚目のボード用
  const currentSquares4 = history4[history4.length - 1]; // 4枚目のボード用

  // 1枚目のボード用
  function handlePlay1(nextSquares) {
    setHistory1([...history1, nextSquares]);
    setXIsNext(!xIsNext);
  }
  // 2枚目のボード用
  function handlePlay2(nextSquares) {
    setHistory2([...history2, nextSquares]);
    setXIsNext(!xIsNext);
  }
  // 3枚目のボード用
  function handlePlay3(nextSquares) {
    setHistory3([...history3, nextSquares]);
    setXIsNext(!xIsNext);
  }
  // 3枚目のボード用
  function handlePlay4(nextSquares) {
    setHistory4([...history4, nextSquares]);
    setXIsNext(!xIsNext);
  }

  return (
    <div className="game">
      <div className="game-board">
        <div>1枚目</div>
        <Board
          xIsNext={xIsNext}
          squares={currentSquares1}
          onPlay={handlePlay1}
        />
        <div>2枚目</div>
        <Board
          xIsNext={xIsNext}
          squares={currentSquares2}
          onPlay={handlePlay2}
          prevSquares={currentSquares1} // 1枚目の状態を渡す
        />
        <div>3枚目</div>
        <Board
          xIsNext={xIsNext}
          squares={currentSquares3}
          onPlay={handlePlay3}
          prevSquares={currentSquares2} // 2枚目の状態を渡す
        />
        <div>4枚目</div>
        <Board
          xIsNext={xIsNext}
          squares={currentSquares4}
          onPlay={handlePlay4}
          prevSquares={currentSquares3} // 3枚目の状態を渡す
          otherSquares={[currentSquares1, currentSquares2, currentSquares3]}
        />
      </div>
      <div className="game-info">
        <ol>{/*TODO*/}</ol>
      </div>
    </div>
  );
}

// 平面的な判定を行う
function calculateWinner(squares) {
  const lines = [
    // 横に揃う
    [0, 1, 2, 3],
    [4, 5, 6, 7],
    [8, 9, 10, 11],
    [12, 13, 14, 15],
    // 縦に揃う
    [0, 4, 8, 12],
    [1, 5, 9, 13],
    [2, 6, 10, 14],
    [3, 7, 11, 15],
    // 斜めに揃う
    [0, 5, 10, 15],
    [3, 6, 9, 12],
  ];
  for (let i = 0; i < lines.length; i++) {
    const [a, b, c, d] = lines[i];
    if (
      squares[a] &&
      squares[a] === squares[b] &&
      squares[a] === squares[c] &&
      squares[a] === squares[d]
    ) {
      return squares[a];
    }
  }
  return null;
}

//　立体的な判定を行う
function calculateWinner3D(squares, otherSquares) {
  const lines = [
    // 各層で横に揃うライン
    [0, 1, 2, 3],
    [4, 5, 6, 7],
    [8, 9, 10, 11],
    [12, 13, 14, 15],
    // 各層で横に揃うライン(逆方向)
    [3, 2, 1, 0],
    [7, 6, 5, 4],
    [11, 10, 9, 8],
    [15, 14, 13, 12],

    // 各層で縦に揃うライン
    [0, 4, 8, 12],
    [1, 5, 9, 13],
    [2, 6, 10, 14],
    [3, 7, 11, 15],
    // 各層で縦に揃うライン(逆方向)
    [12, 8, 4, 0],
    [13, 9, 5, 1],
    [14, 10, 6, 2],
    [15, 11, 7, 3],

    // 各層で斜めに揃うライン
    [0, 5, 10, 15],
    [3, 6, 9, 12],
    // 各層で斜めに揃うライン(逆方向)
    [15, 10, 5, 0],
    [12, 9, 6, 3],
  ];
  for (let i = 0; i < lines.length; i++) {
    const [a, b, c, d] = lines[i];
    // console.log("===確かめ===");
    // console.log("4層目：squares[a]:", squares[a]);
    // console.log("4層目：otherSquares[2][b]:", otherSquares[2][b]);
    // console.log("4層目：otherSquares[1][c]:", otherSquares[1][c]);
    // console.log("4層目：otherSquares[0][d]:", otherSquares[0][d]);
    if (
      squares[a] && // 4層目の現在のボード
      squares[a] === otherSquares[2][b] && // 3層目
      squares[a] === otherSquares[1][c] && // 2層目
      squares[a] === otherSquares[0][d] // 1層目
    ) {
      return squares[a];
    }
  }

  // 各層の同じマスにマークがある判定
  for (let i = 0; i < squares.length; i++) {
    if (
      squares[i] && // 4層目
      squares[i] === otherSquares[0][i] && // 1層目
      squares[i] === otherSquares[1][i] && // 2層目
      squares[i] === otherSquares[2][i] // 3層目
    ) {
      return squares[i];
    }
  }
  return null;
}
```
</details>


現在の時点では、board内で勝者を出しているため、
4枚目の層で勝者が決まっても、1,2,3枚目の層では勝者の情報が渡されておらず、操作できてしまう。

# ４つのboardのうち、いずれかで勝者が決まった場合、その後の操作を無効にする

以下の処理を行う
・Game(親)コンポーネントに勝者の状態を追加する
・Game(親)コンポーネントに 勝者を更新する関数を作成
・Board（子）コンポーネントを呼び出す際に、勝者を更新する関数を渡す
・Board（子）コンポーネント内で、勝者が決まれば、親コンポーネントの勝者更新関数を呼び出す

![スクリーンショット 2024-09-30 0.42.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747635/385361fe-4aea-f3f7-468b-aea34307a9a0.png)

これで親コンポーネントが、Winnerの管理をできるようになった。

<details><summary>App.js全体のコード</summary>

```react:App.js

import { useState } from "react";

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

function Board({
  xIsNext,
  squares,
  onPlay,
  prevSquares,
  otherSquares,
  onUpdate,
}) {
  function handleClick(i) {
    if (calculateWinner(squares) || squares[i]) {
      return;
    }

    if (prevSquares && !prevSquares[i]) {
      // クリックされたマスの１つ前の層にマークがなければ無効
      return;
    }

    const nextSquares = squares.slice();
    if (xIsNext) {
      nextSquares[i] = "X";
    } else {
      nextSquares[i] = "O";
    }
    onPlay(nextSquares);
  }

  let winner = calculateWinner(squares);
  if (!winner && otherSquares) {
    // 平面の判定で勝者が決まらず、４層目の場合は立体の判定を行う
    winner = calculateWinner3D(squares, otherSquares);
  }
  let status;
  if (winner) {
    status = "Winner: " + winner;
    onUpdate(winner);
  }

  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)} />
        <Square value={squares[3]} onSquareClick={() => handleClick(3)} />
      </div>
      <div className="board-row">
        <Square value={squares[4]} onSquareClick={() => handleClick(4)} />
        <Square value={squares[5]} onSquareClick={() => handleClick(5)} />
        <Square value={squares[6]} onSquareClick={() => handleClick(6)} />
        <Square value={squares[7]} onSquareClick={() => handleClick(7)} />
      </div>
      <div className="board-row">
        <Square value={squares[8]} onSquareClick={() => handleClick(8)} />
        <Square value={squares[9]} onSquareClick={() => handleClick(9)} />
        <Square value={squares[10]} onSquareClick={() => handleClick(10)} />
        <Square value={squares[11]} onSquareClick={() => handleClick(11)} />
      </div>
      <div className="board-row">
        <Square value={squares[12]} onSquareClick={() => handleClick(12)} />
        <Square value={squares[13]} onSquareClick={() => handleClick(13)} />
        <Square value={squares[14]} onSquareClick={() => handleClick(14)} />
        <Square value={squares[15]} onSquareClick={() => handleClick(15)} />
      </div>
    </>
  );
}

export default function Game() {
  const [xIsNext, setXIsNext] = useState(true);
  const [history1, setHistory1] = useState([Array(9).fill(null)]); // 1枚目のボード用
  const [history2, setHistory2] = useState([Array(9).fill(null)]); // 2枚目のボード用
  const [history3, setHistory3] = useState([Array(9).fill(null)]); // 3枚目のボード用
  const [history4, setHistory4] = useState([Array(9).fill(null)]); // 4枚目のボード用
  const currentSquares1 = history1[history1.length - 1]; // 1枚目のボード用
  const currentSquares2 = history2[history2.length - 1]; // 2枚目のボード用
  const currentSquares3 = history3[history3.length - 1]; // 3枚目のボード用
  const currentSquares4 = history4[history4.length - 1]; // 4枚目のボード用

  // 勝者が決まった際、親コンポーネントで勝者を共有する
  [winner, setWinner] = useState(null);
  const updateWinner = (newWinner) => {
    setWinner(newWinner);
  };

  // 1枚目のボード用
  function handlePlay1(nextSquares) {
    setHistory1([...history1, nextSquares]);
    setXIsNext(!xIsNext);
  }
  // 2枚目のボード用
  function handlePlay2(nextSquares) {
    setHistory2([...history2, nextSquares]);
    setXIsNext(!xIsNext);
  }
  // 3枚目のボード用
  function handlePlay3(nextSquares) {
    setHistory3([...history3, nextSquares]);
    setXIsNext(!xIsNext);
  }
  // 3枚目のボード用
  function handlePlay4(nextSquares) {
    setHistory4([...history4, nextSquares]);
    setXIsNext(!xIsNext);
  }

  return (
    <div className="game">
      <div className="game-board">
        {winner && <div className="winner">判定: {winner}</div>}
        <div>1枚目</div>
        <Board
          xIsNext={xIsNext}
          squares={currentSquares1}
          onPlay={handlePlay1}
          onUpdate={updateWinner}
        />
        <div>2枚目</div>
        <Board
          xIsNext={xIsNext}
          squares={currentSquares2}
          onPlay={handlePlay2}
          prevSquares={currentSquares1} // 1枚目の状態を渡す
          onUpdate={updateWinner}
        />
        <div>3枚目</div>
        <Board
          xIsNext={xIsNext}
          squares={currentSquares3}
          onPlay={handlePlay3}
          prevSquares={currentSquares2} // 2枚目の状態を渡す
          onUpdate={updateWinner}
        />
        <div>4枚目</div>
        <Board
          xIsNext={xIsNext}
          squares={currentSquares4}
          onPlay={handlePlay4}
          prevSquares={currentSquares3} // 3枚目の状態を渡す
          otherSquares={[currentSquares1, currentSquares2, currentSquares3]}
          onUpdate={updateWinner}
        />
      </div>
      <div className="game-info">
        <ol>{/*TODO*/}</ol>
      </div>
    </div>
  );
}

// 平面的な判定を行う
function calculateWinner(squares) {
  const lines = [
    // 横に揃う
    [0, 1, 2, 3],
    [4, 5, 6, 7],
    [8, 9, 10, 11],
    [12, 13, 14, 15],
    // 縦に揃う
    [0, 4, 8, 12],
    [1, 5, 9, 13],
    [2, 6, 10, 14],
    [3, 7, 11, 15],
    // 斜めに揃う
    [0, 5, 10, 15],
    [3, 6, 9, 12],
  ];
  for (let i = 0; i < lines.length; i++) {
    const [a, b, c, d] = lines[i];
    if (
      squares[a] &&
      squares[a] === squares[b] &&
      squares[a] === squares[c] &&
      squares[a] === squares[d]
    ) {
      return squares[a];
    }
  }
  return null;
}

//　立体的な判定を行う
function calculateWinner3D(squares, otherSquares) {
  const lines = [
    // 各層で横に揃うライン
    [0, 1, 2, 3],
    [4, 5, 6, 7],
    [8, 9, 10, 11],
    [12, 13, 14, 15],
    // 各層で横に揃うライン(逆方向)
    [3, 2, 1, 0],
    [7, 6, 5, 4],
    [11, 10, 9, 8],
    [15, 14, 13, 12],

    // 各層で縦に揃うライン
    [0, 4, 8, 12],
    [1, 5, 9, 13],
    [2, 6, 10, 14],
    [3, 7, 11, 15],
    // 各層で縦に揃うライン(逆方向)
    [12, 8, 4, 0],
    [13, 9, 5, 1],
    [14, 10, 6, 2],
    [15, 11, 7, 3],

    // 各層で斜めに揃うライン
    [0, 5, 10, 15],
    [3, 6, 9, 12],
    // 各層で斜めに揃うライン(逆方向)
    [15, 10, 5, 0],
    [12, 9, 6, 3],
  ];
  for (let i = 0; i < lines.length; i++) {
    const [a, b, c, d] = lines[i];
    // console.log("===確かめ===");
    // console.log("4層目：squares[a]:", squares[a]);
    // console.log("4層目：otherSquares[2][b]:", otherSquares[2][b]);
    // console.log("4層目：otherSquares[1][c]:", otherSquares[1][c]);
    // console.log("4層目：otherSquares[0][d]:", otherSquares[0][d]);
    if (
      squares[a] && // 4層目の現在のボード
      squares[a] === otherSquares[2][b] && // 3層目
      squares[a] === otherSquares[1][c] && // 2層目
      squares[a] === otherSquares[0][d] // 1層目
    ) {
      return squares[a];
    }
  }

  // 各層の同じマスにマークがある判定
  for (let i = 0; i < squares.length; i++) {
    if (
      squares[i] && // 4層目
      squares[i] === otherSquares[0][i] && // 1層目
      squares[i] === otherSquares[1][i] && // 2層目
      squares[i] === otherSquares[2][i] // 3層目
    ) {
      return squares[i];
    }
  }
  return null;
}

```
</details>

# Winnerが決まっていれば、操作をさせないようにする
```react:App.js
function Board({
  xIsNext,
  squares,
  onPlay,
  prevSquares,
  otherSquares,
  onUpdate,
  // 追加
  isWinner,
}) {
  function handleClick(i) {
    // ====ここから変更====
    if (squares[i]) {
      // すでにマークされた部分がクリックされたら何もしない
      return;
    }
    if (isWinner != null) {
      // 勝者が決まっている場合は何もしない
      return;
    }
    // ====ここまで変更====

    // ~省略~
  }
```

gameコンポーネント内

```react:App.js
        <div>1枚目</div>
        <Board
          xIsNext={xIsNext}
          squares={currentSquares1}
          onPlay={handlePlay1}
          onUpdate={updateWinner}
          isWinner={winner} // ここ追加
        />
```

これでロジックは完成！

<details><summary>App.js全文</summary>

```react:App.js
import { useState } from "react";

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

function Board({
  xIsNext,
  squares,
  onPlay,
  prevSquares,
  otherSquares,
  onUpdate,
  isWinner,
}) {
  function handleClick(i) {
    if (squares[i]) {
      // すでにマークされた部分がクリックされたら何もしない
      return;
    }
    if (isWinner != null) {
      // 勝者が決まっている場合は何もしない
      return;
    }

    if (prevSquares && !prevSquares[i]) {
      // クリックされたマスの１つ前の層にマークがなければ無効
      return;
    }

    const nextSquares = squares.slice();
    if (xIsNext) {
      nextSquares[i] = "X";
    } else {
      nextSquares[i] = "O";
    }
    onPlay(nextSquares);
  }

  let winner = calculateWinner(squares);
  if (!winner && otherSquares) {
    // 平面の判定で勝者が決まらず、４層目の場合は立体の判定を行う
    winner = calculateWinner3D(squares, otherSquares);
  }
  let status;
  if (winner) {
    status = "Winner: " + winner;
    onUpdate(winner);
  }

  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)} />
        <Square value={squares[3]} onSquareClick={() => handleClick(3)} />
      </div>
      <div className="board-row">
        <Square value={squares[4]} onSquareClick={() => handleClick(4)} />
        <Square value={squares[5]} onSquareClick={() => handleClick(5)} />
        <Square value={squares[6]} onSquareClick={() => handleClick(6)} />
        <Square value={squares[7]} onSquareClick={() => handleClick(7)} />
      </div>
      <div className="board-row">
        <Square value={squares[8]} onSquareClick={() => handleClick(8)} />
        <Square value={squares[9]} onSquareClick={() => handleClick(9)} />
        <Square value={squares[10]} onSquareClick={() => handleClick(10)} />
        <Square value={squares[11]} onSquareClick={() => handleClick(11)} />
      </div>
      <div className="board-row">
        <Square value={squares[12]} onSquareClick={() => handleClick(12)} />
        <Square value={squares[13]} onSquareClick={() => handleClick(13)} />
        <Square value={squares[14]} onSquareClick={() => handleClick(14)} />
        <Square value={squares[15]} onSquareClick={() => handleClick(15)} />
      </div>
    </>
  );
}

export default function Game() {
  const [xIsNext, setXIsNext] = useState(true);
  const [history1, setHistory1] = useState([Array(9).fill(null)]); // 1枚目のボード用
  const [history2, setHistory2] = useState([Array(9).fill(null)]); // 2枚目のボード用
  const [history3, setHistory3] = useState([Array(9).fill(null)]); // 3枚目のボード用
  const [history4, setHistory4] = useState([Array(9).fill(null)]); // 4枚目のボード用
  const currentSquares1 = history1[history1.length - 1]; // 1枚目のボード用
  const currentSquares2 = history2[history2.length - 1]; // 2枚目のボード用
  const currentSquares3 = history3[history3.length - 1]; // 3枚目のボード用
  const currentSquares4 = history4[history4.length - 1]; // 4枚目のボード用

  // 勝者が決まった際、親コンポーネントで勝者を共有する
  [winner, setWinner] = useState(null);
  const updateWinner = (newWinner) => {
    setWinner(newWinner);
  };

  // 1枚目のボード用
  function handlePlay1(nextSquares) {
    setHistory1([...history1, nextSquares]);
    setXIsNext(!xIsNext);
  }
  // 2枚目のボード用
  function handlePlay2(nextSquares) {
    setHistory2([...history2, nextSquares]);
    setXIsNext(!xIsNext);
  }
  // 3枚目のボード用
  function handlePlay3(nextSquares) {
    setHistory3([...history3, nextSquares]);
    setXIsNext(!xIsNext);
  }
  // 3枚目のボード用
  function handlePlay4(nextSquares) {
    setHistory4([...history4, nextSquares]);
    setXIsNext(!xIsNext);
  }

  return (
    <div className="game">
      <div className="game-board">
        {winner && <div className="winner">判定: {winner}</div>}
        <div>1枚目</div>
        <Board
          xIsNext={xIsNext}
          squares={currentSquares1}
          onPlay={handlePlay1}
          onUpdate={updateWinner}
          isWinner={winner}
        />
        <div>2枚目</div>
        <Board
          xIsNext={xIsNext}
          squares={currentSquares2}
          onPlay={handlePlay2}
          prevSquares={currentSquares1} // 1枚目の状態を渡す
          onUpdate={updateWinner}
          isWinner={winner}
        />
        <div>3枚目</div>
        <Board
          xIsNext={xIsNext}
          squares={currentSquares3}
          onPlay={handlePlay3}
          prevSquares={currentSquares2} // 2枚目の状態を渡す
          onUpdate={updateWinner}
          isWinner={winner}
        />
        <div>4枚目</div>
        <Board
          xIsNext={xIsNext}
          squares={currentSquares4}
          onPlay={handlePlay4}
          prevSquares={currentSquares3} // 3枚目の状態を渡す
          otherSquares={[currentSquares1, currentSquares2, currentSquares3]}
          onUpdate={updateWinner}
          isWinner={winner}
        />
      </div>
      <div className="game-info">
        <ol>{/*TODO*/}</ol>
      </div>
    </div>
  );
}

// 平面的な判定を行う
function calculateWinner(squares) {
  const lines = [
    // 横に揃う
    [0, 1, 2, 3],
    [4, 5, 6, 7],
    [8, 9, 10, 11],
    [12, 13, 14, 15],
    // 縦に揃う
    [0, 4, 8, 12],
    [1, 5, 9, 13],
    [2, 6, 10, 14],
    [3, 7, 11, 15],
    // 斜めに揃う
    [0, 5, 10, 15],
    [3, 6, 9, 12],
  ];
  for (let i = 0; i < lines.length; i++) {
    const [a, b, c, d] = lines[i];
    if (
      squares[a] &&
      squares[a] === squares[b] &&
      squares[a] === squares[c] &&
      squares[a] === squares[d]
    ) {
      return squares[a];
    }
  }
  return null;
}

//　立体的な判定を行う
function calculateWinner3D(squares, otherSquares) {
  const lines = [
    // 各層で横に揃うライン
    [0, 1, 2, 3],
    [4, 5, 6, 7],
    [8, 9, 10, 11],
    [12, 13, 14, 15],
    // 各層で横に揃うライン(逆方向)
    [3, 2, 1, 0],
    [7, 6, 5, 4],
    [11, 10, 9, 8],
    [15, 14, 13, 12],

    // 各層で縦に揃うライン
    [0, 4, 8, 12],
    [1, 5, 9, 13],
    [2, 6, 10, 14],
    [3, 7, 11, 15],
    // 各層で縦に揃うライン(逆方向)
    [12, 8, 4, 0],
    [13, 9, 5, 1],
    [14, 10, 6, 2],
    [15, 11, 7, 3],

    // 各層で斜めに揃うライン
    [0, 5, 10, 15],
    [3, 6, 9, 12],
    // 各層で斜めに揃うライン(逆方向)
    [15, 10, 5, 0],
    [12, 9, 6, 3],
  ];
  for (let i = 0; i < lines.length; i++) {
    const [a, b, c, d] = lines[i];
    // console.log("===確かめ===");
    // console.log("4層目：squares[a]:", squares[a]);
    // console.log("4層目：otherSquares[2][b]:", otherSquares[2][b]);
    // console.log("4層目：otherSquares[1][c]:", otherSquares[1][c]);
    // console.log("4層目：otherSquares[0][d]:", otherSquares[0][d]);
    if (
      squares[a] && // 4層目の現在のボード
      squares[a] === otherSquares[2][b] && // 3層目
      squares[a] === otherSquares[1][c] && // 2層目
      squares[a] === otherSquares[0][d] // 1層目
    ) {
      return squares[a];
    }
  }

  // 各層の同じマスにマークがある判定
  for (let i = 0; i < squares.length; i++) {
    if (
      squares[i] && // 4層目
      squares[i] === otherSquares[0][i] && // 1層目
      squares[i] === otherSquares[1][i] && // 2層目
      squares[i] === otherSquares[2][i] // 3層目
    ) {
      return squares[i];
    }
  }
  return null;
}

```
</details>

![スクリーンショット 2024-09-30 1.20.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747635/17149c36-6462-0e3a-4d34-84b28e74c2d6.png)

これで、勝者がきまれば「判定：XもしくはO」と出て、これ以上の操作ができなくなります。
完成！

# さいごに
立体四目を作ることで、
・親から子にデータを渡す
・子から親のデータを更新する
の理解が深まった。

チュートリアルで言われた通りに三目並べを作るのも楽しいけど
自分で考えて立体四目を作るとさらに楽しかったです。

reactの使い方について知るという目標は達成できました。
気が向いたら、リファクタリングするか、UIを見やすくするかしたいです

reactのお作法的に
「もっとこうするといいよ」なコメント、いただけると大変ありがたいです🙏　よろしくお願いします。