使用随机数的 React Strict 模式

Using React Strict mode with random numbers

提问人:lukeyp 提问时间:8/30/2022 最后编辑:lukeyp 更新时间:8/30/2022 访问量:109

问:

我正在尝试使用 react 用 AI 创建一个井字游戏。我的 AI 有一个函数,可以重新调整随机行和列以放置一块。但是,当使用严格模式时,AI 需要两回合,因为随机数会再次生成。从我所做的阅读来看,这表明我错误地更新了我的电路板状态,但我不确定我哪里出了问题。以下是我在板上放置标记的代码。

const handlePlacePiece = (row: number, col: number) => {
    let currentPiece = board[row][col];
    if (currentPiece === "-") {
        const newBoard = Array.from(board);
        newBoard[row][col] = currentPlayer; // The current players mark either 'X' or 'O'
        setBoard(newBoard);
        setCurrentPlayer(currentPlayer === "X" ? "O" : "X");
    }
};

这是我最初的董事会状态:

const [board, setBoard] = useState([
    ["-", "-", "-"],
    ["-", "-", "-"],
    ["-", "-", "-"],
]);

这是我的 ai 函数:

export function easyAi(board: any) {
    let col = getRandomMove(); //Math.floor(Math.random() * 3);
    let row = getRandomMove();
    while (board[row][col] !== "-") {
        col = getRandomMove();
        row = getRandomMove(); 
    }
    return { row, col };
}

调用 handlePlacePiece(这也是一个 onClick,但这会产生正确的结果):

if (gameType === "AI") {
        if (currentPlayer === aiPiece) {
            const { row, col } = easyAi(board);
            handlePlacePiece(row, col);
        }
    }

GitHub 上的完整文件: https://github.com/lukeypap/Tic-Tac-Toe-React/blob/master/src/components/Board/index.tsx

如果您需要更多详细信息,请告诉我,谢谢。

reactjs 多维数组 react-hooks strict-mode

评论

0赞 Chase 8/30/2022
你在任何地方都有useEffect吗?
0赞 Diesel 8/30/2022
什么叫?是useEffect吗?handlePlacePiece
0赞 Nikki9696 8/30/2022
还可以尝试为您的 setCurrentPlayer 使用 setState 的回调版本
0赞 lukeyp 8/30/2022
我已经更新了,所以你可以看到它的名字,谢谢大家:)编辑:未使用useEffect
0赞 Nikki9696 8/30/2022
尝试将此更改为此setCurrentPlayer(currentPlayer === "X" ? "O" : "X");setCurrentPlayer(current => current === "X" ? "O" : "X");

答:

0赞 Drew Reese 8/30/2022 #1

问题

  1. “AI”轮到它的逻辑就在函数体中,这是一个无意的副作用。这意味着无论何时,无论何时出于任何原因呈现组件,它调用的所有代码。将某些功能运行两次,以帮助您检测意外的副作用React.StrictMode

    if (gameType === "AI" && !gameOver) {
      if (checkDraw(board) || checkWinner(board)) {
        console.log("setting game over true");
        setGameOver(true);
      } else if (currentPlayer === aiPiece && !gameOver) {
        const { row, col } = easyAi(board);
        handlePlacePiece(row, col);
      }
    }
    

    React.StricMode

    严格模式无法自动为您检测副作用,但它可以通过使它们更具确定性来帮助您发现它们。这是通过有意双重调用以下函数来完成的:

    • 类组件 、 和方法constructorrendershouldComponentUpdate
    • 类组件静态方法getDerivedStateFromProps
    • 功能组件主体 <--
    • 状态更新程序函数(第一个参数setState)
    • 传递给 、 或 的函数useStateuseMemouseReducer
  2. 回调正在改变状态对象。所有正在更新的状态和嵌套状态都应该被浅层复制,以便创建新的引用。回调仅创建外部数组的浅拷贝,但所有行数组仍是对先前状态的引用。handlePlacePieceboardboard

    const newBoard = Array.from(board);
    newBoard[row][col] = currentPlayer; // <-- mutation!
    setBoard([...newBoard]);
    

溶液

使用功能状态更新从以前的值更新板状态,用于浅拷贝正在更新的行和列。Array.prototype.map

将“AI”转换逻辑移动到钩子中。我建议将游戏检查也移动到一个钩子中,以便在状态更新时调用。useEffectuseEffectboard

建议:

enum PIECE {
  X = "X",
  O = "O",
  EMPTY = "-",
}

interface props {
  gameType: "AI" | "HUMAN";
  selectedPiece: PIECE;
  startingPlayer: PIECE;
}

const index = ({ gameType, selectedPiece, startingPlayer }: props) => {
  const [board, setBoard] = useState(
    Array(3).fill(Array(3).fill(PIECE.EMPTY))
  );

  const [currentPlayer, setCurrentPlayer] = useState(startingPlayer);
  const [aiPiece, setAiPiece] = useState(
    selectedPiece === PIECE.O ? PIECE.X : PIECE.O
  );
  const [gameOver, setGameOver] = useState(false);
  const [checkingMove, setCheckingMove] = useState(false);

  const handlePlacePiece = (row: number, col: number) => {
    let mark = board[row][col];
    if (mark === PIECE.EMPTY) {
      setCheckingMove(true);
      setBoard((board) =>
        board.map((boardRow: PIECE[], i: number) =>
          i === row
            ? boardRow.map((boardCol: PIECE, j: number) =>
                j === col ? currentPlayer : boardCol
              )
            : boardRow
        )
      );
    }
  };

  useEffect(() => {
    const isWinner = checkWinner(board); // *
    const isDraw = checkDraw(board);

    if (isWinner || isDraw) {
      if (isWinner) {
        console.log(`Winner is: ${isWinner}`);
      } else if (isDraw) {
        console.log("Draw");
      }
      setGameOver(true);
    } else {
      setCurrentPlayer((current) => (current === PIECE.X ? PIECE.O : PIECE.X));
      setCheckingMove(false);
    }
  }, [board]);

  useEffect(() => {
    if (
      !checkingMove &&
      gameType === "AI" &&
      !gameOver &&
      currentPlayer === aiPiece
    ) {
      const { row, col } = easyAi(board);
      handlePlacePiece(row, col);
    }
  }, [checkingMove, currentPlayer, aiPiece, gameType, gameOver, board]);

  return (
    ...
  );
};

* 注意:该实用程序已更新为返回获胜作品。checkWinner

export const checkWinner = (board: string[][]) => {
  //Rows
  if (
    board[0][0] == board[0][1] &&
    board[0][1] == board[0][2] &&
    board[0][0] != "-"
  ) {
    return board[0][0];
  }
  if (
    board[1][0] == board[1][1] &&
    board[1][1] == board[1][2] &&
    board[1][0] != "-"
  ) {
    return board[1][0];
  }
  if (
    board[2][0] == board[2][1] &&
    board[2][1] == board[2][2] &&
    board[2][0] != "-"
  ) {
    return board[2][0];
  }
  //Cols
  if (
    board[0][0] == board[1][0] &&
    board[1][0] == board[2][0] &&
    board[0][0] != "-"
  ) {
    return board[0][0];
  }
  if (
    board[0][1] == board[1][1] &&
    board[1][1] == board[2][1] &&
    board[0][1] != "-"
  ) {
    return board[0][1];
  }
  if (
    board[0][2] == board[1][2] &&
    board[1][2] == board[2][2] &&
    board[0][2] != "-"
  ) {
    return board[0][2];
  }
  //Diags
  if (
    board[0][0] == board[1][1] &&
    board[1][1] == board[2][2] &&
    board[0][0] != "-"
  ) {
    return board[0][0];
  }
  if (
    board[0][2] == board[1][1] &&
    board[1][1] == board[2][0] &&
    board[0][2] != "-"
  ) {
    return board[0][2];
  }
};

评论

2赞 lukeyp 8/30/2022
谢谢德鲁!我正在阅读您在另一个线程上的类似回复,但我无法完全实现它。这就像一个魅力,我需要更加小心地复制数组,并保留有时应该在主函数体之外运行的逻辑。一个小小的澄清,checkingMove 变量的目的是什么?我试着走过,但不明白它的目的是什么,只是如果我删除它,AI 需要 4 圈哈哈。再次感谢大力帮助!
0赞 Drew Reese 8/31/2022
@lukeyp啊,是的,对不起,由于处理“AI”轮到的钩子有几个依赖关系,这是“阻止”或阻止“AI”进行后续/并发轮次的必要状态,而与董事会相关的其他状态以及轮到谁的状态正在被验证和更新。useEffect