Reputation: 645
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
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
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