86 Commits

Author SHA1 Message Date
a3310ab857 New backend function: current_key_label()
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.
2022-12-09 20:48:30 +00:00
c0da615a93 Centralise initial clearing of the puzzle window.
I don't know how I've never thought of this before! Pretty much every
game in this collection has to have a mechanism for noticing when
game_redraw is called for the first time on a new drawstate, and if
so, start by covering the whole window with a filled rectangle of the
background colour. This is a pain for implementers, and also awkward
because the drawstate often has to _work out_ its own pixel size (or
else remember it from when its size method was called).

The backends all do that so that the frontends don't have to guarantee
anything about the initial window contents. But that's a silly
tradeoff to begin with (there are way more backends than frontends, so
this _adds_ work rather than saving it), and also, in this code base
there's a standard way to handle things you don't want to have to do
in every backend _or_ every frontend: do them just once in the midend!

So now that rectangle-drawing operation happens in midend_redraw, and
I've been able to remove it from almost every puzzle. (A couple of
puzzles have other approaches: Slant didn't have a rectangle-draw
because it handles even the game borders using its per-tile redraw
function, and Untangle clears the whole window on every redraw
_anyway_ because it would just be too confusing not to.)

In some cases I've also been able to remove the 'started' flag from
the drawstate. But in many cases that has to stay because it also
triggers drawing of static display furniture other than the
background.
2021-04-25 13:07:59 +01:00
cc7f5503dc Migrate to a CMake-based build system.
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.
2021-03-29 19:02:23 +01:00
72b28b5e71 Fix bit rot in the 'unfinished' subdir.
Several of the source files here won't quite compile any more, because
of minor things like const-correctness and the UI_UPDATE change. Now
they should all build again (without prejudice to how useful they are
once they have built).

The biggest change was to remove the fatal() implementation from the
standalone path.c, because my new plan is that basically everything
that's not linked against a true puzzle frontend will be linked
against nullfe.c, which provides that function anyway.
2021-03-29 18:22:20 +01:00
78bc9ea7f7 Add method for frontends to query the backend's cursor location.
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.
2020-12-07 19:40:06 +00:00
9aa7b7cdfb Group: fix assertion failure in Unreasonable generation.
Generating the game id 6dui#12345 would cause an assertion failure in
a call to latin_solver_place that should never have happened in the
first place, because the "return -1" that ought to have prevented it
was accidentally inside #ifdef STANDALONE_SOLVER.
2020-06-09 14:38:33 +01:00
6285c44610 Group: hard-mode identity deduction.
This fills in the deduction feature I mentioned in commit 7acc554805,
of determining the identity by elimination, having ruled out all other
candidates.

In fact, it goes further: as soon as we know that an element can't be
the group identity, we rule out every possible entry in its row and
column which would involve it acting as a left- or right-identity for
any individual element.

This noticeably increases the number of puzzles that can be solved at
Hard mode without resorting to Unreasonable-level recursion. In a test
of 100 Hard puzzles generated with this change, 80 of them are
reported as Unreasonable by the previous solver.

(One of those puzzles is 12i:m12b9a1zd9i6d10c3y2l11q4r , the example
case that exposed the latin.c validation bug described by the previous
two commits. That was reported as ambiguous with the validation bug,
as Unreasonable with the validation bug fixed, and now it's merely
Hard, because this identity-based deduction eliminates the need for
recursion.)
2020-05-23 09:08:25 +01:00
31cb5227e6 Group: fill in the latin.c validator function.
This actually fixes the example game id mentioned in the previous
commit. Now 12i:m12b9a1zd9i6d10c3y2l11q4r is reported as Unreasonable
rather than ambiguous, on the basis that although the solver still
recurses and finds two filled grids, the validator throws out the
nonsense one at the last minute, leaving only one that's actually
legal.
2020-05-23 09:08:08 +01:00
f21d3e4c74 latin.c: call a user-provided validator function. [NFC]
I've only just realised that there's a false-positive bug in the
latin.c solver framework.

It's designed to solve puzzles in which the solution is a latin square
but with some additional constraints provided by the individual
puzzle, and so during solving, it runs a mixture of its own standard
deduction functions that apply to any latin-square puzzle and extra
functions provided by the client puzzle to do deductions based on the
extra clues or constraints.

But what happens if the _last_ move in the solving process is
performed by one of the latin.c built-in methods, and it causes a
violation of the client puzzle's extra constraints? Nothing will ever
notice, and so the solver will report that the puzzle has a solution
when it actually has none.

An example is the Group game id 12i:m12b9a1zd9i6d10c3y2l11q4r . This
was reported by 'groupsolver -g' as being ambiguous. But if you look
at the two 'solutions' reported in the verbose diagnostics, one of
them is arrant nonsense: it has no identity element at all, and
therefore, it fails associativity all over the place. Actually that
puzzle _does_ have a unique solution.

This bug has been around for ages, and nobody has reported a problem.
For recursive solving, that's not much of a surprise, because it would
cause a spurious accusation of ambiguity, so that at generation time
some valid puzzles would be wrongly discarded, and you'd never see
them. But at non-recursive levels, I can't see a reason why this bug
_couldn't_ have led one of the games to present an actually impossible
puzzle believing it to be soluble.

Possibly this never came up because the other clients of latin.c are
more forgiving of this error in some way. For example, they might all
be very likely to use their extra clues early in the solving process,
so that the requirements are already baked in by the time the final
grid square is filled. I don't know!

Anyway. The fix is to introduce last-minute client-side validation:
whenever the centralised latin_solver thinks it's come up with a
filled grid, it should present it to a puzzle-specific validator
function and check that it's _really_ a legal solution.

This commit does the plumbing for all of that: it introduces the new
validator function as one of the many parameters to latin_solver, and
arranges to call it in an appropriate way during the solving process.
But all the per-puzzle validation functions are empty, for the moment.
2020-05-23 09:08:08 +01:00
8629ef8974 groupsolver: show working when using -v on ambiguous puzzles. 2020-05-22 19:01:52 +01:00
7acc554805 Group: fix loop bounds in the solver.
Applications of the associativity rule were iterating over only n-1 of
the group elements, because I started the loops at 1 rather than 0. So
the solver was a bit underpowered, and would have had trouble with
some perfectly good unambiguous game ids such as 6i:2a5i4a6a1s .

