I'm trying to create a game of Tic-Tac-Toe with an AI using react. I have a function for my AI that retuns a random row and column to place a piece in. However, when using strict mode the AI takes two turns because the random number gets generated again. From the reading that I've done this indicates that I'm updating my board state incorrectly but I'm not sure where I've gone wrong. Below is my code for placing the mark on the board.
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");
}
};
And here is my initial board state:
const [board, setBoard] = useState([
["-", "-", "-"],
["-", "-", "-"],
["-", "-", "-"],
]);
Here is my ai function:
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 };
}
Calling handlePlacePiece (this is also an onClick but this produces the correct outcome):
if (gameType === "AI") {
if (currentPlayer === aiPiece) {
const { row, col } = easyAi(board);
handlePlacePiece(row, col);
}
}
Full file on GitHub: https://github.com/lukeypap/Tic-Tac-Toe-React/blob/master/src/components/Board/index.tsx
Please let me know if you need extra details, Thank you.
Issues
The logic for the "AI" to take its turn is right in the function body as an unintentional side-effect. This means anytime the component is rendered for any reason at all the code it invoked. The
React.StrictModeruns certain functions twice as a way to help you detect unexpected side-effects.React.StricModeThe
handlePlacePiececallback is mutating theboardstate object. All state, and nested state, that is being updated should be shallow copied in order to create new references. The callback only creates a shallow copy of the outerboardarray, but all row arrays are still references to the previous state.Solution
Use a functional state update to update the board state from the previous value, using
Array.prototype.mapto shallow copy the rows and columns that are being updated.Move the "AI" turn logic into a
useEffecthook. I suggest moving also the game check into auseEffecthook to be called when theboardstate updates.Suggestions:
* Note: The
checkWinnerutility was updated to return the winning piece.