The Emacs's Gamegrid library #3

Introduction

What still needs to be shown is how to manage inputs from the player, and how to initialize the display, so that the buffer can show the graphics.

Since the former is easier to explain and also fairly quick to implement, I'll leave the display initialization last.

Terminating the game

When the player “dies” or explicitly wants to quit the current game, a number of steps must be taken: to begin with,
the game loop must be stopped.
However, as I said in a previous article, it's good practice to keep the buffer alive (burying it, at best) so that it's possible to start a
new game from there.

This is done by changing the keymap back to the “null” keymap, like this:

The keymap contains the minimum to play a game: a key to end the game (q), a key to pause game (p), a key to start a new
game while playing (n), and finally the keys to move the player character (w; a; s; d).

When ending the game (either with q or by meeting some condition defined by the game designer), the my-gamegrid-game-end-game function
stops the game loop (i.e. kills the timer started by my-gamegrid-game-start-game), changes the keymap to the one with just “new game”
and “bury buffer”, and finally saves the score to the file defined by my-gamegrid-game-score-file-name.

Having a score is not necessary: the example game I'm building in these articles don't use it, but it's a good idea to show how to do it nonetheless.

Since my-gamegrid-game-start-game calls my-gamegrid-game-reset-game, pressing n during the game is guaranteed to bring
it to its initial state, thus starting a new game.

Shortly my-gamegrid-game-pause-game will be explained.

How input works

When Gamegrid needs to update the game state, it does so by calling a function inside an Emacs timer. This is an important concept to remember when
processing player inputs.

The reason is, timers are not executed when Emacs is receiving inputs. As such, doing input processing directly inside the functions bound
to keys in the keymap
will block the game until every input has been processed.

This is poor user experience, and is particularily annoying in games with objects that “move on their own”, like the ball in Pong
(though the version of Pong inside Emacs is a bit special).

We can find the solution in other Gamegrid games: the input functions (those bound to keys) will push something into a global list. Then, the update
function will pop from this list and update the game accordingly.

This makes the input functions fast enough and the timer will not block because of them (of course, if Emacs is busy doing something else, it will block).

The unless check (and the setq at the end), are necessary: without them, holding down the key would push and infinite number of
conses into the global list.

Aside for potentially blocking Emacs, too many elements will move the player character too many times,
which is not what the player wants. With the checks, elements
are added only after the update function has been called.

Since the input functions merely push elements into the global list, pausing the game is as simple as preventing the update function from popping elements
from the list. As such, to pause the game all it's needed is:

The my-gamegrid-game-paused variable will then be checked in the update function.

Updating the game

In this example game, all there is to do is move a colored square around the enclosed room.

The input functions push a cons on the global list, which indicates where to move the square next: the car is the x coordinate, the cdr the y.
Each update, the values of the popped cons are added to the square current position.

So, let's first add the player to the room. It's just a quick addition to the end of my-gamegrid-game-init-buffer:

The first unless check tells wether or not it should pop an element from the list. If the game is paused, the current buffer
is different than the
game buffer, or the list is null, it will not pop from it.

Based on the type of game, the buffer check might not be needed, but it is required whenever the buffer contents need to be changed, so it should be placed
before any call to gamegrid-set-cell, if the game allows “background execution”.

The following code is fairly straightforward: the cons is popped and the new player position is calculated. gamegrid-get-cell is used to check if
the new position is not a wall. The function will return the “identifier” used with gamegrid-set-cell. Using a number allows for
fast comparisons with =.

If there are no walls, the buffer contents are modified so that the square is displayed at the new position. Then, variables are updated with the new value,
and setting my-gamegrid-game-moved to nil will allow the input functions to add new elements to the global list.

This is all that's needed to process the player's input. Now, all that's needed to explain is how to set up the display.