'use strict' const fs = require('fs') // util function modified from github.com/bucaran/sget const readLineSync = function(message) { message = message || '' const win32 = () => 'win32' === process.platform const readSync = function(buffer) { var fd = win32() ? process.stdin.fd : fs.openSync('/dev/stdin', 'rs') var bytes = fs.readSync(fd, buffer, 0, buffer.length) if (!win32()) fs.closeSync(fd) return bytes } return (function(buffer) { try { process.stdout.write(message + ' ') return buffer.toString(null, 0, readSync(buffer)) } catch (e) { throw e } }(new Buffer(256))) } const range = n => Array(n + 1).join(1).split('').map((x, i) => i) function end(game) { const verticalEnd = range(game.width).some(i => { const column = game.board[i] || [] const max = { p: 0, val: 0 } return range(game.height).some(j => { const value = column[j] if (value && value === max.p) max.val++ else max.p = value, max.val = 1 return max.p && max.val >= game.win }) }) const horizontalEnd = range(game.height).some(j => { const max = { p: 0, val: 0 } return range(game.width).some(i => { const value = (game.board[i] || [])[j] if (value && value === max.p) max.val++ else max.p = value, max.val = 1 return max.p && max.val >= game.win }) }) const diagonalNW = range(game.width + game.height - 1).some(k => { // taking top and left edge let i = k < game.width ? k : 0 let j = k < game.width ? 0 : k - game.width + 1 const max = { p: 0, val: 0 } while(i < game.width && j < game.height) { const value = (game.board[i] || [])[j] if (value && value === max.p) max.val++ else max.p = value, max.val = 1 if (max.p && max.val >= game.win) return true i++, j++ } }) const diagonalNE = range(game.width + game.height - 1).some(k => { // taking top and right edge let i = k < game.width ? k : game.width - 1 let j = k < game.width ? 0 : k - game.width + 1 const max = { p: 0, val: 0 } while(i >= 0 && j < game.height) { const value = (game.board[i] || [])[j] if (value && value === max.p) max.val++ else max.p = value, max.val = 1 if (max.p && max.val >= game.win) return true i--, j++ } }) const coinCount = range(game.width).reduce((mem, i) => ( mem + range(game.height).reduce((deepMem, j) => ( deepMem + Boolean((game.board[i] || [])[j]) ), 0) ), 0) return (coinCount >= game.width * game.height && 'draw') || verticalEnd || horizontalEnd || diagonalNW || diagonalNE } function print(game) { console.log('\x1B[2J\x1B[0f') // Clear screen // x index console.log([' '].concat(range(game.width)).join(' ')); range(game.height).map(j => { console.log( j + ' ', // y index range(game.width).map(i => coin[(game.board[i] || [])[j] || 0] ).join(' ') ) }) console.log('') } function printEnd(state, currentPlayer) { if(state === 'draw') console.log('Game over. The game ended in a draw.\n') else console.log(`Game over. Player "${coin[currentPlayer]}" won.\n`) } function place(i, j, game) { if (isNaN(i) || isNaN(j)) return false if (i < 0 || i > game.width - 1) return false if (j < 0 || j > game.height - 1) return false if (game.currentPlayer < 1) return false if (!game.board[i]) game.board[i] = [] if (game.board[i][j]) return false return game.board[i][j] = game.currentPlayer } function parse(text, game) { const split = text.split(',').length < 2 ? text.split(' ') : text.split(',') return split.map(v => parseInt(v)) } function switchPlayer(game) { game.currentPlayer++ if (game.currentPlayer > 2) game.currentPlayer = 1 } const coin = { 0: '_', 1: 'X', 2: 'O' } ;(function(){ let endState; const game = { board: [], width: 3, height: 3, win: 3, currentPlayer: 1, } print(game) while(!(endState = end(game))) { const position = parse(readLineSync(`Player "${coin[game.currentPlayer]}" (x y):`), game) if(place(position[0], position[1], game)) { print(game) switchPlayer(game) } else { console.log('Invalid move') } } printEnd(endState, game.currentPlayer) })()