Skip to content

Instantly share code, notes, and snippets.

@avimoondra
Created May 7, 2018 03:40
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 avimoondra/0605cb0938023527851c8dde02bd4cb7 to your computer and use it in GitHub Desktop.
Save avimoondra/0605cb0938023527851c8dde02bd4cb7 to your computer and use it in GitHub Desktop.
Conway's Game of Life
<div id="errors" style="
background: #c00;
color: #fff;
display: none;
margin: -20px -20px 20px;
padding: 20px;
white-space: pre-wrap;
"></div>
<div id="root"></div>
<script>
window.addEventListener('mousedown', function(e) {
document.body.classList.add('mouse-navigation');
document.body.classList.remove('kbd-navigation');
});
window.addEventListener('keydown', function(e) {
if (e.keyCode === 9) {
document.body.classList.add('kbd-navigation');
document.body.classList.remove('mouse-navigation');
}
});
window.addEventListener('click', function(e) {
if (e.target.tagName === 'A' && e.target.getAttribute('href') === '#') {
e.preventDefault();
}
});
window.onerror = function(message, source, line, col, error) {
var text = error ? error.stack || error : message + ' (at ' + source + ':' + line + ':' + col + ')';
errors.textContent += text + '\n';
errors.style.display = '';
};
console.error = (function(old) {
return function error() {
errors.textContent += Array.prototype.slice.call(arguments).join(' ') + '\n';
errors.style.display = '';
old.apply(this, arguments);
}
})(console.error);
</script>
function Square(props) {
return (
<button
className="square"
onClick={props.onClick}
style={props.value ? {backgroundColor: 'black'} : {backgroundColor: 'white'}}>
{props.value}
</button>
);
}
class Board extends React.Component {
renderSquare(i, j) {
return (
<Square
key={`${i}, ${j}`}
value={this.props.squares[i][j]}
onClick={() => this.props.onClick(i, j)}
/>
);
}
render() {
return (
<div>
{
_.range(this.props.numRows).map((i) => {
return (
<div key={i} className="board-row">
{
_.range(this.props.numColumns).map((j) => {
return this.renderSquare(i, j)
})
}
</div>
)
})
}
</div>
);
}
}
class Game extends React.Component {
constructor(props) {
super(props);
const numRows = 10
const numCols = 10
this.state = {
numRows: numRows,
numCols: numCols,
squares: this.blankSquares(numRows, numCols),
refreshIntervalId: null,
isPlaying: false,
};
}
handleClick(i, j) {
newSquares = this.state.squares.slice()
newSquares[i][j] = !this.state.squares[i][j]
this.setState({
squares: newSquares
})
};
blankSquares(numRows, numCols) {
return _.range(numRows).map((index) => {
return Array(numCols).fill(false)
})
}
validNeighbors(squares, rowIndex, colIndex) {
maxRowIndex = squares.length - 1
maxColIndex = squares[0].length - 1
return _.filter([
[rowIndex - 1, colIndex - 1],
[rowIndex - 1, colIndex],
[rowIndex - 1, colIndex + 1],
[rowIndex, colIndex + 1],
[rowIndex, colIndex - 1],
[rowIndex + 1, colIndex - 1],
[rowIndex + 1, colIndex],
[rowIndex + 1, colIndex + 1],
], (square) => {
return square[0] >= 0 &&
square[0] <= maxRowIndex &&
square[1] >= 0 &&
square[1] <= maxColIndex
})
}
computeNextSquareState(squares, rowIndex, colIndex) {
validNeighbors = this.validNeighbors(squares, rowIndex, colIndex)
aliveNeighbors = _.filter(validNeighbors, (neighborCoord) => {
return squares[neighborCoord[0]][neighborCoord[1]]
}).length
value = false
if(squares[rowIndex][colIndex]) {
if(aliveNeighbors < 2) {
value = false
}
if(aliveNeighbors == 2 || aliveNeighbors == 3) {
value = true
}
if(aliveNeighbors > 3) {
value = false
}
} else {
if(aliveNeighbors == 3) {
value = true
}
}
return value
}
step() {
newSquares = this.blankSquares(this.state.numRows, this.state.numCols);
_.forEach(newSquares, (row, rowIndex) => {
_.forEach(row, (_, colIndex) => {
newSquares[rowIndex][colIndex] = this.computeNextSquareState(this.state.squares, rowIndex, colIndex)
})
})
this.setState({
squares: newSquares
})
}
reset() {
this.setState({
squares: this.blankSquares(this.state.numRows, this.state.numCols)
})
}
play() {
refreshIntervalId = setInterval(() => {
this.step()
}, 500)
this.setState({
refreshIntervalId: refreshIntervalId,
isPlaying: true,
})
}
pause() {
if(this.state.refreshIntervalId) {
clearInterval(this.state.refreshIntervalId)
this.setState({
refreshIntervalId: null,
isPlaying: false,
})
}
}
render() {
return (
<div className="game">
<div className="game-board">
<Board
numRows={this.state.numRows}
numColumns={this.state.numCols}
squares={this.state.squares}
onClick={(i, j) => this.handleClick(i, j)}
/>
</div>
<div>
<button onClick={() => this.reset() }>reset</button>
<button onClick={() => this.step()}>step</button>
<button disabled={this.state.isPlaying} onClick={() => this.play()}>play</button>
<button disabled={!this.state.isPlaying}onClick={() => this.pause()}>pause</button>
</div>
</div>
);
}
}
// ========================================
ReactDOM.render(<Game />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react-with-addons.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>
body {
font: 14px "Century Gothic", Futura, sans-serif;
margin: 20px;
}
ol, ul {
padding-left: 30px;
}
.board-row:after {
clear: both;
content: "";
display: table;
}
.status {
margin-bottom: 10px;
}
.square {
background: #fff;
border: 1px solid #999;
float: left;
font-size: 24px;
font-weight: bold;
line-height: 34px;
height: 34px;
margin-right: -1px;
margin-top: -1px;
padding: 0;
text-align: center;
width: 34px;
}
.square:focus {
outline: none;
}
.kbd-navigation .square:focus {
background: #ddd;
}
.game {
display: flex;
flex-direction: row;
}
.game-info {
margin-left: 20px;
}
.slidecontainer {
width: 100%; /* Width of the outside container */
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment