1

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>
Hyrule
  • 635
  • 2
  • 10
  • 23
  • 2
    Side note: There are quite a few code smells in your code. Missing semicolons, `-=` inside `for` initialization, etc. – Derek 朕會功夫 Aug 26 '17 at 22:37
  • @Derek朕會功夫 Aren't semicolons just a matter of personal taste since automatic insertion? Still learning. – Hyrule Aug 26 '17 at 22:45
  • 1
    You should [always include semicolons after every single statements](https://stackoverflow.com/a/444082/283863), or else you are going to have fun debugging your code (unless you are an uglifier that makes no mistakes.) [Can you spot where the error is in this example?](https://jsfiddle.net/DerekL/fwb7shjv/) Rule of thumb is that you shouldn't depend on implied mechanisms, such as *implied semicolons* and *implied type casting*. – Derek 朕會功夫 Aug 26 '17 at 22:51

2 Answers2

1

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
}
1

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
}
Ben Hare
  • 4,365
  • 5
  • 27
  • 44
  • I have edited it to - 2 but it didn't seem to fix it. – Hyrule Aug 27 '17 at 01:01
  • 1
    Does it throw a different error or just the same still? – Ben Hare Aug 27 '17 at 01:09
  • same error of 'Uncaught TypeError: Cannot read property '0' of undefined'. Been staring at the code, this is just strange... everything seems logical to me but yet there is an error. – Hyrule Aug 27 '17 at 01:11
  • 1
    It must be going off the end of the arrays some other way. I'd log x+i and y+j inside of that loop before you attempt to get `board[x+i][y+j]` and see what values those are giving you. – Ben Hare Aug 27 '17 at 01:30
  • 1
    Ok I've edited it with another issue with the algorithm I think I've identified. Let me know if it works. – Ben Hare Aug 27 '17 at 01:44
  • Hmm.. i'm seeing this in console when i console.log (x+i): 48 (3) 49 (3) 50 VM809 pen.js:59 Uncaught TypeError: Cannot read property '0' of undefined and when I console.log the initial 50x50 board it goes like this: 46(3) 47(3) 48(3) 1(3). – Hyrule Aug 27 '17 at 01:48
  • 1
    Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/152925/discussion-between-ben-hare-and-hyrule). – Ben Hare Aug 27 '17 at 02:23
  • hmm, the game of life rules break if i change the indices but there is movement now and no error! I'm gonna fully rewrite the app and make it as readable as possible. Learned how important it is. – Hyrule Aug 27 '17 at 07:22