game_ui
This chapter gives a detailed discussion of the interface that each back end must implement.
At the top level, each back end source file exports a single global symbol, which is a const struct game
containing a large number of function pointers and a small amount of constant data. This structure is called by different names depending on what kind of platform the puzzle set is being compiled on:
thegame
’; the front end refers directly to this name, so that compiling the same front end module against a different back end module builds a different puzzle.
list.c
(constructed automatically by the same Perl script that builds the Makefile
s) which contains a complete list of those game structures.
On the latter type of platform, source files may assume that the preprocessor symbol COMBINED
has been defined. Thus, the usual code to declare the game structure looks something like this:
#ifdef COMBINED
#define thegame net /* or whatever this game is called */
#endif
const struct game thegame = {
/* lots of structure initialisation in here */
};
Game back ends must also internally define a number of data structures, for storing their various persistent state. This chapter will first discuss the nature and use of those structures, and then go on to give details of every element of the game structure.
Each game is required to define four separate data structures. This section discusses each one and suggests what sorts of things need to be put in it.
game_params
The game_params
structure contains anything which affects the automatic generation of new puzzles. So if puzzle generation is parametrised in any way, those parameters need to be stored in game_params
.
Most puzzles currently in this collection are played on a grid of squares, meaning that the most obvious parameter is the grid size. Many puzzles have additional parameters; for example, Mines allows you to control the number of mines in the grid independently of its size, Net can be wrapping or non-wrapping, Solo has difficulty levels and symmetry settings, and so on.
A simple rule for deciding whether a data item needs to go in game_params
is: would the user expect to be able to control this data item from either the preset-game-types menu or the ‘Custom’ game type configuration? If so, it's part of game_params
.
game_params
structures are permitted to contain pointers to subsidiary data if they need to. The back end is required to provide functions to create and destroy game_params
, and those functions can allocate and free additional memory if necessary. (It has not yet been necessary to do this in any puzzle so far, but the capability is there just in case.)
game_params
is also the only structure which the game's compute_size()
function may refer to; this means that any aspect of the game which affects the size of the window it needs to be drawn in must be stored in game_params
. In particular, this imposes the fundamental limitation that random game generation may not have a random effect on the window size: game generation algorithms are constrained to work by starting from the grid size rather than generating it as an emergent phenomenon. (Although this is a restriction in theory, it has not yet seemed to be a problem.)
game_state
While the user is actually playing a puzzle, the game_state
structure stores all the data corresponding to the current state of play.
The mid-end keeps game_state
s in a list, and adds to the list every time the player makes a move; the Undo and Redo functions step back and forth through that list.
Therefore, a good means of deciding whether a data item needs to go in game_state
is: would a player expect that data item to be restored on undo? If so, put it in game_state
, and this will automatically happen without you having to lift a finger. If not – for example, the deaths counter in Mines is precisely something that does not want to be reset to its previous state on an undo – then you might have found a data item that needs to go in game_ui
instead.
During play, game_state
s are often passed around without an accompanying game_params
structure. Therefore, any information in game_params
which is important during play (such as the grid size) must be duplicated within the game_state
. One simple method of doing this is to have the game_state
structure contain a game_params
structure as one of its members, although this isn't obligatory if you prefer to do it another way.
game_drawstate
game_drawstate
carries persistent state relating to the current graphical contents of the puzzle window. The same game_drawstate
is passed to every call to the game redraw function, so that it can remember what it has already drawn and what needs redrawing.
A typical use for a game_drawstate
is to have an array mirroring the array of grid squares in the game_state
; then every time the redraw function was passed a game_state
, it would loop over all the squares, and physically redraw any whose description in the game_state
(i.e. what the square needs to look like when the redraw is completed) did not match its description in the game_drawstate
(i.e. what the square currently looks like).
game_drawstate
is occasionally completely torn down and reconstructed by the mid-end, if the user somehow forces a full redraw. Therefore, no data should be stored in game_drawstate
which is not related to the state of the puzzle window, because it might be unexpectedly destroyed.
The back end provides functions to create and destroy game_drawstate
, which means it can contain pointers to subsidiary allocated data if it needs to. A common thing to want to allocate in a game_drawstate
is a blitter
; see section 3.1.13 for more on this subject.
game_ui
game_ui
contains whatever doesn't fit into the above three structures!
A new game_ui
is created when the user begins playing a new instance of a puzzle (i.e. during ‘New Game’ or after entering a game ID etc). It persists until the user finishes playing that game and begins another one (or closes the window); in particular, ‘Restart Game’ does not destroy the game_ui
.
game_ui
is useful for implementing user-interface state which is not part of game_state
. Common examples are keyboard control (you wouldn't want to have to separately Undo through every cursor motion) and mouse dragging. See section 6.3.2 and section 6.3.3, respectively, for more details.
Another use for game_ui
is to store highly persistent data such as the Mines death counter. This is conceptually rather different: where the Net cursor position was not important enough to preserve for the player to restore by Undo, the Mines death counter is too important to permit the player to revert by Undo!
A final use for game_ui
is to pass information to the redraw function about recent changes to the game state. This is used in Mines, for example, to indicate whether a requested ‘flash’ should be a white flash for victory or a red flash for defeat; see section 6.3.5.
In this section I begin to discuss each individual element in the back end structure. To begin with, here are some simple self-contained data elements.
name
const char *name;
This is a simple ASCII string giving the name of the puzzle. This name will be used in window titles, in game selection menus on monolithic platforms, and anywhere else that the front end needs to know the name of a game.
winhelp_topic
const char *winhelp_topic;
This member is used on Windows only, to provide online help. Although the Windows front end provides a separate binary for each puzzle, it has a single monolithic help file; so when a user selects ‘Help’ from the menu, the program needs to open the help file and jump to the chapter describing that particular puzzle.
Therefore, each chapter in puzzles.but
is labelled with a help topic name, similar to this:
\cfg{winhelp-topic}{games.net}
And then the corresponding game back end encodes the topic string (here ‘games.net
’) in the winhelp_topic
element of the game structure.
In this section I present the various functions which handle the game_params
structure.
default_params()
game_params *(*default_params)(void);
This function allocates a new game_params
structure, fills it with the default values, and returns a pointer to it.
fetch_preset()
int (*fetch_preset)(int i, char **name, game_params **params);
This function is used to populate the ‘Type’ menu, which provides a list of conveniently accessible preset parameters for most games.
The function is called with i
equal to the index of the preset required (numbering from zero). It returns FALSE
if that preset does not exist (if i
is less than zero or greater than the largest preset index). Otherwise, it sets *params
to point at a newly allocated game_params
structure containing the preset information, sets *name
to point at a newly allocated C string containing the preset title (to go on the ‘Type’ menu), and returns TRUE
.
If the game does not wish to support any presets at all, this function is permitted to return FALSE
always.
encode_params()
char *(*encode_params)(const game_params *params, int full);
The job of this function is to take a game_params
, and encode it in a string form for use in game IDs. The return value must be a newly allocated C string, and must not contain a colon or a hash (since those characters are used to mark the end of the parameter section in a game ID).
Ideally, it should also not contain any other potentially controversial punctuation; bear in mind when designing a string parameter format that it will probably be used on both Windows and Unix command lines under a variety of exciting shell quoting and metacharacter rules. Sticking entirely to alphanumerics is the safest thing; if you really need punctuation, you can probably get away with commas, periods or underscores without causing anybody any major inconvenience. If you venture far beyond that, you're likely to irritate somebody.
(At the time of writing this, all existing games have purely alphanumeric string parameter formats. Usually these involve a letter denoting a parameter, followed optionally by a number giving the value of that parameter, with a few mandatory parts at the beginning such as numeric width and height separated by ‘x
’.)
If the full
parameter is TRUE
, this function should encode absolutely everything in the game_params
, such that a subsequent call to decode_params()
(section 2.3.4) will yield an identical structure. If full
is FALSE
, however, you should leave out anything which is not necessary to describe a specific puzzle instance, i.e. anything which only takes effect when a new puzzle is generated. For example, the Solo game_params
includes a difficulty rating used when constructing new puzzles; but a Solo game ID need not explicitly include the difficulty, since to describe a puzzle once generated it's sufficient to give the grid dimensions and the location and contents of the clue squares. (Indeed, one might very easily type in a puzzle out of a newspaper without knowing what its difficulty level is in Solo's terminology.) Therefore, Solo's encode_params()
only encodes the difficulty level if full
is set.
decode_params()
void (*decode_params)(game_params *params, char const *string);
This function is the inverse of encode_params()
(section 2.3.3). It parses the supplied string and fills in the supplied game_params
structure. Note that the structure will already have been allocated: this function is not expected to create a new game_params
, but to modify an existing one.
This function can receive a string which only encodes a subset of the parameters. The most obvious way in which this can happen is if the string was constructed by encode_params()
with its full
parameter set to FALSE
; however, it could also happen if the user typed in a parameter set manually and missed something out. Be prepared to deal with a wide range of possibilities.
When dealing with a parameter which is not specified in the input string, what to do requires a judgment call on the part of the programmer. Sometimes it makes sense to adjust other parameters to bring them into line with the new ones. In Mines, for example, you would probably not want to keep the same mine count if the user dropped the grid size and didn't specify one, since you might easily end up with more mines than would actually fit in the grid! On the other hand, sometimes it makes sense to leave the parameter alone: a Solo player might reasonably expect to be able to configure size and difficulty independently of one another.
This function currently has no direct means of returning an error if the string cannot be parsed at all. However, the returned game_params
is almost always subsequently passed to validate_params()
(section 2.3.10), so if you really want to signal parse errors, you could always have a char *
in your parameters structure which stored an error message, and have validate_params()
return it if it is non-NULL
.
free_params()
void (*free_params)(game_params *params);
This function frees a game_params
structure, and any subsidiary allocations contained within it.
dup_params()
game_params *(*dup_params)(const game_params *params);
This function allocates a new game_params
structure and initialises it with an exact copy of the information in the one provided as input. It returns a pointer to the new duplicate.
can_configure
int can_configure;
This boolean data element is set to TRUE
if the back end supports custom parameter configuration via a dialog box. If it is TRUE
, then the functions configure()
and custom_params()
are expected to work. See section 2.3.8 and section 2.3.9 for more details.
configure()
config_item *(*configure)(const game_params *params);
This function is called when the user requests a dialog box for custom parameter configuration. It returns a newly allocated array of config_item
structures, describing the GUI elements required in the dialog box. The array should have one more element than the number of controls, since it is terminated with a C_END
marker (see below). Each array element describes the control together with its initial value; the front end will modify the value fields and return the updated array to custom_params()
(see section 2.3.9).
The config_item
structure contains the following elements:
char *name;
int type;
char *sval;
int ival;
name
is an ASCII string giving the textual label for a GUI control. It is not expected to be dynamically allocated.
type
contains one of a small number of enum
values defining what type of control is being described. The meaning of the sval
and ival
fields depends on the value in type
. The valid values are:
C_STRING
ival
is unused, and sval
contains a dynamically allocated string representing the contents of the input box.
C_BOOLEAN
sval
is unused, and ival
is TRUE
or FALSE
.
C_CHOICES
sval
contains a list of strings describing the choices; the very first character of sval
is used as a delimiter when processing the rest (so that the strings ‘:zero:one:two
’, ‘!zero!one!two
’ and ‘xzeroxonextwo
’ all define a three-element list containing ‘zero
’, ‘one
’ and ‘two
’). ival
contains the index of the currently selected element, numbering from zero (so that in the above example, 0 would mean ‘zero
’ and 2 would mean ‘two
’).
Note that for this control type, sval
is not dynamically allocated, whereas it was for C_STRING
.
C_END
config_item
s. All other fields are unused.
The array returned from this function is expected to have filled in the initial values of all the controls according to the input game_params
structure.
If the game's can_configure
flag is set to FALSE
, this function is never called and need not do anything at all.
custom_params()
game_params *(*custom_params)(const config_item *cfg);
This function is the counterpart to configure()
(section 2.3.8). It receives as input an array of config_item
s which was originally created by configure()
, but in which the control values have since been changed in accordance with user input. Its function is to read the new values out of the controls and return a newly allocated game_params
structure representing the user's chosen parameter set.
(The front end will have modified the controls' values, but there will still always be the same set of controls, in the same order, as provided by configure()
. It is not necessary to check the name
and type
fields, although you could use assert()
if you were feeling energetic.)
This function is not expected to (and indeed must not) free the input config_item
array. (If the parameters fail to validate, the dialog box will stay open.)
If the game's can_configure
flag is set to FALSE
, this function is never called and need not do anything at all.
validate_params()
char *(*validate_params)(const game_params *params, int full);
This function takes a game_params
structure as input, and checks that the parameters described in it fall within sensible limits. (At the very least, grid dimensions should almost certainly be strictly positive, for example.)
Return value is NULL
if no problems were found, or alternatively a (non-dynamically-allocated) ASCII string describing the error in human-readable form.
If the full
parameter is set, full validation should be performed: any set of parameters which would not permit generation of a sensible puzzle should be faulted. If full
is not set, the implication is that these parameters are not going to be used for generating a puzzle; so parameters which can't even sensibly describe a valid puzzle should still be faulted, but parameters which only affect puzzle generation should not be.
(The full
option makes a difference when parameter combinations are non-orthogonal. For example, Net has a boolean option controlling whether it enforces a unique solution; it turns out that it's impossible to generate a uniquely soluble puzzle with wrapping walls and width 2, so validate_params()
will complain if you ask for one. However, if the user had just been playing a unique wrapping puzzle of a more sensible width, and then pastes in a game ID acquired from somebody else which happens to describe a non-unique wrapping width-2 puzzle, then validate_params()
will be passed a game_params
containing the width and wrapping settings from the new game ID and the uniqueness setting from the old one. This would be faulted, if it weren't for the fact that full
is not set during this call, so Net ignores the inconsistency. The resulting game_params
is never subsequently used to generate a puzzle; this is a promise made by the mid-end when it asks for a non-full validation.)
In this section I present the functions that deal with a textual description of a puzzle, i.e. the part that comes after the colon in a descriptive-format game ID.
new_desc()
char *(*new_desc)(const game_params *params, random_state *rs,
char **aux, int interactive);
This function is where all the really hard work gets done. This is the function whose job is to randomly generate a new puzzle, ensuring solubility and uniqueness as appropriate.
As input it is given a game_params
structure and a random state (see section 5.1 for the random number API). It must invent a puzzle instance, encode it in string form, and return a dynamically allocated C string containing that encoding.
Additionally, it may return a second dynamically allocated string in *aux
. (If it doesn't want to, then it can leave that parameter completely alone; it isn't required to set it to NULL
, although doing so is harmless.) That string, if present, will be passed to solve()
(section 2.7.4) later on; so if the puzzle is generated in such a way that a solution is known, then information about that solution can be saved in *aux
for solve()
to use.
The interactive
parameter should be ignored by almost all puzzles. Its purpose is to distinguish between generating a puzzle within a GUI context for immediate play, and generating a puzzle in a command-line context for saving to be played later. The only puzzle that currently uses this distinction (and, I fervently hope, the only one which will ever need to use it) is Mines, which chooses a random first-click location when generating puzzles non-interactively, but which waits for the user to place the first click when interactive. If you think you have come up with another puzzle which needs to make use of this parameter, please think for at least ten minutes about whether there is any alternative!
Note that game description strings are not required to contain an encoding of parameters such as grid size; a game description is never separated from the game_params
it was generated with, so any information contained in that structure need not be encoded again in the game description.
validate_desc()
char *(*validate_desc)(const game_params *params, const char *desc);
This function is given a game description, and its job is to validate that it describes a puzzle which makes sense.
To some extent it's up to the user exactly how far they take the phrase ‘makes sense’; there are no particularly strict rules about how hard the user is permitted to shoot themself in the foot when typing in a bogus game description by hand. (For example, Rectangles will not verify that the sum of all the numbers in the grid equals the grid's area. So a user could enter a puzzle which was provably not soluble, and the program wouldn't complain; there just wouldn't happen to be any sequence of moves which solved it.)
The one non-negotiable criterion is that any game description which makes it through validate_desc()
must not subsequently cause a crash or an assertion failure when fed to new_game()
and thence to the rest of the back end.
The return value is NULL
on success, or a non-dynamically-allocated C string containing an error message.
new_game()
game_state *(*new_game)(midend *me, const game_params *params,
const char *desc);
This function takes a game description as input, together with its accompanying game_params
, and constructs a game_state
describing the initial state of the puzzle. It returns a newly allocated game_state
structure.
Almost all puzzles should ignore the me
parameter. It is required by Mines, which needs it for later passing to midend_supersede_game_desc()
(see section 2.11.2) once the user has placed the first click. I fervently hope that no other puzzle will be awkward enough to require it, so everybody else should ignore it. As with the interactive
parameter in new_desc()
(section 2.4.1), if you think you have a reason to need this parameter, please try very hard to think of an alternative approach!
This section describes the functions which create and destroy game_state
structures.
(Well, except new_game()
, which is in section 2.4.3 instead of under here; but it deals with game descriptions and game states and it had to go in one section or the other.)
dup_game()
game_state *(*dup_game)(const game_state *state);
This function allocates a new game_state
structure and initialises it with an exact copy of the information in the one provided as input. It returns a pointer to the new duplicate.
free_game()
void (*free_game)(game_state *state);
This function frees a game_state
structure, and any subsidiary allocations contained within it.
game_ui
new_ui()
game_ui *(*new_ui)(const game_state *state);
This function allocates and returns a new game_ui
structure for playing a particular puzzle. It is passed a pointer to the initial game_state
, in case it needs to refer to that when setting up the initial values for the new game.
free_ui()
void (*free_ui)(game_ui *ui);
This function frees a game_ui
structure, and any subsidiary allocations contained within it.
encode_ui()
char *(*encode_ui)(const game_ui *ui);
This function encodes any important data in a game_ui
structure in string form. It is only called when saving a half-finished game to a file.
It should be used sparingly. Almost all data in a game_ui
is not important enough to save. The location of the keyboard-controlled cursor, for example, can be reset to a default position on reloading the game without impacting the user experience. If the user should somehow manage to save a game while a mouse drag was in progress, then discarding that mouse drag would be an outright feature.
A typical thing that would be worth encoding in this function is the Mines death counter: it's in the game_ui
rather than the game_state
because it's too important to allow the user to revert it by using Undo, and therefore it's also too important to allow the user to revert it by saving and reloading. (Of course, the user could edit the save file by hand... But if the user is that determined to cheat, they could just as easily modify the game's source.)
decode_ui()
void (*decode_ui)(game_ui *ui, const char *encoding);
This function parses a string previously output by encode_ui()
, and writes the decoded data back into the provided game_ui
structure.
changed_state()
void (*changed_state)(game_ui *ui, const game_state *oldstate,
const game_state *newstate);
This function is called by the mid-end whenever the current game state changes, for any reason. Those reasons include:
interpret_move()
and execute_move()
solve()
and execute_move()
The job of changed_state()
is to update the game_ui
for consistency with the new game state, if any update is necessary. For example, Same Game stores data about the currently selected tile group in its game_ui
, and this data is intrinsically related to the game state it was derived from. So it's very likely to become invalid when the game state changes; thus, Same Game's changed_state()
function clears the current selection whenever it is called.
When anim_length()
or flash_length()
are called, you can be sure that there has been a previous call to changed_state()
. So changed_state()
can set up data in the game_ui
which will be read by anim_length()
and flash_length()
, and those functions will not have to worry about being called without the data having been initialised.
This section describes the functions which actually make moves in the game: that is, the functions which process user input and end up producing new game_state
s.
interpret_move()
char *(*interpret_move)(const game_state *state, game_ui *ui,
const game_drawstate *ds,
int x, int y, int button);
This function receives user input and processes it. Its input parameters are the current game_state
, the current game_ui
and the current game_drawstate
, plus details of the input event. button
is either an ASCII value or a special code (listed below) indicating an arrow or function key or a mouse event; when button
is a mouse event, x
and y
contain the pixel coordinates of the mouse pointer relative to the top left of the puzzle's drawing area.
(The pointer to the game_drawstate
is marked const
, because interpret_move
should not write to it. The normal use of that pointer will be to read the game's tile size parameter in order to divide mouse coordinates by it.)
interpret_move()
may return in three different ways:
NULL
indicates that no action whatsoever occurred in response to the input event; the puzzle was not interested in it at all.
""
) indicates that the input event has resulted in a change being made to the game_ui
which will require a redraw of the game window, but that no actual move was made (i.e. no new game_state
needs to be created).
game_state
must be created. However, instead of actually constructing a new game_state
itself, this function is required to return a string description of the details of the move. This string will be passed to execute_move()
(section 2.7.2) to actually create the new game_state
. (Encoding moves as strings in this way means that the mid-end can keep the strings as well as the game states, and the strings can be written to disk when saving the game and fed to execute_move()
again on reloading.)
The return value from interpret_move()
is expected to be dynamically allocated if and only if it is not either NULL
or the empty string.
After this function is called, the back end is permitted to rely on some subsequent operations happening in sequence:
execute_move()
will be called to convert this move description into a new game_state
changed_state()
will be called with the new game_state
.
This means that if interpret_move()
needs to do updates to the game_ui
which are easier to perform by referring to the new game_state
, it can safely leave them to be done in changed_state()
and not worry about them failing to happen.
(Note, however, that execute_move()
may also be called in other circumstances. It is only interpret_move()
which can rely on a subsequent call to changed_state()
.)
The special key codes supported by this function are:
LEFT_BUTTON
, MIDDLE_BUTTON
, RIGHT_BUTTON
LEFT_DRAG
, MIDDLE_DRAG
, RIGHT_DRAG
LEFT_RELEASE
, MIDDLE_RELEASE
, RIGHT_RELEASE
CURSOR_UP
, CURSOR_DOWN
, CURSOR_LEFT
, CURSOR_RIGHT
CURSOR_SELECT
In addition, there are some modifiers which can be bitwise-ORed into the button
parameter:
MOD_CTRL
, MOD_SHFT
MOD_NUM_KEYPAD
MOD_MASK
~MOD_MASK
to strip all the modifiers off any input value.
execute_move()
game_state *(*execute_move)(const game_state *state, char *move);
This function takes an input game_state
and a move string as output from interpret_move()
. It returns a newly allocated game_state
which contains the result of applying the specified move to the input game state.
This function may return NULL
if it cannot parse the move string (and this is definitely preferable to crashing or failing an assertion, since one way this can happen is if loading a corrupt save file). However, it must not return NULL
for any move string that really was output from interpret_move()
: this is punishable by assertion failure in the mid-end.
can_solve
int can_solve;
This boolean field is set to TRUE
if the game's solve()
function does something. If it's set to FALSE
, the game will not even offer the ‘Solve’ menu option.
solve()
char *(*solve)(const game_state *orig, const game_state *curr,
const char *aux, char **error);
This function is called when the user selects the ‘Solve’ option from the menu.
It is passed two input game states: orig
is the game state from the very start of the puzzle, and curr
is the current one. (Different games find one or other or both of these convenient.) It is also passed the aux
string saved by new_desc()
(section 2.4.1), in case that encodes important information needed to provide the solution.
If this function is unable to produce a solution (perhaps, for example, the game has no in-built solver so it can only solve puzzles it invented internally and has an aux
string for) then it may return NULL
. If it does this, it must also set *error
to an error message to be presented to the user (such as ‘Solution not known for this puzzle’); that error message is not expected to be dynamically allocated.
If this function does produce a solution, it returns a move string suitable for feeding to execute_move()
(section 2.7.2). Like a (non-empty) string returned from interpret_move()
, the returned string should be dynamically allocated.
This section discusses the back end functions that deal with drawing.
new_drawstate()
game_drawstate *(*new_drawstate)(drawing *dr,
const game_state *state);
This function allocates and returns a new game_drawstate
structure for drawing a particular puzzle. It is passed a pointer to a game_state
, in case it needs to refer to that when setting up any initial data.
This function may not rely on the puzzle having been newly started; a new draw state can be constructed at any time if the front end requests a forced redraw. For games like Pattern, in which initial game states are much simpler than general ones, this might be important to keep in mind.
The parameter dr
is a drawing object (see chapter 3) which the function might need to use to allocate blitters. (However, this isn't recommended; it's usually more sensible to wait to allocate a blitter until set_size()
is called, because that way you can tailor it to the scale at which the puzzle is being drawn.)
free_drawstate()
void (*free_drawstate)(drawing *dr, game_drawstate *ds);
This function frees a game_drawstate
structure, and any subsidiary allocations contained within it.
The parameter dr
is a drawing object (see chapter 3), which might be required if you are freeing a blitter.
preferred_tilesize
int preferred_tilesize;
Each game is required to define a single integer parameter which expresses, in some sense, the scale at which it is drawn. This is described in the APIs as ‘tilesize
’, since most puzzles are on a square (or possibly triangular or hexagonal) grid and hence a sensible interpretation of this parameter is to define it as the size of one grid tile in pixels; however, there's no actual requirement that the ‘tile size’ be proportional to the game window size. Window size is required to increase monotonically with ‘tile size’, however.
The data element preferred_tilesize
indicates the tile size which should be used in the absence of a good reason to do otherwise (such as the screen being too small, or the user explicitly requesting a resize if that ever gets implemented).
compute_size()
void (*compute_size)(const game_params *params, int tilesize,
int *x, int *y);
This function is passed a game_params
structure and a tile size. It returns, in *x
and *y
, the size in pixels of the drawing area that would be required to render a puzzle with those parameters at that tile size.
set_size()
void (*set_size)(drawing *dr, game_drawstate *ds,
const game_params *params, int tilesize);
This function is responsible for setting up a game_drawstate
to draw at a given tile size. Typically this will simply involve copying the supplied tilesize
parameter into a tilesize
field inside the draw state; for some more complex games it might also involve setting up other dimension fields, or possibly allocating a blitter (see section 3.1.13).
The parameter dr
is a drawing object (see chapter 3), which is required if a blitter needs to be allocated.
Back ends may assume (and may enforce by assertion) that this function will be called at most once for any game_drawstate
. If a puzzle needs to be redrawn at a different size, the mid-end will create a fresh drawstate.
colours()
float *(*colours)(frontend *fe, int *ncolours);
This function is responsible for telling the front end what colours the puzzle will need to draw itself.
It returns the number of colours required in *ncolours
, and the return value from the function itself is a dynamically allocated array of three times that many float
s, containing the red, green and blue components of each colour respectively as numbers in the range [0,1].
The second parameter passed to this function is a front end handle. The only things it is permitted to do with this handle are to call the front-end function called frontend_default_colour()
(see section 4.40) or the utility function called game_mkhighlight()
(see section 5.4.7). (The latter is a wrapper on the former, so front end implementors only need to provide frontend_default_colour()
.) This allows colours()
to take local configuration into account when deciding on its own colour allocations. Most games use the front end's default colour as their background, apart from a few which depend on drawing relief highlights so they adjust the background colour if it's too light for highlights to show up against it.
Note that the colours returned from this function are for drawing, not for printing. Printing has an entirely different colour allocation policy.
anim_length()
float (*anim_length)(const game_state *oldstate,
const game_state *newstate,
int dir, game_ui *ui);
This function is called when a move is made, undone or redone. It is given the old and the new game_state
, and its job is to decide whether the transition between the two needs to be animated or can be instant.
oldstate
is the state that was current until this call; newstate
is the state that will be current after it. dir
specifies the chronological order of those states: if it is positive, then the transition is the result of a move or a redo (and so newstate
is the later of the two moves), whereas if it is negative then the transition is the result of an undo (so that newstate
is the earlier move).
If this function decides the transition should be animated, it returns the desired length of the animation in seconds. If not, it returns zero.
State changes as a result of a Restart operation are never animated; the mid-end will handle them internally and never consult this function at all. State changes as a result of Solve operations are also not animated by default, although you can change this for a particular game by setting a flag in flags
(section 2.10.7).
The function is also passed a pointer to the local game_ui
. It may refer to information in here to help with its decision (see section 6.3.7 for an example of this), and/or it may write information about the nature of the animation which will be read later by redraw()
.
When this function is called, it may rely on changed_state()
having been called previously, so if anim_length()
needs to refer to information in the game_ui
, then changed_state()
is a reliable place to have set that information up.
Move animations do not inhibit further input events. If the user continues playing before a move animation is complete, the animation will be abandoned and the display will jump straight to the final state.
flash_length()
float (*flash_length)(const game_state *oldstate,
const game_state *newstate,
int dir, game_ui *ui);
This function is called when a move is completed. (‘Completed’ means that not only has the move been made, but any animation which accompanied it has finished.) It decides whether the transition from oldstate
to newstate
merits a ‘flash’.
A flash is much like a move animation, but it is not interrupted by further user interface activity; it runs to completion in parallel with whatever else might be going on on the display. The only thing which will rush a flash to completion is another flash.
The purpose of flashes is to indicate that the game has been completed. They were introduced as a separate concept from move animations because of Net: the habit of most Net players (and certainly me) is to rotate a tile into place and immediately lock it, then move on to another tile. When you make your last move, at the instant the final tile is rotated into place the screen starts to flash to indicate victory – but if you then press the lock button out of habit, then the move animation is cancelled, and the victory flash does not complete. (And if you don't press the lock button, the completed grid will look untidy because there will be one unlocked square.) Therefore, I introduced a specific concept of a ‘flash’ which is separate from a move animation and can proceed in parallel with move animations and any other display activity, so that the victory flash in Net is not cancelled by that final locking move.
The input parameters to flash_length()
are exactly the same as the ones to anim_length()
.
Just like anim_length()
, when this function is called, it may rely on changed_state()
having been called previously, so if it needs to refer to information in the game_ui
then changed_state()
is a reliable place to have set that information up.
(Some games use flashes to indicate defeat as well as victory; Mines, for example, flashes in a different colour when you tread on a mine from the colour it uses when you complete the game. In order to achieve this, its flash_length()
function has to store a flag in the game_ui
to indicate which flash type is required.)
status()
int (*status)(const game_state *state);
This function returns a status value indicating whether the current game is still in play, or has been won, or has been conclusively lost. The mid-end uses this to implement midend_status()
(section 4.27).
The return value should be +1 if the game has been successfully solved. If the game has been lost in a situation where further play is unlikely, the return value should be -1. If neither is true (so play is still ongoing), return zero.
Front ends may wish to use a non-zero status as a cue to proactively offer the option of starting a new game. Therefore, back ends should not return -1 if the game has been technically lost but undoing and continuing is still a realistic possibility.
(For instance, games with hidden information such as Guess or Mines might well return a non-zero status whenever they reveal the solution, whether or not the player guessed it correctly, on the grounds that a player would be unlikely to hide the solution and continue playing after the answer was spoiled. On the other hand, games where you can merely get into a dead end such as Same Game or Inertia might choose to return 0 in that situation, on the grounds that the player would quite likely press Undo and carry on playing.)
redraw()
void (*redraw)(drawing *dr, game_drawstate *ds,
const game_state *oldstate,
const game_state *newstate,
int dir, const game_ui *ui,
float anim_time, float flash_time);
This function is responsible for actually drawing the contents of the game window, and for redrawing every time the game state or the game_ui
changes.
The parameter dr
is a drawing object which may be passed to the drawing API functions (see chapter 3 for documentation of the drawing API). This function may not save dr
and use it elsewhere; it must only use it for calling back to the drawing API functions within its own lifetime.
ds
is the local game_drawstate
, of course, and ui
is the local game_ui
.
newstate
is the semantically-current game state, and is always non-NULL
. If oldstate
is also non-NULL
, it means that a move has recently been made and the game is still in the process of displaying an animation linking the old and new states; in this situation, anim_time
will give the length of time (in seconds) that the animation has already been running. If oldstate
is NULL
, then anim_time
is unused (and will hopefully be set to zero to avoid confusion).
flash_time
, if it is is non-zero, denotes that the game is in the middle of a flash, and gives the time since the start of the flash. See section 2.8.8 for general discussion of flashes.
The very first time this function is called for a new game_drawstate
, it is expected to redraw the entire drawing area. Since this often involves drawing visual furniture which is never subsequently altered, it is often simplest to arrange this by having a special ‘first time’ flag in the draw state, and resetting it after the first redraw.
When this function (or any subfunction) calls the drawing API, it is expected to pass colour indices which were previously defined by the colours()
function.
This section discusses the back end functions that deal with printing puzzles out on paper.
can_print
int can_print;
This flag is set to TRUE
if the puzzle is capable of printing itself on paper. (This makes sense for some puzzles, such as Solo, which can be filled in with a pencil. Other puzzles, such as Twiddle, inherently involve moving things around and so would not make sense to print.)
If this flag is FALSE
, then the functions print_size()
and print()
will never be called.
can_print_in_colour
int can_print_in_colour;
This flag is set to TRUE
if the puzzle is capable of printing itself differently when colour is available. For example, Map can actually print coloured regions in different colours rather than resorting to cross-hatching.
If the can_print
flag is FALSE
, then this flag will be ignored.
print_size()
void (*print_size)(const game_params *params, float *x, float *y);
This function is passed a game_params
structure and a tile size. It returns, in *x
and *y
, the preferred size in millimetres of that puzzle if it were to be printed out on paper.
If the can_print
flag is FALSE
, this function will never be called.
print()
void (*print)(drawing *dr, const game_state *state, int tilesize);
This function is called when a puzzle is to be printed out on paper. It should use the drawing API functions (see chapter 3) to print itself.
This function is separate from redraw()
because it is often very different:
However, there's no reason the printing and redraw functions can't share some code if they want to.
When this function (or any subfunction) calls the drawing API, the colour indices it passes should be colours which have been allocated by the print_*_colour()
functions within this execution of print()
. This is very different from the fixed small number of colours used in redraw()
, because printers do not have a limitation on the total number of colours that may be used. Some puzzles' printing functions might wish to allocate only one ‘ink’ colour and use it for all drawing; others might wish to allocate more colours than are used on screen.
One possible colour policy worth mentioning specifically is that a puzzle's printing function might want to allocate the same colour indices as are used by the redraw function, so that code shared between drawing and printing does not have to keep switching its colour indices. In order to do this, the simplest thing is to make use of the fact that colour indices returned from print_*_colour()
are guaranteed to be in increasing order from zero. So if you have declared an enum
defining three colours COL_BACKGROUND
, COL_THIS
and COL_THAT
, you might then write
int c;
c = print_mono_colour(dr, 1); assert(c == COL_BACKGROUND);
c = print_mono_colour(dr, 0); assert(c == COL_THIS);
c = print_mono_colour(dr, 0); assert(c == COL_THAT);
If the can_print
flag is FALSE
, this function will never be called.
can_format_as_text_ever
int can_format_as_text_ever;
This boolean field is TRUE
if the game supports formatting a game state as ASCII text (typically ASCII art) for copying to the clipboard and pasting into other applications. If it is FALSE
, front ends will not offer the ‘Copy’ command at all.
If this field is TRUE
, the game does not necessarily have to support text formatting for all games: e.g. a game which can be played on a square grid or a triangular one might only support copy and paste for the former, because triangular grids in ASCII art are just too difficult.
If this field is FALSE
, the functions can_format_as_text_now()
(section 2.10.2) and text_format()
(section 2.10.3) are never called.
can_format_as_text_now()
int (*can_format_as_text_now)(const game_params *params);
This function is passed a game_params
and returns a boolean, which is TRUE
if the game can support ASCII text output for this particular game type. If it returns FALSE
, front ends will grey out or otherwise disable the ‘Copy’ command.
Games may enable and disable the copy-and-paste function for different game parameters, but are currently constrained to return the same answer from this function for all game states sharing the same parameters. In other words, the ‘Copy’ function may enable or disable itself when the player changes game preset, but will never change during play of a single game or when another game of exactly the same type is generated.
This function should not take into account aspects of the game parameters which are not encoded by encode_params()
(section 2.3.3) when the full
parameter is set to FALSE
. Such parameters will not necessarily match up between a call to this function and a subsequent call to text_format()
itself. (For instance, game difficulty should not affect whether the game can be copied to the clipboard. Only the actual visible shape of the game can affect that.)
text_format()
char *(*text_format)(const game_state *state);
This function is passed a game_state
, and returns a newly allocated C string containing an ASCII representation of that game state. It is used to implement the ‘Copy’ operation in many front ends.
This function will only ever be called if the back end field can_format_as_text_ever
(section 2.10.1) is TRUE
and the function can_format_as_text_now()
(section 2.10.2) has returned TRUE
for the currently selected game parameters.
The returned string may contain line endings (and will probably want to), using the normal C internal ‘\n
’ convention. For consistency between puzzles, all multi-line textual puzzle representations should end with a newline as well as containing them internally. (There are currently no puzzles which have a one-line ASCII representation, so there's no precedent yet for whether that should come with a newline or not.)
wants_statusbar
int wants_statusbar;
This boolean field is set to TRUE
if the puzzle has a use for a textual status line (to display score, completion status, currently active tiles, etc).
is_timed
int is_timed;
This boolean field is TRUE
if the puzzle is time-critical. If so, the mid-end will maintain a game timer while the user plays.
If this field is FALSE
, then timing_state()
will never be called and need not do anything.
timing_state()
int (*timing_state)(const game_state *state, game_ui *ui);
This function is passed the current game_state
and the local game_ui
; it returns TRUE
if the game timer should currently be running.
A typical use for the game_ui
in this function is to note when the game was first completed (by setting a flag in changed_state()
– see section 2.6.5), and freeze the timer thereafter so that the user can undo back through their solution process without altering their time.
flags
int flags;
This field contains miscellaneous per-backend flags. It consists of the bitwise OR of some combination of the following:
BUTTON_BEATS(x,y)
x
and y
from the set {LEFT_BUTTON
, MIDDLE_BUTTON
, RIGHT_BUTTON
}, this macro evaluates to a bit flag which indicates that when buttons x
and y
are both pressed simultaneously, the mid-end should consider x
to have priority. (In the absence of any such flags, the mid-end will always consider the most recently pressed button to have priority.)
SOLVE_ANIMATES
solve()
(section 2.7.4) are candidates for animation just like any other move. For most games, solve moves should not be animated, so the mid-end doesn't even bother calling anim_length()
(section 2.8.7), thus saving some special-case code in each game. On the rare occasion that animated solve moves are actually required, you can set this flag.
REQUIRE_RBUTTON
REQUIRE_NUMPAD
REQUIRE_RBUTTON
, a puzzle need not specify this simply if its use of the number keys is not critical.
This section describes a couple of things that a back end may choose to do by calling functions elsewhere in the program, which would not otherwise be obvious.
If a back end needs random numbers at some point during normal play, it can create a fresh random_state
by first calling get_random_seed
(section 4.36) and then passing the returned seed data to random_new()
.
This is likely not to be what you want. If a puzzle needs randomness in the middle of play, it's likely to be more sensible to store some sort of random state within the game_state
, so that the random numbers are tied to the particular game state and hence the player can't simply keep undoing their move until they get numbers they like better.
This facility is currently used only in Net, to implement the ‘jumble’ command, which sets every unlocked tile to a new random orientation. This randomness is a reasonable use of the feature, because it's non-adversarial – there's no advantage to the user in getting different random numbers.
In response to a move, a back end is (reluctantly) permitted to call midend_supersede_game_desc()
:
void midend_supersede_game_desc(midend *me,
char *desc, char *privdesc);
When the user selects ‘New Game’, the mid-end calls new_desc()
(section 2.4.1) to get a new game description, and (as well as using that to generate an initial game state) stores it for the save file and for telling to the user. The function above overwrites that game description, and also splits it in two. desc
becomes the new game description which is provided to the user on request, and is also the one used to construct a new initial game state if the user selects ‘Restart’. privdesc
is a ‘private’ game description, used to reconstruct the game's initial state when reloading.
The distinction between the two, as well as the need for this function at all, comes from Mines. Mines begins with a blank grid and no idea of where the mines actually are; new_desc()
does almost no work in interactive mode, and simply returns a string encoding the random_state
. When the user first clicks to open a tile, then Mines generates the mine positions, in such a way that the game is soluble from that starting point. Then it uses this function to supersede the random-state game description with a proper one. But it needs two: one containing the initial click location (because that's what you want to happen if you restart the game, and also what you want to send to a friend so that they play the same game as you), and one without the initial click location (because when you save and reload the game, you expect to see the same blank initial state as you had before saving).
I should stress again that this function is a horrid hack. Nobody should use it if they're not Mines; if you think you need to use it, think again repeatedly in the hope of finding a better way to do whatever it was you needed to do.