... and then decide there was no excuse for renaming the variable, so
now it has the same name it had before I started using
Window.requestAnimationFrame().
This is an API specifically designed for the purposes of timing
animations. Unlike setInterval, it tries to synchronise with the screen
refresh rate. It naturally passes us timing information, saving the
need to construct a Date object every frame. It has the nice feature
that browsers (at least Firefox 91) will call it less frequently when
the puzzle page isn't visible, which saves CPU time in puzzles that run
a timer continuously.
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.
With very small tile sizes, js_canvas_find_font_midpoint() can throw an
exception. When it was called from update_pixel_ratio(), this prevented
the new MediaQueryList from being created, which meant that the puzzle
stopped noticing changes of device pixel ratio.
Now update_pixel_ratio() establishes a new MediaQueryList before calling
rescale_puzzle(), so the exception can't break it. Catching the
exception properly would be even better, of course.
It would have helped in the previous commit if I'd tried actually
_playing_ the game, not just admiring it in its initial state. When I
did, I found that lines weren't being fully overdrawn, which turned
out to be because the clip rectangle was being set too narrow.
This is now important due to Ben's changes in the web frontend. On
high-DPI displays, the canvas is the same overall size as before, but
it's scaled up by increasing the game's tilesize rather than the
browser scaling the image after the game redraws.
Loopy ought to have been scaling its line thicknesses all along, but
forgot. Easily fixed.
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.
This has the entertaining consequence that repeatedly zooming in and out
causes puzzles to gradually shrink, thus demonstrating that recording
the nominal size correctly will be necessary.
Despite my stylistic downgrades, it still used two features not present
in Firefox 48, and hence KaiOS 2.5: passing options to addEventListener,
and calling addEventListener on a MediaQueryList at all. Now it uses
the older addListener method and explicitly removes each listener as
soon as it's called.
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.
At least in modern browsers (and I suspect in all browsers), cancelling
a keydown event ensures that the subsequent keypress event doesn't fire.
See <https://w3c.github.io/uievents/#keys-cancelable-keys>.
So there's no point in having a handler on keypress events that just
tries to cancel them as well. Removing the handler doesn't do much now,
but it opens the possibility of being a bit more selective about which
keydown events we cancel.
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.
Stealing code from the MDN has the consequence that it uses shiny ES6
features like "const", "let", and "=>". This looks a bit odd among
the more conservative style of the rest of Puzzles, so I've
downgraded it to "var" and "function". I'll let the template string
stay because that actually helps readability.
This requires looking at the CSS size of the puzzle canvas rather than
its internal size, and then adjusting the new size to account for the
device pixel ratio.
Because it's the simplest thing to do, when we notice such a change we
keep the current puzzle at its existing size measured in device
pixels. This has the rather odd consequence that when changing the
text size in Firefox, the size of the puzzle remains constant.
The CSS "px" unit isn't always a device pixel. On devices with
high-DPI displays, there can often be multiple device pixels to a CSS
px, while in particularly low-resolution displays (like feature
phones), the user might zoom out to get several CSS px to a device
pixel. And even on desktop browsers, text zooming controls can change
the ratio.
To make Puzzles' rendering look good on an arbitrary device pixel
ratio, we really want the pixels of the canvas to be device pixels,
not CSS px, so that the canvas doesn't have to be scaled by the
browser for display. To correct this, we now control the CSS size of
the puzzle canvas, via its containing <div>, to be the canvas size
divided by the device pixel ratio.
There is a significant gap, which is that this doesn't yet track
changes to the device pixel ratio. This is slightly complicated, so
I'll put it off to the next commit.
Our system for mapping mouse coordinates to canvas coordinates assumed
that the puzzle canvas had the same dimensions in CSS as its own
internal width and height. This is true in the current wrapper HTML,
but it's very easy to accidentally change and there are circumstances
where we might want to deliberately change it in future.
To fix this, we now inspect the CSS size of the canvas when processing
mouse events, and map the coordinates through the scaling and
translation necessary to convert CSS pixels into canvas pixels.
Most of the old URLs don't work any more. As far as I can see, the
new pages have no Flash, and even if they did very few browsers will
still support it.
This is necessary to allow all random seeds to round-trip properly.
It's probably not currently necessary for descriptive game IDs, but it
won't hurt.
I've deliberately gone for encoding only those characters that are not
valid in fragment identifiers to minimise the ugliness of the generated
URLs. For slightly interesting historical reasons, '#' is not valid in
a fragment identifier, so all random seed links end up a little bit
ugly.
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.
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.
The developer documentation claims that save files are long ASCII
strings. This is mostly true, but there's nothing stopping a user
from entering non-ASCII characters as random seeds. The ASCII
property of save files is useful, so encode seeds in hex before
writing them unless they consist only of printable ASCII characters.
Hex-encoded seeds are written under a new key, HEXSEED, to distinguish
them from unencoded seeds. This means that old versions of the code
won't be able to load encoded seeds, but that's not a great loss:
seeds aren't generally portable between versions anyway.
That they are ASCII is implied by their inclusion in save files.
Nothing requires an absence of control characters, but it seems polite
to make them slightly readable.