(The latin.c system is very confusing for this, because for historical
reasons due to its ancestry in Solo, grid coordinates run from 0 to
n-1 inclusive, but grid _contents_ run from 1 to n, using 0 as the
'unknown' value. So there's a constant risk of getting confused as to
which kind of value you're dealing with.)

This solver weakness only affects puzzles in 'identity hidden' mode,
because in normal mode, the omitted row and column are those of the
group identity, and applications of associativity involving the
identity never generate anything interesting.
2020-05-20 21:02:04 +01:00
432590a05c Group: add a special deduction about the group identity.
In identity-hidden mode, as soon as you find any table entry that
matches the element indexing its row or column (i.e. a product of
group elements of the form ab=a or ab=b), then you immediately know
which element is the group identity, and you can fill in the rest of
its row and column trivially.

But the Group solver was not actually able to do this deduction.
Proof: it couldn't solve the game id 4i:1d1d1d1, which is trivial if
you take this into account. (a^2=a, so a is the identity, and once you
fill in ax=xa=x for all x, the rest of the grid is forced.)

So I've added dedicated piece of logic to spot the group identity as
soon as anything in its row and column is filled in. Now that puzzle
can be solved.

(A thing that I _haven't_ done here is the higher-level deduction of
determining the identity by elimination. Any table entry that
_doesn't_ match either its row or column rules out both the row and
the column from being the group identity - so as soon as you have
enough table entries to rule out all but one element, you know the
identity must be the remaining one. At the moment, this is just doing
the simple version of the deduction where a single table entry tells
you what _is_ the identity.)

One slightly tricky part is that because this new identity inference
deduction fills in multiple grid entries at a time, it has to be
careful to avoid triggering an assertion failure if the puzzle is
inconsistent - before filling in each entry, it has to make sure the
value it wants to fill in has not been ruled out by something it
filled in already. Without that care, an insoluble puzzle can cause
the solver to crash - and worse, the same can happen on an insoluble
_branch_ of the guesswork-and-backtracking tree in Unreasonable mode.
In both cases, the answer is to make sure you detect the problem
before hitting the assertion, and return -1 for 'inconsistent puzzle'
if you spot it. Then any guesswork branch that ends up in this
situation is cleanly abandoned, and we move on to another one.
2020-05-20 21:02:04 +01:00
c9b3c3896c unfinished/path: some jottings towards a solver.
I still don't actually have a solver design, but a recent conversation
sent my brain in some more useful directions than I've come up with
before, so I might as well at least note it all down.
2020-05-12 22:05:52 +01:00
47cec547e5 Unruly, Group: reference-count the 'immutable' array.
I noticed this during the bool trawl: both of these games have an
array of flags indicating which grid squares are immutable starting
clues, and copy it in every call to dup_game, which is completely
unnecessary because it doesn't change during play. So now each one
lives in a reference-counted structure, as per my usual practice in
similar cases elsewhere.
2018-11-13 21:58:14 +00:00
53798c08d4 Add a missing const in unfinished/sokoban.c.
I noticed this when I temporarily enabled compilation of all the
unfinished puzzles while doing the bool trawl.
2018-11-13 21:52:26 +00:00
5f5b284c0b Use C99 bool within source modules.
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.
2018-11-13 21:48:24 +00:00
a550ea0a47 Replace TRUE/FALSE with C99 true/false throughout.
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.
2018-11-13 21:48:24 +00:00
a76d269cf2 Adopt C99 bool in the game backend API.
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.
2018-11-13 21:34:42 +00:00
19f46dce8c Add request_keys() to the rest of the unfinished games.
I just realized that only "Group" received the request_keys() field in the
game struct for some reason.
2018-04-24 21:32:45 +01:00
60a929a250 Add a request_keys() function with a midend wrapper.
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.
2018-04-22 17:04:50 +01:00
ef6f6427a2 Recipe: centralise dependencies for latin.c.
It's silly to have every puzzle using latin.c separately specify in
its .R file the list of additional modules that latin.c depends on, or
for that matter to have them all have to separately know how to adjust
that for the STANDALONE_SOLVER mode of latin.c.

So I've centralised a new pair of definitions into the core Recipe
file, called LATIN and LATIN_SOLVER, and now a client of latin.c need
only ask for that to get all the necessary dependencies too.

Also, while I'm here, I've moved the non-puzzle-specific 'latincheck'
test program out of unequal.R into the central Recipe.
2018-04-22 16:45:34 +01:00
3276376d1b Assorted char * -> const char * API changes.
I went through all the char * parameters and return values I could see
in puzzles.h by eye and spotted ones that surely ought to have been
const all along.
2017-10-01 16:35:00 +01:00
b3243d7504 Return error messages as 'const char *', not 'char *'.
They're never dynamically allocated, and are almost always string
literals, so const is more appropriate.
2017-10-01 16:34:41 +01:00
de67801b0f Use a proper union in struct config_item.
This allows me to use different types for the mutable, dynamically
allocated string value in a C_STRING control and the fixed constant
list of option names in a C_CHOICES.
2017-10-01 16:34:41 +01:00
eeb2db283d New name UI_UPDATE for interpret_move's return "".
Now midend.c directly tests the returned pointer for equality to this
value, instead of checking whether it's the empty string.

A minor effect of this is that games may now return a dynamically
allocated empty string from interpret_move() and treat it as just
another legal move description. But I don't expect anyone to be
perverse enough to actually do that! The main purpose is that it
avoids returning a string literal from a function whose return type is
a pointer to _non-const_ char, i.e. we are now one step closer to
being able to make this code base clean under -Wwrite-strings.
2017-10-01 15:18:14 +01:00
a7dc17c425 Rework the preset menu system to permit submenus.
To do this, I've completely replaced the API between mid-end and front
end, so any downstream front end maintainers will have to do some
rewriting of their own (sorry). I've done the necessary work in all
five of the front ends I keep in-tree here - Windows, GTK, OS X,
Javascript/Emscripten, and Java/NestedVM - and I've done it in various
different styles (as each front end found most convenient), so that
should provide a variety of sample code to show downstreams how, if
they should need it.

I've left in the old puzzle back-end API function to return a flat
list of presets, so for the moment, all the puzzle backends are
unchanged apart from an extra null pointer appearing in their
top-level game structure. In a future commit I'll actually use the new
feature in a puzzle; perhaps in the further future it might make sense
to migrate all the puzzles to the new API and stop providing back ends
with two alternative ways of doing things, but this seemed like enough
upheaval for one day.
2017-04-26 21:51:23 +01:00
e3a8e64812 Put the game summaries from the website into gamedesc.txt.
More sensible to bring all the pieces of per-puzzle descriptive text
together into one place, so they can be easily reused everywhere
they're needed.
2015-01-13 19:54:46 +00:00
822243de1b Permit selecting a diagonal of squares at once in Group.
When filling in a cyclic subgroup or one of its cosets, I've often
found I wanted to set an entire diagonal to the same thing at once
(usually SW-NE, but the other way round too in non-abelian groups),
and it's a pain having to do that to each square individually.
Restricting multiple selection to diagonals makes it easy to get the
selection I really wanted.
2014-12-18 09:59:53 +00:00
3a35ed2195 Fix a printf 64-bit-cleanness error.
[originally from svn r10098]
2013-11-28 18:32:29 +00:00
cdc1224233 Fix an edge case of divider-obsoletion in Group.
When you drag group elements around, previous dividers are meant to
dissolve whenever the same two elements are no longer on each side of
it. One case in which this didn't happen was that of dragging an
element from the left of a divider to the far right column - the
divider became invisible, but would then startlingly reappear if you
drag that element back to the left of whatever it was left of before.

[originally from svn r10051]
2013-10-09 20:44:51 +00:00
96626d9c10 Faintly highlight the leading diagonal of Group's grid.
This makes it easier to spot elements whose square is known, which is
useful in turn for identifying subgroups.

[originally from svn r10050]
2013-10-09 20:44:50 +00:00
c06792c076 Add a mechanism to the automake system to allow 'make install' to only
install the actual games, not the auxiliary binaries or nullgame.

[originally from svn r9887]
2013-06-30 10:16:57 +00:00
c4ec358d6e Remove spurious "unfinished/" that appeared at the start of the
unfinished puzzles' gamedesc.txt lines due to a Perl error. Ahem.

[originally from svn r9861]
2013-06-08 16:55:33 +00:00
5dda5cf1d0 Rename wingames.lst to gamedesc.txt, and add a couple of extra fields
to it giving each game's "internal" name (as seen in the source file,
.R etc) and also a brief description of the game. The idea of the
latter is that it should be usable as a comment field in .desktop
files and similar.

