Skip to content

Instantly share code, notes, and snippets.

@alessbell
Created January 3, 2019 18:00
Show Gist options
  • Save alessbell/05a708d18706b21f7ee6213ae3b0b69b to your computer and use it in GitHub Desktop.
Save alessbell/05a708d18706b21f7ee6213ae3b0b69b to your computer and use it in GitHub Desktop.
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
let board = [[' ', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']];
let currentPlayer = 'x';
const charMap = {
A: 0,
B: 1,
C: 2,
};
// Utils
const boardReducer = (acc, curr = []) => [...acc, ...curr];
const rowReducer = (acc, curr) => `${acc} | ${curr}`;
const clearConsole = () => process.stdout.write('\x1B[2J\x1B[3J\x1B[H');
const gameOver = () => playerHasWon(board.reduce(boardReducer), currentPlayer) || isFull();
const boardIncludes = char => board.reduce(boardReducer).includes(char);
const isEmpty = () => !boardIncludes('x') && !boardIncludes('o');
const isFull = () => !boardIncludes(' ');
const printBoard = (board, iter = 11) => {
let boardStr = '\n';
board.forEach(
(row, idx) =>
(boardStr += ` ${row.reduce(rowReducer)}\n${
idx + 1 !== board.length ? `${'-'.repeat(iter)}\n` : ''
}`)
);
return boardStr;
};
const playerHasWon = (board, player) => {
let gameOver = false;
const winningCombinations = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8], // horizontal
[0, 3, 6],
[1, 4, 7],
[2, 5, 8], // vertical
[0, 4, 8],
[2, 4, 6], // diagonal
];
winningCombinations.forEach(c => {
if (board[c[0]] === player && board[c[1]] === player && board[c[2]] === player) {
gameOver = true;
}
});
return gameOver;
};
// Steps
const ChooseCharacter = {
prompt: () => `Human 1, would you like to be player x or player o?`,
validate: [
{
test: answer => answer.toLowerCase() === 'x' || answer.toLowerCase() === 'o',
err: `Please choose either x or o.`,
},
],
onSuccess: answer => {
console.log(`Human 2, you're ${answer.toLowerCase() === 'x' ? 'o' : 'x'}.`);
},
};
const CoinToss = {
prompt: () => `Press enter to flip a coin and see who goes first...`,
validate: () => [{ test: () => true }],
onSuccess: () => {
if (Math.floor(Math.random() * 2) == 0) currentPlayer = 'o';
console.log(`${currentPlayer}, you're up first!`);
},
};
const PlayGame = {
prompt: () =>
`${currentPlayer}, where would you like to move?\n${printBoard(
[['A1', 'A2', 'A3'], ['B1', 'B2', 'B3'], ['C1', 'C2', 'C3']],
13
)}\n${printBoard(board)}`,
validate: [
{
test: answer =>
answer.length === 2 &&
[1, 2, 3].includes(parseInt(answer[1])) &&
Object.keys(charMap).includes(answer[0].toUpperCase()),
err: `Please enter a valid move.`,
},
{
test: answer => board[charMap[answer[0].toUpperCase()]][answer[1] - 1] === ' ',
err: `This space is taken, please try again.`,
},
],
onSuccess: answer => {
let msg = '';
const [row, column] = answer.split('');
board[charMap[row.toUpperCase()]][column - 1] = currentPlayer;
if (gameOver()) msg = `Game over! Congratulations ${currentPlayer}.`;
if (isFull()) msg = `Tie game! Good effort, try again next time.`;
if (gameOver() || isFull()) {
process.stdout.write(`${msg} \n${printBoard(board)}`);
return process.stdin.destroy();
}
// Next player's turn...
if (!isEmpty()) {
currentPlayer = currentPlayer === 'x' ? 'o' : 'x';
}
},
repeat: true,
};
const steps = [ChooseCharacter, CoinToss, PlayGame];
const pose = (q, i = 0) => {
rl.question(`${q.prompt()}\n\n`, answer => {
try {
for (const validator in q.validate) {
if (!q.validate[validator].test(answer)) {
throw q.validate[validator].err;
}
}
clearConsole();
q.onSuccess(answer);
} catch (ex) {
clearConsole();
process.stdout.write(`${ex}\n`);
return pose(q, i);
}
if (!q.repeat) i++;
if (!gameOver()) pose(steps[i], i);
});
};
clearConsole();
process.stdout.write(`Hello! And welcome to Alessia's Tic Tac Toe game.\n`);
// Kick off game
pose(steps[0]);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment