Skip to content

Instantly share code, notes, and snippets.

@nwshane
Last active July 3, 2017 15:38
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 nwshane/7dbf3b61e1c0cee5f47e82d879b4b2fc to your computer and use it in GitHub Desktop.
Save nwshane/7dbf3b61e1c0cee5f47e82d879b4b2fc to your computer and use it in GitHub Desktop.
Tic Tac Toe in the Terminal
// 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