[originally from svn r9858]
2013-06-08 16:29:15 +00:00
251b21c418 Giant const patch of doom: add a 'const' to every parameter in every
puzzle backend function which ought to have it, and propagate those
consts through to per-puzzle subroutines as needed.

I've recently had to do that to a few specific parameters which were
being misused by particular puzzles (r9657, r9830), which suggests
that it's probably a good idea to do the whole lot pre-emptively
before the next such problem shows up.

[originally from svn r9832]
[r9657 == 3b250baa02a7332510685948bf17576c397b8ceb]
[r9830 == 0b93de904a98f119b1a95d3a53029f1ed4bfb9b3]
2013-04-13 10:37:32 +00:00
0b93de904a Add 'const' to the game_params arguments in validate_desc and
new_desc. Oddities in the 'make test' output brought to my attention
that a few puzzles have been modifying their input game_params for
various reasons; they shouldn't do that, because that's the
game_params held permanently by the midend and it will affect
subsequent game generations if they modify it. So now those arguments
are const, and all the games which previously modified their
game_params now take a copy and modify that instead.

[originally from svn r9830]
2013-04-12 17:11:49 +00:00
0e8a375814 Forgot to add the new 'const' in the unfinished subdirectory. Oops.
[originally from svn r9659]
2012-09-10 18:05:54 +00:00
b16eece9fc New puzzle! Or rather, new-ish, because this one has been lying around
in the 'unfinished' directory for a while, and has now been finished
up thanks to James Harvey putting in some effort and galvanising me to
put in the rest. This is 'Pearl', an implementation of Nikoli's 'Masyu'.

The code in Loopy that generates a random loop along grid edges to use
as the puzzle solution has been abstracted out into loopgen.[ch] so
that Pearl can use it for its puzzle solutions too. I've also
introduced a new utility module called 'tdq' (for 'to-do queue').

[originally from svn r9379]
2012-01-22 14:14:26 +00:00
c4e486c2a1 In Group, the keyboard-controlled cursor should respect user
rearrangement of the rows and columns.

[originally from svn r9372]
2011-12-21 13:46:48 +00:00
73daff3937 Changed my mind about midend_is_solved: I've now reprototyped it as
midend_status(), and given it three return codes for win, (permanent)
loss and game-still-in-play. Depending on what the front end wants to
use it for, it may find any or all of these three states worth
distinguishing from each other.

(I suppose a further enhancement might be to add _non_-permanent loss
as a fourth distinct status, to describe situations in which you can't
play further without pressing Undo but doing so is not completely
pointless. That might reasonably include dead-end situations in Same
Game and Pegs, and blown-self-up situations in Mines and Inertia.
However, I haven't done this at present.)

[originally from svn r9179]
2011-06-19 13:43:35 +00:00
2efc77d2fd Fix warnings generated by gcc 4.6.0 about variables set but not
thereafter read. Most of these changes are just removal of pointless
stuff or trivial reorganisations; one change is actually substantive,
and fixes a bug in Keen's clue selection (the variable 'bad' was
unreferenced not because I shouldn't have set it, but because I
_should_ have referenced it!).

[originally from svn r9164]
2011-05-04 18:22:14 +00:00
980880be1f Add a function to every game backend which indicates whether a game
state is in a solved position, and a midend function wrapping it.

(Or, at least, a situation in which further play is pointless. The
point is, given that game state, would it be a good idea for a front
end that does that sort of thing to proactively provide the option to
start a fresh game?)

[originally from svn r9140]
2011-04-02 16:19:12 +00:00
b2e4437d5b Another UI feature for Group: now you can click between two legend
elements to toggle thick lines in the grid. Helps to delineate
subgroups and cosets, so it's easier to remember what you can
legitimately fill in by associativity.

(I should really stop fiddling with this game's UI; it's far too silly.)

[originally from svn r9084]
2011-02-08 22:13:18 +00:00
540716194d Fix error highlighting after table rearrangement.
[originally from svn r9076]
2011-01-09 11:30:09 +00:00
48f6bfa474 Add the ability to reorder the rows and columns in Group. It becomes
much easier to keep track of things if, once you've identified a
cyclic subgroup, you can move it into a contiguous correctly ordered
block.

[originally from svn r9075]
2011-01-08 15:53:25 +00:00
f1ad38971c Memory leak fixes from Jonas Koelker.
[originally from svn r9001]
2010-09-20 10:36:44 +00:00
795ef3c137 Some minor fixes to the unfinished Pearl solver:
- move critical correctness checks out of diagnostic ifdefs (ahem)
 - move declarations to before conditionally compiled code (we don't
   build in C99 mode round here)
 - tidy up an unsightly blank line while I'm here.

[originally from svn r8969]
2010-06-27 11:17:27 +00:00
6776b0afba Oops. Uncomment the difficulty exceptions! (Also add another
constraint in validate_params.)

[originally from svn r8824]
2010-01-09 17:21:36 +00:00
2500531423 Proof that check_errors() is sufficient.
[originally from svn r8813]
2010-01-05 23:40:42 +00:00
dd4c8ceb1f Refer to group elements by letters instead of numbers, in keeping
with usual abstract group notation. In puzzles with a clear
identity, it's called e.

[originally from svn r8812]
2010-01-05 23:40:41 +00:00