This adds a portable, scanline-based polygon filling algorithm, which
fills a polygon by drawing a collection of adjacent horizontal lines.
This change is motivated by the Rockbox port's current lack of a true
polygon fill capability. Until now, it attempted to approximate a
polygon fill by performing a series of triangle fills, but this worked
reliably only for convex polygons. I originally considered making this
new rasterizer part of the Rockbox front end itself, but I ultimately
decided that it made more sense to include it here, in the Puzzles
distribution, where other platforms may benefit from it in the future.
No in-tree front ends use this new function quite yet, but I plan to
follow this commit with a compile-time option to force front ends to
use it for testing.
This new polygon drawing code also comes with its own standalone
driver code to test it out in isolation. This code currently relies on
SDL 2.0 to present a GUI window to the user, which unfortunately adds
a build-time dependency. To lessen the impact of this change, this
program is gated behind a CMake build option. To use it, run:
$ cmake -DBUILD_SDL_PROGRAMS=true
This changes the drawing API so that implementations receive a
`drawing *` pointer with each call, instead of a `void *` pointer as
they did previously. The `void *` context pointer has been moved to be
a member of the `drawing` structure (which has been made public), from
which it can be retrieved via the new `GET_HANDLE_AS_TYPE()` macro. To
signal this breaking change to downstream front end authors, I've
added a version number to the `drawing_api` struct, which will
hopefully force them to notice.
The motivation for this change is the upcoming introduction of a
draw_polygon_fallback() function, which will use a series of calls to
draw_line() to perform software polygon rasterization on platforms
without a native polygon fill primitive. This function is fairly
large, so I desired that it not be included in the binary
distribution, except on platforms which require it (e.g. my Rockbox
port). One way to achieve this is via link-time optimization (LTO,
a.k.a. "interprocedural optimization"/IPO), so that the code is
unconditionally compiled (preventing bit-rot) but only included in the
linked executable if it is actually referenced from elsewhere.
Practically, this precludes the otherwise straightforward route of
including a run-time check of the `draw_polygon` pointer in the
drawing.c middleware. Instead, Simon recommended that a front end be
able to set its `draw_polygon` field to point to
draw_polygon_fallback(). However, the old drawing API's semantics of
passing a `void *` pointer prevented this from working in practice,
since draw_polygon_fallback(), implemented in middleware, would not be
able to perform any drawing operations without a `drawing *` pointer;
with the new API, this restriction is removed, clearing the way for
that function's introduction.
This is a breaking change for front ends, which must update their
implementations of the drawing API to conform. The migration process
is fairly straightforward: every drawing API function which previously
took a `void *` context pointer should be updated to take a `drawing *`
pointer in its place. Then, where each such function would have
previously casted the `void *` pointer to a meaningful type, they now
instead retrieve the context pointer from the `handle` field of the
`drawing` structure. To make this transition easier, the
`GET_HANDLE_AS_TYPE()` macro is introduced to wrap the context pointer
retrieval (see below for usage).
As an example, an old drawing API function implementation would have
looked like this:
void frontend_draw_func(void *handle, ...)
{
frontend *fe = (frontend *)handle;
/* do stuff with fe */
}
After this change, that function would be rewritten as:
void frontend_draw_func(drawing *dr, ...)
{
frontend *fe = GET_HANDLE_AS_TYPE(dr, frontend);
/* do stuff with fe */
}
I have already made these changes to all the in-tree front ends, but
out-of-tree front ends will need to follow the procedure outlined
above.
Simon pointed out that changing the drawing API function pointer
signatures to take `drawing *` instead of `void *` results only in a
compiler warning, not an outright error. Thus, I've introduced a
version field to the beginning of the `drawing_api` struct, which will
cause a compilation error and hopefully force front ends to notice
this. This field should be set to 1 for now. Going forward, it will
provide a clear means of communicating future breaking API changes.
Previously, this function's documentation just stated that `coords`
contained the X and Y coordinates of the polygon's vertices, without
specifying in what order they were listed. Thus, it would have been
reasonable to (wrongly) assume that all the X coordinates were listed
in the first half of the array, followed by all the Y coordinates.
Now it's clear that each point's X and Y coordinates are stored
directly next to each other, and in that order.
Both Inertia and Twiddle previously included static implementations of this
exact same function, which was passed to `qsort()` as a comparator. With
this change, a single global implementation is provided in misc.c, which
will hopefully reduce code duplication going forward.
I'm refactoring this in preparation for the upcoming fallback polygon fill
function, which I'm about to add.
This refactors all instances of bitwise-ANDs with `~MOD_MASK'. There is
a handful of more complex instances I left unchanged (in cube.c, midend.c,
and twiddle.c), since those AND with `~MOD_MASK | MOD_NUM_KEYPAD' or
similar. I don't think it's worth writing a macro for those cases.
Also document this new macro's usage in devel.but.
Previously, a button code of ('\t' | MOD_SHFT) from a frontend would have
the MOD_SHFT flag stripped by midend_process_key, resulting in a bare '\t'
passed to the backend. Now, this combination is allowed to pass through to
the backend. This will be used in the keyboard interface to Untangle, which
I'm about to add.
This adds an extra parameter to move_cursor() that's an optional pointer
to a bool indicating whether the cursor is visible. This allows for
centralising the common idiom of having the keyboard cursor become
visible when a cursor key is pressed. Consistently with the vast
majority of existing puzzles, the cursor moves even if it was invisible
before, and becomes visible even if it can't move.
The function now also returns one of the special constants that can be
returned by interpret_move(), so that the caller can correctly return
MOVE_UI_UPDATE or MOVE_NO_EFFECT without needing to carefully check for
changes itself.
Callers are updated only to the extent that they all pass NULL as the
new argument. Most of them could now be substantially simplified.
This removed the "handled" pointer and instead extends the existing
boolean return value (quit or don't quit) into an enumeration. One of
the values still quits the program, but now there are different values
for keys that had an effect, had no effect, and are not used by the
puzzle at all. The mapping from interpret_move results to process_key
results is roughly:
move string -> PKR_SOME_EFFECT
MOVE_UI_UPDATE -> PKR_SOME_EFFECT
MOVE_NO_EFFECT -> PKR_NO_EFFECT
MOVE_UNUSED -> PKR_UNUSED
The mid-end can also generate results internally, and is the only place
that PKR_QUIT can arise.
For compatibility, PKR_QUIT is zero, so anything expecting a false
return value to mean quit will be unsurprised. The other values are
ordered so that lower values indicate a greater amount of handling of
the key.
These allow for distinguishing the case where a puzzle doesn't have a
use for a key from the case where it just happens to have no effect in
the current state of the puzzle. These were both represented by NULL,
but that now represents the case where a puzzle hasn't been updated to
make the distinction yet.
All the other constants named UI_* are special key names that can be
passed to midend_process_key(), but UI_UPDATE is a special return value
from the back-end interpret_move() function instead. This renaming
makes the distinction clear and provides a naming convention for future
special return values from interpret_move().
This commit introduces a serialisation format for the user preferences
stored in game_ui, using the keyword identifiers that get_prefs is
required to write into its list of config_item. As a result, the
serialisation format looks enough like an ordinary config file that a
user could write one by hand.
The preferences for the game backend are kept in serialised form in
me->be_prefs. The typical use of this is to apply it to a just-created
game_ui by calling midend_apply_prefs(), which deserialises the prefs
buffer into a list of config_item and passes it to the backend's
set_prefs function, overwriting the preference fields (but no others)
of the game_ui.
This is duly done when creating a new game, when loading a game from a
save file, and also when printing a puzzle. To make the latter work,
document_add_puzzle now takes a game_ui (and keeps ownership of it
afterwards), and passes that to the backend's compute_size and print
functions.
The backend's own get_prefs and set_prefs functions are wrapped by
midend_get_prefs and midend_set_prefs. This is partly as a convenience
(it deals with optionally constructing a game_ui specially to call the
backend with), but mostly so that there will be a convenient place in
the midend to add standard preferences applying across all puzzles.
No cross-puzzle preferences are provided yet.
There are two external interfaces to all this, and in this commit,
neither one is yet called by any frontend:
A new pair of midend functions is exposed to the front end, called
midend_load_prefs and midend_save_prefs. These have a similar API to
midend_serialise and midend_deserialise, taking a read/write function
pointer and a context. So front ends that can already load/save a game
to a file on disk should find it easy to add a similar set of
functions loading/saving user preferences.
Secondly, a new value CFG_PREFS is added to the enumeration of
configuration dialog types, alongside the ones for the Custom game
type, entering a game description and entering a random seed. This
should make it easy for frontends to offer a Preferences dialog,
because it will operate almost exactly like three dialogs they already
handle.
This will be necessary in the next commit, so that the midend can make
a game_ui out of nothing in order to store preferences in it.
The only game actually affected by this requirement is Pearl, which
has a preference (GUI style) and also allocates space based on the
game_state's grid size to store the coordinates of a path being
dragged, so if there is no game_state, it can't do the latter - which
is OK, because it also won't be expected to.
These are similar to the existing pair configure() and custom_params()
in that get_prefs() returns an array of config_item describing a set
of dialog-box controls to present to the user, and set_prefs()
receives the same array with answers filled in and implements the
answers. But where configure() and custom_params() operate on a
game_params structure, the new pair operate on a game_ui, and are
intended to permit GUI configuration of all the settings I just moved
into that structure.
However, nothing actually _calls_ these routines yet. All I've done in
this commit is to add them to 'struct game' and implement them for the
functions that need them.
Also, config_item has new fields, permitting each config option to
define a machine-readable identifying keyword as well as the
user-facing description. For options of type C_CHOICES, each choice
also has a keyword. These keyword fields are only defined at all by
the new get_prefs() function - they're left uninitialised in existing
uses of the dialog system. The idea is to use them when writing out
the user's preferences into a configuration file on disk, although I
haven't actually done any of that work in this commit.
I'm about to move some of the bodgy getenv-based options so that they
become fields in game_ui. So these functions, which could previously
access those options directly via getenv, will now need to be given a
game_ui where they can look them up.
Some games would like a way to check that the parameters in the encoded
UI string are consistent with the game parameters. Since this might
depend on the current state of the game (this being what changed_state()
is for), implement this by adding a game_state parameter to decode_ui().
Nothing currently uses it, though Guess usefully could.
The majority of back-ends define encode_ui() to return NULL and
decode_ui() to do nothing. This commit allows them to instead specify
the relevant function pointers as NULL, in which case the mid-end won't
try to call them.
I'm planning to add a parameter to decode_ui(), and if I'm going to have
to touch every back-end's version of decode_ui(), I may as well ensure
that most of them never need to be touched again. And obviously
encode_ui() should go the same way for symmetry.
It now strips off modifier flags from keys that shouldn't have them and
maps printable characters with MOD_CTRL to the corresponding control
characters. It also catches Ctrl+Shift+Z because that obviously belongs
in the midend.
I've updated the JavaScript front-end to take advantage of these
changes. Other front ends are unchanged and should work just as they
did before.
There are various functions pointed to by the game structure that are
only called if certain flags are set. Some of them were documented as
not being called when the flags were clear, but there was no explicit
statement that the pointers could simply be NULL in that case. Now
there is.
It can't signal an error, but it's worth documenting that it can
receive invalid input and should do what it can with it. I assume
that failing to decode game_ui data from a newer version generally
won't be disastrous the way failing to decode a description or move
string would be.
There are lots of places where Puzzles formats integers into
fixed-length buffers using sprintf() with a "%d" format. This isn't
very safe, since C doesn't guarantee any particular maximum size for an
"int". However, the restrictions on representations of integers means
we can infer an upper bound using sizeof(), CHAR_BIT, and an
approximation to the binary log of 10.
Quite a few backends currently generate colours by multiplying the
foreground colour by a fraction, effectively mixing it with black. On a
black background, this might be reasonably replaced by mixing the
background colour with white, but that's rather arithmetically fiddly.
Happily, I already have a function for that and just need to expose it.
This provides a way for the front end to ask how a particular key should
be labelled right now (specifically, for a given game_state and
game_ui). This is useful on feature phones where it's conventional to
put a small caption above each soft key indicating what it currently
does.
The function currently provides labels only for CURSOR_SELECT and
CURSOR_SELECT2. This is because these are the only keys that need
labelling on KaiOS.
The concept of labelling keys also turns up in the request_keys() call,
but there are quite a few differences. The labels returned by
current_key_label() are dynamic and likely to vary with each move, while
the labels provided by request_keys() are constant for a given
game_params. Also, the keys returned by request_keys() don't generally
include CURSOR_SELECT and CURSOR_SELECT2, because those aren't necessary
on platforms with pointing devices. It might be possible to provide a
unified API covering both of this, but I think it would be quite
difficult to work with.
Where a key is to be unlabelled, current_key_label() is expected to
return an empty string. This leaves open the possibility of NULL
indicating a fallback to button2label or the label specified by
request_keys() in the future.
It's tempting to try to implement current_key_label() by calling
interpret_move() and parsing its output. This doesn't work for two
reasons. One is that interpret_move() is entitled to modify the
game_ui, and there isn't really a practical way to back those changes
out. The other is that the information returned by interpret_move()
isn't sufficient to generate a label. For instance, in many puzzles it
generates moves that toggle the state of a square, but we want the label
to reflect which state the square will be toggled to. The result is
that I've generally ended up pulling bits of code from interpret_move()
and execute_move() together to implement current_key_label().
Alongside the back-end function, there's a midend_current_key_label()
that's a thin wrapper around the back-end function. It just adds an
assertion about which key's being requested and a default null
implementation so that back-ends can avoid defining the function if it
will do nothing useful.
At least one puzzle does no actual decoding in decode_ui, but does
re-initialise some fields. This is unnecessary because the mid-end only
calls decode_ui() with a game_ui it just allocated using new_ui().
This adds a new bool * argument, which can be NULL if front ends don't
care whether the keypress was handled. Currently they all do that.
Currently, "undo" and "redo" keys are treated as not handled if there's
no move to undo or redo. This may be a little too strict.
The device pixel ratio indicates how many physical pixels there are in
the platonic ideal of a pixel, at least approximately. In Web browsers,
the device pixel ratio is used to represent "retina" displays with
particularly high pixel densities, and also to reflect user-driven
zooming of the page to different text sizes.
The mid-end uses the device pixel ratio to adjust the tile size at
startup, and can also respond to changes in device pixel ratio by
adjusting the time size later. This is accomplished through a new
argument to midend_size() which can simply be passed as 1.0 in any front
end that doesn't care about this.
Net can have non-alphanumeric characters in its parameter strings. Both
"5x5b0.1" and "5x5b1e-05" are valid parameter strings generated by Net.
So only "most" puzzles use alphanumeric parameter strings.
It's got a bit out of date over the years, with some changes to the
code not fully reflected in it (e.g. not all the int -> bool type
changes were documented, and TRUE and FALSE were still mentioned),
and quite a lot of new functions not added. (In particular, the dsf
API was not documented, and it certainly should have been, if only so
that people can find out what it even stands for!)
As well as correcting for factual accuracy, two content changes in the
advice chapter:
I've reworded the definition of 'fairness' to explicitly mention that
requiring the player to use Undo is cheating. That's always how I
_intended_ the definition, but I didn't say it clearly enough.
And I've added an entire new section describing the normal sensible
way to implement redraw(), via a loop of the form 'work out what this
cell should look like, check it against an array in game_drawstate of
the last state we drew it in, and if they're different, call a redraw
function'. That was mentioned in passing in two other sections, but I
know at least one developer didn't find it, so now it's less well
hidden.
This integer parameter appears in all of the game's anim_length,
flash_length and redraw methods, but the documentation only described
it properly in the section for anim_length.
The section for flash_length refers you to anim_length for the
description of all the parameters, but unhelpfully, it did so without
a conveniently clickable (in HTML) cross-reference.
And the section for game_redraw missed out the description of 'int
dir' completely, which is particularly unhelpful since that's the
function most likely to actually need to care about it! (Even if
forward and reversed move animations look different, they _probably_
still have the same duration, so it's more likely that anim_length
would ignore 'dir' and redraw would use it than vice versa.)
This completely removes the old system of mkfiles.pl + Recipe + .R
files that I used to manage the various per-platform makefiles and
other build scripts in this code base. In its place is a
CMakeLists.txt setup, which is still able to compile for Linux,
Windows, MacOS, NestedVM and Emscripten.
The main reason for doing this is because mkfiles.pl was a horrible
pile of unmaintainable cruft. It was hard to keep up to date (e.g.
didn't reliably support the latest Visual Studio project files); it
was so specific to me that nobody else could maintain it (or was even
interested in trying, and who can blame them?), and it wasn't even
easy to _use_ if you weren't me. And it didn't even produce very good
makefiles.
In fact I've been wanting to hurl mkfiles.pl in the bin for years, but
was blocked by CMake not quite being able to support my clang-cl based
system for cross-compiling for Windows on Linux. But CMake 3.20 was
released this month and fixes the last bug in that area (it had to do
with preprocessing of .rc files), so now I'm unblocked!
CMake is not perfect, but it's better at mkfiles.pl's job than
mkfiles.pl was, and it has the great advantage that lots of other
people already know about it.
Other advantages of the CMake system:
- Easier to build with. At least for the big three platforms, it's
possible to write down a list of build commands that's actually the
same everywhere ("cmake ." followed by "cmake --build ."). There's
endless scope for making your end-user cmake commands more fancy
than that, for various advantages, but very few people _have_ to.
- Less effort required to add a new puzzle. You just add a puzzle()
statement to the top-level CMakeLists.txt, instead of needing to
remember eight separate fiddly things to put in the .R file. (Look
at the reduction in CHECKLST.txt!)
- The 'unfinished' subdirectory is now _built_ unconditionally, even
if the things in it don't go into the 'make install' target. So
they won't bit-rot in future.
- Unix build: unified the old icons makefile with the main build, so
that each puzzle builds without an icon, runs to build its icon,
then relinks with it.
- Windows build: far easier to switch back and forth between debug
and release than with the old makefiles.
- MacOS build: CMake has its own .dmg generator, which is surely
better thought out than my ten-line bodge.
- net reduction in the number of lines of code in the code base. In
fact, that's still true _even_ if you don't count the deletion of
mkfiles.pl itself - that script didn't even have the virtue of
allowing everything else to be done exceptionally concisely.
The Rockbox frontend allows games to be displayed in a "zoomed-in"
state targets with small displays. Currently we use a modal interface
-- a "viewing" mode in which the cursor keys are used to pan around
the rendered bitmap; and an "interaction" mode that actually sends
keys to the game.
This commit adds a midend_get_cursor_location() function to allow the
frontend to retrieve the backend's cursor location or other "region of
interest" -- such as the player location in Cube or Inertia.
With this information, the Rockbox frontend can now intelligently
follow the cursor around in the zoomed-in state, eliminating the need
for a modal interface.
Not many changes here: the 'dotted' flag passed to print_line_dotted
is bool, and so is the printing_in_colour flag passed to
print_get_colour. Also ps_init() takes a bool.
line_dotted is also a method in the drawing API structure, but it's
not actually filled in for any non-print-oriented implementation of
that API. So only front ends that do platform-specific _printing_
should need to make a corresponding change. In-tree, for example,
windows.c needed a fix because it prints via Windows GDI, but gtk.c
didn't have to do anything, because its CLI-based printing facility
just delegates to ps.c.
This changes parameters of midend_size and midend_print_puzzle, the
return types of midend_process_key, midend_wants_statusbar,
midend_can_format_as_text_now and midend_can_{undo,redo}, the 'bval'
field in struct config_item, and finally the return type of the
function pointer passed to midend_deserialise and identify_game.
The last of those changes requires a corresponding fix in clients of
midend_deserialise and identify_game, so in this commit I've also
updated all the in-tree front ends to match. I expect downstream front
ends will need to do the same when they merge this change.
encode_params, validate_params and new_desc now take a bool parameter;
fetch_preset, can_format_as_text_now and timing_state all return bool;
and the data fields is_timed, wants_statusbar and can_* are all bool.
All of those were previously typed as int, but semantically boolean.
This commit changes the API declarations in puzzles.h, updates all the
games to match (including the unfinisheds), and updates the developer
docs as well.
This function gives the front end a way to find out what keys the back
end requires; and as such it is mostly useful for ports without a
keyboard. It is based on changes originally found in Chris Boyle's
Android port, though some modifications were needed to make it more
flexible.