Skip to content

Instantly share code, notes, and snippets.

@exah
Last active November 11, 2025 12:13
Show Gist options
  • Select an option

  • Save exah/026c5386a3a740bd964b56abf2b9a130 to your computer and use it in GitHub Desktop.

Select an option

Save exah/026c5386a3a740bd964b56abf2b9a130 to your computer and use it in GitHub Desktop.
import { useState } from "react";
const PLAYERS = ["O", "X"];
const NEXT_PLAYER = {
[PLAYERS[0]]: PLAYERS[1],
[PLAYERS[1]]: PLAYERS[0],
};
function generateBoard(size) {
return Array(Math.pow(size, 2)).fill(null);
}
function getWinner(board, size) {
const indices = [...Array(size).keys()];
const getCell = (r, c) => board[r * size + c];
// Check rows
for (const row of indices) {
const player = getCell(row, 0);
if (player && indices.every((column) => getCell(row, column) === player)) {
return player;
}
}
// Check columns
for (const column of indices) {
const player = getCell(0, column);
if (player && indices.every((row) => getCell(row, column) === player)) {
return player;
}
}
// Check main diagonal
const mainDiagPlayer = getCell(0, 0);
if (
mainDiagPlayer &&
indices.every((index) => getCell(index, index) === mainDiagPlayer)
) {
return mainDiagPlayer;
}
// Check anti-diagonal
const antiDiagPlayer = getCell(0, size - 1);
if (
antiDiagPlayer &&
indices.every((index) => getCell(index, size - 1 - index) === antiDiagPlayer)
) {
return antiDiagPlayer;
}
return null;
}
export default function TicTacToe({ size = 4 }) {
const [player, setPlayer] = useState(PLAYERS[0]);
const [board, setBoard] = useState(() => generateBoard(size));
const winner = getWinner(board, size);
const move = (cell) => {
setBoard((board) =>
board.map((value, index) =>
cell === index && value === null ? player : value,
),
);
setPlayer(NEXT_PLAYER[player]);
};
return (
<div>
<div
style={{
display: "grid",
gridTemplateColumns: `repeat(${size}, 1fr)`,
aspectRatio: 1,
}}
>
{board.map((value, index) => (
<button key={index} type="button" onClick={() => move(index)}>
{value}
</button>
))}
</div>
{winner && <p>Winner: {winner}</p>}
</div>
);
}
import { useState } from "react";
const GRID = Array(9).fill(null);
const PLAYERS = ["O", "X"];
const NEXT_PLAYER = {
[PLAYERS[0]]: PLAYERS[1],
[PLAYERS[1]]: PLAYERS[0],
};
const COMBOS = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];
function getWinner(board) {
const combo = COMBOS.find(
([a, b, c]) =>
board[a] !== null && board[a] === board[b] && board[a] === board[c],
);
return combo ? board[combo[0]] : null;
}
export default function TicTacToe() {
const [player, setPlayer] = useState(PLAYERS[0]);
const [board, setBoard] = useState(GRID);
const winner = getWinner(board);
const move = (cell) => {
setBoard((board) =>
board.map((value, index) =>
cell === index && value === null ? player : value,
),
);
setPlayer(NEXT_PLAYER[player]);
};
return (
<div>
<div
style={{
display: "grid",
gridTemplateColumns: "repeat(3, 1fr)",
aspectRatio: 1,
}}
>
{board.map((value, index) => (
<button key={index} type="button" onClick={() => move(index)}>
{value}
</button>
))}
</div>
{winner && <p>Winner: {winner}</p>}
</div>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment