简体   繁体   中英

React.js Game of Life: Why doesn't the glider behave as expected?

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>
        );
    } 

});

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>
        );
    } 

});

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM