Skip to content

Instantly share code, notes, and snippets.

@johnrees
Created January 2, 2026 19:18
Show Gist options
  • Select an option

  • Save johnrees/9585e76079cbfa4d46debd44f4d72ba2 to your computer and use it in GitHub Desktop.

Select an option

Save johnrees/9585e76079cbfa4d46debd44f4d72ba2 to your computer and use it in GitHub Desktop.
Tic Tac Toe
let game = createGame();
const moves = [0, 4, 1, 3, 2];
for (const index of moves) {
if (game.winner || game.moves >= 9) break;
game = makeMove(game, index);
}
console.log(game);
type Player = "X" | "O";
type Cell = Player | null;
export type TicTacToeState = {
board: Cell[];
currentPlayer: Player;
winner: Player | null;
moves: number;
};
const initialState: TicTacToeState = {
board: Array.from({ length: 9 }, () => null),
currentPlayer: "X",
winner: null,
moves: 0,
};
// 0 | 1 | 2
// ---------
// 3 | 4 | 5
// ---------
// 6 | 7 | 8
// winningLines encodes:
// - Rows: [0,1,2], [3,4,5], [6,7,8]
// - Columns: [0,3,6], [1,4,7], [2,5,8]
// - Diagonals: [0,4,8], [2,4,6]
const winningLines: [number, number, number][] = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];
export const getWinner = (board: Cell[]): Player | null => {
for (const [a, b, c] of winningLines) {
const cell = board[a];
if (cell && cell === board[b] && cell === board[c]) {
return cell;
}
}
return null;
};
export const createGame = (): TicTacToeState => ({
board: initialState.board.slice(),
currentPlayer: initialState.currentPlayer,
winner: initialState.winner,
moves: initialState.moves,
});
const isDraw = (state: TicTacToeState): boolean =>
state.moves >= 9 && getWinner(state.board) === null;
const togglePlayer = (player: Player): Player => (player === "X" ? "O" : "X");
const assertValidMove = (state: TicTacToeState, index: number): void => {
if (state.winner) {
throw new Error(`Game is over: ${state.winner} won.`);
}
if (isDraw(state)) {
throw new Error("Game is over: the board is full and it's a draw.");
}
if (index < 0 || index >= state.board.length) {
throw new Error(`Invalid move: index ${index} is out of bounds.`);
}
if (state.board[index] !== null) {
throw new Error(`Invalid move: cell ${index} is already occupied.`);
}
};
export const makeMove = (
state: TicTacToeState,
index: number,
): TicTacToeState => {
assertValidMove(state, index);
const board = state.board.slice();
board[index] = state.currentPlayer;
const nextWinner = getWinner(board);
const moves = state.moves + 1;
return {
board,
currentPlayer: nextWinner || moves >= 9
? state.currentPlayer
: togglePlayer(state.currentPlayer),
winner: nextWinner,
moves,
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment