Skip to content

Instantly share code, notes, and snippets.

@jonurry
Created March 28, 2018 09:04
Show Gist options
  • Save jonurry/adefda2a4e8dc1959e92d9b21cf24310 to your computer and use it in GitHub Desktop.
Save jonurry/adefda2a4e8dc1959e92d9b21cf24310 to your computer and use it in GitHub Desktop.
16.2 Pausing The Game (Eloquent JavaScript Solutions)
<link rel="stylesheet" href="css/game.css">
<body>
<script>
function trackKeys(keys) {
let down = Object.create(null);
function track(event) {
if (keys.includes(event.key)) {
down[event.key] = event.type == "keydown";
event.preventDefault();
}
}
window.addEventListener("keydown", track);
window.addEventListener("keyup", track);
down.unregister = () => {
window.removeEventListener("keydown", track);
window.removeEventListener("keyup", track);
}
return down;
}
function runLevel(level, Display) {
let display = new Display(document.body, level);
let state = State.start(level);
let ending = 1;
return new Promise(resolve => {
/* all of the game pausing logic has to happen
inside the promise function because when that
resolves the game level is over */
let gamePaused = false;
// function to determine if the game is paused or not
const isGamePaused = () => gamePaused;
/* function to alternately pause or resume the game
when the escape key is pressed */
const pauseOrResumeGame = (e) => {
if (e.key == 'Escape') {
gamePaused = !gamePaused;
}
};
/* wait for the game to resume
check resume status every 100 ms
once resumed, the animation function is called again */
const waitForResume = () => {
if (isGamePaused()) {
setTimeout(waitForResume, 100);
} else {
runAnimation(animate);
}
};
const animate = (time) => {
state = state.update(time, arrowKeys);
display.setState(state);
if (isGamePaused()) {
// game is paused, wait for it to resume
waitForResume();
// return false to stop the animation
return false;
} else if (state.status == "playing") {
return true;
} else if (ending > 0) {
ending -= time;
return true;
} else {
display.clear();
resolve(state.status);
// stop listening for the escape key
document.removeEventListener('keydown', pauseOrResumeGame);
// unregister control keys
arrowKeys.unregister();
return false;
}
};
// listen for the escape key to be pressed (to pause/resume game)
document.addEventListener('keydown', pauseOrResumeGame);
// register keys to control character
let arrowKeys = trackKeys(["ArrowLeft", "ArrowRight", "ArrowUp"]);
runAnimation(animate);
});
}
runGame(GAME_LEVELS, DOMDisplay);
</script>
</body>
@jonurry
Copy link
Author

jonurry commented Mar 28, 2018

16.2 Pausing The Game

Make it possible to pause (suspend) and unpause the game by pressing the Esc key.

This can be done by changing the runLevel function to use another keyboard event handler and interrupting or resuming the animation whenever the Esc key is hit.

The runAnimation interface may not look like it is suitable for this at first glance, but it is if you rearrange the way runLevel calls it.

When you have that working, there is something else you could try. The way we have been registering keyboard event handlers is somewhat problematic. The arrows object is currently a global binding, and its event handlers are kept around even when no game is running. You could say they leak out of our system. Extend trackKeys to provide a way to unregister its handlers, and then change runLevel to register its handlers when it starts and unregister them again when it is finished.

@jonurry
Copy link
Author

jonurry commented Mar 28, 2018

Hints

An animation can be interrupted by returning false from the function given to runAnimation. It can be continued by calling runAnimation again.

So we need to communicate the fact that we are pausing the game to the function given to runAnimation. For that, you can use a binding that both the event handler and that function have access to.

When finding a way to unregister the handlers registered by trackKeys, remember that the exact same function value that was passed to addEventListener must be passed to removeEventListener to successfully remove a handler. Thus, the handler function value created in trackKeys must be available to the code that unregisters the handlers.

You can add a property to the object returned by trackKeys, containing either that function value or a method that handles the unregistering directly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment