The Emacs's Gamegrid library #1


As many knows, the Emacs editor has, among other things, a number of games. The more notable ones are Tetris, Snake, and Pong.

These games are made using the same core library, gamegrid.el.

Unfortunately, the library doesn't come with a manual and many functions are not documented, so using it to make new games is fairly hard.

As such, I decided to write a number of articles in which I explain how the library works. Most of the things are “reverse engineered”, that is, their behaviour is guessed from reading the source, so they might not be 100% correct.

In this first article, I'll explain some core concepts and how to initialize the library.

What does Gamegrid do

The main purpose of the Gamegrid library is to allow games to easily handle graphics.

Since Emacs is a text editor (one which is meant to work even on non-graphical terminals), using this library means being able to not only create games, but also having support for both graphical and text-only terminals.

However, as the name suggests, this features are limited to grid-based games, which means elements can't overlap each other and they can be moved only by multiples of the grid size.

Besides graphics, Gamegrid provides a way to automatically update the game, so there is no need to manually program a game loop, just the update function. Also, it provides functions to update and save the player's score.

Initializing the library

While some can work on their own, most functions require a buffer to be initialized. Thus, to use the library, the first thing (after requireing the library) should be defining a variable holding this buffer. For example:

(require 'gamegrid)

(defconst my-gamegrid-game-buffer-name "Gamegrid Game")

Gamegrid bases most of its functions on buffer-local variables, so before calling a gamegrid to update the display it's wise to check that the current buffer is the correct buffer.

After defining the variable holding the game's buffer, it's a good idea to define an interactive function to start the game. This function would look like this:

(defun my-gamegrid-game ()
  "How to play the game should be placed in the docstring."
  (switch-to-buffer my-gamegrid-game-buffer-name)

The first thing it should do is, obviously, to make the game's buffer current. Then, by calling gamegrid-kill-timer, the “game loop” is stopped. This is done because a call to this function is considered a new game, so everything is reset.

my-gamegrid-game-mode is a major mode used to setup the proper keybindings and other special features. my-gamegrid-game-start-game will start the game.

The major mode does some important tasks, so the definition should be something like this:

(define-derived-mode my-gamegrid-game-mode special-mode "Gamegrid Game"
  "Mode for my Gamegrid Game."
  (add-hook 'kill-buffer-hook #'gamegrid-kill-timer nil t)
  (use-local-map my-gamegrid-game-null-map)
  (gamegrid-init (my-gamegrid-game-display-options)))

Of course, this is just the minimum, if needed it can call other functions.

The add-hook call is essential: without it, killing the buffer would have no effect on the game's progression. Basically the game would keep going even if it was supposed to be “turned off”.

The local map is particular: while not strictly required, it's a good idea to have two keymaps. One is the keymap used while playing, the other is a special keymap, used before actually starting the game, and when the game has ended.

The purpose is to let the player choose wether to actually start the game or keep the buffer opened for later. It's especially useful after “dying”, as it's possible to start a new game or kill/bury the buffer easily.

So, this “null map” is as simple as:

(defvar my-gamegrid-game-null-map
  (let ((map (make-sparse-keymap)))
    (define-key map (kbd "q") #'bury-buffer)
    (define-key map (kbd "n") #'my-gamegrid-game-start-game)
  "Gamegrid Game's menu keymap.")

The actual game's keymap is defined the same way, so there's no need to show it here.

The call to gamegrid-init will initialize the display, rather than the library itself. As such, it will be explained in a different article.