Hyrule
Hyrule

Reputation: 645

What is going wrong on this line of code? (React, Game Of Life)

there is an error which I don't understand. When I press <button>50x70</button> and press <button>run</button>I get this error. I can't see why, since it works with the standard 50x50 board.

Why is this line of code causing an error? neighbors+=board[x+i][y+j].value https://codepen.io/anon/pen/OjoZKX

TypeError: Cannot read property '0' of undefined
evaluateGen
C:/Www/Projects/gameoflife/src/App.js:65
  62 | 
  63 | for(let i=-1; i <= 1; i++){
  64 |   for(let j=-1; j<= 1; j++){
> 65 |     neighbors+=board[x+i][y+j].value
  66 |   }
  67 | }
  68 | neighbors-=board[x][y].value
View compiled
(anonymous function)
C:/Www/Projects/gameoflife/src/App.js:108
  105 | 
  106 | run(){
  107 |   this.interval = setInterval(() => {
> 108 |       const nextState = evaluateGen(this.state.board)
  109 |       this.setState({paused: false, generation: this.state.generation + 1, board: nextState})
  110 |     }, 50)
  111 | }

// creates the board with random 1/0 value.
const createBoard = function(width, height) {
  let board = []
  for(var x = 0; x < width; x++){
    board.push([])
    for(var y = 0; y < height; y++){
      if(x === 0 || y === 0) {
        board[x].push({value: 0})
      } 
      else if (x === width-1 || y === height-1){
        board[x].push({value: 0})
      }
      else {
        let number = Math.round(Math.random())
        board[x].push({value: number})
      }
    }
  }
  return board
}

//  Game Of Life rules.
const evaluateCell = function(x, cell){
  let value = x
  if(x < 2){
    value = 0
  }
  else if(x === 2){
    value = cell
  }
  else if(x === 3){
    value = 1
  }
  else if(x > 3){
    value = 0
  }
  else {
    console.log('error: default case evaluateCell()')
    value = 0
  }
  return value
}

// evaluates a generation of board state by going through each cell and counting neighbors and calling evaluateCell accordingly.
const evaluateGen = function(board){
  let nextGen = JSON.parse(JSON.stringify(board));
  
  for(let y = 1; y < board.length - 1; y++){
    for(let x = 1; x < board[y].length - 1; x++){
      
      let neighbors = 0
      
      for(let i=-1; i <= 1; i++){
        for(let j=-1; j<= 1; j++){
          neighbors+=board[x+i][y+j].value
        }
      }
      // remove current cell from neighbors.
      neighbors-=board[x][y].value
      let nextvalue = evaluateCell(neighbors, board[x][y].value)
      nextGen[x][y].value = nextvalue
    }
  }

  return nextGen
}






class App extends React.Component {
  
  constructor(){
    super()
    this.state = {
      width: 50,
      height: 50,
      board: createBoard(50, 50),
      paused: false,
      generation: 0
    }
  }

  generateBoard(width, height) {
    this.pause()
    const board = createBoard(width, height)
    this.setState({board, generation: 0, width, height })
  }
  
  pause(){
    clearInterval(this.interval)
    this.setState({paused: true})
  }

  run(){
    this.interval = setInterval(() => {
        const nextState = evaluateGen(this.state.board)
        this.setState({paused: false, generation: this.state.generation + 1, board: nextState})
      }, 50)
  }

  componentDidMount() {
    this.run()
  }


  componentWillUnmount() {
    clearInterval(this.interval)
  }


  render() {
    
    let {board, generation, width, height} = this.state
    return (
      <div className="App">
        <h3>generation:{generation}</h3>
        {board.map(x => x.map(item => {
          return (
            <div className={`state-${item.value}`}></div>
          )
        }))}
        <button onClick={()=> this.run()}>run</button>
        <button onClick={()=> this.pause()}>pause </button>
        <button onClick={()=> this.generateBoard(width, height)}>generate</button>
        <button onClick={()=> this.generateBoard(50, 70)}>50 x 70</button>
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById('root'))
.App div {
  width: 10px; height: 10px;
  outline: 1px solid black;
  
}
.App {
  display: flex;
  flex-wrap: wrap;
  width: 500px; height: 500px;
}

.state-1{
  background-color: red;
}
<div id='root'></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

Upvotes: 1

Views: 102

Answers (2)

Ben Hare
Ben Hare

Reputation: 4415

You should be looping until x and y are less than board.length - 2, not board.length - 1 since you're trying to make sure that it doesn't evaluate the edges of the board. I think you're making the fairly common mistake of forgetting that the length of an array will give you one more than the maximum index of that array.

For example, if one of the rows looks like [4, 6, 2, 3], it has a .length of 4, but if you try to access row[4] you'll get undefined. That's what your code will do if you loop until length - 1 in the outer loop, and then add +1 to each value in the inner loop (you do that since you're looping from i/j between -1 and 1 and then adding that value to to the outer indices).

In addition I think you've got the variables reversed in the inner array - you're using y as the row loop variable and x as the column loop variable in the outer loops but these are reversed in the inner loops. So if it's not a square it'll eventually hit a row which doesn't exist.

Your code overall should look like:

const evaluateGen = function(board){
  let nextGen = JSON.parse(JSON.stringify(board));

  for(let x = 1; x < board.length - 2; x++){
    for(let y = 1; y < board[y].length - 2; y++){

      let neighbors = 0

      for(let i=-1; i <= 1; i++){
        for(let j=-1; j<= 1; j++){
          neighbors+=board[x+i][y+j].value
        }
      }
      // remove current cell from neighbors.
      neighbors-=board[x][y].value
      let nextvalue = evaluateCell(neighbors, board[x][y].value)
      nextGen[x][y].value = nextvalue
    }
  }

  return nextGen
}

Upvotes: 1

Pizza Scripters
Pizza Scripters

Reputation: 46

I think the problem occurs when you are trying to calculate the neighbors of the tiles on the edges. Because you are trying to access tiles that are not on the grid, some variables in your loop become undefined. I recommend adding an if statement to check for this. Something like:

if(x+i >= 0 && y+i >= 0){
    neighbors+=board[x+i][y+j].value
}

Upvotes: 1

Related Questions