Last active
July 3, 2017 15:38
-
-
Save nwshane/7dbf3b61e1c0cee5f47e82d879b4b2fc to your computer and use it in GitHub Desktop.
Tic Tac Toe in the Terminal
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Tested with node v8.1.1 | |
const readline = require('readline') | |
const defaultState = { | |
players: { | |
1: { | |
isComputer: true | |
}, | |
2: { | |
isComputer: true | |
} | |
}, | |
currentPlayerTurn: 1, | |
positionValues: { | |
A1: null, | |
A2: null, | |
A3: null, | |
B1: null, | |
B2: null, | |
B3: null, | |
C1: null, | |
C2: null, | |
C3: null | |
} | |
} | |
let state | |
/* Action Constants */ | |
const PLACE_PIECE = 'PLACE_PIECE' | |
const SWITCH_PLAYER_TURN = 'SWITCH_PLAYER_TURN' | |
/* Dispatch and Reducers */ | |
const dispatch = (function () { | |
const playersReducer = (state, action) => { | |
return state | |
} | |
const positionValuesReducer = (state, action) => { | |
switch (action.type) { | |
case PLACE_PIECE: | |
return Object.assign( | |
{}, | |
state, | |
action.newPositionValues | |
) | |
default: | |
return state | |
} | |
} | |
const currentPlayerTurnReducer = (state, action) => { | |
switch (action.type) { | |
case SWITCH_PLAYER_TURN: | |
if (state === 1) return 2 | |
if (state === 2) return 1 | |
return 1 | |
default: | |
return state | |
} | |
} | |
const reducer = (state = defaultState, action) => ({ | |
players: playersReducer(state.players, action), | |
currentPlayerTurn: currentPlayerTurnReducer(state.currentPlayerTurn, action), | |
positionValues: positionValuesReducer(state.positionValues, action) | |
}) | |
return (action) => { | |
state = reducer(state, action) | |
} | |
}()) | |
/* Actions */ | |
const placePiece = (newPositionValues) => ({ | |
type: PLACE_PIECE, | |
newPositionValues | |
}) | |
const switchPlayerTurn = () => ({ | |
type: SWITCH_PLAYER_TURN | |
}) | |
/* Getters */ | |
const getPositionValues = () => ( | |
state.positionValues | |
) | |
const getCurrentPlayerTurn = () => ( | |
state.currentPlayerTurn | |
) | |
const getPlayerName = (playerId) => ( | |
`Player ${playerId}` | |
) | |
const getCurrentPlayerName = () => ( | |
getPlayerName(getCurrentPlayerTurn()) | |
) | |
const possibleWinningCombinations = [ | |
['A1', 'A2', 'A3'], | |
['B1', 'B2', 'B3'], | |
['C1', 'C2', 'C3'], | |
['A1', 'B1', 'C1'], | |
['A2', 'B2', 'C2'], | |
['A3', 'B3', 'C3'], | |
['A1', 'B2', 'C3'], | |
['A3', 'B2', 'C1'] | |
] | |
const getWinningCombination = () => ( | |
possibleWinningCombinations.find((possibleWinningCombo) => { | |
const values = possibleWinningCombo.map(getPositionValue) | |
return values[0] === values[1] && values[1] === values[2] | |
}) | |
) | |
const getWinner = () => ( | |
!!getWinningCombination() && getPositionValue(getWinningCombination()[0]) | |
) | |
const getWinnerName = () => ( | |
getPlayerName(getWinner()) | |
) | |
const getPositions = () => ( | |
Object.keys(getPositionValues()) | |
) | |
const getPositionValue = (position) => ( | |
getPositionValues()[position] | |
) | |
const getEmptyPositions = () => ( | |
getPositions().filter((position) => ( | |
getPositionValue(position) === null | |
)) | |
) | |
const getFormattedBoard = (function () { | |
const convertValueToGameString = (positionValue) => { | |
if (positionValue === null) return ' ' | |
if (positionValue === 1) return 'X' | |
if (positionValue === 2) return 'O' | |
throw new Error('positionValue must be null, 1 or 2') | |
} | |
const convertValuesToGameStrings = (positionValues) => ( | |
Object.keys(positionValues).reduce( | |
(convertedPositionValues, position) => { | |
convertedPositionValues[position] = convertValueToGameString(positionValues[position]) | |
return convertedPositionValues | |
}, | |
{} | |
) | |
) | |
const formatBoard = ({ A1, A2, A3, B1, B2, B3, C1, C2, C3 }) => ( | |
` | |
1 | 2 | 3 | |
—————————————— | |
A | ${A1} | ${A2} | ${A3} | |
—————————————— | |
B | ${B1} | ${B2} | ${B3} | |
—————————————— | |
C | ${C1} | ${C2} | ${C3} | |
` | |
) | |
return () => ( | |
formatBoard(convertValuesToGameStrings(getPositionValues())) | |
) | |
}()) | |
async function promptUserForMove () { | |
const readlineInterface = readline.createInterface({ | |
input: process.stdin, | |
output: process.stdout | |
}) | |
return new Promise((resolve) => { | |
readlineInterface.question( | |
`\n${getCurrentPlayerName()}: Where would you like to move? `, | |
(answer) => { | |
readlineInterface.close() | |
resolve(answer) | |
} | |
) | |
}) | |
} | |
const userMoveIsValid = (userMove) => ( | |
getEmptyPositions().includes(userMove) | |
) | |
const playMove = (userMove) => { | |
console.log(`${getCurrentPlayerName()} played at position ${userMove}`) | |
const newPositionValues = {} | |
newPositionValues[userMove] = getCurrentPlayerTurn() | |
dispatch(placePiece(newPositionValues)) | |
dispatch(switchPlayerTurn()) | |
} | |
async function getUserMove () { | |
let userMove = await promptUserForMove() | |
while (!userMoveIsValid(userMove)) { | |
console.log('\nSorry, not a valid move!') | |
console.log(`Your valid moves are: ${getEmptyPositions().join(', ')}`) | |
userMove = await promptUserForMove() | |
} | |
return userMove | |
} | |
const printCurrentBoard = () => { | |
console.log(`\n-- -- Current Board -- --`) | |
console.log(getFormattedBoard()) | |
} | |
const getRandomArrayItem = (array) => ( | |
array[Math.floor(Math.random()*array.length)] | |
) | |
const getRandomEmptyPosition = () => ( | |
getRandomArrayItem(getEmptyPositions()) | |
) | |
const getBestComputerMove = () => { | |
return getRandomEmptyPosition() | |
} | |
async function getComputerMove () { | |
return new Promise((resolve) => { | |
setTimeout(() => { | |
resolve(getBestComputerMove()) | |
}, 1000) | |
}) | |
} | |
const currentPlayerIsComputer = () => { | |
return state.players[getCurrentPlayerTurn().toString()].isComputer | |
} | |
async function startGame () { | |
state = defaultState | |
while (!getWinner()) { | |
printCurrentBoard() | |
if (currentPlayerIsComputer()) { | |
playMove(await getComputerMove()) | |
} else { | |
playMove(await getUserMove()) | |
} | |
} | |
printCurrentBoard() | |
console.log(`And the winner is... ${getWinnerName()}`) | |
} | |
async function startApplication () { | |
console.log('Welcome to Terminal Tic Tac Toe!') | |
await startGame() | |
process.exit() | |
} | |
startApplication() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment