Reactチュートリアル三目並べ追加課題最後まで

「どちらかが勝利したときに、勝利につながった 3 つのマス目をハイライト表示する。引き分けになった場合は、引き分けになったという結果をメッセージに表示する。」
「着手履歴リストで、各着手の場所を (row, col) という形式で表示する。」
ここまで達成!
(昨日の「なぜかオブジェクトとして認識される」は調べるの忘れてた……。記録の読み返し大事……。)

勝利につながった3つのマスをハイライト、については勝者判定の関数のところから該当3マス引っ張ってきて、それでstate更新するんだろうと思ってたけど難しく考えすぎてたことが判明。
どうにもうまくできないので例の方の記事を観に行く。
……。あぁそうか。そのまま配列も勝者判定の関数の戻り値として設定してあげてその後その戻り値によって判定するのね……
ちょっとだけ脱力。
でも、「どうやら難しく考えすぎるらしい」ということが分かったのでそれはそれでプラス。
難しく考えてるときにシンプルな考えに切り替えるのはそれこそ大変ではあるけども。

というわけで最後の「着手履歴リストで、各着手の場所を (row, col) という形式で表示する。」
これについては各着手の盤面リストと一個手前の盤面リストで差分をとって、追加されたマスを算出。そのマスの番号は0~8の連番になっており、また3マスで折り返しが発生するためrow=行(横)は

(マスのindex / 3 | 0) +1

col=列(縦)は

(マスのindex % 3) + 1

で求められるのでそれを各手番のところに表示するように書き換えると。
(+1してるのはマス目を数えるときに1から数え始めることが多いから)

で、その後エラー確認見てみたらなんかkeyのところで怒られてたので、なんかkey設定必要なところあったっけ?と思ったら昨日やった「二つのループを使って盤面を出力しましょう」の課題のところで要素プッシュの時に忘れてたらしい。うっかり。
前置詞を入れたりしてユニークにしてkey設定して解決

出来上がったコードを眺めてみて
盤面表示の部分のコードが無駄に関数でくくってるように見えたのでシンプルに盤面データの配列を作って表示するように試してみた。
すんなり動いた。やっぱり無駄だったか。

最終的にできたコードがこちら

import { useState } from 'react';

function Square({ value, addClass, onSquareClick }) {
  const classname = (addClass) ? 'square is-win' : 'square'
  return (
    <button className={classname} 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.winneris;
  } else if(!squares.includes(null)){
    status = 'Draw !'
  } else {
    status = 'Next player: ' + (xIsNext ? 'X' : 'O');
  }

  const winSquares = winner ? calculateWinner(squares).line : null;

  const rowData = []
  for(let i=0; i<3; i++) {
    const squareData = []
    const rowkey = 'row'+i
    for(let j=0; j<3; j++) {
      const num = i*3 + j
      const key = 'square'+num
      const addClass = (winSquares) ? winSquares.includes(num) : false;
      squareData.push(<Square key={key} value={squares[num]} addClass={addClass} onSquareClick={() => handleClick(num)} />)
    }
    rowData.push(<div key={rowkey} className="board-row">{squareData}</div>)
  }

  return (
    <>
      <div className="status">{status}</div>
      {rowData}
    </>
  );
}


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];
  const [isAscending, setIsAscending] = useState(true);

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

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

  const moves = history.map((squares, move) => {
    let selectSquare = '';
    if (move > 0) {
      let diffIndex = 0;
      for(let i=0; i<9; i++) {
        if(history[move - 1][i] !== history[move][i]) diffIndex = i
      }
      const row = (diffIndex / 3 | 0) + 1;
      const col = (diffIndex % 3) + 1;
      selectSquare = '(row:'+row+',col:'+col+')'
    }
    if(move === currentMove) {
      return (
        <li key={move}>You are at move #… {selectSquare}</li>
      );
    } else {
      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>{selectSquare}
        </li>
      );
    }
  });

  function toggleSortOrder() {
    setIsAscending(!isAscending);
  }

  const sortedMoves = isAscending ? moves : moves.slice().reverse();
  const sortDiscription = isAscending ? 'to Desc' : 'to Asc'


  return (
    <div className="game">
      <div className="game-board">
        <Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} />
      </div>
      <div className="game-info">
        <button className="toggle-button" onClick={toggleSortOrder}>{sortDiscription}</button>
        <ol>{sortedMoves}</ol>
      </div>
    </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 {winneris:squares[a], line: lines[i]};
    }
  }
  return null;
}

ところどころ「その変数名はどうだろう」って言うのもあるけれどもいったんこれで。

勝利3マス強調の時に読ませていただいた記事はこっち
https://qiita.com/Ryukuu0919/items/c44c5649d1f08b8e31f7

この記事が気に入ったらサポートをしてみませんか?