Reputation: 4471
SITUATION GIF:
SITUATION:
No errors at all. I put some cells in, click start, they all disappear regardless of configuration. I also tried the glider configuration, it just disappears immediately.
If you find what I did wrong and we are able to make the Game of Life work, I will give you a 50 rep bounty on top of the accepted answer.
UPDATE:
Now, I can put dots and they will evolve but not as should be the case in the Game of Life. For example, see what happens with a glider configuration.
Here is how the Glider should behave:
Here is what happens:
More on the Glider: https://en.wikipedia.org/wiki/Glider_(Conway%27s_Life)
CODE:
Game
var Game = createReactClass({
getInitialState() {
return {
start: false
}
},
handleStartClick() {
this.setState({
start: true
})
},
handleStopClick() {
this.setState({
start: false
})
},
render() {
return (
<div>
<h1>React.js Game of Life</h1>
<div className="buttons">
<button className="btn btn-danger" onClick={this.handleStopClick}>Stop</button>
<button className="btn btn-success" onClick={this.handleStartClick}>Start</button>
</div>
<Board start={this.state.start}/>
</div>
)
}
});
Board
var Board = createReactClass({
getInitialState() {
var array = [];
for (var i = 0; i < 400; i++) {
array.push(<Cell key={i} id={i} cells={array} start={this.props.start} />);
}
return {
cells: array
};
},
render() {
var that = this;
return (
<div className="board">
{
this.state.cells.map(function(item, i) {
return <Cell key={i} id={i} cells={that.state.cells} start={that.props.start}/>
})
}
</div>
);
}
});
Cell
var Cell = createReactClass ({
getInitialState() {
return {
alive: false,
nextAlive: false,
started: false
}
},
componentWillReceiveProps(nextProps) {
var evolution;
if(nextProps.start && this.state.started == false) {
let evolution = setInterval(() => {
this.life();
this.nextLife();
}, 500);
this.setState({
started: true,
evolution
})
}
else {
clearInterval(this.state.evolution);
this.setState({
started: false
})
}
},
isAlive(r, c){
var size = Math.sqrt(this.props.cells.length)
if (r == -1) {
r = size - 1
}
if (r == size) {
r = 0
}
if (c == -1) {
c = size - 1
}
if (c == size) {
c = 0
}
var id = r * size + c
return this.props.cells[id].state.alive
},
life() {
var neighbours = 0
var size = Math.sqrt(this.props.cells.length)
var row = Math.floor( this.props.id / size )
var col = this.props.id - row * size
if (this.isAlive(row - 1, col)) {
neighbours++
}
if (this.isAlive(row - 1, col + 1)) {
neighbours++
}
if (this.isAlive(row - 1, col - 1)) {
neighbours++
}
if (this.isAlive(row, col + 1)) {
neighbours++
}
if (this.isAlive(row, col - 1)) {
neighbours++
}
if (this.isAlive(row + 1, col)) {
neighbours ++
}
if (this.isAlive(row + 1, col + 1)) {
neighbours ++
}
if (this.isAlive(row + 1, col - 1)) {
neighbours ++
}
this.state.nextState = false
if (this.state.alive){
if( neighbours < 2) {
this.setState ({
nextAlive: false
})
}
if (neighbours > 3) {
this.setState ({
nextAlive: false
})
}
if (neighbours == 3 || neighbours == 2) {
this.setState ({
nextAlive: true
})
}
}
else{
if (neighbours == 3) {
this.setState ({
nextAlive: true
})
}
}
},
nextLife () {
this.setState({
alive: this.state.nextAlive
})
},
componentDidMount() {
this.props.cells[this.props.id] = this;
},
toggleLife() {
this.setState({
alive: !this.state.alive
})
},
render() {
return (
<div className={this.state.alive ? "cell alive" : "cell"} onClick={this.toggleLife}></div>
);
}
});
Upvotes: 1
Views: 286
Reputation: 1273
I answer your update separately.
Your next bug is more tricky: you update the cells independently without any synchronization. That means that depending on your luck, neighbors may be in completely different generation. Therefore what you get when you hit start is random.
In order to solve this problem, you need to move the ruling code to the Board
element this way:
var Board = React.createClass({
componentWillReceiveProps(nextProps) { // <- from here ...
var evolution;
if(nextProps.start && this.state.started == false) {
let evolution = setInterval(() => {
this.state.cells.forEach( cell => cell.life() )
this.state.cells.forEach( cell => cell.nextLife() )
}, 500);
this.setState({
started: true,
evolution
})
}
else {
clearInterval(this.state.evolution);
this.setState({
started: false
})
}
}, // <- ... to here
getInitialState() {
var array = [];
for (var i = 0; i < 400; i++) {
array.push(<Cell key={i} id={i} cells={array} start={this.props.start} />);
}
return {
cells: array,
started: false
};
},
render() {
var that = this;
return (
<div className="board">
{
this.state.cells.map(function(item, i) {
return <Cell key={i} id={i} cells={that.state.cells} start={that.props.start}/>
})
}
</div>
);
}
});
Then you should remove componentWillReceiveProps()
from the cells element:
var Cell = createReactClass ({
getInitialState() {
return {
alive: false,
nextAlive: false,
started: false
}
},
componentWillReceiveProps(nextProps) { /* I'm useless now */ },
isAlive(r, c){
var size = Math.sqrt(this.props.cells.length)
if (r == -1) {
r = size - 1
}
if (r == size) {
r = 0
}
if (c == -1) {
c = size - 1
}
if (c == size) {
c = 0
}
var id = r * size + c
return this.props.cells[id].state.alive
},
life() {
var neighbours = 0
var size = Math.sqrt(this.props.cells.length)
var row = Math.floor( this.props.id / size )
var col = this.props.id - row * size
if (this.isAlive(row - 1, col)) {
neighbours++
}
if (this.isAlive(row - 1, col + 1)) {
neighbours++
}
if (this.isAlive(row - 1, col - 1)) {
neighbours++
}
if (this.isAlive(row, col + 1)) {
neighbours++
}
if (this.isAlive(row, col - 1)) {
neighbours++
}
if (this.isAlive(row + 1, col)) {
neighbours ++
}
if (this.isAlive(row + 1, col + 1)) {
neighbours ++
}
if (this.isAlive(row + 1, col - 1)) {
neighbours ++
}
this.state.nextState = false
if (this.state.alive){ // bug 2
if( neighbours < 2) {
this.setState ({
nextAlive: false
})
}
if (neighbours > 3) {
this.setState ({
nextAlive: false
})
}
if (neighbours == 3 || neighbours == 2) {
this.setState ({
nextAlive: true
})
}
}
else{
if (neighbours == 3) {
this.setState ({
nextAlive: true
})
}
}
},
nextLife () {
this.setState({
alive: this.state.nextAlive
})
},
componentDidMount() {
this.props.cells[this.props.id] = this;
},
toggleLife() {
this.setState({
alive: !this.state.alive
})
},
render() {
return (
<div className={this.state.alive ? "cell alive" : "cell"} onClick={this.toggleLife}></div>
);
}
});
As a side note you can also remove the started
state and the start
props from Cell
too.
Upvotes: 1
Reputation: 1273
Your bug is probably not where you were looking at. In the Cell
component's componentWillReceiveProps
function:
if(nextProps.start && this.state.started == false) {
let evolution = setInterval(() => {
this.life();
this.nextLife();
}, 500);
this.nextLife(); // <- remove this line !!
this.setState({
started: true,
evolution
})
}
You should remove the this.nextLife()
(or call this.life()
before).
Indeed, this function sets alive
to nextAlive
before it to be computed by nextLife()
: in other words you set to its default value, that is, false
.
By the way, I spotted another bug in the life()
function of the cell component: you access to this.state.selected
instead of this.state.alive
.
In conclusion the code of your Cell
component should be:
var Cell = createReactClass ({
getInitialState() {
return {
alive: false,
nextAlive: false,
started: false
}
},
componentWillReceiveProps(nextProps) {
var evolution;
if(nextProps.start && this.state.started == false) {
let evolution = setInterval(() => {
this.life();
this.nextLife();
}, 500);
// this.nextLife(); // bug 1
this.setState({
started: true,
evolution
})
}
else {
clearInterval(this.state.evolution);
this.setState({
started: false
})
}
},
isAlive(r, c){
var size = Math.sqrt(this.props.cells.length)
if (r == -1) {
r = size - 1
}
if (r == size) {
r = 0
}
if (c == -1) {
c = size - 1
}
if (c == size) {
c = 0
}
var id = r * size + c
return this.props.cells[id].state.alive
},
life() {
var neighbours = 0
var size = Math.sqrt(this.props.cells.length)
var row = Math.floor( this.props.id / size )
var col = this.props.id - row * size
if (this.isAlive(row - 1, col)) {
neighbours++
}
if (this.isAlive(row - 1, col + 1)) {
neighbours++
}
if (this.isAlive(row - 1, col - 1)) {
neighbours++
}
if (this.isAlive(row, col + 1)) {
neighbours++
}
if (this.isAlive(row, col - 1)) {
neighbours++
}
if (this.isAlive(row + 1, col)) {
neighbours ++
}
if (this.isAlive(row + 1, col + 1)) {
neighbours ++
}
if (this.isAlive(row + 1, col - 1)) {
neighbours ++
}
this.state.nextState = false
if (this.state.alive){ // bug 2
if( neighbours < 2) {
this.setState ({
nextAlive: false
})
}
if (neighbours > 3) {
this.setState ({
nextAlive: false
})
}
if (neighbours == 3 || neighbours == 2) {
this.setState ({
nextAlive: true
})
}
}
else{
if (neighbours == 3) {
this.setState ({
nextAlive: true
})
}
}
},
nextLife () {
this.setState({
alive: this.state.nextAlive
})
},
componentDidMount() {
this.props.cells[this.props.id] = this;
},
toggleLife() {
this.setState({
alive: !this.state.alive
})
},
render() {
return (
<div className={this.state.alive ? "cell alive" : "cell"} onClick={this.toggleLife}></div>
);
}
});
Upvotes: 1