Introduction
For the last article of this series about gamegrid.el, finally the display initialization will be explained.
Setting up the display is pretty complex: to begin with, it uses lists structured in a certain way, which is not related to alists or plists, so building them needs some care.
Additionally, in some cases it uses symbols rather than lists, and other times it requires a vector of three elements.
This article is focused mostly on Emacs instances with support for colors and glyphs.
As of Emacs 26, the executable provided by default (either from the official website, or from a package manager) supports both of them, and the hardware targeted by Gamegrid that doesn't support colors or glyph isn't being manufactured anymore, so in general there shouldn't be any particular issue if some informations are missing.
Setting display informations for each displayable entity
To be able to show something in the buffer, Gamegrid needs to know what to do for each defined entity.
In the example built so far, the entities are my-gamegrid-game-empty, my-gamegrid-game-floor, my-gamegrid-game-wall and my-gamegrid-game-player.
Gamegrid will look at a vector (that is, the Elisp data type written using square brackets), of 256 elements and, for each non-nil element, use the informations stored at that index to show the entity.
256 is a fixed number, it can't be customized through a variable nor it can be changed with an advice function or the like. The only way would be to rewrite the relevant function to use a different value. Otherwise, a game can't have more than 256 displayable entities.
That said, the my-gamegrid-game-display-options
function that was used within the gamegrid-init
call, will look like this:
(defun my-gamegrid-game-display-options () "Return a vector with display informations." (let ((vec (make-vector 256 nil))) (dotimes (c 256) (aset vec c (cond ((= c my-gamegrid-game-empty) my-gamegrid-game-empty-options) ((= c my-gamegrid-game-floor) my-gamegrid-game-floor-options) ((= c my-gamegrid-game-wall) my-gamegrid-game-wall-options) ((= c my-gamegrid-game-player) my-gamegrid-game-player-options) (t '(nil nil nil))))) vec))
This function shows the true nature of the identifier used until now as argument to e.g. gamegrid-set-cell
: that number is the
index of the vector element with the display informations.
The name of the variables used in the cond
is worth a note: they contain “options” in their name because, as
said already, Gamegrid supports multiple configurations (with or without colors, etc.), so these variables offer multiple options to pick based on
which features Emacs support.
The t branch of the cond
is a special value: with that kind of list, if an invalid index is used, nothing will be shown.
It also provides a hint of the content of the “options” variables: a list of three elements.
Understanding the structure of a display option
This is the hardest part to understand (and to explain too) of the whole Gamegrid library.
As said earlier, the value must be a list of three elements. The simplest list is this:
(nil nil nil)
With this list, nothing will be shown.
“Nothing” is used in its literal sense: if a row of 4 elements contains one element with a (nil nil nil)
display option,
that row will display 3 elements.
To show a blank space (or something equivalent), the first element of the list must be a non-empty list, structured in a special way. In fact, this list is actually a list of lists.
This list must always contain a list with t as its first element, and a positive integer as its second element. The integer is actually a character, so using e.g. ?愛 is allowed.
This list is the “fallback” list. If Gamegrid can't find a suitable option, the entity will be displayed as the character provided. With just this it's actually possible to write the display options for the my-gamegrid-game-empty entity. This is because the “empty” cell in this case is used for uninitialized cells (initialized cells are “floor” or “wall” cells).
(defconst my-gamegrid-game-empty-options '(((t 32)) nil nil))
32 is the simple blank space. The number is more readable than ? . This way, uninitalized cells will be displayed using the space character.
Emacs, however, supports more than just characters, and, in fact, Gamegrid allows to use full-fledged images to display entities.
To do this, the list must contain a list with glyph as the first element. The second element can take two different forms, but right now only one will be explained, the symbol colorize. The other form will be explained later.
With “glyph”, Gamegrid refers to anything that is not a character, so it's actually a subset of an Emacs's glyph. However, in practice this simply means pictures (e.g. PNG).
By default, Gamegrid comes with a picture of a tridimensional block encoded in the XPM format. One of the features of this format is that it's possible to define the color of some pixels outside of the encoding, meaning that it's possible to change the colors of a picture at any time (this is not possible with other formats).
Using this feature, Gamegrid can recolor this block so that each entity looks different. This is the trick used in Snake, Pong, etc.
By using the colorize symbol as the second element of the glyph list, Gamegrid acknoweledges that the entity will be displayed using the default XPM image.
It's important to note that the t list must always come last, otherwise Gamegrid will always show a character regardless of other options.
The list can also have a third type of list, in which the first element is emacs-tty. However, it will be explained later.
Since to display walls and the floor nothing special is needed, let's just use the default image for them. As such, the first element of their display options would look like this:
((glyph colorize) (t 32))
for the floor, and
((glyph colorize) (t ?+))
for the walls. The other two elements will be written after explaining what they mean.
With this setup, if glyphs are not available, the floor would be “invisible” (but still there, since it's a space character), and the walls would look like plus signs.
Normally, if Emacs can't display glyphs it means that it's running in a non-graphical environment (like the Linux virtual tty), but Gamegrid has two variables, gamegrid-use-glyphs and gamegrid-use-color, that will enable or disable certain features.
These variables are buffer-local, so there is no easy way to change them before starting a game (unless you change the game's code).
The second element of the options list deals exactly with the case when glyphs or colors are not available.
This element is, again, a list of list. These list always have two elements, specifying the face to use when a certain condition is met.
The available faces are set by Gamegrid, and can't be changed.
The first element of a face list specifies when to use the face. It must be a symbol indicating one of the available display types. These types are: color-x, mono-x, color-tty, mono-tty.
glyph and emacs-tty are display types too, but they are kind of special and are not used here.
These elements are listed from the more featureful to the least featureful.
color-x means Emacs is running in a graphical environment with support for colors, but not for glyphs.
mono-x is a graphical environment with no support for color or glyph. In the end, this means that Gamegrid will use the characters specified in the t list.
color-tty means that Emacs is running in a non-graphical environment (as the name suggests, the typical case is a tty) that still can support colors. As of 2018 pretty much all the non-graphical terminals support colors by default, so if Gamegrid doesn't pick this case it's either because gamegrid-use-color is nil or the player's environment has been specifically configured to advertise the lack of colors.
mono-tty is supposed to be the monochromatic alternative to color-tty, but the gamegrid.el
source code shows that
to get this display type, one needs a non-nil return value from display-multi-font-p
,
which is an alias of display-graphic-p
. Maybe it's a bug, maybe it's some legacy code that was never updated, but the result is that as of
now, mono-tty is never used.
There is one last display type, emacs-tty. It was not included in the list as it's used only in the first element, alongside glyph. In practice, it's simply an override of the t list, as emacs-tty is valid only when there are no colors, no glyphs, and Emacs is running in a non-graphical environment.
It might look like a replacement of mono-tty, but emacs-tty uses characters, while mono-tty is supposed to use faces.
Each of these types (except emacs-tty) need their own list, it's not possible to specify more than one face in a single list.
The second element is a symbol, and must be one of these: color-x, grid-x, mono-x, color-tty, mono-tty.
Aside from grid-x, the others make sense only when the first element of the list is the same symbol. grid-x is a special face that can be used in monochrome faces when defining walls and the like.
color-x and color-tty can be customized, as they are used when colors are available.
After this explanation, the second element of each entities in the end would look the same, that is:
((color-x color-x) (mono-x mono-x) (color-tty color-tty))
The last element of the options list is the color specification. Through this list, glyphs and faces supporting colors can be customized.
The list has two elements (as only two display types support colors), but they are structured differently.
The first list specify the color to use for colorize and the color-x face, while the second list is for the color-tty face.
Since colorize list supports virtually every colors (the limit is the hardware), the specification is done through a vector of three elements, in which each element is a color as a floating point number going from 0 to 1. The order is red, green, blue.
The color-tty, instead, uses a string. The content of the string is the name of one of the supported colors, e.g. "yellow"
.
Generally these colors are those defined by ANSI escape values, but in name rather than the actual escape code.
The first element of the colorize list is another list: (glyph color-x)
. For color-tty, instead, the first
element is simply color-tty.
So, it's now possible to fully define the display options for the floor and the walls:
(defconst my-gamegrid-game-floor-options '(((glyph colorize) (t 32)) ((color-x color-x) (mono-x mono-x) (color-tty color-tty)) (((glyph color-x) [0 0 0]) (color-tty "black")))) (defconst my-gamegrid-game-wall-options '(((glyph colorize) (t ?+)) ((color-x color-x) (mono-x mono-x) (color-tty color-tty)) (((glyph color-x) [0.5 0.5 0.5]) (color-tty "gray"))))
In the floor options, the color vector has 0 for each color. That way, both the XPM block and the colored face will be fully black. The same for the color-tty case.
The wall, instead, will be gray. Since the block was drawn to look tridimensional, the values in the vector will be automatically made darker or lighter based on the side they are used on.
Yet, with this it's possible to use only colored blocks, when glyphs are available. Many games actually require for entities to be different from blocks.
For example, a puzzle game where the goal is to move blocks, might want to display the player as a human being, and maybe have enemies like dragons or goblins too.
Gamegrid can do that. However, as the source code tells us, this is considered an experimental feature. In my tests sometimes I would get strange results, but everything has been fairly non-deterministic, so it's hard to say what the pitfalls are.
Still, if things are kept simple, this feature works and it's possible to display custom graphics.
The range of picture formats supported by Gamegrid is the same supported by Emacs. So, if Emacs can display PNG images, so does Gamegrid.
To use a custom image, the colorize symbol must be substituted with a list. This list is a list of image specifications to be used with
find-image
, as defined by the Emacs manual.
As such, to use a custom picture for the player, the display options would be something like:
(defconst my-gamegrid-game-player-options '(((glyph ((:type xpm :file "my-player.xpm"))) (t ?P)) ((color-x color-x) (mono-x mono-x) (color-tty color-tty)) (((glyph color-x) [0.9 0.3 0.7]) (color-tty "yellow"))))
This way, the player will be displayed using the XPM image called “my-player.xmp”.
Conclusion
Now that how to initialize the display has been explained too, this series on gamegrid.el
has ended.
The display has been quite the feat to explain. Hopefully things are clear.
I also hope to see games written using this library, since it offers some interesting features.