Skip to content

Instantly share code, notes, and snippets.

@iamwilhelm
Created January 15, 2024 19:32
Show Gist options
  • Save iamwilhelm/3ff3fbd6b570f63c000f0211453b6c3f to your computer and use it in GitHub Desktop.
Save iamwilhelm/3ff3fbd6b570f63c000f0211453b6c3f to your computer and use it in GitHub Desktop.
// tic tac toe
import * as readline from "readline";
enum Turn {
Player = "player",
Computer = "computer",
}
type Piece = string;
type Board = Piece[][];
type GameState = {
turn: Turn;
board: Board;
};
type GameInput = {
piece: Piece;
position: [number, number] | null; // row, col
};
function initializeBoard(): Board {
return [
[" ", " ", " "],
[" ", " ", " "],
[" ", " ", " "],
];
}
function winner(gameState: GameState): Piece | null {
// check for winner rows
for (const piece of ["O", "X"]) {
for (const row of gameState.board) {
if (row.every((col) => col === piece)) return piece;
}
}
// check for col rows
for (const piece of ["O", "X"]) {
for (const col of [0, 1, 2]) {
if (
gameState.board[0][col] === piece &&
gameState.board[1][col] === piece &&
gameState.board[2][col] === piece
)
return piece;
}
}
// check for diagonals
for (const piece of ["O", "X"]) {
if (
gameState.board[0][0] === piece &&
gameState.board[1][1] === piece &&
gameState.board[2][2] === piece
)
return piece;
if (
gameState.board[0][2] === piece &&
gameState.board[1][1] === piece &&
gameState.board[2][0] === piece
)
return piece;
}
return null;
}
function isTie(gameState: GameState): boolean {
return gameState.board.every((row) => {
return row.every((col) => {
return col !== " ";
});
});
}
function isValidInput(gameState: GameState, input: GameInput): boolean {
if (input.position === null) return false;
if (input.position[0] < 0 || input.position[0] > 2) return false;
if (input.position[1] < 0 || input.position[1] > 2) return false;
if (gameState.board[input.position[0]][input.position[1]] !== " ")
return false;
return true;
}
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.on("SIGINT", () => {
console.log("quitting...");
rl.close();
process.exit();
});
async function getPlayerInput(gameState: GameState): Promise<GameInput> {
let input: GameInput = {
piece: "O",
position: null,
};
return new Promise<GameInput>(async (resolve) => {
// keep asking until we get a valid move
while (!isValidInput(gameState, input)) {
input = await new Promise<GameInput>((resolve) => {
rl.question("Please enter your move: ", (answer) => {
const gameInput = {
piece: "O",
position: answer.split(",").map(Number) as [number, number],
};
resolve(gameInput);
});
});
}
resolve(input);
});
}
function getAiInput(gameState: GameState): GameInput {
const validMoves = gameState.board
.flatMap((row, row_idx) => {
return row.map((col, col_idx) => {
return col === " " ? [row_idx, col_idx] : null;
});
})
.filter((move) => move !== null);
const randIdx = Math.floor(Math.random() * validMoves.length);
return {
piece: "X",
position: validMoves[randIdx],
};
}
function updateGameState(gameState: GameState, input: GameInput): GameState {
if (input.position === null) return gameState;
gameState.board[input.position[0]][input.position[1]] = input.piece;
return gameState;
}
function render(gameState: GameState) {
console.log(" 0 1 2");
console.log("0 ", gameState.board[0].join(" | "));
console.log(" ---------");
console.log("1 ", gameState.board[1].join(" | "));
console.log(" ---------");
console.log("2 ", gameState.board[2].join(" | "));
}
async function gameloop() {
// setup
let gameState = {
turn: Turn.Player,
board: initializeBoard(),
};
render(gameState);
while (winner(gameState) === null && !isTie(gameState)) {
// get player or ai input based on turn
let input;
if (gameState.turn === Turn.Player) {
input = await getPlayerInput(gameState);
console.log(`Player move (${input.piece}) at ${input.position}`);
gameState.turn = Turn.Computer;
} else {
input = getAiInput(gameState);
console.log(`Computer move (${input.piece}) at ${input.position}`);
gameState.turn = Turn.Player;
}
// change game state
gameState = updateGameState(gameState, input);
// render game state
render(gameState);
}
if (winner(gameState)) {
console.log("Winner is: ", winner(gameState), "!");
} else {
console.log("Tie!");
}
}
await gameloop();
console.log("game ended");
process.exit();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment