Created
December 28, 2016 04:23
-
-
Save chemok78/756b4d6aa7edc9aeff36f83a5fc4ba40 to your computer and use it in GitHub Desktop.
React JS Conway Game of Life
This file contains hidden or 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
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')); |
This file contains hidden or 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
<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> |
This file contains hidden or 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
.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