This uses a tile shape very similar to the hat, but the tiling
_structure_ is totally changed so that there aren't any reflected
copies of the tile.
I'm not sure how much difference this makes to gameplay: the two
tilings are very similar for Loopy purposes. But the code was fun to
write, and I think the Spectre shape is noticeably prettier, so I'm
adding this to the collection anyway.
The test programs also generate a pile of SVG images used in the
companion article on my website.
A test-build with a modern clang points out a number of 'set but not
used' variables, which clang seems to have got better at recently.
In cases where there's conditioned-out or commented-out code using the
variable, I've left it in and added a warning-suppressing cast to
void. Otherwise I've just deleted the variables.
The 'core' library contains almost all the same objects as 'common',
but leaves out hat.c. And the auxiliary program 'hatgen' now links
against that slightly reduced core library instead of 'common'.
This avoids a dependency loop: one of hatgen's jobs is to generate
hat-tables.h, but hat-tables.h is a dependency of it.
Of course, the generated hat-tables.h is already committed, so this
doesn't present a bootstrapping problem in a normal build. But if
someone modifies hatgen.c in order to regenerate hat-tables.h, and
does so in a way that makes it uncompilable, they can't rebuild hatgen
and try again! Of course you can always revert changes with git, but
it's annoying to have to. Better to keep the dependencies non-cyclic
in the first place.
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().
Some puzzles have keys that make changes to the display style in ways
that would probably have been user preferences if they had existed.
I've added a user preference for each of these. The keys still work,
and unlike the preferences can be changed without saving any state.
The affected settings are:
* Labelling colours with numbers in Guess ("L" key)
* Labelling regions with numbers in Map ("L" key)
* Whether monsters are shown as letters or pictures in Undead ("A" key)
The C stack used by Emscripten is quite small, so passing more than a
few klilobytes of data on it tends to cause an overflow. Current
versions of puzzles will only generate tiny preferences files, but this
might change in future and in any case Puzzles shouldn't crash just
because the preferences in local storage have got corrupted.
To fix this, we now have JavaScript allocate a suitable amount of C heap
memory using malloc() and stick the preferences file in there.
This could plausibly fail if the preferences file were really big, but
that's unlikely since browsers generally limit an origin to about 5 MB
of local storage. In any case, if malloc() does fail, we'll just ignore
the preferences file, which is probably the right thing to do.
Trying to access window.localStorage will generate an exception if the
local storage is for some reason inaccessible. This can be
demonstrated in Firefox by configuring it to block a site from using
site data. Writing to local storage might also cause an exception if,
for instance, the quota of data for a site is exceeded.
If an exception is raised while loading preferences we log the fact
but don't make any report to the user, behaving as if no preferences
were found. This avoids annoying the user on every visit to a puzzle
page if they're not trying to use preferences.
If something goes wrong when saving, we do currently report that to
the user in an alert box. This seems reasonable since it's in
response to an explicit user action.
If the puzzle canvas is at a ludicrously small size, so that you
attempt to use a zero-height font, then obviously nothing sensible
will appear in the way of text, but you'd at least like to avoid a
crash. But currently, js_canvas_find_font_midpoint will make a canvas,
print some height-0 text into it, and try to retrieve the image pixels
to see what the actual font height was - and this will involve asking
getImageData for a zero-sized rectangle of pixels, which is an error.
Of course, there's only one possible return value from this function
if the font height is 0, so we can just return it without going via
getImageData at all.
(This crash can be provoked by trying to resize the puzzle canvas to
Far Too Small, or by interleaving canvas resizes with browser-tab
zooming. I've had one report that it also occurs in less silly
situations, which I haven't been able to reproduce. However, this
seems like a general improvement anyway.)
dot_bbox() wasn't taking into account the new size of the dots, so
sometimes a rectangle of the grid would be redrawn without a partial
dot it should have contained, because nothing had noticed that that
dot overlapped that rectangle.
Actually I'm not sure why this bug wasn't happening _before_ I
enlarged the dots, because the previous code seemed to think dots had
a fixed size in pixels regardless of tile size, which wasn't even
true _before_ my recent commit 4de9836bc8c36cd. Perhaps it did occur,
just never while I was watching.
Preferences that adjust the display, such as Pearl graphics style or
Light Up lit-blobs toggling, shouldn't affect the official icons, even
if a ~/.config/sgt-puzzles exists in the account that builds the
puzzles.
A user pointed out today that IDM_PREFS overlaps the second preset,
because I forgot that IDM_PRESETS was not a single id but the base for
an open-ended series.
Looking more closely, there are several other problems with the IDM_*
constants. IDM_GAMES (used in COMBINED mode) shouldn't be at an
arbitrary distance _above_ IDM_PRESETS, because that risks a
collision; instead, the games' and presets' ids should interleave.
Also, the ids above IDM_GAMES were going up in steps of 1, which
should have been 0x10, for the usual reason that the bottom four bits
of the id aren't guaranteed. And IDM_KEYEMUL was completely unused (I
suspect it was part of the discarded WinCE support).
Now the #defines that are the bases of series are labelled as
IDM_FOO_BASE; they interleave as they should; and there's a clear
comment.
Requested by a user who otherwise found themself spending too much
time struggling to get lines nicely horizontal or vertical.
The implementation is easy, but the question is what size of grid is
appropriate. Untangle's own generated games are constructed by making
a planar graph drawn on an extremely coarse grid - but snapping to
_that_ grid would give away information about the puzzle solution, and
also, Untangle wouldn't know any similar information about graphs
generated by any other method.
So a better approach is to choose a size of grid that guarantees that
_any_ graph with n vertices can be drawn on it with nonintersecting
straight edges. That sounds like a tricky maths problem - but happily,
the solution is given in a book I already had a copy of. References in
a comment (plus a proof of a pedantic followup detail about multiple
planar embeddings).
Where possible, that is. If our compilation environment has provided
int64_t, we can just make our int64 type be that, and not have to mess
around with multi-word arithmetic at all.
Just spotted this in puzzles.h. We don't need to guess any more from
the C standards version whether stdint.h is available: we've actually
checked _precisely that_ in cmake, so it's better to use the answer.
In the Hats tiling, each tile has two consecutive edges collinear.
When both edges are turned on, i.e. drawn in black just like the dot,
it becomes _just slightly_ tricky to spot the dot in the middle of
that straight line, which is important if you're counting edges around
the face to check a clue.
Increasing the radius from 2/32 to 3/32 of tile size is far too big.
2.5/32 seems reasonable, though.
Normally, we put two spaces between row clues, but if there are a lot
then even with a smaller font size they might overflow the left edge
of the window. To compensate, go down to one space between clues
instead of two if there are a lot of them. On my laptop the GTK build
demonstrates overflow with "13x1:1//1//1//1//1//1//1/1.1.1.1.1.1.1".
If you have a particularly large number of clues in a row, they can
end up falling off the left edge of the window. This used not to be a
problem because the rendering code would squash them closer together
if necessary. But then I switched to drawing them all as a single
string (so that two-digit row clues would get enough space with a
large font) and that broke the old mechanism.
Now we detect if there are enough clues that our conservative guess at
the string length looks like overflowing in the big font, and switch
to the small one if necessary. If we had a drawing call to measure a
string then we could be cleverer about this, but we don't.
This problem can be demonstrated with "7x1:1//1//1//1/1.1.1.1" in the
GTK port with the fonts my laptop has.
I think overflow can still occur even with a small font, so once I've
demonstrated that I'll try to fix it.
I picked pathname + "#preferences" because Ben suggested either that
or pathname + "/preferences", and I thought the former sounded neater.
But Ben now suggests that it might be better to avoid using # in the
key, just in case anyone should later want to use the whole URL
_including_ the fragment ID as the key for some other localStorage
usage - for example, autosaved progress per puzzle _instance_, keying
off the puzzle URL with a fragment ID in the game description.
So, per Ben's replacement suggestion, change the "#" to a space.
Here, user preferences are stored in localStorage, so that they can
persist when you come back to the same puzzle page later.
localStorage is global across a whole web server, which means we need
to take care to put our uses of it in a namespace reasonably unlikely
to collide with unrelated web pages on the same server. Ben suggested
that a good way to do this would be to store things in localStorage
under keys derived from location.pathname. In this case I've appended
a fragment id "#preferences" to that, so that space alongside it
remains for storing other things we might want in future (such as
serialised saved-game files used as quick-save slots).
When loading preferences, I've chosen to pass the whole serialised
preferences buffer from Javascript to C as a single C string argument
to a callback, rather than reusing the existing system for C to read
the save file a chunk at a time. Partly that's because preferences
data is bounded in size whereas saved games can keep growing; also
it's because the way I'm storing preferences data means it will be a
UTF-8 string, and I didn't fancy trying to figure out byte offsets in
the data on the JS side.
I think at this point I should stop keeping a list in the docs of
which frontends support preferences. Most of the in-tree ones do now,
and that means the remaining interesting frontends are ones I don't
have a full list of. At this moment I guess no out-of-tree frontends
support preferences (unless someone is _very_ quick off the mark), but
as and when that changes, I won't necessarily know, and don't want to
have to keep updating the docs when I find out.
It wasn't ever used! Looks as if I pasted it in here from one of the
other implementations, before realising that wasn't how I was going to
read save files at all.
We're about to append one character to the buffer _and_ put a \0 after
it, so we need the buffer to be at least _two_ characters longer than
where the current position is.
I think this bug would have had a hard time showing up in normal use,
but I managed to trigger it by completely messing up a prototype
Emscripten preferences implementation, and a good thing too.
The low-level load and save routines are basically copy-pasted from
gtk.c, with only minor changes to deal with the different locally
appropriate config file location and the lack of savefile_write_ctx.
After a failed rename(), we should find out what went wrong by looking
in errno itself, not in wctx->error which reported a problem in the
previous step.
Apparently I wrote the new-look code that sets up the prefs field in
the UI, but didn't remember to rewrite the code in interpret_move that
ought to read it.
I had to do this in the Windows front end to cope with compiling in
both COMBINED and one-puzzle mode: you can't refer to &thegame because
it might not exist in this build, so instead, if you want to load/save
preferences for a given midend, you ask that midend for _its_ game
structure, and use that to make the necessary file names.
On Unix, we don't have COMBINED mode. But now I've thought of it, this
seems like a good idiom anyway, for the sake of futureproofing against
the day someone decides to implement combined mode on Unix.
delete_prefs() doesn't get passed a frontend _or_ a midend, so that
just has to take a bare 'const game *' parameter, and in main() we
pass &thegame to it. So that will still need changing in a combined
mode, if one is ever added.
Just noticed that if prefs_dir() returns NULL, we'll already have
passed it to this function before the calling functions get round to
checking. I think it's less wordy all round to make this helper
function propagate NULL than to mess about with lots of extra if
statements in between all the calls.
Net's loop highlighting detects any loop in the current state of the
grid. I've occasionally found that to be a bit of a spoiler, since
sometimes it can point out a deduction I should make before I've
figured it out for myself - e.g. when I've just locked all but two of
the squares involved in the loop, and the last two _just happen_ to be
oriented so as to complete the loop. In that situation I'd prefer if
the loop _didn't_ immediately light up and point out to me that I need
to arrange that those squares aren't connected to each other.
The simple answer is to only count edges connecting two _locked_
squares, for the purposes of loop detection. But this is obviously
unacceptable to some players - in particular, those who play without
the locking feature at all. So it should be a user preference.
This must have been introduced during a last-minute rebase, or similar
- I'm sure it worked a couple of days ago! Because midend_set_config
passed a NULL game_ui to midend_set_prefs, the latter would make up a
temporary UI and apply the changes to that. As a result, the midend's
main UI would keep the original backend preferences, and those would
also be the ones saved to the config file.
If you want to remove your saved preferences for a puzzle for any
reason (perhaps because of one of those unsympathetic managers, or
perhaps because it's become corrupted in some way), you can of course
manually go and find the config file and delete it. But if you're not
sure where it is, it's helpful to have a method of telling the puzzle
itself to delete the config file.
Perhaps it would be useful to expose this in the GUI as well, though
I'm not quite sure where is the best place to put it.
(_Perhaps_ it would also be useful to have a more thorough option that
deleted _all_ puzzles' configurations - although that would involve
telling every separate puzzle binary in this collection what all the
other ones' names are, which would be an entirely new build headache.)
Finally, some user-visible behaviour changes as a payoff for all that
preparation work! In this commit, the GTK puzzles get a 'Preferences'
option in the menu, which presents a dialog box to configure the
preference settings.
On closing that dialog box, the puzzle preferences are enacted
immediately, and also saved to a configuration file where the next run
of the same puzzle will reload them.
The default file location is ~/.config/sgt-puzzles/<puzzlename>.conf,
although you can override the .config dir via $XDG_CONFIG_HOME or
override the Puzzles-specific subdir with $SGT_PUZZLES_DIR.
This is the first commit that actually exposes all the new preferences
work to the user, and therefore, I've also added documentation of all
the current preference options.
With this option turned off (it's on by default), the single-letter
keyboard shortcuts like 'q' for quit and 'n' for new game don't
function any more. You can still access the same functions via more
complicated shortcuts like Ctrl-Q or Ctrl-N, and front ends can
provide any other UI they like for the same operations, but this way,
people aren't at risk of blowing away half an hour of puzzling with
one misaimed key.
This is a thing people have occasionally asked for, and I've generally
resisted on the grounds that I have sympathy for people playing
puzzles at work who need to be able to close the game quickly when an
unsympathetic boss wanders by. But now we have a preferences system,
we can cater to those people _and_ the ones who don't mind.
More immediately useful: adding _at least one_ universal preference in
the initial version of this system means that there will be no games
with no preference options at all, and therefore, no need to put
conditionals all through the participating frontends to deal with
whether the Preferences menu option should even be provided. That
would have been a waste of time because all those conditionals would
just have to be removed again as soon as the first universal
preference came along - so adding an easy one _now_ means we can save
the effort of putting in the temporary conditionals in the first
place.