Skip to content

Instantly share code, notes, and snippets.

@RodEsp
Last active September 7, 2023 22:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save RodEsp/b350d82731dc77afff2a4d14800841ad to your computer and use it in GitHub Desktop.
Save RodEsp/b350d82731dc77afff2a4d14800841ad to your computer and use it in GitHub Desktop.
Interactive tic-tac-toe in a terminal
const clearScreen = () => {
process.stdout.cursorTo(0, 0);
process.stdout.clearScreenDown();
};
clearScreen();
console.log('Welcome to TicTacToe!');
console.log('Please select your opponent:');
console.log(' 1. Human');
console.log(' 2. Computer');
process.stdin.setRawMode(true);
process.stdin.setEncoding('utf8');
process.stdin.on('data', function (key) {
if (key === '1' || key === '2') {
opponent = key === '1' ? 'human' : 'computer';
process.stdin.removeAllListeners('data');
process.stdin.on('data', function (key) {
switch (key) {
case '\u001B\u005B\u0041': // Arrow up
moveCursor('up');
break;
case '\u001B\u005B\u0042': // Arrow down
moveCursor('down');
break;
case '\u001B\u005B\u0043': // Arrow right
moveCursor('right');
break;
case '\u001B\u005B\u0044': // Arrow left
moveCursor('left');
break;
case '\u000D': // Carrige Return
if (squareIsEmpty()) {
updateBoardState();
checkForWinner();
switchPlayer();
renderBoard();
} else {
renderBoard('That square is already taken. Try again.');
}
break;
case '\u0003': // Ctrl + C
clearScreen();
process.exit();
break;
default:
break;
}
});
renderBoard();
}
});
let opponent = undefined;
let winner = undefined;
let currentPlayer = {
name: 'Player 1',
mark: 'X',
};
const cursorBoardPosition = {
row: 0,
column: 0,
};
const boardState = [
[' ', ' ', ' '],
[' ', ' ', ' '],
[' ', ' ', ' '],
];
const renderBoard = (message) => {
clearScreen();
console.log(boardState[0].join('|'));
console.log(`—————`);
console.log(boardState[1].join('|'));
console.log(`—————`);
console.log(boardState[2].join('|'));
if (!winner) {
console.log(`\n${message ? `${message}\n` : ''}${currentPlayer.name}'s turn.`);
console.log('\nUse the arrow keys to move the cursor and press enter to place your mark.');
process.stdout.cursorTo(cursorBoardPosition.column * 2, cursorBoardPosition.row * 2);
} else {
if (winner === 'tie') {
console.log(`\nIt's a tie!`);
} else {
console.log(`\n${winner} wins!`);
}
process.exit();
}
};
const moveCursor = (direction) => {
switch (direction) {
case 'up':
if (cursorBoardPosition.row > 0) {
cursorBoardPosition.row -= 1;
process.stdout.moveCursor(0, -2);
}
break;
case 'right':
if (cursorBoardPosition.column < 2) {
cursorBoardPosition.column += 1;
process.stdout.moveCursor(2, 0);
}
break;
case 'down':
if (cursorBoardPosition.row < 2) {
cursorBoardPosition.row += 1;
process.stdout.moveCursor(0, 2);
}
break;
case 'left':
if (cursorBoardPosition.column > 0) {
cursorBoardPosition.column -= 1;
process.stdout.moveCursor(-2, 0);
}
break;
}
};
const updateBoardState = () => {
boardState[cursorBoardPosition.row][cursorBoardPosition.column] = currentPlayer.mark;
};
const squareIsEmpty = () => {
return boardState[cursorBoardPosition.row][cursorBoardPosition.column] === ' ';
};
const checkForWinner = () => {
const mark = currentPlayer.mark;
const rowsWin = () => {
return boardState[0].every((square) => square === mark) || boardState[1].every((square) => square === mark) || boardState[2].every((square) => square === mark);
};
const columnsWin = () => {
return (
(boardState[0][0] === mark && boardState[1][0] === mark && boardState[2][0] === mark) ||
(boardState[0][1] === mark && boardState[1][1] === mark && boardState[2][1] === mark) ||
(boardState[0][2] === mark && boardState[1][2] === mark && boardState[2][2] === mark)
);
};
const diagonalsWin = () => {
return (boardState[0][0] === mark && boardState[1][1] === mark && boardState[2][2] === mark) || (boardState[0][2] === mark && boardState[1][1] === mark && boardState[2][0] === mark);
};
if (rowsWin() || columnsWin() || diagonalsWin()) {
winner = currentPlayer.name;
} else if (boardState.every((row) => row.every((square) => square !== ' '))) { // Check for tie
winner = 'tie';
}
};
const switchPlayer = () => {
if (currentPlayer.name === 'Player 1') {
if (opponent === 'human') {
currentPlayer = { name: 'Player 2', mark: 'O' };
} else if (opponent === 'computer') {
currentPlayer = { name: 'TicBot', mark: 'O' };
// Bot player will make a move with a small delay to make it seem like it's thinking
setTimeout(() => {
while (squareIsEmpty() === false) {
cursorBoardPosition.row = Math.floor(Math.random() * 3);
cursorBoardPosition.column = Math.floor(Math.random() * 3);
}
updateBoardState();
checkForWinner();
switchPlayer();
renderBoard();
}, 300)
}
} else {
currentPlayer = { name: 'Player 1', mark: 'X' };
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment