开发者问题收集

这行代码出了什么问题?(React,Game Of Life)

2017-08-26
100

出现一个我无法理解的错误。当我按下 <button>50x70</button> 并按下 <button>run</button> 时,我收到此错误。 我不明白为什么,因为它适用于标准 50x50 板。

为什么这行代码会导致错误? 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>
2个回答

我认为问题发生在您尝试计算边缘上的图块的邻居时。由于您尝试访问不在网格上的图块,因此循环中的某些变量变得未定义。我建议添加 if 语句来检查这一点。类似于:

if(x+i >= 0 && y+i >= 0){
    neighbors+=board[x+i][y+j].value
}
Pizza Scripters
2017-08-26

您应该循环直到 x 和 y 小于 board.length - 2,而不是 board.length - 1,因为您要确保它不会评估板的边缘​​。我认为您犯了一个相当常见的错误,即忘记了数组的长度将比该数组的最大索引多一个。

例如,如果其中一行看起来像 [4, 6, 2, 3],它的 .length 为 4,但如果您尝试访问 row[4],您将得到未定义的结果。如果您在外循环中循环直到长度 - 1,然后在内循环中将每个值加 1(您这样做是因为您在 -1 和 1 之间循环 i/j,然后将该值添加到外索引),您的代码就会执行上述操作。

此外 我认为您在内部数组中反转了变量 - 在外循环中使用 y 作为行循环变量,使用 x 作为列循环变量,但在内循环中这些变量被反转了。因此,如果它不是正方形,它最终会碰到不存在的行。

您的代码整体应如下所示:

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
2017-08-27