Having a bit of trouble with requestAnimationFrame in Game of Life

Hey all,

So I have everything in my Game of Life working with React & Redux. I’ve been trying to replace setInterval with requestAnimationFrame, and have run into some issues.

What I’m currently trying to do in my ButtonsContainer is this:

  start() {
    this.play();
    this.props.play(this.requestID);
  }

  play() {
    this.requestID = requestAnimationFrame(this.play);
    this.props.stepForward();
  }

  stop() {
    cancelAnimationFrame(this.props.board.requestID);
    this.props.stop();
  }

Where this.start is invoked both in componentDidMount and when you press the play button. The issue that I’m running into is unless this.requestID (which is currently being defined, e.g. let requestID = null in the constructor) is this.props.board.requestID (pulling from the Redux store) in start and play, the stop button doesn’t actually work to stop the animation, although the action is dispatched and the state changes. Anyone know why this is or how to fix it?

I’m not actually that savvy with Redux, but don’t you need to dispatch this.requestID to the store in order for it to propagate back to the props object? I might be missing something obvious, but I see this.requestID get set to the return value of requestAnimationFrame, but I don’t see how that gets set to this.props.board.requestID.

When your board component mounts, I would recommend saving the id into the state along with a bool that indicates the game is running.

Then you can have a click handler for button that checks if the game is running, and if so calls your cancelAnimationFrame with the saved id.

// Action creators

const startGame = (id) => {
  return {
    type: "START_GAME",
    id: id
  }
}

const stopGame = () => {
  return {
    type: "STOP_GAME"
  }
}

// Your board reducer

const boardReducer(state = {
  id: null,
  isRunning: false
}, action) => {
  switch(action.type) {
    case "START_GAME":
    state = {...state, { id: action.id }, { isRunning: true }};
    break;
    case "STOP_GAME":
    state = {...state, { id: null }, { isRunning: false }};
    break;
  }
  return state;
}

// Inside your state container

toggleStart() {
  if (this.props.isRunning) {
    window.cancelAnimationFrame(this.props.id);
    this.props.stopGame;
  } else {
    const id = 
    window.requestAnimationFrame(yourCallbackToStartGameTimer);
    this.props.startGame(id);
  }
}

// Your button

<button onClick={ this.toggleStart.bind(this) }>Start/Stop</button>

@PortableStick - I think you’re right; I do need to dispatch it. I guess my issue is that I can’t dispatch it in the ButtonsContainer–I’m not sure how to get it back to the store? (dispatch is undefined in the container, and I also tried importing the store and using store.dispatch but that didn’t quite work either…)

@jmcilhargey - I actually am doing both; just didn’t include that code here.

// action creator

export const play = (requestID) => {
  return {
    type: PLAY,
    requestID
  };
};

// reducer

case PLAY:
      newState.isPlaying = true;
      newState.requestID = action.requestID;
      break;

I think this might help - here’s the full code for the component where all of this is. I’m 95% sure the issue is just that it just isn’t updating the store, but I haven’t yet been able to or figured out how to make that happen given my current setup.

I am at mobile at the moment so I’ll try to explain in more details of what I’ve done using requestAnimation and Redux (when I get home tonight), but you can check my code of what I did in the meantime:

demo: http://kenzomendoza.com/FCC-Game-of-Life/

2 Likes

I took a look at your code. Your reducer and action creator looks fine. You do however need to save the ID and then dispatch your action with the saved requestID. Here’s the fix:

play() {
  const requestID = requestAnimationFrame(this.play);
  this.props.play(requestID);
  this.props.stepForward();
}

You may also want to create a game loop from within your play method if you want to throttle the speed or allow the player to increase/decrease. Here’s a great link on that:

http://codeincomplete.com/posts/javascript-game-foundations-the-game-loop/

1 Like

So I think that was actually the first thing I tried, except for some reason when I did it didn’t fully work haha. At the time, I also didn’t like the fact that it dispatched play every time it updated, but thinking about it now, it makes sense that it does. And I’ll definitely look into the game loop. :slight_smile:

Thanks everyone!