Created
September 22, 2019 02:07
-
-
Save agronick/4cbd662a1e03d35ac99ed8f375eb5431 to your computer and use it in GitHub Desktop.
This file contains 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
import React from 'react'; | |
import './App.css'; | |
import { useState } from 'react'; | |
import cloneDeep from "lodash-es/cloneDeep"; | |
import shuffle from "lodash-es/shuffle"; | |
class WinLocation { | |
constructor() { | |
this.posArray = []; | |
} | |
push(winLocation) { | |
this.posArray.push(winLocation); | |
} | |
posToArray(board) { | |
return this.posArray.reduce((arr, cur)=> { | |
return [...arr, board[cur[0]][cur[1]]]; | |
}, []) | |
} | |
checkWin(board, who) { | |
return this.posToArray(board).every(i => i === who); | |
} | |
shouldMove(opponent, board) { | |
const asArray = this.posToArray(board); | |
if (asArray.filter(i => i === opponent).length === 2) { | |
for (const i in asArray) { | |
if (asArray[i] === null) { | |
return this.posArray[i]; | |
} | |
} | |
} | |
return false; | |
} | |
} | |
function *makeWinLocations() { | |
for (const rowFirst of [true, false]) { | |
for (let i = 0; i < 3; i++) { | |
const loc = new WinLocation(); | |
for (let j = 0; j < 3; j++) { | |
loc.push(rowFirst ? [i, j] : [j, i]); | |
} | |
yield loc; | |
} | |
} | |
const posListDag1 = new WinLocation(); | |
const posListDag2= new WinLocation(); | |
for (let i = 0; i < 3; i++) { | |
posListDag1.push([i, i]); | |
posListDag2.push([i, 2 - i]); | |
} | |
yield posListDag1; | |
yield posListDag2; | |
} | |
function alertAfterDraw(msg, cb) { | |
requestAnimationFrame(()=> { | |
requestAnimationFrame(()=> { | |
alert(msg); | |
if (cb) cb(); | |
}) | |
}) | |
} | |
function App() { | |
const initialBoard = [ | |
[null, null, null], | |
[null, null, null], | |
[null, null, null], | |
]; | |
const [board, setBoard] = useState(initialBoard); | |
const [readyReset, setReadyReset] = useState(false); | |
const reset = () => { | |
setBoard(cloneDeep(initialBoard)); | |
setReadyReset(false); | |
}; | |
const changeBoardValue = (x, y, value) => { | |
if (board[x][y] === null) { | |
board[x][y] = value; | |
setBoard(cloneDeep(board)); | |
return true; | |
} | |
return false; | |
}; | |
const checkWin = (who) => { | |
for (const loc of makeWinLocations()){ | |
if (loc.checkWin(board, who)) { | |
alertAfterDraw(`${who} wins`); | |
setReadyReset(true); | |
return true; | |
} | |
} | |
}; | |
const takeTurnComputer = () => { | |
const optimalSort = shuffle(Array.from(makeWinLocations())).map(loc => { | |
const asValues = loc.posToArray(board); | |
const unusable = asValues.some(j => j === 'x'); | |
return { | |
loc, | |
asValues, | |
unusable, | |
selCount: asValues.filter(j => j === 'o').length | |
}; | |
}).sort((a, b) => b.selCount - a.selCount).filter(i => !i.unusable); | |
// Check if no one can win | |
if (!optimalSort.length) { | |
return false; | |
} | |
// Check if we are 1 move away from winning or losing | |
for (const look of ['o', 'x']) { | |
for (const loc of makeWinLocations()) { | |
const result = loc.shouldMove(look, board); | |
if (result && changeBoardValue(...result, 'o')) { | |
return true; | |
} | |
} | |
} | |
// Set one of the best looking options | |
const selected = optimalSort[0]; | |
for (const pos in shuffle([0, 1, 2])) { | |
if (selected.asValues[pos] === null) { | |
changeBoardValue(...selected.loc.posArray[pos], 'o'); | |
return true; | |
} | |
} | |
return false; | |
}; | |
const changeValue = (i, j) => { | |
if (readyReset) { | |
return reset(); | |
} | |
if (changeBoardValue(i, j, 'x')) { | |
if (checkWin('x')) { | |
return; | |
} | |
if (!takeTurnComputer()) { | |
alertAfterDraw('No one wins'); | |
setReadyReset(true); | |
return; | |
} | |
checkWin('o'); | |
} | |
}; | |
const table = board.reduce((all, row, i)=> { | |
return [...all, ...row.map((col, j)=> { | |
return ( | |
<Cell changeValue={() => changeValue(i, j, 'x')} value={board[i][j]} key={`${i}|${j}`}/> | |
); | |
})]; | |
}, []); | |
return ( | |
<div className="App"> | |
{table} | |
</div> | |
); | |
} | |
function Cell({changeValue, value}) { | |
return ( | |
<div onClick={changeValue} className="cell"> | |
<div className="cellVal">{value}</div> | |
</div> | |
) | |
} | |
ReactDOM.render(<App />, document.getElementById('root')); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment