2222 Commits

Author SHA1 Message Date
6fb890e0ea Reference my just-published article about aperiodic tilings.
In commit 8d6647548f7d005 I added the Hats grid type to Loopy, and
mentioned in the commit message that I was very pleased with the
algorithm I came up with.

In fact, I was so pleased with it that I've decided it deserves a
proper public writeup. So I've spent the Easter weekend producing one:

  https://www.chiark.greenend.org.uk/~sgtatham/quasiblog/aperiodic-tilings/

In this commit I adjust the header comments in both penrose.c and
hat.c to refer to the article (replacing a previous comment in
penrose.c to a much less polished page containing a copy of my
jotting-grade personal notes that I sent James Harvey once). Also,
added some code to hatgen.c to output Python hat descriptions in a
similar style to hat-test, which I used to generate a couple of the
more difficult diagrams in the new article, and didn't want to lose.
2023-04-10 14:59:05 +01:00
71cf891fdc Don't allow zero clues in Pattern
Some nonogram implementations allow zero clues so that a row or column
with a single zero clue is equivalent to one with no clues, that is it
has no black squares in it.  Pattern, however, doesn't interpret them
like this and treats a puzzle with a zero clue as insoluble, so it's
not helpful to permit them.

Permitting zero clues also confuses Pattern's memory allocation so
that it can suffer a buffer overrun.  As an example, before this
commit a build with AddressSanitizer would report a buffer overrun
with the description "1:0/0.0" because it tries to put two clues in a
row that can have a maximum of one.
2023-04-08 20:08:16 +01:00
a4c6f21b8e Net: validate co-ordinates in decode_ui()
The offset and centre location should be within the grid.  Otherwise the
redraw code will suffer an assertion failure.  This save file
demonstrates the problem:

SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
VERSION :1:1
GAME    :3:Net
PARAMS  :4:5x5w
CPARAMS :4:5x5w
DESC    :25:9893e85285bb72e6de5182741
UI      :9:O0,0;C6,6
NSTATES :1:1
STATEPOS:1:1
2023-04-08 20:08:16 +01:00
9be7db547a Add a game_state argument to decode_ui()
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.
2023-04-08 20:08:16 +01:00
418cb3a567 Make encode_ui() and decode_ui() optional in back-ends
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.
2023-04-08 20:08:16 +01:00
e411db788c Net: assert that cx and cy are in range in compute_active()
This avoids an out-of-range heap write shortly afterwards.  An assertion
failure is better than a buffer overrun, but still not ideal.  Fixing
the problem properly will require fairly wide-ranging changes, though.

The bug can be demonstrated by loading this save file into a build with
AddressSanitizer:

SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
VERSION :1:1
GAME    :3:Net
PARAMS  :4:5x5w
CPARAMS :4:5x5w
DESC    :25:9893e85285bb72e6de5182741
UI      :9:O0,0;C6,6
NSTATES :1:1
STATEPOS:1:1
2023-04-08 20:08:16 +01:00
d505f08f67 js: explicitly tell Emscripten which browsers to target
Emscripten has settings indicating which browser versions it should
build code for.  These are now by default slightly newer than I'd been
targeting with my hand-written JavaScript.  They also don't include
Firefox 48, which KaiOS 2.5 is based on.

This commit adds CMake variables to set the minimum versions that we
pass to Emscripten.  They default to the earliest versions with
WebAssembly support, except that Firefox 48 is also supported.

I think the main consequence of this change is to stop Emscripten using
sign-extension and mutable-globals in WebAssembly, which it's done by
default since version 3.1.26.
2023-04-06 23:05:52 +01:00
b5f87e6175 js: set -s ENVIRONMENT=web in Emscripten
Puzzles only runs in Web browsers, so there's no need to include
support for Node or (for now at least) running in a Web worker.  This
removes about 5 kiB of code from the boilerplate JavaScript.
2023-04-06 23:05:52 +01:00
3b9cafa09f Fall back to <math.h> if <tgmath.h> doesn't work.
This fixes a build failure introduced by commit 2e48ce132e011e8
yesterday.

When I saw that commit I expected the most likely problem would be in
the NestedVM build, which is currently the thing with the most most
out-of-date C implementation. And indeed the NestedVM toolchain
doesn't have <tgmath.h> - but much more surprisingly, our _Windows_
builds failed too, with a compile error inside <tgmath.h> itself!

I haven't looked closely into the problem yet. Our Windows builds are
done with clang, which comes with its own <tgmath.h> superseding the
standard Windows one. So you'd _hope_ that clang could make sense of
its own header! But perhaps the problem is that this is an unusual
compile mode and hasn't been tested.

My fix is to simply add a cmake check for <tgmath.h> - which doesn't
just check the file's existence, it actually tries compiling a file
that #includes it, so it will detect 'file exists but is mysteriously
broken' just as easily as 'not there at all'. So this makes the builds
start working again, precisely on Ben's theory of opportunistically
using <tgmath.h> where possible and falling back to <math.h>
otherwise.

It looks ugly, though! I'm half tempted to make a new header file
whose job is to include a standard set of system headers, just so that
that nasty #ifdef doesn't have to sit at the top of almost all the
source files. But for the moment this at least gets the build working
again.
2023-04-06 07:08:04 +01:00
d9355041a5 Remove obsolete testbuild.c.
It was originally used for probing the available warning flags in the
compiler, by adding them one by one and making sure this test file
still compiled.

But that whole mechanism was removed in commit 306fab356e357ef, and
since then, testbuild.c has been unused. Belatedly throw it away.
2023-04-06 07:07:30 +01:00
7a66855947 KaiOS: include extra copyright notices in manual
The KaiOS build includes compiled versions of various Emscripten library
files.  These are generally under the MIT licence like Puzzles itself.
The MIT licence requires that the licence, and the copyright notice, be
"included in all copies or substantial portions of the Software."

Since each KaiOS package includes the full manual, which already
contains the licence for Puzzles itself, adding the copyright notices
there seems like the best approach.  I've done this by providing an
additional input file that contains the licences for source files used
by a current Emscripten build.  More automation might be nice, but the
set of copyright notices is unlikely to change very much.  There are
basically one for Emscripten, one for musl, and a few for odd bits of
third-party code embedded in musl.
2023-04-06 00:21:43 +01:00
2e48ce132e Replace <math.h> with <tgmath.h> throughout
C89 provided only double-precision mathematical functions (sin() etc),
and so despite using single-precision elsewhere, those are what Puzzles
has traditionally used.  C99 introduced single-precision equivalents
(sinf() etc), and I hope it's been long enough that we can safely use
them.  Maybe they'll even be faster.

Rather than directly use the single-precision functions, though, we use
the magic macros from <tgmath.h> that automatically choose the precision
of mathematical functions based on their arguments.  This has the
advantage that we only need to change which header we include, and thus
that we can switch back again if some platform has trouble with the new
header.
2023-04-04 21:43:25 +01:00
4fcc1ea601 js: stop using EXTRA_EXPORTED_RUNTIME_METHODS
Current Emscripten has deprecated it in favour of
EXPORTED_RUNTIME_METHODS, and using that avoids generating warnings.
2023-04-03 22:49:27 +01:00
4de5d20368 js: use the "load" event for loading save files
This is in place of the "loadend" event.  In Chromium, (and in the
specification), "loadend" is triggered not only when the file is
loaded but also when loading fails.  Obviously when loading fails we
don't want to try to parse the (nonexistent) resulting file.

Using the "load" event works better, since it's only fired on success,
and we can also have an "error" handler to report problems with
loading files, albeit with no detail at all.

This doesn't seem to make any difference in Firefox, which in my
testing fires "load" and "loadend" on success and nothing at all on
failure.
2023-04-03 22:11:42 +01:00
36c282aaa9 js: Load save files into the C side incrementally
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.
2023-04-03 21:09:57 +01:00
8d3a93ce47 js: load games using FileReader.readAsArrayBuffer()
Using .readAsText() meant that trying to load a non-text file (for
instance something that's not a save file at all) would generate an
"RuntimeError: index out of bounds".  This would then leave the
Emscripten runtime in a broken state.

It might even be possible for a real save file not to be valid UTF-8,
for instance if it came from a platform that used a different character
encoding for random seeds.

There's still a problem with opening very large files, apparently
because Emscripten tries to stuff the entire file onto the C stack.
That will probably have to be fixed by properly exposing the incremental
file-loading API to JavaScript.
2023-04-02 21:17:11 +01:00
2499eb47fa hat-test: more scaling and clipping options.
This adds the ability to turn off hat-test's normal scaling of the
bounding box to fit on an A4 page, which I intended for printing test
patches (but never actually found a need to print one). The --unscaled
mode seems more useful if you're planning to turn the output into an
image, e.g. to use as a desktop background.

Also added --clip, which generates a rectangle completely covered in
hats (i.e. shows any hat that overlaps the output rectangle at all),
as opposed to the normal mode which omits any hat that doesn't fit
_entirely_ in the output rectangle (more similar to what Loopy wants).

Actually generating a desktop background by this method is still a bit
fiddly to get right, but it's better than before.
2023-04-02 14:35:12 +01:00
7956148591 hat-test: fix array underrun.
Having _checked_ whether a hat index in my four-colouring maps was -1, I
then went ahead and used it as an array index anyway, oops!
2023-04-02 14:35:12 +01:00
2296d6f078 Remove penrose_count_tiles().
Nothing uses it, and that's not surprising, because if anything had,
we'd have noticed that it never actually got written!
2023-04-02 14:35:12 +01:00
83244294f5 Move other test main()s out of library source files.
Having stated the principle in the previous commit, I should apply it
consistently. A source file linked into the Puzzles library of common
support code should not also define a main() under ifdef.

This commit only goes as far as the _library_ support modules. It
would be a much bigger job to do the same for all the actual _puzzles_
that have test main()s or standalone-solver main()s. And it's not
necessary, because modifying one of those source files only triggers a
rebuild of _one_ puzzle, not absolutely everything. (Not to mention
that it's quite likely the puzzle and the test main() will need to be
modified in conjunction anyway.)

As in the previous commit, this has required exposing a few internal
API functions as global, and maybe editing them a bit. In particular,
the one-shot internal function that divvy_rectangle() loops on until
it succeeds is now exposed as divvy_rectangle_attempt(), which means
the test program doesn't have to condition a failure counter into the
real function.

I've thrown away penrose-vector-test completely, because that didn't
look like a test program with any ongoing use at all - it was surely
vestigial, while James was getting the vector representation up and
running in the first place.
2023-04-02 14:35:12 +01:00
71e1776094 Move hat-test into its own source file.
I noticed while hacking on hat-test recently that it's quite awkward
to be compiling a test main() program that lives in a source file also
built into the Puzzles support library, because every modification to
main() also triggers a rebuild of the library, and thence of all the
actual puzzles. So it's better if such a test main() has its own
source file.

In order to make hat-test work standalone, I've had to move a lot of
hat.c's internal declarations out into a second header file. This also
means making a bunch of internal functions global, which means they're
also in the namespace of programs other than hat-test, which means in
turn that they should have names with less implicit context.
2023-04-02 14:35:12 +01:00
0bd1a80578 Magnets: add a check that magnets don't wrap between lines
There was nothing in Magnet's description validation to prevent there
being the left end of a magnet at the right end of a row and the right
end of a magnet at the left end of the row below.  Indeed as far as I
can such a game (e.g. 3x3:..2,2..,...,1.1,TLRB*LRLR) plays entirely
correctly except that one magnet is discontinuous.

While this worked, it was entirely an artefact of the particular memory
layout that Magnets uses and shouldn't have been allowed, so I've added
an additional validation rule to stop it.
2023-04-01 20:44:47 +01:00
91735e5019 Correct a range check in Magnets' layout verification
Squares in the grid are numbered from 0, so the upper limit check
needs to use "<=" rather than "<".  Without this, invalid descriptions
can cause a read overrun off the end of the board.
2023-03-31 20:45:37 +01:00
1af1204b9c hat-test: option to generate four-coloured hat tilings.
This commit is purely frivolous even by Puzzles standards, in that
it's totally unrelated to any actual puzzle. But I know at least one
person has already used the 'hat-test' tool in this code base to
generate a patch of hat tiling for decorative purposes, so it's useful
in its own right. Also, now that I've worked out _how_ to do this,
it's a shame not to keep the code.

Of course, any tiling of the plane _can_ be four-coloured, just by the
Four Colour Theorem. But for a tiling with structure it's nicer if the
colouring is related to the structure in some way. And there's a
reasonably nice explicit construction that does just that: the paper
introducing the tiling observes that if each reflected hat is fused
with a particular one of its neighbours, the resulting tiling is
graph-theoretically equivalent to a tiling of the plane by hexagons.
And _that_ tiling can be three-coloured, in a unique way up to colour
choices. This induces a four-colouring of the hat tiling in which the
reflected hats have a colour to themselves, and everything else is
coloured the same as its corresponding hexagon in the three-colouring.

Actually implementing this turns out not to be too difficult using my
coordinate system. I hand-wrote tables giving a patch of colouring for
each of the four kitemaps; then, whenever two kitemaps meet, you can
determine how the colours map to each other by looking at the
overlapping tiles. So I can have hat-test work out the colour of each
tile as it goes.

So hat-test now supports a '--fourcolour' option to apply this
colouring to the output tiling.
2023-03-31 19:29:28 +01:00
52d801a06a Require a grid description for hats grid
Without this, you can cause a null-pointer dereference by passing Loopy
a game description with no grid description, like "10x10t16:".
2023-03-31 15:35:50 +01:00
e6aa7ab6dd hat-test: allow choosing a random number seed.
The default one is always the same, because the main purpose of this
tool is debugging. But one person has already wanted to use it for
actually generating a tiling patch for another use, so let's make it
easier to vary the randomness!
2023-03-30 08:45:06 +01:00
73dab39bf5 Hats: choose the tiling's starting hat more uniformly.
This fills in the missing piece of commit 6f75879e9fe7cb5, which was
trying to make the output patches of tiling as uniformly random as
possible across the whole space of possible ones. I fixed every
_intermediate_ step in the algorithm, but forgot the starting one!
2023-03-30 08:37:17 +01:00
796d0f372f Hats: factor out the parent-choosing system.
NFC, but I'm about to want to use it again elsewhere.
2023-03-30 08:34:57 +01:00
4720eeb1aa Loopy: widen clip rectangle for redrawing clues.
The new Hats tiling generates a lot of clues that are 2-digit numbers.
At large puzzle sizes, the previous clip rectangle didn't quite
include the ends of such a number, meaning that if the number had to
be redrawn in red to highlight an error, the leftmost and rightmost
parts of the text would remain black.
2023-03-28 20:51:26 +01:00
827051dafe hat-test: alternative data output mode to write Python.
This mode emits a sequence of calls to an imaginary Python function.
Should be useful to anyone wanting to post-process the tiling in any
way.
2023-03-28 20:51:26 +01:00
828c7da785 hat-test: allow specifying tiling size on the command line.
I'm tired of recompiling every time I want a different size of test
patch.
2023-03-28 20:51:26 +01:00
22417efad6 Hats tiling: make hat-test draw each hat in one go.
By introducing a second callback between the client of hat.c and the
maybe_report_hat function, I enable the test main() to provide a
different version of that callback, so that instead of enumerating
each kite, it can directly generate a Postscript path per actual hat.
This should make it more useful to people wanting to generate hat
patterns for any other purpose.

The internal callback gets more details than the external one; in
particular, it receives a HatCoords, so that it can colour hats based
on their position in the hierarchical structure.
2023-03-28 20:51:26 +01:00
6f75879e9f Hats tiling: more uniform parent selection.
This tweak improves the uniformity of the generated patches of hat
tiling, by selecting from (the closest 32-bit approximation I can get
to) the limiting probability distribution of finite patches in the
whole plane.

This shouldn't invalidate any grid description that contains enough
coordinates to uniquely specify a piece of tiling - in particular, any
generated by the game itself. But if anyone's been brave enough to
hand-type a grid description in the last two days and left off some of
the coordinates, then those might be invalidated.
2023-03-28 20:51:02 +01:00
2b1167d82a Fix references to the renamed 'auxiliary' directory.
I renamed it in a hurry this morning after the first report of a git
error message on Windows. Now I realise that several source files
referred to the old name, and also need fixing.
2023-03-27 19:31:14 +01:00
0af537d2c0 Rename the 'aux' subdirectory to avoid Windows restrictions.
James Harvey points out that Windows still forbids calling a file
'aux' in any context. Even a directory. Gaaah.
2023-03-27 09:23:41 +01:00
8d6647548f Loopy / grid.c: new grid type, 'Hats'.
The big mathematical news this month is that a polygon has been
discovered that will tile the plane but only aperiodically. Penrose
tiles achieve this with two tile types; it's been an open question for
decades whether you could do it with only one tile. Now someone has
announced the discovery of such a thing, so _obviously_ this
mathematically exciting tiling ought to be one of the Loopy grid
options!

The polygon, named a 'hat' by its discoverers, consists of the union
of eight cells of the 'Kites' periodic tiling that Loopy already
implements. So all the vertex coordinates of the whole tiling are
vertices of the Kites grid, which makes handling the coordinates in an
exact manner a lot easier than Penrose tilings.

What's _harder_ than Penrose tilings is that, although this tiling can
be generated by a vaguely similar system of recursive expansion, the
expansion is geometrically distorting, which means you can't easily
figure out which tiles can be discarded early to save CPU. Instead
I've come up with a completely different system for generating a patch
of tiling, by using a hierarchical coordinate system to track a
location within many levels of the expansion process without ever
simulating the process as a whole. I'm really quite pleased with that
technique, and am tempted to try switching the Penrose generator over
to it too - except that we'd have to keep the old generator around to
stop old game ids being invalidated, and also, I think it would be
slightly trickier without an underlying fixed grid and without
overlaps in the tile expansion system.

However, before coming up with that, I got most of the way through
implementing the more obvious system of actually doing the expansions.
The result worked, but was very slow (because I changed approach
rather than try to implement tree-pruning under distortion). But the
code was reusable for two other useful purposes: it generated the
lookup tables needed for the production code, and it also generated a
lot of useful diagrams. So I've committed it anyway as a supporting
program, in a new 'aux' source subdirectory, and in aux/doc is a
writeup of the coordinate system's concepts, with all those diagrams.
(That's the kind of thing I'd normally put in a huge comment at the
top of the file, but doing all those diagrams in ASCII art would be
beyond miserable.)

From a gameplay perspective: the hat polygon has 13 edges, but one of
them has a vertex of the Kites tiling in the middle, and sometimes two
other tile boundaries meet at that vertex. I've chosen to represent
every hat as having degree 14 for Loopy purposes, because if you only
included that extra vertex when it was needed, then people would be
forever having to check whether this was a 13-hat or a 14-hat and it
would be nightmarish to play.

Even so, there's a lot of clicking involved to turn all those fiddly
individual edges on or off. This grid is noticeably nicer to play in
'autofollow' mode, by setting LOOPY_AUTOFOLLOW in the environment to
either 'fixed' or 'adaptive'. I'm tempted to make 'fixed' the default,
except that I think it would confuse players of ordinary square Loopy!
2023-03-26 20:32:38 +01:00
255744676c KaiOS: be more careful detecting the presence of KaiAds
Now that we catch JavaScript errors and report them to the user, I could
tell that the way we were detecting the presence of getKaiAd() didn't
work because it caused an alert every time a build without KaiAds was
started.  Detecting the presence of a global variable is slightly
tricky, but explicitly looking for it on "window" works and isn't very
ugly.
2023-03-22 22:54:19 +00:00
b66a38bbdc Turn on PUZZLES_SHOW_CURSOR on KaiOS
Most KaiOS devices are primarily keyboard-based, so this seems like a
reasonable approach.

I've also switched to specifying boolean values as JSON booleans because
that works now.
2023-03-22 17:59:14 +00:00
0632a3c2e4 Treat environment variable values beginning with "T" as true
So a value is true iff it begins with 'T', 't', 'Y', or 'y'.  This is
mostly so that naively converting JSON "true" to a string will work
properly, but it should keep LISPers happy too.
2023-03-22 17:56:10 +00:00
6dac51795e Add an environment variable to control initial cursor visibility
If you define PUZZLES_INITIAL_CURSOR=y, puzzles that have a keyboard
cursor will default to making it visible rather than invisible at the
start of a new game.  Behaviour is otherwise the same, so mouse actions
will cause the cursor to vanish and keyboard actions will cause it to
appear.  It's just the default that has changed.

The purpose of this is for use on devices and platforms where the
primary or only means of interaction is keyboard-based.  In those cases,
starting with the keyboard cursor invisible is weird and a bit
confusing.
2023-03-22 16:58:22 +00:00
09c15f206e New shared function, getenv_bool()
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.
2023-03-22 16:06:18 +00:00
adf2a09829 Galaxies: skew grid generation in favour of wiggliness.
Ben complained yesterday that Galaxies had a nasty habit of generating
games whose solution was a boring set of rectangles. Now that even at
Normal mode the solver is better at coping with wiggly tentacled
regions, it seems like a good moment to fix that.

This change arranges that when we initially generate a filled grid, we
try ten times, and pick the wiggliest of the grids we found. This
doesn't make any boring rectangle-filled grid _impossible_ - players
will still have to stay on their toes, and can't rely 100% on at least
a certain number of wiggles existing - but it makes the interesting
grids a lot more likely to come up.

This skew happens before checking solubility. So it doesn't increase
grid generation time by a factor of ten (as it would if we generated
ten _soluble_ grids and picked the wiggliest). It's still a speed
drop, of course, but a more modest one than that.
2023-03-12 15:18:33 +00:00
51818780f8 Galaxies: remove 'solver_recurse_depth' in live use.
It's horrible to have static mutable state in the live puzzle game.
What if some downstream wanted to run the system in multiple threads?

For purposes of limiting the recursion depth, we now pass an 'int depth'
argument to each call to solver_state_inner(). So in the normal build
of the actual puzzle, the static variable isn't needed at all. We only
include it in binaries that are going to want to use it for printing
indented diagnostics: the CLI solver program, and the live puzzle only
if DEBUGGING is defined. The rest of the time, it's absent.

A side effect of this change is that when the recursion code makes a
guess at a particular tile, the message about that is now indented to
the _outer_ level instead of the inner one, because the previous
'depth++' and 'depth--' statements wrapped the whole loop rather than
just the recursive call to the solver inside. This makes recursive
solving much easier to follow!
2023-03-12 14:41:05 +00:00
20f95e3e22 Galaxies: add some higher Unreasonable presets.
10x10 and 15x15 Unreasonable are now feasible, so why not include them?
2023-03-12 14:26:45 +00:00
47de8f449c Galaxies: remove the 'maxtries' system.
Most games in this collection don't have one. If you ask them for a
hard puzzle, they'll just keep looping round until they actually
manage to deliver one. We try to arrange that the standard presets
don't take too long to generate, but if the user turns up the game
size _and_ uses an expensive difficulty level, our view is that that's
up to them.

After fixing the bug from the previous commit in which the Galaxies
recursion depth limit was not actually doing anything, even 15x15
Unreasonable now generates happily in under a second. So I don't see
any reason why Galaxies should be an exception.

Hence, now if you ask Galaxies for an Unreasonable puzzle, you _will_
get a puzzle that it grades as Unreasonable, even if that takes a long
time to generate.
2023-03-12 14:24:19 +00:00
f018ef97d3 Galaxies: fix recursion depth limit in solver.
The static variable 'solver_recurse_depth' is _mostly_ used by the
standalone solver, to appropriately indent the solver diagnostics for
the current recursion level. So most uses of it are guarded by an
'#ifdef STANDALONE_SOLVER' statement, or some equivalent (such as
being inside the solvep() macro).

One exception is the check that limits the recursion depth to 5, to
avoid getting hung up forever on a too-hard game. Unfortunately, this
check depends on the variable actually incrementing when we recurse
another level - and it wasn't, because the increment itself was under
ifdef! So the generator in live Galaxies could recurse arbitrarily
deep, and generate puzzles that the standalone solver found too hard
_even_ at Unreasonable mode.

Removed the ifdefs, so that solver_recurse_depth is now incremented
and decremented. Also, make sure to initialise the depth to 0 at the
start of a solver run, just in case it had a bogus value left over
from a previous run.
2023-03-12 14:24:19 +00:00
08009f3949 galaxiessolver: fix soak-test mode.
It called new_game_desc with aux=NULL. But new_game_desc
unconditionally writes through aux, expecting it to be valid always.
2023-03-12 14:24:19 +00:00
1dfc38c2ec Galaxies: new deduction by counting liberties of exclaves.
I've often noticed that Galaxies on 7x7 Unreasonable often generates
puzzles that _I_ don't feel as if I had to use recursion and
backtracking to solve, suggesting that there's an efficient mode of
reasoning available that the puzzle could be using and isn't.

One reason for this is that sometimes Galaxies gives up on trying to
generate an Unreasonable puzzle (if its MAXTRIES counter runs out) and
knowingly falls back to Normal. But that's not the only reason. The
other day I got the puzzle 7x7:gsjgzhfedwgzhd, which Galaxies's own
solver rates as Unreasonable, and I still think it should be Normal.

The full solution to that puzzle is this:

  +-+-+-+-+-+-+-+
  |y x|  o  | | |
  + +-+-+-+-+ +o+
  | |   | | o | |
  + + o + + +-+-+
  | |   | | | | |
  + +-+-+ +-+o+ +
  |   o   |o| |o|
  + +-+-+ +-+-+ +
  | |   | | o | |
  + + o + +-+-+-+
  | |   | |     |
  +-+-+-+ + +o+ +
  | o |x y|     |
  +-+-+-+-+-+-+-+

and Galaxies's Normal-mode solver gets stuck on it at the point where
it's managed to deduce that the tiles labelled 'x' can't possibly be
associated with any dot other than leftmost dot on the centre row, but
is then unable to figure out that the tiles labelled 'y' must also be
associated with that dot. But clearly they must, because they're boxed
in on all other sides (by the grid edge, and by tiles whose
associations are totally obvious), so if either 'x' tile is to find
_any_ path back to its home dot, it must go through the neighbouring
'y' tile.

So, this commit adds the missing deduction: we use a dsf to identify
'exclaves' (connected sets of tiles all associated to the same dot,
but which do not actually contain the dot), and for each exclave we
count its 'liberties' (unassociated tiles bordering the exclave, i.e.
which would extend the exclave if associated to the same dot). Any
exclave with only one liberty must extend into that tile, or else it
would be cut off completely from its home dot. In this case, each 'x'
tile is an exclave by itself, with 'y' its only liberty. (And once
that deduction is done, the pair {x,y} become a larger exclave, which
can be deduced in the same way to connect to the next tile.)

I think this is a deduction rule simple and obvious enough that it
should go in at Normal mode. I've been using it all along in my own
play, and was surprised to find the game wasn't already taking it into
account. In addition, in a quick cross-test of the two versions,
_most_ 7x7 Normal games generated by the modified Galaxies are still
rated as Normal by the old less powerful solver. So it doesn't extend
the difficulty of Normal mode by very much, if at all.

Another benefit is that this should make Normal puzzles more likely to
contain twisty regions of this type.

Also, of course, the usual effect of adding extra deductions at levels
below Unreasonable means that actually Unreasonable puzzles become
that much more tricky!

I have a couple of ideas for extending this technique to be more
powerful still (filled in as comments at the top of the file). For the
moment, I've just done the most obvious version. Perhaps the others
might need to go in at a higher difficulty level.
2023-03-12 11:20:43 +00:00
28aefee8fc Galaxies: add a missing \n in a diagnostic. 2023-03-12 10:57:53 +00:00
8c5077ee88 Galaxies: fix edge coordinates in a diagnostic.
The coordinates in this 'Setting edge' message from the solver don't
match the coordinates of the edge that is actually set. No wonder the
solver output was confusing!
2023-03-10 18:46:06 +00:00