Juan Juan - 2 months ago 7
React JSX Question

Pure reactJS: Game of Life rules are displayed wrong due to float:left grid building not corresponding with 2d Array

I'm having travel with the game of life which I'm building on pure reactJS. I let the user touch a button to pick the size of the grid but the problem is that the positions are not colored correctly due to that is not syncronized with the array that keeps track of the state of a cell (alive or not). The grid gets rendered likea normal array and then it's displayed as a grid using float:left and a certain width depending on the size of the cell.

This is the codepen http://codepen.io/juanf03/pen/zKvzBY?editors=0011 . You have to enter the dots and then press start one time(I deactivated the loop cause it was not working properly so it will only pass generation one time. This is the code:

class Header extends React.Component {

render(){
return ( <h1 className="title text-center">The Game Of Life</h1>);
}
}


class Cell extends React.Component {

toggleState(){
const i=this.props.row;
const j=this.props.column;
this.props.changeCellHandler(i,j);
}

render(){

var style="cell";
if(this.props.alive){
style+=" alive";
}




return ( <div className={style} id={this.props.id} onClick={this.toggleState.bind(this)}></div>);
}
}


class Board extends React.Component {

constructor(props) {
super(props);
this.state = {lifeStateMatrix:[],boardWidth:50,boardHeight:30,simulationIsRunning:false, generationNumber: 0};
this.timer;
}

componentWillMount(){
this.setBoardSizeAndStatusMatrix(70,50);
}



//add all the neighbours of a position to an Array to check later how many neighbours the position has
applyLifeRules(i,j){

const minX=0, minY=0,maxX=this.state.boardWidth,maxY=this.state.boardHeight;
let totalLiveNeighbours=0;
let currentStatusMatrix=this.state.lifeStateMatrix;
//check for limits
const left= i-1>=minX;
const topLeft= i-1 >=minX && j+1<maxY;
const topMiddle= j+1<maxY;
const topRight= i+1<maxX && j+1<maxY;
const right=i+1<maxX;
const bottomRight= i+1<maxX && j-1>=minY;
const bottomMiddle= j-1>=minY;
const bottomLeft= i-1>=minX && j-1>=minY;


if(left){
if(currentStatusMatrix[i-1][j]){

totalLiveNeighbours+=1;
}
}

if(topLeft){
if(currentStatusMatrix[i-1][j+1]){

totalLiveNeighbours+=1;
}
}

if(topMiddle){
if(currentStatusMatrix[i][j+1]){

totalLiveNeighbours+=1;
}
}

if(topRight){
if(currentStatusMatrix[i+1][j+1]){

totalLiveNeighbours+=1;
}
}

if(right){
if(currentStatusMatrix[i+1][j]){
totalLiveNeighbours+=1;
}
}

if(bottomRight){
if(currentStatusMatrix[i+1][j-1]){
totalLiveNeighbours+=1;
}
}

if(bottomMiddle){
if(currentStatusMatrix[i][j-1]){

totalLiveNeighbours+=1;
}
}

if(bottomLeft){
if(currentStatusMatrix[i-1][j-1]){

totalLiveNeighbours+=1;
}
}


if(totalLiveNeighbours>0 && i==0 && j==0){
console.log('totalNeib',totalLiveNeighbours,"left",left,"topLeft",topLeft,"topMiddle",topMiddle,"topRight",topRight,"right",right,"bottomright",bottomRight,"bottomMiddle",bottomMiddle,"bottomLeft",bottomLeft);
}
if(i===1 && j===0){
console.log(totalLiveNeighbours);
}

if(currentStatusMatrix[i][j] && totalLiveNeighbours < 2){
//underpopulation. cell dies
return false;
}else if(currentStatusMatrix[i][j] && totalLiveNeighbours>3){
//overpopulation. cell dies
return false;

}else if(currentStatusMatrix[i][j] && currentStatusMatrix[i][j]>=2 && currentStatusMatrix[i][j]<=3){
return true;
}else if(!currentStatusMatrix[i][j] && totalLiveNeighbours===3){
//a new cell is born
return true;
}else{
return false
}


}

changeCell(i,j){
//put the cell as alive and add the pairs that have the cell as the neighbour if the simulation is not running

let newArray=this.state.lifeStateMatrix;
newArray[i][j]= !this.state.lifeStateMatrix[i][j];
this.setState({ lifeStateMatrix: newArray});

}



moveToNextGeneration(){

let nextGenMatrix=[]
for(let i=0,maxX=this.state.boardWidth; i<maxX ;i++){
let nextGenRow=[];
for(let j=0,maxY=this.state.boardHeight; j<maxY;j++){
nextGenRow.push(this.applyLifeRules(i,j));
}
nextGenMatrix.push(nextGenRow);
}
const genNumber= this.state.generationNumber+1;
this.setState({ lifeStateMatrix: nextGenMatrix, generationNumber:genNumber});

/*if(this.state.simulationIsRunning){
this.moveToNextGeneration();
}else{
console.log("game stopped");
}*/

if(this.state.simulationIsRunning){
this.start();
}

}

setBoardSizeAndStatusMatrix(x,y){

let lifeStateMatrix=[];

for(let i=0,len=x;i<len;i++){


let rowStatus=[];


for(let j=0,len=y;j<len;j++){

rowStatus.push(false);

}

lifeStateMatrix.push(rowStatus);
}


this.setState({ lifeStateMatrix: lifeStateMatrix, boardWidth: x, boardHeight: y});

}

start(firstTime=false){
this.setState({ simulationIsRunning:true});
this.moveToNextGeneration();
/* const that=this;
if(firstTime){
console.log("hola");
that.setState({ simulationIsRunning:true},that.moveToNextGeneration);
}

if(this.state.simulationIsRunning){
this.timer=setInterval(function next() {

that.moveToNextGeneration();

}, 2000);

}else{
console.log("tiene que terminar");
clearTimeout(this.timer);
}*/


}

pause(){
console.log("pausa");
this.setState({ simulationIsRunning:false});

}

clear(x,y){


this.setState({simulationIsRunning:false,lifeStateMatrix: [], generationNumber: 0});
this.setBoardSizeAndStatusMatrix(x,y);

}

render(){

let generatedId=0, cellsRenderMatrix=[], x= this.state.boardWidth, y= this.state.boardHeight;
let clickableState;


for(let i=0,len=x;i<len;i++){

let rowRender=[];

for(let j=0,len=y;j<len;j++){

rowRender.push(<Cell id={generatedId}
changeCellHandler={this.changeCell.bind(this)} row={i} column={j}
alive={this.state.lifeStateMatrix[i][j]} height={x} />); generatedId++;
}
cellsRenderMatrix.push(rowRender);
}



return (
<div className="holder well">
<Header />
<div className="topButtons">
<button className="run btn gridPicker" onClick={this.start.bind(this,true)}>Run</button>
<button className="pause btn gridPicker" onClick={this.pause.bind(this)}>Pause</button>
<button className="clear btn gridPicker" onClick={this.clear.bind(this,x,y)}>Clear</button>
<span id="generation">Generation: {this.state.generationNumber}</span>
</div>
<div className="board" style={{width: (y*10)+"px"}}>

{cellsRenderMatrix}
</div>
<div className="bottomButtons">
<button className="fiftyThirty btn gridPicker" onClick={this.setBoardSizeAndStatusMatrix.bind(this,50,30)}>50x30</button>
<button className="fiftyThirty btn gridPicker" onClick={this.setBoardSizeAndStatusMatrix.bind(this,70,50)}>70x50</button>
<button className="fiftyThirty btn gridPicker" onClick={this.setBoardSizeAndStatusMatrix.bind(this,100,80)}>100x80</button>
</div>
</div>);
}

}



ReactDOM.render(
<Board />,
document.getElementById('container')
);


I think the main problem is on the applyLifeRules(i,j) method where I set the rules to see if a neighbour is alive, i think the problem is here according to some logging I've been doing:

const left= i-1>=minX;
const topLeft= i-1 >=minX && j+1<maxY;
const topMiddle= j+1<maxY;
const topRight= i+1<maxX && j+1<maxY;
const right=i+1<maxX;
const bottomRight= i+1<maxX && j-1>=minY;
const bottomMiddle= j-1>=minY;
const bottomLeft= i-1>=minX && j-1>=minY;


I've tried a lot of things but I'm not seeing the solution. I'm sure the problem must be with the i, j indexes used for the loop and the incorrect grid representation of that matrix, but I don't know exactly what it is. Any ideas?. Thank you very much!.

Answer

I found the solution. Instead of

else if(currentStatusMatrix[i][j] && currentStatusMatrix[i][j]>=2 && currentStatusMatrix[i][j]<=3){
      return true;

it was  else if(currentStatusMatrix[i][j] && totalLiveNeighbours>=2 && totalLiveNeighbours<=3){
      return true;

the final code is

class Header extends React.Component {

  render(){
  return ( <h1 className="title text-center">The Game Of Life</h1>);
  }
}


class Cell extends React.Component {

  toggleState(){
   const i=this.props.row;
   const j=this.props.column;  
   this.props.changeCellHandler(i,j);
  }

  render(){

    var style="cell";
    if(this.props.alive){
      style+=" alive";
    }




  return ( <div className={style} id={this.props.id} onClick={this.toggleState.bind(this)}></div>);
  }
}


class Board extends React.Component {

  constructor(props) {
  super(props);
  this.state = {lifeStateMatrix:[],boardWidth:50,boardHeight:30,simulationIsRunning:false, generationNumber: 0};
  this.timer;
   }

  componentWillMount(){
    this.setBoardSizeAndStatusMatrix(70,50);
  }



  //add all the neighbours of a position to an Array to check later how many neighbours the position has
 applyLifeRules(i,j){

    const minX=0, minY=0,maxX=this.state.boardWidth,maxY=this.state.boardHeight;
    let totalLiveNeighbours=0;
     let currentStatusMatrix=this.state.lifeStateMatrix;
  //check for limits  
  const left= i-1>=minX;
  const topLeft= i-1 >=minX && j+1<maxY;   
  const topMiddle= j+1<maxY;
  const topRight= i+1<maxX && j+1<maxY;  
  const right=i+1<maxX;
  const bottomRight= i+1<maxX && j-1>=minY;
  const bottomMiddle= j-1>=minY;
  const bottomLeft= i-1>=minX && j-1>=minY;


    if(left){
       if(currentStatusMatrix[i-1][j]){

       totalLiveNeighbours+=1;
     }
    }

    if(topLeft){
     if(currentStatusMatrix[i-1][j+1]){

       totalLiveNeighbours+=1;
     }
    }

    if(topMiddle){
       if(currentStatusMatrix[i][j+1]){

       totalLiveNeighbours+=1;
     }
    }

    if(topRight){
       if(currentStatusMatrix[i+1][j+1]){

        totalLiveNeighbours+=1;
     }
    }

    if(right){
      if(currentStatusMatrix[i+1][j]){
       totalLiveNeighbours+=1;
     }
    }

    if(bottomRight){
      if(currentStatusMatrix[i+1][j-1]){
       totalLiveNeighbours+=1;
     }
    }

    if(bottomMiddle){
            if(currentStatusMatrix[i][j-1]){

       totalLiveNeighbours+=1;
     }
    }

    if(bottomLeft){
       if(currentStatusMatrix[i-1][j-1]){

       totalLiveNeighbours+=1;
     }
    }


   if(totalLiveNeighbours>0 && i==0 && j==0){
  console.log('totalNeib',totalLiveNeighbours,"left",left,"topLeft",topLeft,"topMiddle",topMiddle,"topRight",topRight,"right",right,"bottomright",bottomRight,"bottomMiddle",bottomMiddle,"bottomLeft",bottomLeft);
   }
   if(i===1 && j===0){
     console.log(totalLiveNeighbours);
   }

    if(currentStatusMatrix[i][j] && totalLiveNeighbours < 2){
      //underpopulation. cell dies
      return false;
    }else if(currentStatusMatrix[i][j] && totalLiveNeighbours>3){
      //overpopulation. cell dies
      return false;

    }else if(currentStatusMatrix[i][j] && currentStatusMatrix[i][j]>=2 && currentStatusMatrix[i][j]<=3){
      return true;
    }else if(!currentStatusMatrix[i][j] && totalLiveNeighbours===3){
      //a new cell is born
      return true;
    }else{
      return false
    }


 }

  changeCell(i,j){
    //put the cell as alive and add the pairs that have the cell as the neighbour if the simulation is not running

    let newArray=this.state.lifeStateMatrix;
    newArray[i][j]= !this.state.lifeStateMatrix[i][j];
    this.setState({ lifeStateMatrix: newArray});     

  }



  moveToNextGeneration(){

    let nextGenMatrix=[]
    for(let i=0,maxX=this.state.boardWidth; i<maxX ;i++){
         let nextGenRow=[];
      for(let j=0,maxY=this.state.boardHeight; j<maxY;j++){
        nextGenRow.push(this.applyLifeRules(i,j));
      }
      nextGenMatrix.push(nextGenRow);      
    }
    const genNumber= this.state.generationNumber+1;
       this.setState({ lifeStateMatrix: nextGenMatrix, generationNumber:genNumber});  

    /*if(this.state.simulationIsRunning){
      this.moveToNextGeneration();
    }else{
      console.log("game stopped");
    }*/

  if(this.state.simulationIsRunning){  
    this.start();
  }

  }

  setBoardSizeAndStatusMatrix(x,y){

    let lifeStateMatrix=[];

     for(let i=0,len=x;i<len;i++){


      let rowStatus=[];


      for(let j=0,len=y;j<len;j++){

         rowStatus.push(false);                  

      }

      lifeStateMatrix.push(rowStatus);
    }


        this.setState({ lifeStateMatrix: lifeStateMatrix, boardWidth: x, boardHeight: y}); 

  }

  start(firstTime=false){
        this.setState({ simulationIsRunning:true});
       this.moveToNextGeneration();
  /*  const that=this;
    if(firstTime){
      console.log("hola");
     that.setState({ simulationIsRunning:true},that.moveToNextGeneration);
    }

 if(this.state.simulationIsRunning){     
    this.timer=setInterval(function next() {

    that.moveToNextGeneration(); 

        }, 2000);

 }else{
   console.log("tiene que terminar");
       clearTimeout(this.timer);
 }*/


  }

  pause(){
    console.log("pausa");
    this.setState({ simulationIsRunning:false}); 

  }

  clear(x,y){


    this.setState({simulationIsRunning:false,lifeStateMatrix: [], generationNumber: 0});
    this.setBoardSizeAndStatusMatrix(x,y);

  }

  render(){

    let generatedId=0, cellsRenderMatrix=[], x= this.state.boardWidth, y= this.state.boardHeight;
    let clickableState;


    for(let i=0,len=x;i<len;i++){

      let  rowRender=[];

      for(let j=0,len=y;j<len;j++){

                 rowRender.push(<Cell id={generatedId}  
           changeCellHandler={this.changeCell.bind(this)} row={i} column={j}
     alive={this.state.lifeStateMatrix[i][j]} height={x} />);                                                                     generatedId++;
      }
      cellsRenderMatrix.push(rowRender);
    }



  return (
    <div className="holder well">
    <Header />
      <div className="topButtons">
         <button className="run btn gridPicker" onClick={this.start.bind(this,true)}>Run</button>
          <button className="pause btn gridPicker" onClick={this.pause.bind(this)}>Pause</button>
            <button className="clear btn gridPicker" onClick={this.clear.bind(this,x,y)}>Clear</button> 
           <span id="generation">Generation: {this.state.generationNumber}</span>
      </div>
    <div className="board" style={{width: (y*10)+"px"}}>

      {cellsRenderMatrix}
    </div>
      <div className="bottomButtons">
            <button className="fiftyThirty btn gridPicker" onClick={this.setBoardSizeAndStatusMatrix.bind(this,50,30)}>50x30</button>
          <button className="fiftyThirty btn gridPicker" onClick={this.setBoardSizeAndStatusMatrix.bind(this,70,50)}>70x50</button>
            <button className="fiftyThirty btn gridPicker" onClick={this.setBoardSizeAndStatusMatrix.bind(this,100,80)}>100x80</button>
      </div>
      </div>);
  }

}



ReactDOM.render(
  <Board />,
  document.getElementById('container')
);