Skip to content

Instantly share code, notes, and snippets.

@chemok78
Created December 28, 2016 04:23
Show Gist options
  • Save chemok78/756b4d6aa7edc9aeff36f83a5fc4ba40 to your computer and use it in GitHub Desktop.
Save chemok78/756b4d6aa7edc9aeff36f83a5fc4ba40 to your computer and use it in GitHub Desktop.
React JS Conway Game of Life
var Button = ReactBootstrap.Button;
var ButtonToolbar = ReactBootstrap.ButtonToolbar;
var App = React.createClass({
//function to generate board and set initial state
createBoard: function(width,height){
//function to create an array with board configuration
var array = [];
for(var i = 0; i < height; i++){
//height is the amount of rows = amount of elements in array
array[i] = [];
for( var j = 0; j < width; j++){
//loop through every row and generate cells with 0 for width
array[i].push(0);
}
}
return array;
},
generateRandomBoard: function(board){
//takes an empty board config and generates 0 or 1 randomly for each cell
for(var i = 0; i < board.length; i ++){
//loop through the board array = every row
for(var j = 0; j < board[i].length; j++){
//loop through every array element and generate a 0 or 1 for the column
var random = Math.round(Math.random());
board[i][j] = random;
}
}
return board;
},
getInitialState(){
var board = this.createBoard(70,50);
//start with a 70(width) x 50(height) board
var randomBoard = this.generateRandomBoard(board);
//generate a random board to start the game with
return{
boardWidth: 70,
boardHeight: 50,
running: true,
speed: 1000,
iterations: 0,
boardConfig: randomBoard
}
},
handleClick: function(value, row, column){
//click event handler for every passed via Board > Cell
//loop through this.state.boardConfig and toggle the value in the array 0 to 1 and vice versa
var nextBoard = this.state.boardConfig.slice();
if(value === 0){
nextBoard[row][column] = 1;
} else {
nextBoard[row][column] = 0;
}
this.setState({boardConfig: nextBoard}, function(){
console.log(this.state.boardConfig[row][column]);
});
},
getNextState: function(){
//function to generate next state based on Game of Life algorithm
var nextState = this.state.boardConfig.slice();
//copy the the current state of the board
for(var i = 0; i < nextState.length; i++){
//loop through every element = row
for(var j = 0; j < nextState[i].length; j++){
//loop through every subelement = columm
var scores = [
//array[i] is the row
//array.length is the height
//array[i].length is the row width
i > 0 && j > 0 ? nextState[i-1][j-1] : 0,
//Corner:upperLeft
//first row AND first column
i > 0 ? nextState[i-1][j] : 0,
//topSide
//first row, there is no top side
i > 0 && j < nextState[i].length-1 ? nextState[i-1][j+1] : 0,
//Corner:upperRight
//first row and last column
j > 0 ? nextState[i][j-1] : 0,
//leftSide
//first column
j < nextState[i].length-1 ? nextState[i][j+1] : 0,
//rightSide
//last column
i < nextState.length-1 && j > 0 ? nextState[i+1][j-1] : 0,
//Corner: bottomLeft
//last row and first column
i < nextState.length-1 ? nextState[i+1][j] : 0,
//bottomSide
//last row
i < nextState.length-1 && j < nextState[i].length-1 ? nextState[i+1][j+1] : 0
//last row and last column
//bottomRight
]
var totalScore = scores.reduce(function(total, currentValue){
//loop through scores array and return a single total score
return total += currentValue;
})
//conditions to change each cell or not
if(nextState[i][j] === 1 && (totalScore <= 1 || totalScore >= 4)){
//Any live cell with fewer than two live neighbours dies, as if caused by underpopulation.(0 or 1 points)
//Any live cell with two or three live neighbours lives on to the next generation.(2 or 3 points)
//Any live cell with more than three live neighbours dies, as if by overpopulation.(4 or more points)
nextState[i][j] = 0;
} else if(nextState[i][j] === 0 && totalScore === 3) {
//Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
nextState[i][j] = 1;
}
}
}
var iterations = this.state.iterations + 1;
//add one to the amount of iterations kept in state
this.setState({boardConfig: nextState, iterations: iterations});
},//getNextState
clearBoard: function(width,height){
//function to clear the board
//passed to ClearButton component
var board = this.createBoard(width, height);
//create new empty board with the current width and height settings
this.setState({boardConfig: board, boardWidth: width, boardHeight: height, iterations: 0}, function(){
//set the state to new empty board and set iterations to zero
this.runGame("pause");
//pause the game when board is cleared. User must click run to start the game again.
});
},
runGame: function(action){
//called when game is loaded in ComponentDidMount
//called onClick in RunGame component
//called onClick in PauseGame component
if(action === "run"){
clearInterval(this.state.intervalId);
var intervalId = setInterval(this.getNextState, this.state.speed);
this.setState({intervalId: intervalId, running: true});
} else if(action === "pause") {
this.setState({running: false}, function(){
clearInterval(this.state.intervalId);
})
}
},
changeSpeed: function(speed){
//function to change the speed the game is currently running at
clearInterval(this.state.intervalId);
var intervalId = setInterval(this.getNextState, speed);
this.setState({speed:speed,intervalId: intervalId}, function(){
});
},
componentDidMount: function(){
var intervalId = setInterval(this.getNextState, this.state.speed);
this.setState({intervalId: intervalId, running: true});
},
ComponentWillUnmount: function(){
clearInterval(this.state.intervalId);
},
render: function(){
//return <Board boardConfig = {this.state.boardConfig} />
return(
<div>
<h1 className="text-center">React JS Game of Life</h1>
<br/>
<ButtonToolbar>
<ClearButton clearBoard={this.clearBoard} boardWidth={this.state.boardWidth} boardHeight={this.state.boardHeight}/>
<PauseButton runGame = {this.runGame}/>
<RunButton runGame = {this.runGame}/>
</ButtonToolbar>
<h4 className="generations">Generations: {this.state.iterations}</h4>
<br/>
<Board board={this.state.boardConfig} handleClick={this.handleClick}/>
<div className="well">
<SpeedButtons changeSpeed = {this.changeSpeed} speed={this.state.speed}/>
<br/>
<SizeButtons clearBoard = {this.clearBoard} boardHeight={this.state.boardHeight} boardWidth={this.state.boardWidth}/>
</div>
</div>
)
}
});
var Board = function(props){
//this.props.board is the current board from APP parent component
//every element is an array representing a row
return (
<table className="table table-bordered">
{
props.board.map(function(item,index){
var row = index;
return(
<tr>
{
item.map(function(item,index){
return <Cell value={item} row={row} column={index} clickevent={props.handleClick} />
}.bind(this))
}
</tr>
)
}.bind(this))
}
</table>
)
};
var Cell = React.createClass({
//component for every cell on the board
//this.props.value is either 0 or 1 from Board Component
//this.props.row and this.props.column are available
render: function(){
var status = "";
if(this.props.value === 1){
//set class to set CSS background dynamically
status = "alive";
} else {
status = "dead";
}
return <td className={status} onClick={()=> this.props.clickevent(this.props.value, this.props.row, this.props.column)}></td>
}
});
var ClearButton = function(props){
return <Button bsStyle="primary" bsSize="small" onClick={() => props.clearBoard(props.boardWidth, props.boardHeight)}>Clear</Button>
};
var PauseButton = function(props){
return <Button bsStyle="primary" bsSize="small" onClick={() => props.runGame("pause")}>Pause</Button>
};
var RunButton = function(props){
return <Button bsStyle="primary" bsSize="small" onClick={() => props.runGame("run")}>Run</Button>
};
var SpeedButtons = function(props){
//this.props.changeSpeed from App Component
return (
<ButtonToolbar>
<Button bsStyle="primary" bsSize="small" onClick={() => props.changeSpeed(1000)} className={props.speed === 1000 ? "active": "nonactive"}>Slow</Button>
<Button bsStyle="primary" bsSize="small" onClick={() => props.changeSpeed(500)} className={props.speed === 500 ? "active": "nonactive"}>Medium</Button>
<Button bsStyle="primary" bsSize="small" onClick={() => props.changeSpeed(250)} className={props.speed === 250 ? "active": "nonactive"}>Fast</Button>
<Button bsStyle="primary" bsSize="small" onClick={() => props.changeSpeed(100)} className={props.speed === 100 ? "active": "nonactive"}>Ultra</Button>
</ButtonToolbar>
)
};
var SizeButtons = function(props){
//this.props.clearBoard from App Component
return (
<ButtonToolbar>
<Button bsStyle="primary" bsSize="small" onClick={() =>props.clearBoard(50,30)} className={props.boardWidth === 50 && props.boardWith === 30 ? "active": "nonactive"}>Size:50x30</Button>
<Button bsStyle="primary" bsSize="small" onClick={() =>props.clearBoard(70,50)} className={props.boardWidth === 70 && props.boardWith === 50 ? "active": "nonactive"}>Size:70x50</Button>
<Button bsStyle="primary" bsSize="small" onClick={() =>props.clearBoard(100,80)} className={props.boardWidth === 100 && props.boardWith === 80 ? "active": "nonactive"}>Size:100x80</Button>
</ButtonToolbar>
)
};
ReactDOM.render(<App />, document.getElementById('app'));
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Bitter" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Rubik" rel="stylesheet">
</head>
<body>
<div class="container">
<div id="app">
</div>
</div>
</body>
</html>
.container {
margin-top: 20px;
}
body {
font-family: 'Rubik', sans-serif;
}
td {
border-style: solid;
border-width: 0.2px;
height: 10px;
width: 10px;
border-color:grey;
}
.alive {
background-color: #c0392b;
}
.dead {
background-color: white;
}
.table {
border-width:5px;
}
.generations {
margin-left:5px;
font-weight:bold;
display: inline;
float:left;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment