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.
Before this commit, JavaScript Puzzles loaded a save file by pushing the
entire file onto the Emscripten stack and then reading it from there.
This worked tolerably for typical save files, but Emscripten's stack
defaults to only having 64 kiB of space. That meant that trying to load
something that wasn't a real save file tended to cause a stack overflow.
I expect that at least some real save files would suffer from the same
problem.
The stack overflow would generally cause a JavaScript exception and then
leave the stack pointer outside the stack, so that any future attempt to
call into C would fail as well.
To fix this, arrange that the C function for reading data from the save
file calls out to JavaScript. The JavaScript can then copy just the
requested data into the caller's buffer. We can't pass a JavaScript
function pointer to C, but since only one file can be loaded at a time,
we can just have a global variable that's the current loading callback.
There might still be a problem if you try to load a stupendously large
file, since I think FileReader.readAsArrayBuffer() reads the whole file
into the browser's RAM. It works on my laptop with files up to a few
hundred megabytes, though.
This provides a standard way to get a boolean from an environment
variable. It treats the variable as true iff its value begins with 'y'
or 'Y', like most of the current implementations. The function takes a
default value which it returns if the environment variable is undefined.
This replaces the various ad-hoc tests of environment variable scattered
around and mostly doesn't change their behaviour. The exceptions are
TOWERS_2D in Towers and DEBUG_PUZZLES in the Windows front end. Both of
those were treated as true if they were defined at all, but now follow
the same rules as other boolean environment variables.
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.
For reasons now lost to history, Puzzles generally uses single-precision
floating point. However, C floating-point constants are by default
double-precision, and if they're then operated on along with a
single-precision variable the value of the variable gets promoted to
double precision, then the operation gets done, and then often the
result gets converted back to single precision again.
This is obviously silly, so I've used Clang's "-Wdouble-promotion" to
find instances of this and mark the constants as single-precision as
well. This is a bit awkward for PI, which ends up with a cast. Maybe
there should be a PIF, or maybe PI should just be single-precision.
This doesn't eliminate all warnings from -Wdouble-promotion. Some of
the others might merit fixing but adding explicit casts to double just
to shut the compiler up would be going too far, I feel.
Also -Wstrict-prototypes and -Wmissing-variable-declarations.
Functions that are called from JavaScript now have a separate
declaration at the top of the file with a comment reminding one to
update emcclib.js if they're changed.
They're proving to be a right nuisance and will probably require a
substantial overhaul to how they work across the entire JavaScript
front-end. I don't think any of the functionality provided by the
dialogue boxes is critical, so in the interests of getting a minimum
viable product actually released I've disabled those features.
In most cases, this just involves commenting out bits of HTML. The
"Custom..." menu item is added by C code, though, so there I've fallen
back to the standard Puzzles way to implement a nasty hack: an
environment variable.
Previously, we initialised all of the JavaScript event handlers as soon
at the DOM was loaded, and then called main() ourselves once the
Emscripten runtime was ready. This was slightly dangerous since it
depended on none of those event handlers' being called before main().
In practice this was difficult because most of the elements the event
handlers were attached to were invisible, but it did limit what event
handlers could safely be used.
Now, the event handlers are initialised from main(). This makes things
work in a sufficiently conventional way that we can just let the
Emscripten run-time call main() in its usual way, rather than involving
ourselves in the minutiae of Emscripten's startup.
I was hoping that I could treat SoftLeft and SoftRight the same in C and
arrange to filter one of them out in JavaScript, but that turned out to
be impractical.
This allows the HTML/CSS to decide that it would like a different
background colour without the need to recompile. The default if the CSS
specifies no colour (and hence the canvas is transparent) is the same
grey as before.
This means that the calculated font properties of the HTML canvas now
control what font is used. The size is overridden, and for monospaced
text so is the family.
I'd like to be able to also specify the monospaced font, maybe using a
CSS variable, but that looks like being quite a lot of extra complexity.
My experience when testing this was that constructing a valid "font"
string for a canvas context is prone to breakage, but broke in a way
that left the font unchanged, so we always set a simple specification
first before trying to construct one from CSS.
It's sometimes useful to be able to have an HTML element that visually
forms an extension of the puzzle's border. By putting the puzzle's
colour 0 (which we assume to be its background) into a CSS variable,
such elements can do something like "background-color:
var(--puzzle-background)" to get that effect even if the puzzle uses a
non-default background colour.
Menu items that open dialogue boxes are now marked with ellipses, while
menu items that lead to submenus have pointing triangles.
The triangles are implemented as SVG files embedded in data: URLs in the
CSS, which is kind of silly but also works really well. There are
suitable characters in Unicode, but some of my test systems don't have
fonts containing them, so maybe the SVG is better.
I'm generally in favour of putting HTML in HTML rather the constructing
it in JavaScript, and this will allow for simplifying the code
eventually. This only changes the JavaScript to make sure that's in
people's caches before I change the HTML itself.
Games with neither presets nor configuration (which may only be the Null
Game) have been slightly broken since the introduction of hierarchical
preset menus, in that the code to remove the "Type..." menu stopped
being called then. My switch to using radio buttons in menus then broke
them utterly because it's not possible to set the value of an empty
radio group, causing a crash at startup.
Fix this by detected when there's no preset menu, removing the item from
the menu bar, and setting the variable that's meant to indicate this has
been done.
The solve button problem was more subtle, in that only the <button> was
being hidden and not the <li> containing it, which led to the right border of the menu bar being two pixels thick. Switch to fully removing
the <li> from the DOM, like we now do with the presets menu, since that
also makes my keyboard handler (in another branch) simpler.
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.
This way, the front end can intercept one of SoftLeft and SoftRight as a
menu key and leave the other one for the puzzle. And while we don't
have a working menu, I can use whichever is more convenient.
This is the left soft key on KaiOS phones. The centre soft key
already sends "Enter", which eventually becomes CURSOR_SELECT. The
right soft key I'm planning to use to open the menu.
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.
This is a bit of a hack. When setting the puzzle to its default size,
either at startup or from a right-click on the resize handle, we now
scale the default size from midend_size() by the device pixel ratio,
and then pass that back to midend_size(). This does more or less the
right thing, in that the puzzle now starts up at a size that scales
with the font size.
There are still some slight inconsistencies, where sequences of DPR
changes and puzzle parameter changes can have order-dependent effects
on the size of the puzzle. Happily these effects are small and fairly
hard to observe.
This adds a new callback, rescale_puzzle(), that's called when the
device pixel ratio changes. This means that resize_puzzle() can safely
set the nominal canvas size, which means that manual resizing of the
puzzle now sticks.
Still missing: paying attention to the device pixel ratio when choosing
the initial (or reset) size.
Now we only cancel a keydown event if the C keyboard handler recognises
the key and passes it on to the midend. This doesn't necessarily mean
that the midend has actually done anything with it, of course. Still,
this is enough to allow F12 to open the developer tools even when the
input focus is on the puzzle. It also allows for tabbing out of the
puzzle into the links below it.
This requires passing in KeyboardEvent.location from JavaScript so
that we can detect the numeric keypad properly. Out of caution we
currently only set MOD_NUM_KEYPAD on numbers, like we always have,
but we have enough information to set it on arrow keys, Enter, "+",
etc.
This finally gets '/' and '\' working in Slant again.
KeyboardEvent.keyCode is a platform-dependent mess.
KeyboardEvent.char is better-defined, but in my browser it's always
null. KeyboardEvent.key is the right thing to use when we want to
know the semantics of a key.
This commit just re-organises the big "else if" chain in key() so
that all the tests on "key" come first. That way at least if key and
keyCode disagree, we'll use the more trustworthy one.
Now that save files are (even more) officially ASCII, it's perfectly
safe to pass them to JavaScript as UTF-8 strings.
This means that the form in which save files are shipped from C to
JavaScript is the same is the form in which they're shipped from
JavaScript to C. That allows for doing new things with them, like
writing them to local storage.
This reverts commit f729f51e475ff98d0caf529f0723ef810b1c88ef.
Without this, the "Undo" button ends up greyed even though it actually
works. I'm not sure about whether updating the permalinks is necessary:
maybe we don't need that in new_game() either.
Load/save has been in the JavaScript backend for a while, as have
prettier controls. And JavaScript-capable touchscreens are all around
us, if still poorly supported by Puzzles.
The previous fix worked OK, but it was conceptually wrong. Puzzles
save files are better regarded as binary, not text: the length fields
are measured in bytes, so translating the file into a different
multibyte character encoding would invalidate them.
So it was wrong to fetch a C byte string containing the exactly right
binary data, then translate it into a Javascript string as if decoding
from UTF-8, then retranslate to a representation of a bytewise
encoding via encodeURIComponent, and then label the result as
application/octet-stream.
This probably wouldn't have caused any problems in practice, because I
don't remember any situation in which my save files use characters
outside printable ASCII (plus newline). But it's not actually
forbidden, so a save file might choose to do that some day, so that
UTF-8 decode/reencode hidden in the JS was a latent bug.
Now the URI-encoding is done on the C side, while we still know
exactly what the binary data ought to look like and can be sure we're
translating it byte for byte into the output encoding for the data:
URI. By the time the JS receives a string pointer from get_save_file,
it's already URI-encoded, which _guarantees_ that it's in ASCII and
won't be messed about with by Emscripten's UTF8ToString.
This is the main bulk of this boolification work, but although it's
making the largest actual change, it should also be the least
disruptive to anyone interacting with this code base downstream of me,
because it doesn't modify any interface between modules: all the
inter-module APIs were updated one by one in the previous commits.
This just cleans up the code within each individual source file to use
bool in place of int where I think that makes things clearer.
This commit removes the old #defines of TRUE and FALSE from puzzles.h,
and does a mechanical search-and-replace throughout the code to
replace them with the C99 standard lowercase spellings.
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.