chiark / gitweb /
sgt-puzzles (20161228.7cae89f-1) unstable; urgency=medium
authorBen Hutchings <ben@decadent.org.uk>
Tue, 17 Jan 2017 23:57:33 +0000 (23:57 +0000)
committerBen Hutchings <ben@decadent.org.uk>
Tue, 17 Jan 2017 23:57:33 +0000 (23:57 +0000)
  * New upstream version
  * debian/rules: Generate menu file automatically, fixing the omission
    of Undead and Unruly (Closes: #832797)
  * Use debhelper compatibility level 9
  * debian/control: Update Standards-Version to 3.9.8; no changes needed
  * Build with Gtk+ 3

[dgit import unpatched sgt-puzzles 20161228.7cae89f-1]

885 files changed:
HACKING [new file with mode: 0644]
LICENCE [new file with mode: 0644]
Makefile.am [new file with mode: 0644]
Makefile.cyg [new file with mode: 0644]
Makefile.doc [new file with mode: 0644]
Makefile.emcc [new file with mode: 0644]
Makefile.gnustep [new file with mode: 0644]
Makefile.gtk [new file with mode: 0644]
Makefile.in [new file with mode: 0644]
Makefile.nestedvm [new file with mode: 0644]
Makefile.osx [new file with mode: 0644]
Makefile.vc [new file with mode: 0644]
Makefile.wce [new file with mode: 0644]
README [new file with mode: 0644]
Recipe [new file with mode: 0644]
aclocal.m4 [new file with mode: 0644]
blackbox.R [new file with mode: 0644]
blackbox.c [new file with mode: 0644]
bridges.R [new file with mode: 0644]
bridges.c [new file with mode: 0644]
chm.but [new file with mode: 0644]
combi.c [new file with mode: 0644]
compile [new file with mode: 0755]
configure [new file with mode: 0755]
configure.ac [new file with mode: 0644]
cube.R [new file with mode: 0644]
cube.c [new file with mode: 0644]
debian/README.Debian [moved from README.Debian with 100% similarity]
debian/changelog [moved from changelog with 100% similarity]
debian/compat [moved from compat with 100% similarity]
debian/control [moved from control with 100% similarity]
debian/copyright [moved from copyright with 100% similarity]
debian/desktop/sgt-blackbox.desktop [moved from desktop/sgt-blackbox.desktop with 100% similarity]
debian/desktop/sgt-bridges.desktop [moved from desktop/sgt-bridges.desktop with 100% similarity]
debian/desktop/sgt-cube.desktop [moved from desktop/sgt-cube.desktop with 100% similarity]
debian/desktop/sgt-dominosa.desktop [moved from desktop/sgt-dominosa.desktop with 100% similarity]
debian/desktop/sgt-fifteen.desktop [moved from desktop/sgt-fifteen.desktop with 100% similarity]
debian/desktop/sgt-filling.desktop [moved from desktop/sgt-filling.desktop with 100% similarity]
debian/desktop/sgt-flip.desktop [moved from desktop/sgt-flip.desktop with 100% similarity]
debian/desktop/sgt-flood.desktop [moved from desktop/sgt-flood.desktop with 100% similarity]
debian/desktop/sgt-galaxies.desktop [moved from desktop/sgt-galaxies.desktop with 100% similarity]
debian/desktop/sgt-guess.desktop [moved from desktop/sgt-guess.desktop with 100% similarity]
debian/desktop/sgt-inertia.desktop [moved from desktop/sgt-inertia.desktop with 100% similarity]
debian/desktop/sgt-keen.desktop [moved from desktop/sgt-keen.desktop with 100% similarity]
debian/desktop/sgt-lightup.desktop [moved from desktop/sgt-lightup.desktop with 100% similarity]
debian/desktop/sgt-loopy.desktop [moved from desktop/sgt-loopy.desktop with 100% similarity]
debian/desktop/sgt-magnets.desktop [moved from desktop/sgt-magnets.desktop with 100% similarity]
debian/desktop/sgt-map.desktop [moved from desktop/sgt-map.desktop with 100% similarity]
debian/desktop/sgt-mines.desktop [moved from desktop/sgt-mines.desktop with 100% similarity]
debian/desktop/sgt-net.desktop [moved from desktop/sgt-net.desktop with 100% similarity]
debian/desktop/sgt-netslide.desktop [moved from desktop/sgt-netslide.desktop with 100% similarity]
debian/desktop/sgt-palisade.desktop [moved from desktop/sgt-palisade.desktop with 100% similarity]
debian/desktop/sgt-pattern.desktop [moved from desktop/sgt-pattern.desktop with 100% similarity]
debian/desktop/sgt-pearl.desktop [moved from desktop/sgt-pearl.desktop with 100% similarity]
debian/desktop/sgt-pegs.desktop [moved from desktop/sgt-pegs.desktop with 100% similarity]
debian/desktop/sgt-range.desktop [moved from desktop/sgt-range.desktop with 100% similarity]
debian/desktop/sgt-rect.desktop [moved from desktop/sgt-rect.desktop with 100% similarity]
debian/desktop/sgt-samegame.desktop [moved from desktop/sgt-samegame.desktop with 100% similarity]
debian/desktop/sgt-signpost.desktop [moved from desktop/sgt-signpost.desktop with 100% similarity]
debian/desktop/sgt-singles.desktop [moved from desktop/sgt-singles.desktop with 100% similarity]
debian/desktop/sgt-sixteen.desktop [moved from desktop/sgt-sixteen.desktop with 100% similarity]
debian/desktop/sgt-slant.desktop [moved from desktop/sgt-slant.desktop with 100% similarity]
debian/desktop/sgt-solo.desktop [moved from desktop/sgt-solo.desktop with 100% similarity]
debian/desktop/sgt-tents.desktop [moved from desktop/sgt-tents.desktop with 100% similarity]
debian/desktop/sgt-towers.desktop [moved from desktop/sgt-towers.desktop with 100% similarity]
debian/desktop/sgt-tracks.desktop [moved from desktop/sgt-tracks.desktop with 100% similarity]
debian/desktop/sgt-twiddle.desktop [moved from desktop/sgt-twiddle.desktop with 100% similarity]
debian/desktop/sgt-undead.desktop [moved from desktop/sgt-undead.desktop with 100% similarity]
debian/desktop/sgt-unequal.desktop [moved from desktop/sgt-unequal.desktop with 100% similarity]
debian/desktop/sgt-unruly.desktop [moved from desktop/sgt-unruly.desktop with 100% similarity]
debian/desktop/sgt-untangle.desktop [moved from desktop/sgt-untangle.desktop with 100% similarity]
debian/patches/102_fix-pearl-min-dimensions.diff [moved from patches/102_fix-pearl-min-dimensions.diff with 100% similarity]
debian/patches/201_make-more-docs.diff [moved from patches/201_make-more-docs.diff with 100% similarity]
debian/patches/202_online-help.diff [moved from patches/202_online-help.diff with 100% similarity]
debian/patches/206_translate-docs.diff [moved from patches/206_translate-docs.diff with 100% similarity]
debian/patches/207_slant-shade-filled.diff [moved from patches/207_slant-shade-filled.diff with 100% similarity]
debian/patches/302_rename-binaries.diff [moved from patches/302_rename-binaries.diff with 100% similarity]
debian/patches/303_show-debian-version-number.diff [moved from patches/303_show-debian-version-number.diff with 100% similarity]
debian/patches/304_combine-binaries.diff [moved from patches/304_combine-binaries.diff with 100% similarity]
debian/patches/fix-ftbfs-with-gcc-6.patch [moved from patches/fix-ftbfs-with-gcc-6.patch with 100% similarity]
debian/patches/series [moved from patches/series with 100% similarity]
debian/po/de.po [moved from po/de.po with 100% similarity]
debian/po/puzzles-doc.pot [moved from po/puzzles-doc.pot with 100% similarity]
debian/po/puzzles-doc.pot.head [moved from po/puzzles-doc.pot.head with 100% similarity]
debian/rules [moved from rules with 100% similarity]
debian/sgt-puzzles.dirs [moved from sgt-puzzles.dirs with 100% similarity]
debian/sgt-puzzles.docs [moved from sgt-puzzles.docs with 100% similarity]
debian/source/format [moved from source/format with 100% similarity]
depcomp [new file with mode: 0755]
devel.but [new file with mode: 0644]
divvy.c [new file with mode: 0644]
dominosa.R [new file with mode: 0644]
dominosa.c [new file with mode: 0644]
drawing.c [new file with mode: 0644]
dsf.c [new file with mode: 0644]
emcc.c [new file with mode: 0644]
fifteen.R [new file with mode: 0644]
fifteen.c [new file with mode: 0644]
filling.R [new file with mode: 0644]
filling.c [new file with mode: 0644]
findloop.c [new file with mode: 0644]
flip.R [new file with mode: 0644]
flip.c [new file with mode: 0644]
flood.R [new file with mode: 0644]
flood.c [new file with mode: 0644]
galaxies.R [new file with mode: 0644]
galaxies.c [new file with mode: 0644]
grid.c [new file with mode: 0644]
grid.h [new file with mode: 0644]
gtk.c [new file with mode: 0644]
guess.R [new file with mode: 0644]
guess.c [new file with mode: 0644]
icons/Makefile [new file with mode: 0644]
icons/blackbox-16d24.png [new file with mode: 0644]
icons/blackbox-16d4.png [new file with mode: 0644]
icons/blackbox-16d8.png [new file with mode: 0644]
icons/blackbox-32d24.png [new file with mode: 0644]
icons/blackbox-32d4.png [new file with mode: 0644]
icons/blackbox-32d8.png [new file with mode: 0644]
icons/blackbox-48d24.png [new file with mode: 0644]
icons/blackbox-48d4.png [new file with mode: 0644]
icons/blackbox-48d8.png [new file with mode: 0644]
icons/blackbox-base.png [new file with mode: 0644]
icons/blackbox-ibase.png [new file with mode: 0644]
icons/blackbox-ibase4.png [new file with mode: 0644]
icons/blackbox-icon.c [new file with mode: 0644]
icons/blackbox-web.png [new file with mode: 0644]
icons/blackbox.ico [new file with mode: 0644]
icons/blackbox.rc [new file with mode: 0644]
icons/blackbox.sav [new file with mode: 0644]
icons/bridges-16d24.png [new file with mode: 0644]
icons/bridges-16d4.png [new file with mode: 0644]
icons/bridges-16d8.png [new file with mode: 0644]
icons/bridges-32d24.png [new file with mode: 0644]
icons/bridges-32d4.png [new file with mode: 0644]
icons/bridges-32d8.png [new file with mode: 0644]
icons/bridges-48d24.png [new file with mode: 0644]
icons/bridges-48d4.png [new file with mode: 0644]
icons/bridges-48d8.png [new file with mode: 0644]
icons/bridges-base.png [new file with mode: 0644]
icons/bridges-ibase.png [new file with mode: 0644]
icons/bridges-ibase4.png [new file with mode: 0644]
icons/bridges-icon.c [new file with mode: 0644]
icons/bridges-web.png [new file with mode: 0644]
icons/bridges.ico [new file with mode: 0644]
icons/bridges.rc [new file with mode: 0644]
icons/bridges.sav [new file with mode: 0644]
icons/cicon.pl [new file with mode: 0755]
icons/crop.sh [new file with mode: 0755]
icons/cube-16d24.png [new file with mode: 0644]
icons/cube-16d4.png [new file with mode: 0644]
icons/cube-16d8.png [new file with mode: 0644]
icons/cube-32d24.png [new file with mode: 0644]
icons/cube-32d4.png [new file with mode: 0644]
icons/cube-32d8.png [new file with mode: 0644]
icons/cube-48d24.png [new file with mode: 0644]
icons/cube-48d4.png [new file with mode: 0644]
icons/cube-48d8.png [new file with mode: 0644]
icons/cube-base.png [new file with mode: 0644]
icons/cube-ibase.png [new file with mode: 0644]
icons/cube-ibase4.png [new file with mode: 0644]
icons/cube-icon.c [new file with mode: 0644]
icons/cube-web.png [new file with mode: 0644]
icons/cube.ico [new file with mode: 0644]
icons/cube.rc [new file with mode: 0644]
icons/cube.sav [new file with mode: 0644]
icons/dominosa-16d24.png [new file with mode: 0644]
icons/dominosa-16d4.png [new file with mode: 0644]
icons/dominosa-16d8.png [new file with mode: 0644]
icons/dominosa-32d24.png [new file with mode: 0644]
icons/dominosa-32d4.png [new file with mode: 0644]
icons/dominosa-32d8.png [new file with mode: 0644]
icons/dominosa-48d24.png [new file with mode: 0644]
icons/dominosa-48d4.png [new file with mode: 0644]
icons/dominosa-48d8.png [new file with mode: 0644]
icons/dominosa-base.png [new file with mode: 0644]
icons/dominosa-ibase.png [new file with mode: 0644]
icons/dominosa-ibase4.png [new file with mode: 0644]
icons/dominosa-icon.c [new file with mode: 0644]
icons/dominosa-web.png [new file with mode: 0644]
icons/dominosa.ico [new file with mode: 0644]
icons/dominosa.rc [new file with mode: 0644]
icons/dominosa.sav [new file with mode: 0644]
icons/fifteen-16d24.png [new file with mode: 0644]
icons/fifteen-16d4.png [new file with mode: 0644]
icons/fifteen-16d8.png [new file with mode: 0644]
icons/fifteen-32d24.png [new file with mode: 0644]
icons/fifteen-32d4.png [new file with mode: 0644]
icons/fifteen-32d8.png [new file with mode: 0644]
icons/fifteen-48d24.png [new file with mode: 0644]
icons/fifteen-48d4.png [new file with mode: 0644]
icons/fifteen-48d8.png [new file with mode: 0644]
icons/fifteen-base.png [new file with mode: 0644]
icons/fifteen-ibase.png [new file with mode: 0644]
icons/fifteen-ibase4.png [new file with mode: 0644]
icons/fifteen-icon.c [new file with mode: 0644]
icons/fifteen-web.png [new file with mode: 0644]
icons/fifteen.ico [new file with mode: 0644]
icons/fifteen.rc [new file with mode: 0644]
icons/fifteen.sav [new file with mode: 0644]
icons/filling-16d24.png [new file with mode: 0644]
icons/filling-16d4.png [new file with mode: 0644]
icons/filling-16d8.png [new file with mode: 0644]
icons/filling-32d24.png [new file with mode: 0644]
icons/filling-32d4.png [new file with mode: 0644]
icons/filling-32d8.png [new file with mode: 0644]
icons/filling-48d24.png [new file with mode: 0644]
icons/filling-48d4.png [new file with mode: 0644]
icons/filling-48d8.png [new file with mode: 0644]
icons/filling-base.png [new file with mode: 0644]
icons/filling-ibase.png [new file with mode: 0644]
icons/filling-ibase4.png [new file with mode: 0644]
icons/filling-icon.c [new file with mode: 0644]
icons/filling-web.png [new file with mode: 0644]
icons/filling.ico [new file with mode: 0644]
icons/filling.rc [new file with mode: 0644]
icons/filling.sav [new file with mode: 0644]
icons/flip-16d24.png [new file with mode: 0644]
icons/flip-16d4.png [new file with mode: 0644]
icons/flip-16d8.png [new file with mode: 0644]
icons/flip-32d24.png [new file with mode: 0644]
icons/flip-32d4.png [new file with mode: 0644]
icons/flip-32d8.png [new file with mode: 0644]
icons/flip-48d24.png [new file with mode: 0644]
icons/flip-48d4.png [new file with mode: 0644]
icons/flip-48d8.png [new file with mode: 0644]
icons/flip-base.png [new file with mode: 0644]
icons/flip-ibase.png [new file with mode: 0644]
icons/flip-ibase4.png [new file with mode: 0644]
icons/flip-icon.c [new file with mode: 0644]
icons/flip-web.png [new file with mode: 0644]
icons/flip.ico [new file with mode: 0644]
icons/flip.rc [new file with mode: 0644]
icons/flip.sav [new file with mode: 0644]
icons/flood-16d24.png [new file with mode: 0644]
icons/flood-16d4.png [new file with mode: 0644]
icons/flood-16d8.png [new file with mode: 0644]
icons/flood-32d24.png [new file with mode: 0644]
icons/flood-32d4.png [new file with mode: 0644]
icons/flood-32d8.png [new file with mode: 0644]
icons/flood-48d24.png [new file with mode: 0644]
icons/flood-48d4.png [new file with mode: 0644]
icons/flood-48d8.png [new file with mode: 0644]
icons/flood-base.png [new file with mode: 0644]
icons/flood-ibase.png [new file with mode: 0644]
icons/flood-ibase4.png [new file with mode: 0644]
icons/flood-icon.c [new file with mode: 0644]
icons/flood-web.png [new file with mode: 0644]
icons/flood.ico [new file with mode: 0644]
icons/flood.rc [new file with mode: 0644]
icons/flood.sav [new file with mode: 0644]
icons/galaxies-16d24.png [new file with mode: 0644]
icons/galaxies-16d4.png [new file with mode: 0644]
icons/galaxies-16d8.png [new file with mode: 0644]
icons/galaxies-32d24.png [new file with mode: 0644]
icons/galaxies-32d4.png [new file with mode: 0644]
icons/galaxies-32d8.png [new file with mode: 0644]
icons/galaxies-48d24.png [new file with mode: 0644]
icons/galaxies-48d4.png [new file with mode: 0644]
icons/galaxies-48d8.png [new file with mode: 0644]
icons/galaxies-base.png [new file with mode: 0644]
icons/galaxies-ibase.png [new file with mode: 0644]
icons/galaxies-ibase4.png [new file with mode: 0644]
icons/galaxies-icon.c [new file with mode: 0644]
icons/galaxies-web.png [new file with mode: 0644]
icons/galaxies.ico [new file with mode: 0644]
icons/galaxies.rc [new file with mode: 0644]
icons/galaxies.sav [new file with mode: 0644]
icons/guess-16d24.png [new file with mode: 0644]
icons/guess-16d4.png [new file with mode: 0644]
icons/guess-16d8.png [new file with mode: 0644]
icons/guess-32d24.png [new file with mode: 0644]
icons/guess-32d4.png [new file with mode: 0644]
icons/guess-32d8.png [new file with mode: 0644]
icons/guess-48d24.png [new file with mode: 0644]
icons/guess-48d4.png [new file with mode: 0644]
icons/guess-48d8.png [new file with mode: 0644]
icons/guess-base.png [new file with mode: 0644]
icons/guess-ibase.png [new file with mode: 0644]
icons/guess-ibase4.png [new file with mode: 0644]
icons/guess-icon.c [new file with mode: 0644]
icons/guess-web.png [new file with mode: 0644]
icons/guess.ico [new file with mode: 0644]
icons/guess.rc [new file with mode: 0644]
icons/guess.sav [new file with mode: 0644]
icons/icon.pl [new file with mode: 0755]
icons/inertia-16d24.png [new file with mode: 0644]
icons/inertia-16d4.png [new file with mode: 0644]
icons/inertia-16d8.png [new file with mode: 0644]
icons/inertia-32d24.png [new file with mode: 0644]
icons/inertia-32d4.png [new file with mode: 0644]
icons/inertia-32d8.png [new file with mode: 0644]
icons/inertia-48d24.png [new file with mode: 0644]
icons/inertia-48d4.png [new file with mode: 0644]
icons/inertia-48d8.png [new file with mode: 0644]
icons/inertia-base.png [new file with mode: 0644]
icons/inertia-ibase.png [new file with mode: 0644]
icons/inertia-ibase4.png [new file with mode: 0644]
icons/inertia-icon.c [new file with mode: 0644]
icons/inertia-web.png [new file with mode: 0644]
icons/inertia.ico [new file with mode: 0644]
icons/inertia.rc [new file with mode: 0644]
icons/inertia.sav [new file with mode: 0644]
icons/keen-16d24.png [new file with mode: 0644]
icons/keen-16d4.png [new file with mode: 0644]
icons/keen-16d8.png [new file with mode: 0644]
icons/keen-32d24.png [new file with mode: 0644]
icons/keen-32d4.png [new file with mode: 0644]
icons/keen-32d8.png [new file with mode: 0644]
icons/keen-48d24.png [new file with mode: 0644]
icons/keen-48d4.png [new file with mode: 0644]
icons/keen-48d8.png [new file with mode: 0644]
icons/keen-base.png [new file with mode: 0644]
icons/keen-ibase.png [new file with mode: 0644]
icons/keen-ibase4.png [new file with mode: 0644]
icons/keen-icon.c [new file with mode: 0644]
icons/keen-web.png [new file with mode: 0644]
icons/keen.ico [new file with mode: 0644]
icons/keen.rc [new file with mode: 0644]
icons/keen.sav [new file with mode: 0644]
icons/lightup-16d24.png [new file with mode: 0644]
icons/lightup-16d4.png [new file with mode: 0644]
icons/lightup-16d8.png [new file with mode: 0644]
icons/lightup-32d24.png [new file with mode: 0644]
icons/lightup-32d4.png [new file with mode: 0644]
icons/lightup-32d8.png [new file with mode: 0644]
icons/lightup-48d24.png [new file with mode: 0644]
icons/lightup-48d4.png [new file with mode: 0644]
icons/lightup-48d8.png [new file with mode: 0644]
icons/lightup-base.png [new file with mode: 0644]
icons/lightup-ibase.png [new file with mode: 0644]
icons/lightup-ibase4.png [new file with mode: 0644]
icons/lightup-icon.c [new file with mode: 0644]
icons/lightup-web.png [new file with mode: 0644]
icons/lightup.ico [new file with mode: 0644]
icons/lightup.rc [new file with mode: 0644]
icons/lightup.sav [new file with mode: 0644]
icons/loopy-16d24.png [new file with mode: 0644]
icons/loopy-16d4.png [new file with mode: 0644]
icons/loopy-16d8.png [new file with mode: 0644]
icons/loopy-32d24.png [new file with mode: 0644]
icons/loopy-32d4.png [new file with mode: 0644]
icons/loopy-32d8.png [new file with mode: 0644]
icons/loopy-48d24.png [new file with mode: 0644]
icons/loopy-48d4.png [new file with mode: 0644]
icons/loopy-48d8.png [new file with mode: 0644]
icons/loopy-base.png [new file with mode: 0644]
icons/loopy-ibase.png [new file with mode: 0644]
icons/loopy-ibase4.png [new file with mode: 0644]
icons/loopy-icon.c [new file with mode: 0644]
icons/loopy-web.png [new file with mode: 0644]
icons/loopy.ico [new file with mode: 0644]
icons/loopy.rc [new file with mode: 0644]
icons/loopy.sav [new file with mode: 0644]
icons/magnets-16d24.png [new file with mode: 0644]
icons/magnets-16d4.png [new file with mode: 0644]
icons/magnets-16d8.png [new file with mode: 0644]
icons/magnets-32d24.png [new file with mode: 0644]
icons/magnets-32d4.png [new file with mode: 0644]
icons/magnets-32d8.png [new file with mode: 0644]
icons/magnets-48d24.png [new file with mode: 0644]
icons/magnets-48d4.png [new file with mode: 0644]
icons/magnets-48d8.png [new file with mode: 0644]
icons/magnets-base.png [new file with mode: 0644]
icons/magnets-ibase.png [new file with mode: 0644]
icons/magnets-ibase4.png [new file with mode: 0644]
icons/magnets-icon.c [new file with mode: 0644]
icons/magnets-web.png [new file with mode: 0644]
icons/magnets.ico [new file with mode: 0644]
icons/magnets.rc [new file with mode: 0644]
icons/magnets.sav [new file with mode: 0644]
icons/map-16d24.png [new file with mode: 0644]
icons/map-16d4.png [new file with mode: 0644]
icons/map-16d8.png [new file with mode: 0644]
icons/map-32d24.png [new file with mode: 0644]
icons/map-32d4.png [new file with mode: 0644]
icons/map-32d8.png [new file with mode: 0644]
icons/map-48d24.png [new file with mode: 0644]
icons/map-48d4.png [new file with mode: 0644]
icons/map-48d8.png [new file with mode: 0644]
icons/map-base.png [new file with mode: 0644]
icons/map-ibase.png [new file with mode: 0644]
icons/map-ibase4.png [new file with mode: 0644]
icons/map-icon.c [new file with mode: 0644]
icons/map-web.png [new file with mode: 0644]
icons/map.ico [new file with mode: 0644]
icons/map.rc [new file with mode: 0644]
icons/map.sav [new file with mode: 0644]
icons/mines-16d24.png [new file with mode: 0644]
icons/mines-16d4.png [new file with mode: 0644]
icons/mines-16d8.png [new file with mode: 0644]
icons/mines-32d24.png [new file with mode: 0644]
icons/mines-32d4.png [new file with mode: 0644]
icons/mines-32d8.png [new file with mode: 0644]
icons/mines-48d24.png [new file with mode: 0644]
icons/mines-48d4.png [new file with mode: 0644]
icons/mines-48d8.png [new file with mode: 0644]
icons/mines-base.png [new file with mode: 0644]
icons/mines-ibase.png [new file with mode: 0644]
icons/mines-ibase4.png [new file with mode: 0644]
icons/mines-icon.c [new file with mode: 0644]
icons/mines-web.png [new file with mode: 0644]
icons/mines.ico [new file with mode: 0644]
icons/mines.rc [new file with mode: 0644]
icons/mines.sav [new file with mode: 0644]
icons/net-16d24.png [new file with mode: 0644]
icons/net-16d4.png [new file with mode: 0644]
icons/net-16d8.png [new file with mode: 0644]
icons/net-32d24.png [new file with mode: 0644]
icons/net-32d4.png [new file with mode: 0644]
icons/net-32d8.png [new file with mode: 0644]
icons/net-48d24.png [new file with mode: 0644]
icons/net-48d4.png [new file with mode: 0644]
icons/net-48d8.png [new file with mode: 0644]
icons/net-base.png [new file with mode: 0644]
icons/net-ibase.png [new file with mode: 0644]
icons/net-ibase4.png [new file with mode: 0644]
icons/net-icon.c [new file with mode: 0644]
icons/net-web.png [new file with mode: 0644]
icons/net.ico [new file with mode: 0644]
icons/net.rc [new file with mode: 0644]
icons/net.sav [new file with mode: 0644]
icons/netslide-16d24.png [new file with mode: 0644]
icons/netslide-16d4.png [new file with mode: 0644]
icons/netslide-16d8.png [new file with mode: 0644]
icons/netslide-32d24.png [new file with mode: 0644]
icons/netslide-32d4.png [new file with mode: 0644]
icons/netslide-32d8.png [new file with mode: 0644]
icons/netslide-48d24.png [new file with mode: 0644]
icons/netslide-48d4.png [new file with mode: 0644]
icons/netslide-48d8.png [new file with mode: 0644]
icons/netslide-base.png [new file with mode: 0644]
icons/netslide-ibase.png [new file with mode: 0644]
icons/netslide-ibase4.png [new file with mode: 0644]
icons/netslide-icon.c [new file with mode: 0644]
icons/netslide-web.png [new file with mode: 0644]
icons/netslide.ico [new file with mode: 0644]
icons/netslide.rc [new file with mode: 0644]
icons/netslide.sav [new file with mode: 0644]
icons/palisade-16d24.png [new file with mode: 0644]
icons/palisade-16d4.png [new file with mode: 0644]
icons/palisade-16d8.png [new file with mode: 0644]
icons/palisade-32d24.png [new file with mode: 0644]
icons/palisade-32d4.png [new file with mode: 0644]
icons/palisade-32d8.png [new file with mode: 0644]
icons/palisade-48d24.png [new file with mode: 0644]
icons/palisade-48d4.png [new file with mode: 0644]
icons/palisade-48d8.png [new file with mode: 0644]
icons/palisade-base.png [new file with mode: 0644]
icons/palisade-ibase.png [new file with mode: 0644]
icons/palisade-ibase4.png [new file with mode: 0644]
icons/palisade-icon.c [new file with mode: 0644]
icons/palisade-web.png [new file with mode: 0644]
icons/palisade.ico [new file with mode: 0644]
icons/palisade.rc [new file with mode: 0644]
icons/palisade.sav [new file with mode: 0644]
icons/pattern-16d24.png [new file with mode: 0644]
icons/pattern-16d4.png [new file with mode: 0644]
icons/pattern-16d8.png [new file with mode: 0644]
icons/pattern-32d24.png [new file with mode: 0644]
icons/pattern-32d4.png [new file with mode: 0644]
icons/pattern-32d8.png [new file with mode: 0644]
icons/pattern-48d24.png [new file with mode: 0644]
icons/pattern-48d4.png [new file with mode: 0644]
icons/pattern-48d8.png [new file with mode: 0644]
icons/pattern-base.png [new file with mode: 0644]
icons/pattern-ibase.png [new file with mode: 0644]
icons/pattern-ibase4.png [new file with mode: 0644]
icons/pattern-icon.c [new file with mode: 0644]
icons/pattern-web.png [new file with mode: 0644]
icons/pattern.ico [new file with mode: 0644]
icons/pattern.rc [new file with mode: 0644]
icons/pattern.sav [new file with mode: 0644]
icons/pearl-16d24.png [new file with mode: 0644]
icons/pearl-16d4.png [new file with mode: 0644]
icons/pearl-16d8.png [new file with mode: 0644]
icons/pearl-32d24.png [new file with mode: 0644]
icons/pearl-32d4.png [new file with mode: 0644]
icons/pearl-32d8.png [new file with mode: 0644]
icons/pearl-48d24.png [new file with mode: 0644]
icons/pearl-48d4.png [new file with mode: 0644]
icons/pearl-48d8.png [new file with mode: 0644]
icons/pearl-base.png [new file with mode: 0644]
icons/pearl-ibase.png [new file with mode: 0644]
icons/pearl-ibase4.png [new file with mode: 0644]
icons/pearl-icon.c [new file with mode: 0644]
icons/pearl-web.png [new file with mode: 0644]
icons/pearl.ico [new file with mode: 0644]
icons/pearl.rc [new file with mode: 0644]
icons/pearl.sav [new file with mode: 0644]
icons/pegs-16d24.png [new file with mode: 0644]
icons/pegs-16d4.png [new file with mode: 0644]
icons/pegs-16d8.png [new file with mode: 0644]
icons/pegs-32d24.png [new file with mode: 0644]
icons/pegs-32d4.png [new file with mode: 0644]
icons/pegs-32d8.png [new file with mode: 0644]
icons/pegs-48d24.png [new file with mode: 0644]
icons/pegs-48d4.png [new file with mode: 0644]
icons/pegs-48d8.png [new file with mode: 0644]
icons/pegs-base.png [new file with mode: 0644]
icons/pegs-ibase.png [new file with mode: 0644]
icons/pegs-ibase4.png [new file with mode: 0644]
icons/pegs-icon.c [new file with mode: 0644]
icons/pegs-web.png [new file with mode: 0644]
icons/pegs.ico [new file with mode: 0644]
icons/pegs.rc [new file with mode: 0644]
icons/pegs.sav [new file with mode: 0644]
icons/range-16d24.png [new file with mode: 0644]
icons/range-16d4.png [new file with mode: 0644]
icons/range-16d8.png [new file with mode: 0644]
icons/range-32d24.png [new file with mode: 0644]
icons/range-32d4.png [new file with mode: 0644]
icons/range-32d8.png [new file with mode: 0644]
icons/range-48d24.png [new file with mode: 0644]
icons/range-48d4.png [new file with mode: 0644]
icons/range-48d8.png [new file with mode: 0644]
icons/range-base.png [new file with mode: 0644]
icons/range-ibase.png [new file with mode: 0644]
icons/range-ibase4.png [new file with mode: 0644]
icons/range-icon.c [new file with mode: 0644]
icons/range-web.png [new file with mode: 0644]
icons/range.ico [new file with mode: 0644]
icons/range.rc [new file with mode: 0644]
icons/range.sav [new file with mode: 0644]
icons/rect-16d24.png [new file with mode: 0644]
icons/rect-16d4.png [new file with mode: 0644]
icons/rect-16d8.png [new file with mode: 0644]
icons/rect-32d24.png [new file with mode: 0644]
icons/rect-32d4.png [new file with mode: 0644]
icons/rect-32d8.png [new file with mode: 0644]
icons/rect-48d24.png [new file with mode: 0644]
icons/rect-48d4.png [new file with mode: 0644]
icons/rect-48d8.png [new file with mode: 0644]
icons/rect-base.png [new file with mode: 0644]
icons/rect-ibase.png [new file with mode: 0644]
icons/rect-ibase4.png [new file with mode: 0644]
icons/rect-icon.c [new file with mode: 0644]
icons/rect-web.png [new file with mode: 0644]
icons/rect.ico [new file with mode: 0644]
icons/rect.rc [new file with mode: 0644]
icons/rect.sav [new file with mode: 0644]
icons/samegame-16d24.png [new file with mode: 0644]
icons/samegame-16d4.png [new file with mode: 0644]
icons/samegame-16d8.png [new file with mode: 0644]
icons/samegame-32d24.png [new file with mode: 0644]
icons/samegame-32d4.png [new file with mode: 0644]
icons/samegame-32d8.png [new file with mode: 0644]
icons/samegame-48d24.png [new file with mode: 0644]
icons/samegame-48d4.png [new file with mode: 0644]
icons/samegame-48d8.png [new file with mode: 0644]
icons/samegame-base.png [new file with mode: 0644]
icons/samegame-ibase.png [new file with mode: 0644]
icons/samegame-ibase4.png [new file with mode: 0644]
icons/samegame-icon.c [new file with mode: 0644]
icons/samegame-web.png [new file with mode: 0644]
icons/samegame.ico [new file with mode: 0644]
icons/samegame.rc [new file with mode: 0644]
icons/samegame.sav [new file with mode: 0644]
icons/screenshot.sh [new file with mode: 0755]
icons/signpost-16d24.png [new file with mode: 0644]
icons/signpost-16d4.png [new file with mode: 0644]
icons/signpost-16d8.png [new file with mode: 0644]
icons/signpost-32d24.png [new file with mode: 0644]
icons/signpost-32d4.png [new file with mode: 0644]
icons/signpost-32d8.png [new file with mode: 0644]
icons/signpost-48d24.png [new file with mode: 0644]
icons/signpost-48d4.png [new file with mode: 0644]
icons/signpost-48d8.png [new file with mode: 0644]
icons/signpost-base.png [new file with mode: 0644]
icons/signpost-ibase.png [new file with mode: 0644]
icons/signpost-ibase4.png [new file with mode: 0644]
icons/signpost-icon.c [new file with mode: 0644]
icons/signpost-web.png [new file with mode: 0644]
icons/signpost.ico [new file with mode: 0644]
icons/signpost.rc [new file with mode: 0644]
icons/signpost.sav [new file with mode: 0644]
icons/singles-16d24.png [new file with mode: 0644]
icons/singles-16d4.png [new file with mode: 0644]
icons/singles-16d8.png [new file with mode: 0644]
icons/singles-32d24.png [new file with mode: 0644]
icons/singles-32d4.png [new file with mode: 0644]
icons/singles-32d8.png [new file with mode: 0644]
icons/singles-48d24.png [new file with mode: 0644]
icons/singles-48d4.png [new file with mode: 0644]
icons/singles-48d8.png [new file with mode: 0644]
icons/singles-base.png [new file with mode: 0644]
icons/singles-ibase.png [new file with mode: 0644]
icons/singles-ibase4.png [new file with mode: 0644]
icons/singles-icon.c [new file with mode: 0644]
icons/singles-web.png [new file with mode: 0644]
icons/singles.ico [new file with mode: 0644]
icons/singles.rc [new file with mode: 0644]
icons/singles.sav [new file with mode: 0644]
icons/sixteen-16d24.png [new file with mode: 0644]
icons/sixteen-16d4.png [new file with mode: 0644]
icons/sixteen-16d8.png [new file with mode: 0644]
icons/sixteen-32d24.png [new file with mode: 0644]
icons/sixteen-32d4.png [new file with mode: 0644]
icons/sixteen-32d8.png [new file with mode: 0644]
icons/sixteen-48d24.png [new file with mode: 0644]
icons/sixteen-48d4.png [new file with mode: 0644]
icons/sixteen-48d8.png [new file with mode: 0644]
icons/sixteen-base.png [new file with mode: 0644]
icons/sixteen-ibase.png [new file with mode: 0644]
icons/sixteen-ibase4.png [new file with mode: 0644]
icons/sixteen-icon.c [new file with mode: 0644]
icons/sixteen-web.png [new file with mode: 0644]
icons/sixteen.ico [new file with mode: 0644]
icons/sixteen.rc [new file with mode: 0644]
icons/sixteen.sav [new file with mode: 0644]
icons/slant-16d24.png [new file with mode: 0644]
icons/slant-16d4.png [new file with mode: 0644]
icons/slant-16d8.png [new file with mode: 0644]
icons/slant-32d24.png [new file with mode: 0644]
icons/slant-32d4.png [new file with mode: 0644]
icons/slant-32d8.png [new file with mode: 0644]
icons/slant-48d24.png [new file with mode: 0644]
icons/slant-48d4.png [new file with mode: 0644]
icons/slant-48d8.png [new file with mode: 0644]
icons/slant-base.png [new file with mode: 0644]
icons/slant-ibase.png [new file with mode: 0644]
icons/slant-ibase4.png [new file with mode: 0644]
icons/slant-icon.c [new file with mode: 0644]
icons/slant-web.png [new file with mode: 0644]
icons/slant.ico [new file with mode: 0644]
icons/slant.rc [new file with mode: 0644]
icons/slant.sav [new file with mode: 0644]
icons/solo-16d24.png [new file with mode: 0644]
icons/solo-16d4.png [new file with mode: 0644]
icons/solo-16d8.png [new file with mode: 0644]
icons/solo-32d24.png [new file with mode: 0644]
icons/solo-32d4.png [new file with mode: 0644]
icons/solo-32d8.png [new file with mode: 0644]
icons/solo-48d24.png [new file with mode: 0644]
icons/solo-48d4.png [new file with mode: 0644]
icons/solo-48d8.png [new file with mode: 0644]
icons/solo-base.png [new file with mode: 0644]
icons/solo-ibase.png [new file with mode: 0644]
icons/solo-ibase4.png [new file with mode: 0644]
icons/solo-icon.c [new file with mode: 0644]
icons/solo-web.png [new file with mode: 0644]
icons/solo.ico [new file with mode: 0644]
icons/solo.rc [new file with mode: 0644]
icons/solo.sav [new file with mode: 0644]
icons/square.pl [new file with mode: 0755]
icons/tents-16d24.png [new file with mode: 0644]
icons/tents-16d4.png [new file with mode: 0644]
icons/tents-16d8.png [new file with mode: 0644]
icons/tents-32d24.png [new file with mode: 0644]
icons/tents-32d4.png [new file with mode: 0644]
icons/tents-32d8.png [new file with mode: 0644]
icons/tents-48d24.png [new file with mode: 0644]
icons/tents-48d4.png [new file with mode: 0644]
icons/tents-48d8.png [new file with mode: 0644]
icons/tents-base.png [new file with mode: 0644]
icons/tents-ibase.png [new file with mode: 0644]
icons/tents-ibase4.png [new file with mode: 0644]
icons/tents-icon.c [new file with mode: 0644]
icons/tents-web.png [new file with mode: 0644]
icons/tents.ico [new file with mode: 0644]
icons/tents.rc [new file with mode: 0644]
icons/tents.sav [new file with mode: 0644]
icons/towers-16d24.png [new file with mode: 0644]
icons/towers-16d4.png [new file with mode: 0644]
icons/towers-16d8.png [new file with mode: 0644]
icons/towers-32d24.png [new file with mode: 0644]
icons/towers-32d4.png [new file with mode: 0644]
icons/towers-32d8.png [new file with mode: 0644]
icons/towers-48d24.png [new file with mode: 0644]
icons/towers-48d4.png [new file with mode: 0644]
icons/towers-48d8.png [new file with mode: 0644]
icons/towers-base.png [new file with mode: 0644]
icons/towers-ibase.png [new file with mode: 0644]
icons/towers-ibase4.png [new file with mode: 0644]
icons/towers-icon.c [new file with mode: 0644]
icons/towers-web.png [new file with mode: 0644]
icons/towers.ico [new file with mode: 0644]
icons/towers.rc [new file with mode: 0644]
icons/towers.sav [new file with mode: 0644]
icons/tracks-16d24.png [new file with mode: 0644]
icons/tracks-16d4.png [new file with mode: 0644]
icons/tracks-16d8.png [new file with mode: 0644]
icons/tracks-32d24.png [new file with mode: 0644]
icons/tracks-32d4.png [new file with mode: 0644]
icons/tracks-32d8.png [new file with mode: 0644]
icons/tracks-48d24.png [new file with mode: 0644]
icons/tracks-48d4.png [new file with mode: 0644]
icons/tracks-48d8.png [new file with mode: 0644]
icons/tracks-base.png [new file with mode: 0644]
icons/tracks-ibase.png [new file with mode: 0644]
icons/tracks-ibase4.png [new file with mode: 0644]
icons/tracks-icon.c [new file with mode: 0644]
icons/tracks-web.png [new file with mode: 0644]
icons/tracks.ico [new file with mode: 0644]
icons/tracks.rc [new file with mode: 0644]
icons/tracks.sav [new file with mode: 0644]
icons/twiddle-16d24.png [new file with mode: 0644]
icons/twiddle-16d4.png [new file with mode: 0644]
icons/twiddle-16d8.png [new file with mode: 0644]
icons/twiddle-32d24.png [new file with mode: 0644]
icons/twiddle-32d4.png [new file with mode: 0644]
icons/twiddle-32d8.png [new file with mode: 0644]
icons/twiddle-48d24.png [new file with mode: 0644]
icons/twiddle-48d4.png [new file with mode: 0644]
icons/twiddle-48d8.png [new file with mode: 0644]
icons/twiddle-base.png [new file with mode: 0644]
icons/twiddle-ibase.png [new file with mode: 0644]
icons/twiddle-ibase4.png [new file with mode: 0644]
icons/twiddle-icon.c [new file with mode: 0644]
icons/twiddle-web.png [new file with mode: 0644]
icons/twiddle.ico [new file with mode: 0644]
icons/twiddle.rc [new file with mode: 0644]
icons/twiddle.sav [new file with mode: 0644]
icons/undead-16d24.png [new file with mode: 0644]
icons/undead-16d4.png [new file with mode: 0644]
icons/undead-16d8.png [new file with mode: 0644]
icons/undead-32d24.png [new file with mode: 0644]
icons/undead-32d4.png [new file with mode: 0644]
icons/undead-32d8.png [new file with mode: 0644]
icons/undead-48d24.png [new file with mode: 0644]
icons/undead-48d4.png [new file with mode: 0644]
icons/undead-48d8.png [new file with mode: 0644]
icons/undead-base.png [new file with mode: 0644]
icons/undead-ibase.png [new file with mode: 0644]
icons/undead-ibase4.png [new file with mode: 0644]
icons/undead-icon.c [new file with mode: 0644]
icons/undead-web.png [new file with mode: 0644]
icons/undead.ico [new file with mode: 0644]
icons/undead.rc [new file with mode: 0644]
icons/undead.sav [new file with mode: 0644]
icons/unequal-16d24.png [new file with mode: 0644]
icons/unequal-16d4.png [new file with mode: 0644]
icons/unequal-16d8.png [new file with mode: 0644]
icons/unequal-32d24.png [new file with mode: 0644]
icons/unequal-32d4.png [new file with mode: 0644]
icons/unequal-32d8.png [new file with mode: 0644]
icons/unequal-48d24.png [new file with mode: 0644]
icons/unequal-48d4.png [new file with mode: 0644]
icons/unequal-48d8.png [new file with mode: 0644]
icons/unequal-base.png [new file with mode: 0644]
icons/unequal-ibase.png [new file with mode: 0644]
icons/unequal-ibase4.png [new file with mode: 0644]
icons/unequal-icon.c [new file with mode: 0644]
icons/unequal-web.png [new file with mode: 0644]
icons/unequal.ico [new file with mode: 0644]
icons/unequal.rc [new file with mode: 0644]
icons/unequal.sav [new file with mode: 0644]
icons/unruly-16d24.png [new file with mode: 0644]
icons/unruly-16d4.png [new file with mode: 0644]
icons/unruly-16d8.png [new file with mode: 0644]
icons/unruly-32d24.png [new file with mode: 0644]
icons/unruly-32d4.png [new file with mode: 0644]
icons/unruly-32d8.png [new file with mode: 0644]
icons/unruly-48d24.png [new file with mode: 0644]
icons/unruly-48d4.png [new file with mode: 0644]
icons/unruly-48d8.png [new file with mode: 0644]
icons/unruly-base.png [new file with mode: 0644]
icons/unruly-ibase.png [new file with mode: 0644]
icons/unruly-ibase4.png [new file with mode: 0644]
icons/unruly-icon.c [new file with mode: 0644]
icons/unruly-web.png [new file with mode: 0644]
icons/unruly.ico [new file with mode: 0644]
icons/unruly.rc [new file with mode: 0644]
icons/unruly.sav [new file with mode: 0644]
icons/untangle-16d24.png [new file with mode: 0644]
icons/untangle-16d4.png [new file with mode: 0644]
icons/untangle-16d8.png [new file with mode: 0644]
icons/untangle-32d24.png [new file with mode: 0644]
icons/untangle-32d4.png [new file with mode: 0644]
icons/untangle-32d8.png [new file with mode: 0644]
icons/untangle-48d24.png [new file with mode: 0644]
icons/untangle-48d4.png [new file with mode: 0644]
icons/untangle-48d8.png [new file with mode: 0644]
icons/untangle-base.png [new file with mode: 0644]
icons/untangle-ibase.png [new file with mode: 0644]
icons/untangle-ibase4.png [new file with mode: 0644]
icons/untangle-icon.c [new file with mode: 0644]
icons/untangle-web.png [new file with mode: 0644]
icons/untangle.ico [new file with mode: 0644]
icons/untangle.rc [new file with mode: 0644]
icons/untangle.sav [new file with mode: 0644]
icons/win16pal.xpm [new file with mode: 0644]
inertia.R [new file with mode: 0644]
inertia.c [new file with mode: 0644]
install-sh [new file with mode: 0755]
keen.R [new file with mode: 0644]
keen.c [new file with mode: 0644]
latin.c [new file with mode: 0644]
latin.h [new file with mode: 0644]
laydomino.c [new file with mode: 0644]
lightup.R [new file with mode: 0644]
lightup.c [new file with mode: 0644]
list.c [new file with mode: 0644]
loopgen.c [new file with mode: 0644]
loopgen.h [new file with mode: 0644]
loopy.R [new file with mode: 0644]
loopy.c [new file with mode: 0644]
magnets.R [new file with mode: 0644]
magnets.c [new file with mode: 0644]
malloc.c [new file with mode: 0644]
map.R [new file with mode: 0644]
map.c [new file with mode: 0644]
maxflow.c [new file with mode: 0644]
maxflow.h [new file with mode: 0644]
midend.c [new file with mode: 0644]
mines.R [new file with mode: 0644]
mines.c [new file with mode: 0644]
misc.c [new file with mode: 0644]
missing [new file with mode: 0755]
mkauto.sh [new file with mode: 0755]
mkfiles.pl [new file with mode: 0755]
nestedvm.c [new file with mode: 0644]
net.R [new file with mode: 0644]
net.c [new file with mode: 0644]
netslide.R [new file with mode: 0644]
netslide.c [new file with mode: 0644]
no-icon.c [new file with mode: 0644]
noicon.rc [new file with mode: 0644]
nullfe.c [new file with mode: 0644]
nullgame.R [new file with mode: 0644]
nullgame.c [new file with mode: 0644]
obfusc.c [new file with mode: 0644]
osx-help.but [new file with mode: 0644]
osx-info.plist [new file with mode: 0644]
osx.icns [new file with mode: 0644]
osx.m [new file with mode: 0644]
palisade.R [new file with mode: 0644]
palisade.c [new file with mode: 0644]
pattern.R [new file with mode: 0644]
pattern.c [new file with mode: 0644]
pearl.R [new file with mode: 0644]
pearl.c [new file with mode: 0644]
pegs.R [new file with mode: 0644]
pegs.c [new file with mode: 0644]
penrose.c [new file with mode: 0644]
penrose.h [new file with mode: 0644]
preprocessed.but [new file with mode: 0644]
printing.c [new file with mode: 0644]
ps.c [new file with mode: 0644]
puzzles.but [new file with mode: 0644]
puzzles.cnt [new file with mode: 0644]
puzzles.h [new file with mode: 0644]
puzzles.hlp [new file with mode: 0644]
puzzles.rc2 [new file with mode: 0644]
puzzles.txt [new file with mode: 0644]
random.c [new file with mode: 0644]
range.R [new file with mode: 0644]
range.c [new file with mode: 0644]
rect.R [new file with mode: 0644]
rect.c [new file with mode: 0644]
resource.h [new file with mode: 0644]
samegame.R [new file with mode: 0644]
samegame.c [new file with mode: 0644]
signpost.R [new file with mode: 0644]
signpost.c [new file with mode: 0644]
singles.R [new file with mode: 0644]
singles.c [new file with mode: 0644]
sixteen.R [new file with mode: 0644]
sixteen.c [new file with mode: 0644]
slant.R [new file with mode: 0644]
slant.c [new file with mode: 0644]
solo.R [new file with mode: 0644]
solo.c [new file with mode: 0644]
tdq.c [new file with mode: 0644]
tents.R [new file with mode: 0644]
tents.c [new file with mode: 0644]
towers.R [new file with mode: 0644]
towers.c [new file with mode: 0644]
tracks.R [new file with mode: 0644]
tracks.c [new file with mode: 0644]
tree234.c [new file with mode: 0644]
tree234.h [new file with mode: 0644]
twiddle.R [new file with mode: 0644]
twiddle.c [new file with mode: 0644]
undead.R [new file with mode: 0644]
undead.c [new file with mode: 0644]
unequal.R [new file with mode: 0644]
unequal.c [new file with mode: 0644]
unruly.R [new file with mode: 0644]
unruly.c [new file with mode: 0644]
untangle.R [new file with mode: 0644]
untangle.c [new file with mode: 0644]
version.c [new file with mode: 0644]
version.h [new file with mode: 0644]
windows.c [new file with mode: 0644]

diff --git a/HACKING b/HACKING
new file mode 100644 (file)
index 0000000..91defd4
--- /dev/null
+++ b/HACKING
@@ -0,0 +1,4749 @@
+Developer documentation for Simon Tatham's puzzle collection
+============================================================
+
+This is a guide to the internal structure of Simon Tatham's Portable
+Puzzle Collection (henceforth referred to simply as `Puzzles'), for
+use by anyone attempting to implement a new puzzle or port to a new
+platform.
+
+This guide is believed correct as of r6190. Hopefully it will be updated
+along with the code in future, but if not, I've at least left this
+version number in here so you can figure out what's changed by tracking
+commit comments from there onwards.
+
+1. Introduction
+---------------
+
+The Puzzles code base is divided into four parts: a set of
+interchangeable front ends, a set of interchangeable back ends, a
+universal `middle end' which acts as a buffer between the two, and a
+bunch of miscellaneous utility functions. In the following sections I
+give some general discussion of each of these parts.
+
+1.1. Front end
+--------------
+
+The front end is the non-portable part of the code: it's the bit that
+you replace completely when you port to a different platform. So it's
+responsible for all system calls, all GUI interaction, and anything else
+platform-specific.
+
+The current front ends in the main code base are for Windows, GTK and
+MacOS X; I also know of a third-party front end for PalmOS.
+
+The front end contains main() or the local platform's equivalent. Top-
+level control over the application's execution flow belongs to the front
+end (it isn't, for example, a set of functions called by a universal
+main() somewhere else).
+
+The front end has complete freedom to design the GUI for any given
+port of Puzzles. There is no centralised mechanism for maintaining the
+menu layout, for example. This has a cost in consistency (when I _do_
+want the same menu layout on more than one platform, I have to edit
+two pieces of code in parallel every time I make a change), but the
+advantage is that local GUI conventions can be conformed to and local
+constraints adapted to. For example, MacOS X has strict human interface
+guidelines which specify a different menu layout from the one I've used
+on Windows and GTK; there's nothing stopping the OS X front end from
+providing a menu layout consistent with those guidelines.
+
+Although the front end is mostly caller rather than the callee in its
+interactions with other parts of the code, it is required to implement
+a small API for other modules to call, mostly of drawing functions for
+games to use when drawing their graphics. The drawing API is documented
+in chapter 3; the other miscellaneous front end API functions are
+documented in section 4.35.
+
+1.2. Back end
+-------------
+
+A `back end', in this collection, is synonymous with a `puzzle'. Each
+back end implements a different game.
+
+At the top level, a back end is simply a data structure, containing a
+few constants (flag words, preferred pixel size) and a large number of
+function pointers. Back ends are almost invariably callee rather than
+caller, which means there's a limitation on what a back end can do on
+its own initiative.
+
+The persistent state in a back end is divided into a number of data
+structures, which are used for different purposes and therefore likely
+to be switched around, changed without notice, and otherwise updated by
+the rest of the code. It is important when designing a back end to put
+the right pieces of data into the right structures, or standard midend-
+provided features (such as Undo) may fail to work.
+
+The functions and variables provided in the back end data structure are
+documented in chapter 2.
+
+1.3. Middle end
+---------------
+
+Puzzles has a single and universal `middle end'. This code is common to
+all platforms and all games; it sits in between the front end and the
+back end and provides standard functionality everywhere.
+
+People adding new back ends or new front ends should generally not need
+to edit the middle end. On rare occasions there might be a change that
+can be made to the middle end to permit a new game to do something not
+currently anticipated by the middle end's present design; however, this
+is terribly easy to get wrong and should probably not be undertaken
+without consulting the primary maintainer (me). Patch submissions
+containing unannounced mid-end changes will be treated on their merits
+like any other patch; this is just a friendly warning that mid-end
+changes will need quite a lot of merits to make them acceptable.
+
+Functionality provided by the mid-end includes:
+
+ -  Maintaining a list of game state structures and moving back and
+    forth along that list to provide Undo and Redo.
+
+ -  Handling timers (for move animations, flashes on completion, and in
+    some cases actually timing the game).
+
+ -  Handling the container format of game IDs: receiving them, picking
+    them apart into parameters, description and/or random seed, and
+    so on. The game back end need only handle the individual parts
+    of a game ID (encoded parameters and encoded game description);
+    everything else is handled centrally by the mid-end.
+
+ -  Handling standard keystrokes and menu commands, such as `New Game',
+    `Restart Game' and `Quit'.
+
+ -  Pre-processing mouse events so that the game back ends can rely on
+    them arriving in a sensible order (no missing button-release events,
+    no sudden changes of which button is currently pressed, etc).
+
+ -  Handling the dialog boxes which ask the user for a game ID.
+
+ -  Handling serialisation of entire games (for loading and saving a
+    half-finished game to a disk file, or for handling application
+    shutdown and restart on platforms such as PalmOS where state is
+    expected to be saved).
+
+Thus, there's a lot of work done once by the mid-end so that individual
+back ends don't have to worry about it. All the back end has to do is
+cooperate in ensuring the mid-end can do its work properly.
+
+The API of functions provided by the mid-end to be called by the front
+end is documented in chapter 4.
+
+1.4. Miscellaneous utilities
+----------------------------
+
+In addition to these three major structural components, the Puzzles code
+also contains a variety of utility modules usable by all of the above
+components. There is a set of functions to provide platform-independent
+random number generation; functions to make memory allocation easier;
+functions which implement a balanced tree structure to be used as
+necessary in complex algorithms; and a few other miscellaneous
+functions. All of these are documented in chapter 5.
+
+1.5. Structure of this guide
+----------------------------
+
+There are a number of function call interfaces within Puzzles, and this
+guide will discuss each one in a chapter of its own. After that, chapter
+6 discusses how to design new games, with some general design thoughts
+and tips.
+
+2. Interface to the back end
+----------------------------
+
+This chapter gives a detailed discussion of the interface that each back
+end must implement.
+
+At the top level, each back end source file exports a single global
+symbol, which is a `const struct game' containing a large number of
+function pointers and a small amount of constant data. This structure is
+called by different names depending on what kind of platform the puzzle
+set is being compiled on:
+
+ -  On platforms such as Windows and GTK, which build a separate binary
+    for each puzzle, the game structure in every back end has the same
+    name, `thegame'; the front end refers directly to this name, so that
+    compiling the same front end module against a different back end
+    module builds a different puzzle.
+
+ -  On platforms such as MacOS X and PalmOS, which build all the puzzles
+    into a single monolithic binary, the game structure in each back end
+    must have a different name, and there's a helper module `list.c'
+    (constructed automatically by the same Perl script that builds the
+    Makefiles) which contains a complete list of those game structures.
+
+On the latter type of platform, source files may assume that the
+preprocessor symbol `COMBINED' has been defined. Thus, the usual code to
+declare the game structure looks something like this:
+
+  #ifdef COMBINED
+  #define thegame net    /* or whatever this game is called */
+  #endif
+  
+  const struct game thegame = {
+      /* lots of structure initialisation in here */
+  };
+
+Game back ends must also internally define a number of data structures,
+for storing their various persistent state. This chapter will first
+discuss the nature and use of those structures, and then go on to give
+details of every element of the game structure.
+
+2.1. Data structures
+--------------------
+
+Each game is required to define four separate data structures. This
+section discusses each one and suggests what sorts of things need to be
+put in it.
+
+2.1.1. `game_params'
+--------------------
+
+The `game_params' structure contains anything which affects the
+automatic generation of new puzzles. So if puzzle generation is
+parametrised in any way, those parameters need to be stored in
+`game_params'.
+
+Most puzzles currently in this collection are played on a grid of
+squares, meaning that the most obvious parameter is the grid size. Many
+puzzles have additional parameters; for example, Mines allows you to
+control the number of mines in the grid independently of its size, Net
+can be wrapping or non-wrapping, Solo has difficulty levels and symmetry
+settings, and so on.
+
+A simple rule for deciding whether a data item needs to go in
+`game_params' is: would the user expect to be able to control this data
+item from either the preset-game-types menu or the `Custom' game type
+configuration? If so, it's part of `game_params'.
+
+`game_params' structures are permitted to contain pointers to subsidiary
+data if they need to. The back end is required to provide functions to
+create and destroy `game_params', and those functions can allocate and
+free additional memory if necessary. (It has not yet been necessary to
+do this in any puzzle so far, but the capability is there just in case.)
+
+`game_params' is also the only structure which the game's compute_size()
+function may refer to; this means that any aspect of the game which
+affects the size of the window it needs to be drawn in must be stored in
+`game_params'. In particular, this imposes the fundamental limitation
+that random game generation may not have a random effect on the window
+size: game generation algorithms are constrained to work by starting
+from the grid size rather than generating it as an emergent phenomenon.
+(Although this is a restriction in theory, it has not yet seemed to be a
+problem.)
+
+2.1.2. `game_state'
+-------------------
+
+While the user is actually playing a puzzle, the `game_state' structure
+stores all the data corresponding to the current state of play.
+
+The mid-end keeps `game_state's in a list, and adds to the list every
+time the player makes a move; the Undo and Redo functions step back and
+forth through that list.
+
+Therefore, a good means of deciding whether a data item needs to go in
+`game_state' is: would a player expect that data item to be restored on
+undo? If so, put it in `game_state', and this will automatically happen
+without you having to lift a finger. If not - for example, the deaths
+counter in Mines is precisely something that does _not_ want to be reset
+to its previous state on an undo - then you might have found a data item
+that needs to go in `game_ui' instead.
+
+During play, `game_state's are often passed around without an
+accompanying `game_params' structure. Therefore, any information in
+`game_params' which is important during play (such as the grid size)
+must be duplicated within the `game_state'. One simple method of doing
+this is to have the `game_state' structure _contain_ a `game_params'
+structure as one of its members, although this isn't obligatory if you
+prefer to do it another way.
+
+2.1.3. `game_drawstate'
+-----------------------
+
+`game_drawstate' carries persistent state relating to the current
+graphical contents of the puzzle window. The same `game_drawstate'
+is passed to every call to the game redraw function, so that it can
+remember what it has already drawn and what needs redrawing.
+
+A typical use for a `game_drawstate' is to have an array mirroring the
+array of grid squares in the `game_state'; then every time the redraw
+function was passed a `game_state', it would loop over all the squares,
+and physically redraw any whose description in the `game_state' (i.e.
+what the square needs to look like when the redraw is completed) did
+not match its description in the `game_drawstate' (i.e. what the square
+currently looks like).
+
+`game_drawstate' is occasionally completely torn down and reconstructed
+by the mid-end, if the user somehow forces a full redraw. Therefore, no
+data should be stored in `game_drawstate' which is _not_ related to the
+state of the puzzle window, because it might be unexpectedly destroyed.
+
+The back end provides functions to create and destroy `game_drawstate',
+which means it can contain pointers to subsidiary allocated data if it
+needs to. A common thing to want to allocate in a `game_drawstate' is a
+`blitter'; see section 3.1.13 for more on this subject.
+
+2.1.4. `game_ui'
+----------------
+
+`game_ui' contains whatever doesn't fit into the above three structures!
+
+A new `game_ui' is created when the user begins playing a new instance
+of a puzzle (i.e. during `New Game' or after entering a game ID etc). It
+persists until the user finishes playing that game and begins another
+one (or closes the window); in particular, `Restart Game' does _not_
+destroy the `game_ui'.
+
+`game_ui' is useful for implementing user-interface state which is not
+part of `game_state'. Common examples are keyboard control (you wouldn't
+want to have to separately Undo through every cursor motion) and mouse
+dragging. See section 6.3.2 and section 6.3.3, respectively, for more
+details.
+
+Another use for `game_ui' is to store highly persistent data such as
+the Mines death counter. This is conceptually rather different: where
+the Net cursor position was _not important enough_ to preserve for the
+player to restore by Undo, the Mines death counter is _too important_ to
+permit the player to revert by Undo!
+
+A final use for `game_ui' is to pass information to the redraw function
+about recent changes to the game state. This is used in Mines, for
+example, to indicate whether a requested `flash' should be a white flash
+for victory or a red flash for defeat; see section 6.3.5.
+
+2.2. Simple data in the back end
+--------------------------------
+
+In this section I begin to discuss each individual element in the back
+end structure. To begin with, here are some simple self-contained data
+elements.
+
+2.2.1. `name'
+-------------
+
+  const char *name;
+
+This is a simple ASCII string giving the name of the puzzle. This name
+will be used in window titles, in game selection menus on monolithic
+platforms, and anywhere else that the front end needs to know the name
+of a game.
+
+2.2.2. `winhelp_topic'
+----------------------
+
+  const char *winhelp_topic;
+
+This member is used on Windows only, to provide online help. Although
+the Windows front end provides a separate binary for each puzzle, it has
+a single monolithic help file; so when a user selects `Help' from the
+menu, the program needs to open the help file and jump to the chapter
+describing that particular puzzle.
+
+Therefore, each chapter in `puzzles.but' is labelled with a _help topic_
+name, similar to this:
+
+  \cfg{winhelp-topic}{games.net}
+
+And then the corresponding game back end encodes the topic string (here
+`games.net') in the `winhelp_topic' element of the game structure.
+
+2.3. Handling game parameter sets
+---------------------------------
+
+In this section I present the various functions which handle the
+`game_params' structure.
+
+2.3.1. default_params()
+-----------------------
+
+  game_params *(*default_params)(void);
+
+This function allocates a new `game_params' structure, fills it with the
+default values, and returns a pointer to it.
+
+2.3.2. fetch_preset()
+---------------------
+
+  int (*fetch_preset)(int i, char **name, game_params **params);
+
+This function is used to populate the `Type' menu, which provides a list
+of conveniently accessible preset parameters for most games.
+
+The function is called with `i' equal to the index of the preset
+required (numbering from zero). It returns FALSE if that preset does
+not exist (if `i' is less than zero or greater than the largest preset
+index). Otherwise, it sets `*params' to point at a newly allocated
+`game_params' structure containing the preset information, sets `*name'
+to point at a newly allocated C string containing the preset title (to
+go on the `Type' menu), and returns TRUE.
+
+If the game does not wish to support any presets at all, this function
+is permitted to return FALSE always.
+
+2.3.3. encode_params()
+----------------------
+
+  char *(*encode_params)(const game_params *params, int full);
+
+The job of this function is to take a `game_params', and encode it in
+a string form for use in game IDs. The return value must be a newly
+allocated C string, and _must_ not contain a colon or a hash (since
+those characters are used to mark the end of the parameter section in a
+game ID).
+
+Ideally, it should also not contain any other potentially controversial
+punctuation; bear in mind when designing a string parameter format
+that it will probably be used on both Windows and Unix command lines
+under a variety of exciting shell quoting and metacharacter rules.
+Sticking entirely to alphanumerics is the safest thing; if you really
+need punctuation, you can probably get away with commas, periods or
+underscores without causing anybody any major inconvenience. If you
+venture far beyond that, you're likely to irritate _somebody_.
+
+(At the time of writing this, all existing games have purely
+alphanumeric string parameter formats. Usually these involve a letter
+denoting a parameter, followed optionally by a number giving the value
+of that parameter, with a few mandatory parts at the beginning such as
+numeric width and height separated by `x'.)
+
+If the `full' parameter is TRUE, this function should encode absolutely
+everything in the `game_params', such that a subsequent call to
+decode_params() (section 2.3.4) will yield an identical structure.
+If `full' is FALSE, however, you should leave out anything which
+is not necessary to describe a _specific puzzle instance_, i.e.
+anything which only takes effect when a new puzzle is _generated_.
+For example, the Solo `game_params' includes a difficulty rating used
+when constructing new puzzles; but a Solo game ID need not explicitly
+include the difficulty, since to describe a puzzle once generated it's
+sufficient to give the grid dimensions and the location and contents
+of the clue squares. (Indeed, one might very easily type in a puzzle
+out of a newspaper without _knowing_ what its difficulty level is in
+Solo's terminology.) Therefore, Solo's encode_params() only encodes the
+difficulty level if `full' is set.
+
+2.3.4. decode_params()
+----------------------
+
+  void (*decode_params)(game_params *params, char const *string);
+
+This function is the inverse of encode_params() (section 2.3.3). It
+parses the supplied string and fills in the supplied `game_params'
+structure. Note that the structure will _already_ have been allocated:
+this function is not expected to create a _new_ `game_params', but to
+modify an existing one.
+
+This function can receive a string which only encodes a subset of the
+parameters. The most obvious way in which this can happen is if the
+string was constructed by encode_params() with its `full' parameter set
+to FALSE; however, it could also happen if the user typed in a parameter
+set manually and missed something out. Be prepared to deal with a wide
+range of possibilities.
+
+When dealing with a parameter which is not specified in the input
+string, what to do requires a judgment call on the part of the
+programmer. Sometimes it makes sense to adjust other parameters to bring
+them into line with the new ones. In Mines, for example, you would
+probably not want to keep the same mine count if the user dropped the
+grid size and didn't specify one, since you might easily end up with
+more mines than would actually fit in the grid! On the other hand,
+sometimes it makes sense to leave the parameter alone: a Solo player
+might reasonably expect to be able to configure size and difficulty
+independently of one another.
+
+This function currently has no direct means of returning an error if the
+string cannot be parsed at all. However, the returned `game_params' is
+almost always subsequently passed to validate_params() (section 2.3.10),
+so if you really want to signal parse errors, you could always have a
+`char *' in your parameters structure which stored an error message, and
+have validate_params() return it if it is non-NULL.
+
+2.3.5. free_params()
+--------------------
+
+  void (*free_params)(game_params *params);
+
+This function frees a `game_params' structure, and any subsidiary
+allocations contained within it.
+
+2.3.6. dup_params()
+-------------------
+
+  game_params *(*dup_params)(const game_params *params);
+
+This function allocates a new `game_params' structure and initialises it
+with an exact copy of the information in the one provided as input. It
+returns a pointer to the new duplicate.
+
+2.3.7. `can_configure'
+----------------------
+
+  int can_configure;
+
+This boolean data element is set to TRUE if the back end supports
+custom parameter configuration via a dialog box. If it is TRUE, then
+the functions configure() and custom_params() are expected to work. See
+section 2.3.8 and section 2.3.9 for more details.
+
+2.3.8. configure()
+------------------
+
+  config_item *(*configure)(const game_params *params);
+
+This function is called when the user requests a dialog box for
+custom parameter configuration. It returns a newly allocated array of
+config_item structures, describing the GUI elements required in the
+dialog box. The array should have one more element than the number of
+controls, since it is terminated with a C_END marker (see below). Each
+array element describes the control together with its initial value; the
+front end will modify the value fields and return the updated array to
+custom_params() (see section 2.3.9).
+
+The config_item structure contains the following elements:
+
+  char *name;
+  int type;
+  char *sval;
+  int ival;
+
+`name' is an ASCII string giving the textual label for a GUI control. It
+is _not_ expected to be dynamically allocated.
+
+`type' contains one of a small number of `enum' values defining what
+type of control is being described. The meaning of the `sval' and `ival'
+fields depends on the value in `type'. The valid values are:
+
+`C_STRING'
+
+    Describes a text input box. (This is also used for numeric input.
+    The back end does not bother informing the front end that the box is
+    numeric rather than textual; some front ends do have the capacity
+    to take this into account, but I decided it wasn't worth the extra
+    complexity in the interface.) For this type, `ival' is unused, and
+    `sval' contains a dynamically allocated string representing the
+    contents of the input box.
+
+`C_BOOLEAN'
+
+    Describes a simple checkbox. For this type, `sval' is unused, and
+    `ival' is TRUE or FALSE.
+
+`C_CHOICES'
+
+    Describes a drop-down list presenting one of a small number of
+    fixed choices. For this type, `sval' contains a list of strings
+    describing the choices; the very first character of `sval' is
+    used as a delimiter when processing the rest (so that the strings
+    `:zero:one:two', `!zero!one!two' and `xzeroxonextwo' all define
+    a three-element list containing `zero', `one' and `two'). `ival'
+    contains the index of the currently selected element, numbering from
+    zero (so that in the above example, 0 would mean `zero' and 2 would
+    mean `two').
+
+    Note that for this control type, `sval' is _not_ dynamically
+    allocated, whereas it was for `C_STRING'.
+
+`C_END'
+
+    Marks the end of the array of `config_item's. All other fields are
+    unused.
+
+The array returned from this function is expected to have filled in the
+initial values of all the controls according to the input `game_params'
+structure.
+
+If the game's `can_configure' flag is set to FALSE, this function is
+never called and need not do anything at all.
+
+2.3.9. custom_params()
+----------------------
+
+  game_params *(*custom_params)(const config_item *cfg);
+
+This function is the counterpart to configure() (section 2.3.8). It
+receives as input an array of `config_item's which was originally
+created by configure(), but in which the control values have since been
+changed in accordance with user input. Its function is to read the new
+values out of the controls and return a newly allocated `game_params'
+structure representing the user's chosen parameter set.
+
+(The front end will have modified the controls' _values_, but there will
+still always be the same set of controls, in the same order, as provided
+by configure(). It is not necessary to check the `name' and `type'
+fields, although you could use assert() if you were feeling energetic.)
+
+This function is not expected to (and indeed _must not_) free the input
+`config_item' array. (If the parameters fail to validate, the dialog box
+will stay open.)
+
+If the game's `can_configure' flag is set to FALSE, this function is
+never called and need not do anything at all.
+
+2.3.10. validate_params()
+-------------------------
+
+  char *(*validate_params)(const game_params *params, int full);
+
+This function takes a `game_params' structure as input, and checks that
+the parameters described in it fall within sensible limits. (At the very
+least, grid dimensions should almost certainly be strictly positive, for
+example.)
+
+Return value is NULL if no problems were found, or alternatively a (non-
+dynamically-allocated) ASCII string describing the error in human-
+readable form.
+
+If the `full' parameter is set, full validation should be performed: any
+set of parameters which would not permit generation of a sensible puzzle
+should be faulted. If `full' is _not_ set, the implication is that
+these parameters are not going to be used for _generating_ a puzzle; so
+parameters which can't even sensibly _describe_ a valid puzzle should
+still be faulted, but parameters which only affect puzzle generation
+should not be.
+
+(The `full' option makes a difference when parameter combinations are
+non-orthogonal. For example, Net has a boolean option controlling
+whether it enforces a unique solution; it turns out that it's impossible
+to generate a uniquely soluble puzzle with wrapping walls and width
+2, so validate_params() will complain if you ask for one. However,
+if the user had just been playing a unique wrapping puzzle of a more
+sensible width, and then pastes in a game ID acquired from somebody else
+which happens to describe a _non_-unique wrapping width-2 puzzle, then
+validate_params() will be passed a `game_params' containing the width
+and wrapping settings from the new game ID and the uniqueness setting
+from the old one. This would be faulted, if it weren't for the fact that
+`full' is not set during this call, so Net ignores the inconsistency.
+The resulting `game_params' is never subsequently used to generate a
+puzzle; this is a promise made by the mid-end when it asks for a non-
+full validation.)
+
+2.4. Handling game descriptions
+-------------------------------
+
+In this section I present the functions that deal with a textual
+description of a puzzle, i.e. the part that comes after the colon in a
+descriptive-format game ID.
+
+2.4.1. new_desc()
+-----------------
+
+  char *(*new_desc)(const game_params *params, random_state *rs,
+                    char **aux, int interactive);
+
+This function is where all the really hard work gets done. This is
+the function whose job is to randomly generate a new puzzle, ensuring
+solubility and uniqueness as appropriate.
+
+As input it is given a `game_params' structure and a random state
+(see section 5.1 for the random number API). It must invent a puzzle
+instance, encode it in string form, and return a dynamically allocated C
+string containing that encoding.
+
+Additionally, it may return a second dynamically allocated string
+in `*aux'. (If it doesn't want to, then it can leave that parameter
+completely alone; it isn't required to set it to NULL, although doing
+so is harmless.) That string, if present, will be passed to solve()
+(section 2.7.4) later on; so if the puzzle is generated in such a way
+that a solution is known, then information about that solution can be
+saved in `*aux' for solve() to use.
+
+The `interactive' parameter should be ignored by almost all puzzles.
+Its purpose is to distinguish between generating a puzzle within a GUI
+context for immediate play, and generating a puzzle in a command-line
+context for saving to be played later. The only puzzle that currently
+uses this distinction (and, I fervently hope, the only one which will
+_ever_ need to use it) is Mines, which chooses a random first-click
+location when generating puzzles non-interactively, but which waits
+for the user to place the first click when interactive. If you think
+you have come up with another puzzle which needs to make use of this
+parameter, please think for at least ten minutes about whether there is
+_any_ alternative!
+
+Note that game description strings are not required to contain an
+encoding of parameters such as grid size; a game description is
+never separated from the `game_params' it was generated with, so any
+information contained in that structure need not be encoded again in the
+game description.
+
+2.4.2. validate_desc()
+----------------------
+
+  char *(*validate_desc)(const game_params *params, const char *desc);
+
+This function is given a game description, and its job is to validate
+that it describes a puzzle which makes sense.
+
+To some extent it's up to the user exactly how far they take the phrase
+`makes sense'; there are no particularly strict rules about how hard the
+user is permitted to shoot themself in the foot when typing in a bogus
+game description by hand. (For example, Rectangles will not verify that
+the sum of all the numbers in the grid equals the grid's area. So a user
+could enter a puzzle which was provably not soluble, and the program
+wouldn't complain; there just wouldn't happen to be any sequence of
+moves which solved it.)
+
+The one non-negotiable criterion is that any game description which
+makes it through validate_desc() _must not_ subsequently cause a crash
+or an assertion failure when fed to new_game() and thence to the rest of
+the back end.
+
+The return value is NULL on success, or a non-dynamically-allocated C
+string containing an error message.
+
+2.4.3. new_game()
+-----------------
+
+  game_state *(*new_game)(midend *me, const game_params *params,
+                          const char *desc);
+
+This function takes a game description as input, together with its
+accompanying `game_params', and constructs a `game_state' describing the
+initial state of the puzzle. It returns a newly allocated `game_state'
+structure.
+
+Almost all puzzles should ignore the `me' parameter. It is required by
+Mines, which needs it for later passing to midend_supersede_game_desc()
+(see section 2.11.2) once the user has placed the first click. I
+fervently hope that no other puzzle will be awkward enough to require
+it, so everybody else should ignore it. As with the `interactive'
+parameter in new_desc() (section 2.4.1), if you think you have a reason
+to need this parameter, please try very hard to think of an alternative
+approach!
+
+2.5. Handling game states
+-------------------------
+
+This section describes the functions which create and destroy
+`game_state' structures.
+
+(Well, except new_game(), which is in section 2.4.3 instead of under
+here; but it deals with game descriptions _and_ game states and it had
+to go in one section or the other.)
+
+2.5.1. dup_game()
+-----------------
+
+  game_state *(*dup_game)(const game_state *state);
+
+This function allocates a new `game_state' structure and initialises it
+with an exact copy of the information in the one provided as input. It
+returns a pointer to the new duplicate.
+
+2.5.2. free_game()
+------------------
+
+  void (*free_game)(game_state *state);
+
+This function frees a `game_state' structure, and any subsidiary
+allocations contained within it.
+
+2.6. Handling `game_ui'
+-----------------------
+
+2.6.1. new_ui()
+---------------
+
+  game_ui *(*new_ui)(const game_state *state);
+
+This function allocates and returns a new `game_ui' structure for
+playing a particular puzzle. It is passed a pointer to the initial
+`game_state', in case it needs to refer to that when setting up the
+initial values for the new game.
+
+2.6.2. free_ui()
+----------------
+
+  void (*free_ui)(game_ui *ui);
+
+This function frees a `game_ui' structure, and any subsidiary
+allocations contained within it.
+
+2.6.3. encode_ui()
+------------------
+
+  char *(*encode_ui)(const game_ui *ui);
+
+This function encodes any _important_ data in a `game_ui' structure in
+string form. It is only called when saving a half-finished game to a
+file.
+
+It should be used sparingly. Almost all data in a `game_ui' is not
+important enough to save. The location of the keyboard-controlled
+cursor, for example, can be reset to a default position on reloading
+the game without impacting the user experience. If the user should
+somehow manage to save a game while a mouse drag was in progress, then
+discarding that mouse drag would be an outright _feature_.
+
+A typical thing that _would_ be worth encoding in this function is the
+Mines death counter: it's in the `game_ui' rather than the `game_state'
+because it's too important to allow the user to revert it by using Undo,
+and therefore it's also too important to allow the user to revert it by
+saving and reloading. (Of course, the user could edit the save file by
+hand... But if the user is _that_ determined to cheat, they could just
+as easily modify the game's source.)
+
+2.6.4. decode_ui()
+------------------
+
+  void (*decode_ui)(game_ui *ui, const char *encoding);
+
+This function parses a string previously output by encode_ui(), and
+writes the decoded data back into the provided `game_ui' structure.
+
+2.6.5. changed_state()
+----------------------
+
+  void (*changed_state)(game_ui *ui, const game_state *oldstate,
+                        const game_state *newstate);
+
+This function is called by the mid-end whenever the current game state
+changes, for any reason. Those reasons include:
+
+ -  a fresh move being made by interpret_move() and execute_move()
+
+ -  a solve operation being performed by solve() and execute_move()
+
+ -  the user moving back and forth along the undo list by means of the
+    Undo and Redo operations
+
+ -  the user selecting Restart to go back to the initial game state.
+
+The job of changed_state() is to update the `game_ui' for consistency
+with the new game state, if any update is necessary. For example,
+Same Game stores data about the currently selected tile group in its
+`game_ui', and this data is intrinsically related to the game state it
+was derived from. So it's very likely to become invalid when the game
+state changes; thus, Same Game's changed_state() function clears the
+current selection whenever it is called.
+
+When anim_length() or flash_length() are called, you can be sure that
+there has been a previous call to changed_state(). So changed_state()
+can set up data in the `game_ui' which will be read by anim_length() and
+flash_length(), and those functions will not have to worry about being
+called without the data having been initialised.
+
+2.7. Making moves
+-----------------
+
+This section describes the functions which actually make moves in
+the game: that is, the functions which process user input and end up
+producing new `game_state's.
+
+2.7.1. interpret_move()
+-----------------------
+
+  char *(*interpret_move)(const game_state *state, game_ui *ui,
+                          const game_drawstate *ds,
+                          int x, int y, int button);
+
+This function receives user input and processes it. Its input parameters
+are the current `game_state', the current `game_ui' and the current
+`game_drawstate', plus details of the input event. `button' is either
+an ASCII value or a special code (listed below) indicating an arrow or
+function key or a mouse event; when `button' is a mouse event, `x' and
+`y' contain the pixel coordinates of the mouse pointer relative to the
+top left of the puzzle's drawing area.
+
+(The pointer to the `game_drawstate' is marked `const', because
+`interpret_move' should not write to it. The normal use of that pointer
+will be to read the game's tile size parameter in order to divide mouse
+coordinates by it.)
+
+interpret_move() may return in three different ways:
+
+ -  Returning NULL indicates that no action whatsoever occurred in
+    response to the input event; the puzzle was not interested in it at
+    all.
+
+ -  Returning the empty string ("") indicates that the input event has
+    resulted in a change being made to the `game_ui' which will require
+    a redraw of the game window, but that no actual _move_ was made
+    (i.e. no new `game_state' needs to be created).
+
+ -  Returning anything else indicates that a move was made and that
+    a new `game_state' must be created. However, instead of actually
+    constructing a new `game_state' itself, this function is required to
+    return a string description of the details of the move. This string
+    will be passed to execute_move() (section 2.7.2) to actually create
+    the new `game_state'. (Encoding moves as strings in this way means
+    that the mid-end can keep the strings as well as the game states,
+    and the strings can be written to disk when saving the game and fed
+    to execute_move() again on reloading.)
+
+The return value from interpret_move() is expected to be dynamically
+allocated if and only if it is not either NULL _or_ the empty string.
+
+After this function is called, the back end is permitted to rely on some
+subsequent operations happening in sequence:
+
+ -  execute_move() will be called to convert this move description into
+    a new `game_state'
+
+ -  changed_state() will be called with the new `game_state'.
+
+This means that if interpret_move() needs to do updates to the `game_ui'
+which are easier to perform by referring to the new `game_state', it can
+safely leave them to be done in changed_state() and not worry about them
+failing to happen.
+
+(Note, however, that execute_move() may _also_ be called in other
+circumstances. It is only interpret_move() which can rely on a
+subsequent call to changed_state().)
+
+The special key codes supported by this function are:
+
+LEFT_BUTTON, MIDDLE_BUTTON, RIGHT_BUTTON
+
+    Indicate that one of the mouse buttons was pressed down.
+
+LEFT_DRAG, MIDDLE_DRAG, RIGHT_DRAG
+
+    Indicate that the mouse was moved while one of the mouse buttons was
+    still down. The mid-end guarantees that when one of these events is
+    received, it will always have been preceded by a button-down event
+    (and possibly other drag events) for the same mouse button, and no
+    event involving another mouse button will have appeared in between.
+
+LEFT_RELEASE, MIDDLE_RELEASE, RIGHT_RELEASE
+
+    Indicate that a mouse button was released. The mid-end guarantees
+    that when one of these events is received, it will always have been
+    preceded by a button-down event (and possibly some drag events) for
+    the same mouse button, and no event involving another mouse button
+    will have appeared in between.
+
+CURSOR_UP, CURSOR_DOWN, CURSOR_LEFT, CURSOR_RIGHT
+
+    Indicate that an arrow key was pressed.
+
+CURSOR_SELECT
+
+    On platforms which have a prominent `select' button alongside their
+    cursor keys, indicates that that button was pressed.
+
+In addition, there are some modifiers which can be bitwise-ORed into the
+`button' parameter:
+
+MOD_CTRL, MOD_SHFT
+
+    These indicate that the Control or Shift key was pressed alongside
+    the key. They only apply to the cursor keys, not to mouse buttons or
+    anything else.
+
+MOD_NUM_KEYPAD
+
+    This applies to some ASCII values, and indicates that the key code
+    was input via the numeric keypad rather than the main keyboard. Some
+    puzzles may wish to treat this differently (for example, a puzzle
+    might want to use the numeric keypad as an eight-way directional
+    pad), whereas others might not (a game involving numeric input
+    probably just wants to treat the numeric keypad as numbers).
+
+MOD_MASK
+
+    This mask is the bitwise OR of all the available modifiers; you can
+    bitwise-AND with ~MOD_MASK to strip all the modifiers off any input
+    value.
+
+2.7.2. execute_move()
+---------------------
+
+  game_state *(*execute_move)(const game_state *state, char *move);
+
+This function takes an input `game_state' and a move string as output
+from interpret_move(). It returns a newly allocated `game_state' which
+contains the result of applying the specified move to the input game
+state.
+
+This function may return NULL if it cannot parse the move string (and
+this is definitely preferable to crashing or failing an assertion, since
+one way this can happen is if loading a corrupt save file). However, it
+must not return NULL for any move string that really was output from
+interpret_move(): this is punishable by assertion failure in the mid-
+end.
+
+2.7.3. `can_solve'
+------------------
+
+  int can_solve;
+
+This boolean field is set to TRUE if the game's solve() function does
+something. If it's set to FALSE, the game will not even offer the
+`Solve' menu option.
+
+2.7.4. solve()
+--------------
+
+  char *(*solve)(const game_state *orig, const game_state *curr,
+                 const char *aux, char **error);
+
+This function is called when the user selects the `Solve' option from
+the menu.
+
+It is passed two input game states: `orig' is the game state from the
+very start of the puzzle, and `curr' is the current one. (Different
+games find one or other or both of these convenient.) It is also passed
+the `aux' string saved by new_desc() (section 2.4.1), in case that
+encodes important information needed to provide the solution.
+
+If this function is unable to produce a solution (perhaps, for example,
+the game has no in-built solver so it can only solve puzzles it invented
+internally and has an `aux' string for) then it may return NULL. If it
+does this, it must also set `*error' to an error message to be presented
+to the user (such as `Solution not known for this puzzle'); that error
+message is not expected to be dynamically allocated.
+
+If this function _does_ produce a solution, it returns a move string
+suitable for feeding to execute_move() (section 2.7.2). Like a (non-
+empty) string returned from interpret_move(), the returned string should
+be dynamically allocated.
+
+2.8. Drawing the game graphics
+------------------------------
+
+This section discusses the back end functions that deal with drawing.
+
+2.8.1. new_drawstate()
+----------------------
+
+  game_drawstate *(*new_drawstate)(drawing *dr,
+                                   const game_state *state);
+
+This function allocates and returns a new `game_drawstate' structure for
+drawing a particular puzzle. It is passed a pointer to a `game_state',
+in case it needs to refer to that when setting up any initial data.
+
+This function may not rely on the puzzle having been newly started; a
+new draw state can be constructed at any time if the front end requests
+a forced redraw. For games like Pattern, in which initial game states
+are much simpler than general ones, this might be important to keep in
+mind.
+
+The parameter `dr' is a drawing object (see chapter 3) which the
+function might need to use to allocate blitters. (However, this isn't
+recommended; it's usually more sensible to wait to allocate a blitter
+until set_size() is called, because that way you can tailor it to the
+scale at which the puzzle is being drawn.)
+
+2.8.2. free_drawstate()
+-----------------------
+
+  void (*free_drawstate)(drawing *dr, game_drawstate *ds);
+
+This function frees a `game_drawstate' structure, and any subsidiary
+allocations contained within it.
+
+The parameter `dr' is a drawing object (see chapter 3), which might be
+required if you are freeing a blitter.
+
+2.8.3. `preferred_tilesize'
+---------------------------
+
+  int preferred_tilesize;
+
+Each game is required to define a single integer parameter which
+expresses, in some sense, the scale at which it is drawn. This is
+described in the APIs as `tilesize', since most puzzles are on a
+square (or possibly triangular or hexagonal) grid and hence a sensible
+interpretation of this parameter is to define it as the size of one grid
+tile in pixels; however, there's no actual requirement that the `tile
+size' be proportional to the game window size. Window size is required
+to increase monotonically with `tile size', however.
+
+The data element `preferred_tilesize' indicates the tile size which
+should be used in the absence of a good reason to do otherwise (such as
+the screen being too small, or the user explicitly requesting a resize
+if that ever gets implemented).
+
+2.8.4. compute_size()
+---------------------
+
+  void (*compute_size)(const game_params *params, int tilesize,
+                       int *x, int *y);
+
+This function is passed a `game_params' structure and a tile size. It
+returns, in `*x' and `*y', the size in pixels of the drawing area that
+would be required to render a puzzle with those parameters at that tile
+size.
+
+2.8.5. set_size()
+-----------------
+
+  void (*set_size)(drawing *dr, game_drawstate *ds,
+                   const game_params *params, int tilesize);
+
+This function is responsible for setting up a `game_drawstate' to draw
+at a given tile size. Typically this will simply involve copying the
+supplied `tilesize' parameter into a `tilesize' field inside the draw
+state; for some more complex games it might also involve setting up
+other dimension fields, or possibly allocating a blitter (see section
+3.1.13).
+
+The parameter `dr' is a drawing object (see chapter 3), which is
+required if a blitter needs to be allocated.
+
+Back ends may assume (and may enforce by assertion) that this function
+will be called at most once for any `game_drawstate'. If a puzzle needs
+to be redrawn at a different size, the mid-end will create a fresh
+drawstate.
+
+2.8.6. colours()
+----------------
+
+  float *(*colours)(frontend *fe, int *ncolours);
+
+This function is responsible for telling the front end what colours the
+puzzle will need to draw itself.
+
+It returns the number of colours required in `*ncolours', and the return
+value from the function itself is a dynamically allocated array of three
+times that many `float's, containing the red, green and blue components
+of each colour respectively as numbers in the range [0,1].
+
+The second parameter passed to this function is a front end handle.
+The only things it is permitted to do with this handle are to call the
+front-end function called frontend_default_colour() (see section 4.40)
+or the utility function called game_mkhighlight() (see section 5.4.7).
+(The latter is a wrapper on the former, so front end implementors only
+need to provide frontend_default_colour().) This allows colours() to
+take local configuration into account when deciding on its own colour
+allocations. Most games use the front end's default colour as their
+background, apart from a few which depend on drawing relief highlights
+so they adjust the background colour if it's too light for highlights to
+show up against it.
+
+Note that the colours returned from this function are for _drawing_,
+not for printing. Printing has an entirely different colour allocation
+policy.
+
+2.8.7. anim_length()
+--------------------
+
+  float (*anim_length)(const game_state *oldstate,
+                       const game_state *newstate,
+                       int dir, game_ui *ui);
+
+This function is called when a move is made, undone or redone. It is
+given the old and the new `game_state', and its job is to decide whether
+the transition between the two needs to be animated or can be instant.
+
+`oldstate' is the state that was current until this call; `newstate'
+is the state that will be current after it. `dir' specifies the
+chronological order of those states: if it is positive, then the
+transition is the result of a move or a redo (and so `newstate' is the
+later of the two moves), whereas if it is negative then the transition
+is the result of an undo (so that `newstate' is the _earlier_ move).
+
+If this function decides the transition should be animated, it returns
+the desired length of the animation in seconds. If not, it returns zero.
+
+State changes as a result of a Restart operation are never animated; the
+mid-end will handle them internally and never consult this function at
+all. State changes as a result of Solve operations are also not animated
+by default, although you can change this for a particular game by
+setting a flag in `flags' (section 2.10.7).
+
+The function is also passed a pointer to the local `game_ui'. It may
+refer to information in here to help with its decision (see section
+6.3.7 for an example of this), and/or it may _write_ information about
+the nature of the animation which will be read later by redraw().
+
+When this function is called, it may rely on changed_state() having been
+called previously, so if anim_length() needs to refer to information in
+the `game_ui', then changed_state() is a reliable place to have set that
+information up.
+
+Move animations do not inhibit further input events. If the user
+continues playing before a move animation is complete, the animation
+will be abandoned and the display will jump straight to the final state.
+
+2.8.8. flash_length()
+---------------------
+
+  float (*flash_length)(const game_state *oldstate,
+                        const game_state *newstate,
+                        int dir, game_ui *ui);
+
+This function is called when a move is completed. (`Completed'
+means that not only has the move been made, but any animation which
+accompanied it has finished.) It decides whether the transition from
+`oldstate' to `newstate' merits a `flash'.
+
+A flash is much like a move animation, but it is _not_ interrupted by
+further user interface activity; it runs to completion in parallel with
+whatever else might be going on on the display. The only thing which
+will rush a flash to completion is another flash.
+
+The purpose of flashes is to indicate that the game has been completed.
+They were introduced as a separate concept from move animations because
+of Net: the habit of most Net players (and certainly me) is to rotate a
+tile into place and immediately lock it, then move on to another tile.
+When you make your last move, at the instant the final tile is rotated
+into place the screen starts to flash to indicate victory - but if you
+then press the lock button out of habit, then the move animation is
+cancelled, and the victory flash does not complete. (And if you _don't_
+press the lock button, the completed grid will look untidy because there
+will be one unlocked square.) Therefore, I introduced a specific concept
+of a `flash' which is separate from a move animation and can proceed in
+parallel with move animations and any other display activity, so that
+the victory flash in Net is not cancelled by that final locking move.
+
+The input parameters to flash_length() are exactly the same as the ones
+to anim_length().
+
+Just like anim_length(), when this function is called, it may rely on
+changed_state() having been called previously, so if it needs to refer
+to information in the `game_ui' then changed_state() is a reliable place
+to have set that information up.
+
+(Some games use flashes to indicate defeat as well as victory; Mines,
+for example, flashes in a different colour when you tread on a mine from
+the colour it uses when you complete the game. In order to achieve this,
+its flash_length() function has to store a flag in the `game_ui' to
+indicate which flash type is required.)
+
+2.8.9. status()
+---------------
+
+  int (*status)(const game_state *state);
+
+This function returns a status value indicating whether the current game
+is still in play, or has been won, or has been conclusively lost. The
+mid-end uses this to implement midend_status() (section 4.27).
+
+The return value should be +1 if the game has been successfully solved.
+If the game has been lost in a situation where further play is unlikely,
+the return value should be -1. If neither is true (so play is still
+ongoing), return zero.
+
+Front ends may wish to use a non-zero status as a cue to proactively
+offer the option of starting a new game. Therefore, back ends should
+not return -1 if the game has been _technically_ lost but undoing and
+continuing is still a realistic possibility.
+
+(For instance, games with hidden information such as Guess or Mines
+might well return a non-zero status whenever they reveal the solution,
+whether or not the player guessed it correctly, on the grounds that a
+player would be unlikely to hide the solution and continue playing after
+the answer was spoiled. On the other hand, games where you can merely
+get into a dead end such as Same Game or Inertia might choose to return
+0 in that situation, on the grounds that the player would quite likely
+press Undo and carry on playing.)
+
+2.8.10. redraw()
+----------------
+
+  void (*redraw)(drawing *dr, game_drawstate *ds,
+                 const game_state *oldstate,
+                 const game_state *newstate,
+                 int dir, const game_ui *ui,
+                 float anim_time, float flash_time);
+
+This function is responsible for actually drawing the contents of
+the game window, and for redrawing every time the game state or the
+`game_ui' changes.
+
+The parameter `dr' is a drawing object which may be passed to the
+drawing API functions (see chapter 3 for documentation of the drawing
+API). This function may not save `dr' and use it elsewhere; it must only
+use it for calling back to the drawing API functions within its own
+lifetime.
+
+`ds' is the local `game_drawstate', of course, and `ui' is the local
+`game_ui'.
+
+`newstate' is the semantically-current game state, and is always non-
+NULL. If `oldstate' is also non-NULL, it means that a move has recently
+been made and the game is still in the process of displaying an
+animation linking the old and new states; in this situation, `anim_time'
+will give the length of time (in seconds) that the animation has already
+been running. If `oldstate' is NULL, then `anim_time' is unused (and
+will hopefully be set to zero to avoid confusion).
+
+`flash_time', if it is is non-zero, denotes that the game is in the
+middle of a flash, and gives the time since the start of the flash. See
+section 2.8.8 for general discussion of flashes.
+
+The very first time this function is called for a new `game_drawstate',
+it is expected to redraw the _entire_ drawing area. Since this often
+involves drawing visual furniture which is never subsequently altered,
+it is often simplest to arrange this by having a special `first time'
+flag in the draw state, and resetting it after the first redraw.
+
+When this function (or any subfunction) calls the drawing API, it is
+expected to pass colour indices which were previously defined by the
+colours() function.
+
+2.9. Printing functions
+-----------------------
+
+This section discusses the back end functions that deal with printing
+puzzles out on paper.
+
+2.9.1. `can_print'
+------------------
+
+  int can_print;
+
+This flag is set to TRUE if the puzzle is capable of printing itself
+on paper. (This makes sense for some puzzles, such as Solo, which can
+be filled in with a pencil. Other puzzles, such as Twiddle, inherently
+involve moving things around and so would not make sense to print.)
+
+If this flag is FALSE, then the functions print_size() and print() will
+never be called.
+
+2.9.2. `can_print_in_colour'
+----------------------------
+
+  int can_print_in_colour;
+
+This flag is set to TRUE if the puzzle is capable of printing itself
+differently when colour is available. For example, Map can actually
+print coloured regions in different _colours_ rather than resorting to
+cross-hatching.
+
+If the `can_print' flag is FALSE, then this flag will be ignored.
+
+2.9.3. print_size()
+-------------------
+
+  void (*print_size)(const game_params *params, float *x, float *y);
+
+This function is passed a `game_params' structure and a tile size. It
+returns, in `*x' and `*y', the preferred size in _millimetres_ of that
+puzzle if it were to be printed out on paper.
+
+If the `can_print' flag is FALSE, this function will never be called.
+
+2.9.4. print()
+--------------
+
+  void (*print)(drawing *dr, const game_state *state, int tilesize);
+
+This function is called when a puzzle is to be printed out on paper. It
+should use the drawing API functions (see chapter 3) to print itself.
+
+This function is separate from redraw() because it is often very
+different:
+
+ -  The printing function may not depend on pixel accuracy, since
+    printer resolution is variable. Draw as if your canvas had infinite
+    resolution.
+
+ -  The printing function sometimes needs to display things in a
+    completely different style. Net, for example, is very different as
+    an on-screen puzzle and as a printed one.
+
+ -  The printing function is often much simpler since it has no need to
+    deal with repeated partial redraws.
+
+However, there's no reason the printing and redraw functions can't share
+some code if they want to.
+
+When this function (or any subfunction) calls the drawing API, the
+colour indices it passes should be colours which have been allocated by
+the print_*_colour() functions within this execution of print(). This is
+very different from the fixed small number of colours used in redraw(),
+because printers do not have a limitation on the total number of colours
+that may be used. Some puzzles' printing functions might wish to
+allocate only one `ink' colour and use it for all drawing; others might
+wish to allocate _more_ colours than are used on screen.
+
+One possible colour policy worth mentioning specifically is that a
+puzzle's printing function might want to allocate the _same_ colour
+indices as are used by the redraw function, so that code shared between
+drawing and printing does not have to keep switching its colour indices.
+In order to do this, the simplest thing is to make use of the fact that
+colour indices returned from print_*_colour() are guaranteed to be in
+increasing order from zero. So if you have declared an `enum' defining
+three colours COL_BACKGROUND, COL_THIS and COL_THAT, you might then
+write
+
+  int c;
+  c = print_mono_colour(dr, 1); assert(c == COL_BACKGROUND);
+  c = print_mono_colour(dr, 0); assert(c == COL_THIS);
+  c = print_mono_colour(dr, 0); assert(c == COL_THAT);
+
+If the `can_print' flag is FALSE, this function will never be called.
+
+2.10. Miscellaneous
+-------------------
+
+2.10.1. `can_format_as_text_ever'
+---------------------------------
+
+  int can_format_as_text_ever;
+
+This boolean field is TRUE if the game supports formatting a game state
+as ASCII text (typically ASCII art) for copying to the clipboard and
+pasting into other applications. If it is FALSE, front ends will not
+offer the `Copy' command at all.
+
+If this field is TRUE, the game does not necessarily have to support
+text formatting for _all_ games: e.g. a game which can be played on
+a square grid or a triangular one might only support copy and paste
+for the former, because triangular grids in ASCII art are just too
+difficult.
+
+If this field is FALSE, the functions can_format_as_text_now() (section
+2.10.2) and text_format() (section 2.10.3) are never called.
+
+2.10.2. `can_format_as_text_now()'
+----------------------------------
+
+  int (*can_format_as_text_now)(const game_params *params);
+
+This function is passed a `game_params' and returns a boolean, which is
+TRUE if the game can support ASCII text output for this particular game
+type. If it returns FALSE, front ends will grey out or otherwise disable
+the `Copy' command.
+
+Games may enable and disable the copy-and-paste function for different
+game _parameters_, but are currently constrained to return the same
+answer from this function for all game _states_ sharing the same
+parameters. In other words, the `Copy' function may enable or disable
+itself when the player changes game preset, but will never change during
+play of a single game or when another game of exactly the same type is
+generated.
+
+This function should not take into account aspects of the game
+parameters which are not encoded by encode_params() (section 2.3.3)
+when the `full' parameter is set to FALSE. Such parameters will not
+necessarily match up between a call to this function and a subsequent
+call to text_format() itself. (For instance, game _difficulty_ should
+not affect whether the game can be copied to the clipboard. Only the
+actual visible _shape_ of the game can affect that.)
+
+2.10.3. text_format()
+---------------------
+
+  char *(*text_format)(const game_state *state);
+
+This function is passed a `game_state', and returns a newly allocated C
+string containing an ASCII representation of that game state. It is used
+to implement the `Copy' operation in many front ends.
+
+This function will only ever be called if the back end field
+`can_format_as_text_ever' (section 2.10.1) is TRUE _and_ the function
+can_format_as_text_now() (section 2.10.2) has returned TRUE for the
+currently selected game parameters.
+
+The returned string may contain line endings (and will probably want
+to), using the normal C internal `\n' convention. For consistency
+between puzzles, all multi-line textual puzzle representations should
+_end_ with a newline as well as containing them internally. (There are
+currently no puzzles which have a one-line ASCII representation, so
+there's no precedent yet for whether that should come with a newline or
+not.)
+
+2.10.4. wants_statusbar
+-----------------------
+
+  int wants_statusbar;
+
+This boolean field is set to TRUE if the puzzle has a use for a textual
+status line (to display score, completion status, currently active
+tiles, etc).
+
+2.10.5. `is_timed'
+------------------
+
+  int is_timed;
+
+This boolean field is TRUE if the puzzle is time-critical. If so, the
+mid-end will maintain a game timer while the user plays.
+
+If this field is FALSE, then timing_state() will never be called and
+need not do anything.
+
+2.10.6. timing_state()
+----------------------
+
+  int (*timing_state)(const game_state *state, game_ui *ui);
+
+This function is passed the current `game_state' and the local
+`game_ui'; it returns TRUE if the game timer should currently be
+running.
+
+A typical use for the `game_ui' in this function is to note when the
+game was first completed (by setting a flag in changed_state() - see
+section 2.6.5), and freeze the timer thereafter so that the user can
+undo back through their solution process without altering their time.
+
+2.10.7. `flags'
+---------------
+
+  int flags;
+
+This field contains miscellaneous per-backend flags. It consists of the
+bitwise OR of some combination of the following:
+
+BUTTON_BEATS(x,y)
+
+    Given any x and y from the set {LEFT_BUTTON, MIDDLE_BUTTON,
+    RIGHT_BUTTON}, this macro evaluates to a bit flag which indicates
+    that when buttons x and y are both pressed simultaneously, the mid-
+    end should consider x to have priority. (In the absence of any such
+    flags, the mid-end will always consider the most recently pressed
+    button to have priority.)
+
+SOLVE_ANIMATES
+
+    This flag indicates that moves generated by solve() (section 2.7.4)
+    are candidates for animation just like any other move. For most
+    games, solve moves should not be animated, so the mid-end doesn't
+    even bother calling anim_length() (section 2.8.7), thus saving some
+    special-case code in each game. On the rare occasion that animated
+    solve moves are actually required, you can set this flag.
+
+REQUIRE_RBUTTON
+
+    This flag indicates that the puzzle cannot be usefully played
+    without the use of mouse buttons other than the left one. On some
+    PDA platforms, this flag is used by the front end to enable right-
+    button emulation through an appropriate gesture. Note that a puzzle
+    is not required to set this just because it _uses_ the right button,
+    but only if its use of the right button is critical to playing the
+    game. (Slant, for example, uses the right button to cycle through
+    the three square states in the opposite order from the left button,
+    and hence can manage fine without it.)
+
+REQUIRE_NUMPAD
+
+    This flag indicates that the puzzle cannot be usefully played
+    without the use of number-key input. On some PDA platforms it
+    causes an emulated number pad to appear on the screen. Similarly to
+    REQUIRE_RBUTTON, a puzzle need not specify this simply if its use of
+    the number keys is not critical.
+
+2.11. Things a back end may do on its own initiative
+----------------------------------------------------
+
+This section describes a couple of things that a back end may choose
+to do by calling functions elsewhere in the program, which would not
+otherwise be obvious.
+
+2.11.1. Create a random state
+-----------------------------
+
+If a back end needs random numbers at some point during normal play, it
+can create a fresh `random_state' by first calling `get_random_seed'
+(section 4.36) and then passing the returned seed data to random_new().
+
+This is likely not to be what you want. If a puzzle needs randomness in
+the middle of play, it's likely to be more sensible to store some sort
+of random state within the `game_state', so that the random numbers are
+tied to the particular game state and hence the player can't simply keep
+undoing their move until they get numbers they like better.
+
+This facility is currently used only in Net, to implement the `jumble'
+command, which sets every unlocked tile to a new random orientation.
+This randomness _is_ a reasonable use of the feature, because it's non-
+adversarial - there's no advantage to the user in getting different
+random numbers.
+
+2.11.2. Supersede its own game description
+------------------------------------------
+
+In response to a move, a back end is (reluctantly) permitted to call
+midend_supersede_game_desc():
+
+  void midend_supersede_game_desc(midend *me,
+                                  char *desc, char *privdesc);
+
+When the user selects `New Game', the mid-end calls new_desc()
+(section 2.4.1) to get a new game description, and (as well as using
+that to generate an initial game state) stores it for the save file
+and for telling to the user. The function above overwrites that
+game description, and also splits it in two. `desc' becomes the new
+game description which is provided to the user on request, and is
+also the one used to construct a new initial game state if the user
+selects `Restart'. `privdesc' is a `private' game description, used to
+reconstruct the game's initial state when reloading.
+
+The distinction between the two, as well as the need for this function
+at all, comes from Mines. Mines begins with a blank grid and no
+idea of where the mines actually are; new_desc() does almost no
+work in interactive mode, and simply returns a string encoding the
+`random_state'. When the user first clicks to open a tile, _then_ Mines
+generates the mine positions, in such a way that the game is soluble
+from that starting point. Then it uses this function to supersede the
+random-state game description with a proper one. But it needs two: one
+containing the initial click location (because that's what you want to
+happen if you restart the game, and also what you want to send to a
+friend so that they play _the same game_ as you), and one without the
+initial click location (because when you save and reload the game, you
+expect to see the same blank initial state as you had before saving).
+
+I should stress again that this function is a horrid hack. Nobody should
+use it if they're not Mines; if you think you need to use it, think
+again repeatedly in the hope of finding a better way to do whatever it
+was you needed to do.
+
+3. The drawing API
+------------------
+
+The back end function redraw() (section 2.8.10) is required to draw
+the puzzle's graphics on the window's drawing area, or on paper if the
+puzzle is printable. To do this portably, it is provided with a drawing
+API allowing it to talk directly to the front end. In this chapter I
+document that API, both for the benefit of back end authors trying to
+use it and for front end authors trying to implement it.
+
+The drawing API as seen by the back end is a collection of global
+functions, each of which takes a pointer to a `drawing' structure (a
+`drawing object'). These objects are supplied as parameters to the back
+end's redraw() and print() functions.
+
+In fact these global functions are not implemented directly by the front
+end; instead, they are implemented centrally in `drawing.c' and form a
+small piece of middleware. The drawing API as supplied by the front end
+is a structure containing a set of function pointers, plus a `void *'
+handle which is passed to each of those functions. This enables a single
+front end to switch between multiple implementations of the drawing API
+if necessary. For example, the Windows API supplies a printing mechanism
+integrated into the same GDI which deals with drawing in windows, and
+therefore the same API implementation can handle both drawing and
+printing; but on Unix, the most common way for applications to print
+is by producing PostScript output directly, and although it would be
+_possible_ to write a single (say) draw_rect() function which checked
+a global flag to decide whether to do GTK drawing operations or output
+PostScript to a file, it's much nicer to have two separate functions and
+switch between them as appropriate.
+
+When drawing, the puzzle window is indexed by pixel coordinates, with
+the top left pixel defined as (0,0) and the bottom right pixel (w-1,h-
+1), where `w' and `h' are the width and height values returned by the
+back end function compute_size() (section 2.8.4).
+
+When printing, the puzzle's print area is indexed in exactly the same
+way (with an arbitrary tile size provided by the printing module
+`printing.c'), to facilitate sharing of code between the drawing and
+printing routines. However, when printing, puzzles may no longer assume
+that the coordinate unit has any relationship to a pixel; the printer's
+actual resolution might very well not even be known at print time, so
+the coordinate unit might be smaller or larger than a pixel. Puzzles'
+print functions should restrict themselves to drawing geometric shapes
+rather than fiddly pixel manipulation.
+
+_Puzzles' redraw functions may assume that the surface they draw on is
+persistent_. It is the responsibility of every front end to preserve
+the puzzle's window contents in the face of GUI window expose issues
+and similar. It is not permissible to request that the back end redraw
+any part of a window that it has already drawn, unless something has
+actually changed as a result of making moves in the puzzle.
+
+Most front ends accomplish this by having the drawing routines draw on a
+stored bitmap rather than directly on the window, and copying the bitmap
+to the window every time a part of the window needs to be redrawn.
+Therefore, it is vitally important that whenever the back end does any
+drawing it informs the front end of which parts of the window it has
+accessed, and hence which parts need repainting. This is done by calling
+draw_update() (section 3.1.11).
+
+Persistence of old drawing is convenient. However, a puzzle should be
+very careful about how it updates its drawing area. The problem is that
+some front ends do anti-aliased drawing: rather than simply choosing
+between leaving each pixel untouched or painting it a specified colour,
+an antialiased drawing function will _blend_ the original and new
+colours in pixels at a figure's boundary according to the proportion of
+the pixel occupied by the figure (probably modified by some heuristic
+fudge factors). All of this produces a smoother appearance for curves
+and diagonal lines.
+
+An unfortunate effect of drawing an anti-aliased figure repeatedly
+is that the pixels around the figure's boundary come steadily more
+saturated with `ink' and the boundary appears to `spread out'. Worse,
+redrawing a figure in a different colour won't fully paint over the old
+boundary pixels, so the end result is a rather ugly smudge.
+
+A good strategy to avoid unpleasant anti-aliasing artifacts is to
+identify a number of rectangular areas which need to be redrawn, clear
+them to the background colour, and then redraw their contents from
+scratch, being careful all the while not to stray beyond the boundaries
+of the original rectangles. The clip() function (section 3.1.9) comes in
+very handy here. Games based on a square grid can often do this fairly
+easily. Other games may need to be somewhat more careful. For example,
+Loopy's redraw function first identifies portions of the display which
+need to be updated. Then, if the changes are fairly well localised, it
+clears and redraws a rectangle containing each changed area. Otherwise,
+it gives up and redraws the entire grid from scratch.
+
+It is possible to avoid clearing to background and redrawing from
+scratch if one is very careful about which drawing functions one
+uses: if a function is documented as not anti-aliasing under some
+circumstances, you can rely on each pixel in a drawing either being left
+entirely alone or being set to the requested colour, with no blending
+being performed.
+
+In the following sections I first discuss the drawing API as seen by the
+back end, and then the _almost_ identical function-pointer form seen by
+the front end.
+
+3.1. Drawing API as seen by the back end
+----------------------------------------
+
+This section documents the back-end drawing API, in the form of
+functions which take a `drawing' object as an argument.
+
+3.1.1. draw_rect()
+------------------
+
+  void draw_rect(drawing *dr, int x, int y, int w, int h,
+                 int colour);
+
+Draws a filled rectangle in the puzzle window.
+
+`x' and `y' give the coordinates of the top left pixel of the rectangle.
+`w' and `h' give its width and height. Thus, the horizontal extent of
+the rectangle runs from `x' to `x+w-1' inclusive, and the vertical
+extent from `y' to `y+h-1' inclusive.
+
+`colour' is an integer index into the colours array returned by the back
+end function colours() (section 2.8.6).
+
+There is no separate pixel-plotting function. If you want to plot a
+single pixel, the approved method is to use draw_rect() with width and
+height set to 1.
+
+Unlike many of the other drawing functions, this function is guaranteed
+to be pixel-perfect: the rectangle will be sharply defined and not anti-
+aliased or anything like that.
+
+This function may be used for both drawing and printing.
+
+3.1.2. draw_rect_outline()
+--------------------------
+
+  void draw_rect_outline(drawing *dr, int x, int y, int w, int h,
+                         int colour);
+
+Draws an outline rectangle in the puzzle window.
+
+`x' and `y' give the coordinates of the top left pixel of the rectangle.
+`w' and `h' give its width and height. Thus, the horizontal extent of
+the rectangle runs from `x' to `x+w-1' inclusive, and the vertical
+extent from `y' to `y+h-1' inclusive.
+
+`colour' is an integer index into the colours array returned by the back
+end function colours() (section 2.8.6).
+
+From a back end perspective, this function may be considered to be part
+of the drawing API. However, front ends are not required to implement
+it, since it is actually implemented centrally (in misc.c) as a wrapper
+on draw_polygon().
+
+This function may be used for both drawing and printing.
+
+3.1.3. draw_line()
+------------------
+
+  void draw_line(drawing *dr, int x1, int y1, int x2, int y2,
+                 int colour);
+
+Draws a straight line in the puzzle window.
+
+`x1' and `y1' give the coordinates of one end of the line. `x2' and `y2'
+give the coordinates of the other end. The line drawn includes both
+those points.
+
+`colour' is an integer index into the colours array returned by the back
+end function colours() (section 2.8.6).
+
+Some platforms may perform anti-aliasing on this function. Therefore,
+do not assume that you can erase a line by drawing the same line over
+it in the background colour; anti-aliasing might lead to perceptible
+ghost artefacts around the vanished line. Horizontal and vertical lines,
+however, are pixel-perfect and not anti-aliased.
+
+This function may be used for both drawing and printing.
+
+3.1.4. draw_polygon()
+---------------------
+
+  void draw_polygon(drawing *dr, int *coords, int npoints,
+                    int fillcolour, int outlinecolour);
+
+Draws an outlined or filled polygon in the puzzle window.
+
+`coords' is an array of (2*npoints) integers, containing the `x' and `y'
+coordinates of `npoints' vertices.
+
+`fillcolour' and `outlinecolour' are integer indices into the colours
+array returned by the back end function colours() (section 2.8.6).
+`fillcolour' may also be -1 to indicate that the polygon should be
+outlined only.
+
+The polygon defined by the specified list of vertices is first filled in
+`fillcolour', if specified, and then outlined in `outlinecolour'.
+
+`outlinecolour' may _not_ be -1; it must be a valid colour (and front
+ends are permitted to enforce this by assertion). This is because
+different platforms disagree on whether a filled polygon should include
+its boundary line or not, so drawing _only_ a filled polygon would
+have non-portable effects. If you want your filled polygon not to
+have a visible outline, you must set `outlinecolour' to the same as
+`fillcolour'.
+
+Some platforms may perform anti-aliasing on this function. Therefore, do
+not assume that you can erase a polygon by drawing the same polygon over
+it in the background colour. Also, be prepared for the polygon to extend
+a pixel beyond its obvious bounding box as a result of this; if you
+really need it not to do this to avoid interfering with other delicate
+graphics, you should probably use clip() (section 3.1.9). You can rely
+on horizontal and vertical lines not being anti-aliased.
+
+This function may be used for both drawing and printing.
+
+3.1.5. draw_circle()
+--------------------
+
+  void draw_circle(drawing *dr, int cx, int cy, int radius,
+                   int fillcolour, int outlinecolour);
+
+Draws an outlined or filled circle in the puzzle window.
+
+`cx' and `cy' give the coordinates of the centre of the circle. `radius'
+gives its radius. The total horizontal pixel extent of the circle is
+from `cx-radius+1' to `cx+radius-1' inclusive, and the vertical extent
+similarly around `cy'.
+
+`fillcolour' and `outlinecolour' are integer indices into the colours
+array returned by the back end function colours() (section 2.8.6).
+`fillcolour' may also be -1 to indicate that the circle should be
+outlined only.
+
+The circle is first filled in `fillcolour', if specified, and then
+outlined in `outlinecolour'.
+
+`outlinecolour' may _not_ be -1; it must be a valid colour (and front
+ends are permitted to enforce this by assertion). This is because
+different platforms disagree on whether a filled circle should include
+its boundary line or not, so drawing _only_ a filled circle would
+have non-portable effects. If you want your filled circle not to
+have a visible outline, you must set `outlinecolour' to the same as
+`fillcolour'.
+
+Some platforms may perform anti-aliasing on this function. Therefore, do
+not assume that you can erase a circle by drawing the same circle over
+it in the background colour. Also, be prepared for the circle to extend
+a pixel beyond its obvious bounding box as a result of this; if you
+really need it not to do this to avoid interfering with other delicate
+graphics, you should probably use clip() (section 3.1.9).
+
+This function may be used for both drawing and printing.
+
+3.1.6. draw_thick_line()
+------------------------
+
+  void draw_thick_line(drawing *dr, float thickness,
+                       float x1, float y1, float x2, float y2,
+                       int colour)
+
+Draws a line in the puzzle window, giving control over the line's
+thickness.
+
+`x1' and `y1' give the coordinates of one end of the line. `x2' and `y2'
+give the coordinates of the other end. `thickness' gives the thickness
+of the line, in pixels.
+
+Note that the coordinates and thickness are floating-point: the
+continuous coordinate system is in effect here. It's important to be
+able to address points with better-than-pixel precision in this case,
+because one can't otherwise properly express the endpoints of lines with
+both odd and even thicknesses.
+
+Some platforms may perform anti-aliasing on this function. The precise
+pixels affected by a thick-line drawing operation may vary between
+platforms, and no particular guarantees are provided. Indeed, even
+horizontal or vertical lines may be anti-aliased.
+
+This function may be used for both drawing and printing.
+
+3.1.7. draw_text()
+------------------
+
+  void draw_text(drawing *dr, int x, int y, int fonttype,
+                 int fontsize, int align, int colour, char *text);
+
+Draws text in the puzzle window.
+
+`x' and `y' give the coordinates of a point. The relation of this point
+to the location of the text is specified by `align', which is a bitwise
+OR of horizontal and vertical alignment flags:
+
+ALIGN_VNORMAL
+
+    Indicates that `y' is aligned with the baseline of the text.
+
+ALIGN_VCENTRE
+
+    Indicates that `y' is aligned with the vertical centre of the
+    text. (In fact, it's aligned with the vertical centre of normal
+    _capitalised_ text: displaying two pieces of text with ALIGN_VCENTRE
+    at the same y-coordinate will cause their baselines to be aligned
+    with one another, even if one is an ascender and the other a
+    descender.)
+
+ALIGN_HLEFT
+
+    Indicates that `x' is aligned with the left-hand end of the text.
+
+ALIGN_HCENTRE
+
+    Indicates that `x' is aligned with the horizontal centre of the
+    text.
+
+ALIGN_HRIGHT
+
+    Indicates that `x' is aligned with the right-hand end of the text.
+
+`fonttype' is either FONT_FIXED or FONT_VARIABLE, for a monospaced
+or proportional font respectively. (No more detail than that may be
+specified; it would only lead to portability issues between different
+platforms.)
+
+`fontsize' is the desired size, in pixels, of the text. This size
+corresponds to the overall point size of the text, not to any internal
+dimension such as the cap-height.
+
+`colour' is an integer index into the colours array returned by the back
+end function colours() (section 2.8.6).
+
+This function may be used for both drawing and printing.
+
+The character set used to encode the text passed to this function is
+specified _by the drawing object_, although it must be a superset of
+ASCII. If a puzzle wants to display text that is not contained in ASCII,
+it should use the text_fallback() function (section 3.1.8) to query the
+drawing object for an appropriate representation of the characters it
+wants.
+
+3.1.8. text_fallback()
+----------------------
+
+  char *text_fallback(drawing *dr, const char *const *strings,
+                      int nstrings);
+
+This function is used to request a translation of UTF-8 text into
+whatever character encoding is expected by the drawing object's
+implementation of draw_text().
+
+The input is a list of strings encoded in UTF-8: nstrings gives the
+number of strings in the list, and strings[0], strings[1], ...,
+strings[nstrings-1] are the strings themselves.
+
+The returned string (which is dynamically allocated and must be freed
+when finished with) is derived from the first string in the list that
+the drawing object expects to be able to display reliably; it will
+consist of that string translated into the character set expected by
+draw_text().
+
+Drawing implementations are not required to handle anything outside
+ASCII, but are permitted to assume that _some_ string will be
+successfully translated. So every call to this function must include
+a string somewhere in the list (presumably the last element) which
+consists of nothing but ASCII, to be used by any front end which cannot
+handle anything else.
+
+For example, if a puzzle wished to display a string including a
+multiplication sign (U+00D7 in Unicode, represented by the bytes C3 97
+in UTF-8), it might do something like this:
+
+  static const char *const times_signs[] = { "\xC3\x97", "x" };
+  char *times_sign = text_fallback(dr, times_signs, 2);
+  sprintf(buffer, "%d%s%d", width, times_sign, height);
+  draw_text(dr, x, y, font, size, align, colour, buffer);
+  sfree(buffer);
+
+which would draw a string with a times sign in the middle on platforms
+that support it, and fall back to a simple ASCII `x' where there was no
+alternative.
+
+3.1.9. clip()
+-------------
+
+  void clip(drawing *dr, int x, int y, int w, int h);
+
+Establishes a clipping rectangle in the puzzle window.
+
+`x' and `y' give the coordinates of the top left pixel of the clipping
+rectangle. `w' and `h' give its width and height. Thus, the horizontal
+extent of the rectangle runs from `x' to `x+w-1' inclusive, and the
+vertical extent from `y' to `y+h-1' inclusive. (These are exactly the
+same semantics as draw_rect().)
+
+After this call, no drawing operation will affect anything outside the
+specified rectangle. The effect can be reversed by calling unclip()
+(section 3.1.10). The clipping rectangle is pixel-perfect: pixels within
+the rectangle are affected as usual by drawing functions; pixels outside
+are completely untouched.
+
+Back ends should not assume that a clipping rectangle will be
+automatically cleared up by the front end if it's left lying around;
+that might work on current front ends, but shouldn't be relied upon.
+Always explicitly call unclip().
+
+This function may be used for both drawing and printing.
+
+3.1.10. unclip()
+----------------
+
+  void unclip(drawing *dr);
+
+Reverts the effect of a previous call to clip(). After this call, all
+drawing operations will be able to affect the entire puzzle window
+again.
+
+This function may be used for both drawing and printing.
+
+3.1.11. draw_update()
+---------------------
+
+  void draw_update(drawing *dr, int x, int y, int w, int h);
+
+Informs the front end that a rectangular portion of the puzzle window
+has been drawn on and needs to be updated.
+
+`x' and `y' give the coordinates of the top left pixel of the update
+rectangle. `w' and `h' give its width and height. Thus, the horizontal
+extent of the rectangle runs from `x' to `x+w-1' inclusive, and the
+vertical extent from `y' to `y+h-1' inclusive. (These are exactly the
+same semantics as draw_rect().)
+
+The back end redraw function _must_ call this function to report any
+changes it has made to the window. Otherwise, those changes may not
+become immediately visible, and may then appear at an unpredictable
+subsequent time such as the next time the window is covered and re-
+exposed.
+
+This function is only important when drawing. It may be called when
+printing as well, but doing so is not compulsory, and has no effect.
+(So if you have a shared piece of code between the drawing and printing
+routines, that code may safely call draw_update().)
+
+3.1.12. status_bar()
+--------------------
+
+  void status_bar(drawing *dr, char *text);
+
+Sets the text in the game's status bar to `text'. The text is copied
+from the supplied buffer, so the caller is free to deallocate or modify
+the buffer after use.
+
+(This function is not exactly a _drawing_ function, but it shares with
+the drawing API the property that it may only be called from within the
+back end redraw function, so this is as good a place as any to document
+it.)
+
+The supplied text is filtered through the mid-end for optional rewriting
+before being passed on to the front end; the mid-end will prepend the
+current game time if the game is timed (and may in future perform other
+rewriting if it seems like a good idea).
+
+This function is for drawing only; it must never be called during
+printing.
+
+3.1.13. Blitter functions
+-------------------------
+
+This section describes a group of related functions which save and
+restore a section of the puzzle window. This is most commonly used to
+implement user interfaces involving dragging a puzzle element around the
+window: at the end of each call to redraw(), if an object is currently
+being dragged, the back end saves the window contents under that
+location and then draws the dragged object, and at the start of the next
+redraw() the first thing it does is to restore the background.
+
+The front end defines an opaque type called a `blitter', which is
+capable of storing a rectangular area of a specified size.
+
+Blitter functions are for drawing only; they must never be called during
+printing.
+
+3.1.13.1. blitter_new()
+-----------------------
+
+  blitter *blitter_new(drawing *dr, int w, int h);
+
+Creates a new blitter object which stores a rectangle of size `w' by `h'
+pixels. Returns a pointer to the blitter object.
+
+Blitter objects are best stored in the `game_drawstate'. A good time to
+create them is in the set_size() function (section 2.8.5), since it is
+at this point that you first know how big a rectangle they will need to
+save.
+
+3.1.13.2. blitter_free()
+------------------------
+
+  void blitter_free(drawing *dr, blitter *bl);
+
+Disposes of a blitter object. Best called in free_drawstate(). (However,
+check that the blitter object is not NULL before attempting to free it;
+it is possible that a draw state might be created and freed without ever
+having set_size() called on it in between.)
+
+3.1.13.3. blitter_save()
+------------------------
+
+  void blitter_save(drawing *dr, blitter *bl, int x, int y);
+
+This is a true drawing API function, in that it may only be called from
+within the game redraw routine. It saves a rectangular portion of the
+puzzle window into the specified blitter object.
+
+`x' and `y' give the coordinates of the top left corner of the saved
+rectangle. The rectangle's width and height are the ones specified when
+the blitter object was created.
+
+This function is required to cope and do the right thing if `x' and `y'
+are out of range. (The right thing probably means saving whatever part
+of the blitter rectangle overlaps with the visible area of the puzzle
+window.)
+
+3.1.13.4. blitter_load()
+------------------------
+
+  void blitter_load(drawing *dr, blitter *bl, int x, int y);
+
+This is a true drawing API function, in that it may only be called from
+within the game redraw routine. It restores a rectangular portion of the
+puzzle window from the specified blitter object.
+
+`x' and `y' give the coordinates of the top left corner of the rectangle
+to be restored. The rectangle's width and height are the ones specified
+when the blitter object was created.
+
+Alternatively, you can specify both `x' and `y' as the special value
+BLITTER_FROMSAVED, in which case the rectangle will be restored to
+exactly where it was saved from. (This is probably what you want to do
+almost all the time, if you're using blitters to implement draggable
+puzzle elements.)
+
+This function is required to cope and do the right thing if `x' and
+`y' (or the equivalent ones saved in the blitter) are out of range.
+(The right thing probably means restoring whatever part of the blitter
+rectangle overlaps with the visible area of the puzzle window.)
+
+If this function is called on a blitter which had previously been saved
+from a partially out-of-range rectangle, then the parts of the saved
+bitmap which were not visible at save time are undefined. If the blitter
+is restored to a different position so as to make those parts visible,
+the effect on the drawing area is undefined.
+
+3.1.14. print_mono_colour()
+---------------------------
+
+  int print_mono_colour(drawing *dr, int grey);
+
+This function allocates a colour index for a simple monochrome colour
+during printing.
+
+`grey' must be 0 or 1. If `grey' is 0, the colour returned is black; if
+`grey' is 1, the colour is white.
+
+3.1.15. print_grey_colour()
+---------------------------
+
+  int print_grey_colour(drawing *dr, float grey);
+
+This function allocates a colour index for a grey-scale colour during
+printing.
+
+`grey' may be any number between 0 (black) and 1 (white); for example,
+0.5 indicates a medium grey.
+
+The chosen colour will be rendered to the limits of the printer's
+halftoning capability.
+
+3.1.16. print_hatched_colour()
+------------------------------
+
+  int print_hatched_colour(drawing *dr, int hatch);
+
+This function allocates a colour index which does not represent a
+literal _colour_. Instead, regions shaded in this colour will be hatched
+with parallel lines. The `hatch' parameter defines what type of hatching
+should be used in place of this colour:
+
+HATCH_SLASH
+
+    This colour will be hatched by lines slanting to the right at 45
+    degrees.
+
+HATCH_BACKSLASH
+
+    This colour will be hatched by lines slanting to the left at 45
+    degrees.
+
+HATCH_HORIZ
+
+    This colour will be hatched by horizontal lines.
+
+HATCH_VERT
+
+    This colour will be hatched by vertical lines.
+
+HATCH_PLUS
+
+    This colour will be hatched by criss-crossing horizontal and
+    vertical lines.
+
+HATCH_X
+
+    This colour will be hatched by criss-crossing diagonal lines.
+
+Colours defined to use hatching may not be used for drawing lines or
+text; they may only be used for filling areas. That is, they may be
+used as the `fillcolour' parameter to draw_circle() and draw_polygon(),
+and as the colour parameter to draw_rect(), but may not be used as the
+`outlinecolour' parameter to draw_circle() or draw_polygon(), or with
+draw_line() or draw_text().
+
+3.1.17. print_rgb_mono_colour()
+-------------------------------
+
+  int print_rgb_mono_colour(drawing *dr, float r, float g,
+                            float b, float grey);
+
+This function allocates a colour index for a fully specified RGB colour
+during printing.
+
+`r', `g' and `b' may each be anywhere in the range from 0 to 1.
+
+If printing in black and white only, these values will be ignored, and
+either pure black or pure white will be used instead, according to the
+`grey' parameter. (The fallback colour is the same as the one which
+would be allocated by print_mono_colour(grey).)
+
+3.1.18. print_rgb_grey_colour()
+-------------------------------
+
+  int print_rgb_grey_colour(drawing *dr, float r, float g,
+                            float b, float grey);
+
+This function allocates a colour index for a fully specified RGB colour
+during printing.
+
+`r', `g' and `b' may each be anywhere in the range from 0 to 1.
+
+If printing in black and white only, these values will be ignored, and
+a shade of grey given by the `grey' parameter will be used instead.
+(The fallback colour is the same as the one which would be allocated by
+print_grey_colour(grey).)
+
+3.1.19. print_rgb_hatched_colour()
+----------------------------------
+
+  int print_rgb_hatched_colour(drawing *dr, float r, float g,
+                               float b, float hatched);
+
+This function allocates a colour index for a fully specified RGB colour
+during printing.
+
+`r', `g' and `b' may each be anywhere in the range from 0 to 1.
+
+If printing in black and white only, these values will be ignored, and
+a form of cross-hatching given by the `hatch' parameter will be used
+instead; see section 3.1.16 for the possible values of this parameter.
+(The fallback colour is the same as the one which would be allocated by
+print_hatched_colour(hatch).)
+
+3.1.20. print_line_width()
+--------------------------
+
+  void print_line_width(drawing *dr, int width);
+
+This function is called to set the thickness of lines drawn during
+printing. It is meaningless in drawing: all lines drawn by draw_line(),
+draw_circle and draw_polygon() are one pixel in thickness. However, in
+printing there is no clear definition of a pixel and so line widths must
+be explicitly specified.
+
+The line width is specified in the usual coordinate system. Note,
+however, that it is a hint only: the central printing system may choose
+to vary line thicknesses at user request or due to printer capabilities.
+
+3.1.21. print_line_dotted()
+---------------------------
+
+  void print_line_dotted(drawing *dr, int dotted);
+
+This function is called to toggle the drawing of dotted lines during
+printing. It is not supported during drawing.
+
+The parameter `dotted' is a boolean; TRUE means that future lines drawn
+by draw_line(), draw_circle and draw_polygon() will be dotted, and FALSE
+means that they will be solid.
+
+Some front ends may impose restrictions on the width of dotted lines.
+Asking for a dotted line via this front end will override any line width
+request if the front end requires it.
+
+3.2. The drawing API as implemented by the front end
+----------------------------------------------------
+
+This section describes the drawing API in the function-pointer form in
+which it is implemented by a front end.
+
+(It isn't only platform-specific front ends which implement this API;
+the platform-independent module `ps.c' also provides an implementation
+of it which outputs PostScript. Thus, any platform which wants to do PS
+printing can do so with minimum fuss.)
+
+The following entries all describe function pointer fields in a
+structure called `drawing_api'. Each of the functions takes a `void *'
+context pointer, which it should internally cast back to a more useful
+type. Thus, a drawing _object_ (`drawing *)' suitable for passing to
+the back end redraw or printing functions is constructed by passing a
+`drawing_api' and a `void *' to the function drawing_new() (see section
+3.3.1).
+
+3.2.1. draw_text()
+------------------
+
+  void (*draw_text)(void *handle, int x, int y, int fonttype,
+                    int fontsize, int align, int colour, char *text);
+
+This function behaves exactly like the back end draw_text() function;
+see section 3.1.7.
+
+3.2.2. draw_rect()
+------------------
+
+  void (*draw_rect)(void *handle, int x, int y, int w, int h,
+                    int colour);
+
+This function behaves exactly like the back end draw_rect() function;
+see section 3.1.1.
+
+3.2.3. draw_line()
+------------------
+
+  void (*draw_line)(void *handle, int x1, int y1, int x2, int y2,
+                    int colour);
+
+This function behaves exactly like the back end draw_line() function;
+see section 3.1.3.
+
+3.2.4. draw_polygon()
+---------------------
+
+  void (*draw_polygon)(void *handle, int *coords, int npoints,
+                       int fillcolour, int outlinecolour);
+
+This function behaves exactly like the back end draw_polygon() function;
+see section 3.1.4.
+
+3.2.5. draw_circle()
+--------------------
+
+  void (*draw_circle)(void *handle, int cx, int cy, int radius,
+                      int fillcolour, int outlinecolour);
+
+This function behaves exactly like the back end draw_circle() function;
+see section 3.1.5.
+
+3.2.6. draw_thick_line()
+------------------------
+
+  void draw_thick_line(drawing *dr, float thickness,
+                       float x1, float y1, float x2, float y2,
+                       int colour)
+
+This function behaves exactly like the back end draw_thick_line()
+function; see section 3.1.6.
+
+An implementation of this API which doesn't provide high-quality
+rendering of thick lines is permitted to define this function pointer
+to be NULL. The middleware in drawing.c will notice and provide a low-
+quality alternative using draw_polygon().
+
+3.2.7. draw_update()
+--------------------
+
+  void (*draw_update)(void *handle, int x, int y, int w, int h);
+
+This function behaves exactly like the back end draw_update() function;
+see section 3.1.11.
+
+An implementation of this API which only supports printing is permitted
+to define this function pointer to be NULL rather than bothering to
+define an empty function. The middleware in drawing.c will notice and
+avoid calling it.
+
+3.2.8. clip()
+-------------
+
+  void (*clip)(void *handle, int x, int y, int w, int h);
+
+This function behaves exactly like the back end clip() function; see
+section 3.1.9.
+
+3.2.9. unclip()
+---------------
+
+  void (*unclip)(void *handle);
+
+This function behaves exactly like the back end unclip() function; see
+section 3.1.10.
+
+3.2.10. start_draw()
+--------------------
+
+  void (*start_draw)(void *handle);
+
+This function is called at the start of drawing. It allows the front end
+to initialise any temporary data required to draw with, such as device
+contexts.
+
+Implementations of this API which do not provide drawing services may
+define this function pointer to be NULL; it will never be called unless
+drawing is attempted.
+
+3.2.11. end_draw()
+------------------
+
+  void (*end_draw)(void *handle);
+
+This function is called at the end of drawing. It allows the front end
+to do cleanup tasks such as deallocating device contexts and scheduling
+appropriate GUI redraw events.
+
+Implementations of this API which do not provide drawing services may
+define this function pointer to be NULL; it will never be called unless
+drawing is attempted.
+
+3.2.12. status_bar()
+--------------------
+
+  void (*status_bar)(void *handle, char *text);
+
+This function behaves exactly like the back end status_bar() function;
+see section 3.1.12.
+
+Front ends implementing this function need not worry about it
+being called repeatedly with the same text; the middleware code in
+status_bar() will take care of this.
+
+Implementations of this API which do not provide drawing services may
+define this function pointer to be NULL; it will never be called unless
+drawing is attempted.
+
+3.2.13. blitter_new()
+---------------------
+
+  blitter *(*blitter_new)(void *handle, int w, int h);
+
+This function behaves exactly like the back end blitter_new() function;
+see section 3.1.13.1.
+
+Implementations of this API which do not provide drawing services may
+define this function pointer to be NULL; it will never be called unless
+drawing is attempted.
+
+3.2.14. blitter_free()
+----------------------
+
+  void (*blitter_free)(void *handle, blitter *bl);
+
+This function behaves exactly like the back end blitter_free() function;
+see section 3.1.13.2.
+
+Implementations of this API which do not provide drawing services may
+define this function pointer to be NULL; it will never be called unless
+drawing is attempted.
+
+3.2.15. blitter_save()
+----------------------
+
+  void (*blitter_save)(void *handle, blitter *bl, int x, int y);
+
+This function behaves exactly like the back end blitter_save() function;
+see section 3.1.13.3.
+
+Implementations of this API which do not provide drawing services may
+define this function pointer to be NULL; it will never be called unless
+drawing is attempted.
+
+3.2.16. blitter_load()
+----------------------
+
+  void (*blitter_load)(void *handle, blitter *bl, int x, int y);
+
+This function behaves exactly like the back end blitter_load() function;
+see section 3.1.13.4.
+
+Implementations of this API which do not provide drawing services may
+define this function pointer to be NULL; it will never be called unless
+drawing is attempted.
+
+3.2.17. begin_doc()
+-------------------
+
+  void (*begin_doc)(void *handle, int pages);
+
+This function is called at the beginning of a printing run. It gives the
+front end an opportunity to initialise any required printing subsystem.
+It also provides the number of pages in advance.
+
+Implementations of this API which do not provide printing services may
+define this function pointer to be NULL; it will never be called unless
+printing is attempted.
+
+3.2.18. begin_page()
+--------------------
+
+  void (*begin_page)(void *handle, int number);
+
+This function is called during printing, at the beginning of each page.
+It gives the page number (numbered from 1 rather than 0, so suitable for
+use in user-visible contexts).
+
+Implementations of this API which do not provide printing services may
+define this function pointer to be NULL; it will never be called unless
+printing is attempted.
+
+3.2.19. begin_puzzle()
+----------------------
+
+  void (*begin_puzzle)(void *handle, float xm, float xc,
+                       float ym, float yc, int pw, int ph, float wmm);
+
+This function is called during printing, just before printing a single
+puzzle on a page. It specifies the size and location of the puzzle on
+the page.
+
+`xm' and `xc' specify the horizontal position of the puzzle on the page,
+as a linear function of the page width. The front end is expected to
+multiply the page width by `xm', add `xc' (measured in millimetres), and
+use the resulting x-coordinate as the left edge of the puzzle.
+
+Similarly, `ym' and `yc' specify the vertical position of the puzzle as
+a function of the page height: the page height times `ym', plus `yc'
+millimetres, equals the desired distance from the top of the page to the
+top of the puzzle.
+
+(This unwieldy mechanism is required because not all printing systems
+can communicate the page size back to the software. The PostScript back
+end, for example, writes out PS which determines the page size at print
+time by means of calling `clippath', and centres the puzzles within
+that. Thus, exactly the same PS file works on A4 or on US Letter paper
+without needing local configuration, which simplifies matters.)
+
+pw and ph give the size of the puzzle in drawing API coordinates. The
+printing system will subsequently call the puzzle's own print function,
+which will in turn call drawing API functions in the expectation that an
+area pw by ph units is available to draw the puzzle on.
+
+Finally, wmm gives the desired width of the puzzle in millimetres. (The
+aspect ratio is expected to be preserved, so if the desired puzzle
+height is also needed then it can be computed as wmm*ph/pw.)
+
+Implementations of this API which do not provide printing services may
+define this function pointer to be NULL; it will never be called unless
+printing is attempted.
+
+3.2.20. end_puzzle()
+--------------------
+
+  void (*end_puzzle)(void *handle);
+
+This function is called after the printing of a specific puzzle is
+complete.
+
+Implementations of this API which do not provide printing services may
+define this function pointer to be NULL; it will never be called unless
+printing is attempted.
+
+3.2.21. end_page()
+------------------
+
+  void (*end_page)(void *handle, int number);
+
+This function is called after the printing of a page is finished.
+
+Implementations of this API which do not provide printing services may
+define this function pointer to be NULL; it will never be called unless
+printing is attempted.
+
+3.2.22. end_doc()
+-----------------
+
+  void (*end_doc)(void *handle);
+
+This function is called after the printing of the entire document is
+finished. This is the moment to close files, send things to the print
+spooler, or whatever the local convention is.
+
+Implementations of this API which do not provide printing services may
+define this function pointer to be NULL; it will never be called unless
+printing is attempted.
+
+3.2.23. line_width()
+--------------------
+
+  void (*line_width)(void *handle, float width);
+
+This function is called to set the line thickness, during printing only.
+Note that the width is a float here, where it was an int as seen by the
+back end. This is because drawing.c may have scaled it on the way past.
+
+However, the width is still specified in the same coordinate system as
+the rest of the drawing.
+
+Implementations of this API which do not provide printing services may
+define this function pointer to be NULL; it will never be called unless
+printing is attempted.
+
+3.2.24. text_fallback()
+-----------------------
+
+  char *(*text_fallback)(void *handle, const char *const *strings,
+                         int nstrings);
+
+This function behaves exactly like the back end text_fallback()
+function; see section 3.1.8.
+
+Implementations of this API which do not support any characters outside
+ASCII may define this function pointer to be NULL, in which case the
+central code in drawing.c will provide a default implementation.
+
+3.3. The drawing API as called by the front end
+-----------------------------------------------
+
+There are a small number of functions provided in drawing.c which the
+front end needs to _call_, rather than helping to implement. They are
+described in this section.
+
+3.3.1. drawing_new()
+--------------------
+
+  drawing *drawing_new(const drawing_api *api, midend *me,
+                       void *handle);
+
+This function creates a drawing object. It is passed a `drawing_api',
+which is a structure containing nothing but function pointers; and also
+a `void *' handle. The handle is passed back to each function pointer
+when it is called.
+
+The `midend' parameter is used for rewriting the status bar contents:
+status_bar() (see section 3.1.12) has to call a function in the mid-
+end which might rewrite the status bar text. If the drawing object
+is to be used only for printing, or if the game is known not to call
+status_bar(), this parameter may be NULL.
+
+3.3.2. drawing_free()
+---------------------
+
+  void drawing_free(drawing *dr);
+
+This function frees a drawing object. Note that the `void *' handle is
+not freed; if that needs cleaning up it must be done by the front end.
+
+3.3.3. print_get_colour()
+-------------------------
+
+  void print_get_colour(drawing *dr, int colour, int printincolour,
+                        int *hatch, float *r, float *g, float *b)
+
+This function is called by the implementations of the drawing API
+functions when they are called in a printing context. It takes a colour
+index as input, and returns the description of the colour as requested
+by the back end.
+
+`printincolour' is TRUE iff the implementation is printing in colour.
+This will alter the results returned if the colour in question was
+specified with a black-and-white fallback value.
+
+If the colour should be rendered by hatching, `*hatch' is filled with
+the type of hatching desired. See section 3.1.15 for details of the
+values this integer can take.
+
+If the colour should be rendered as solid colour, `*hatch' is given a
+negative value, and `*r', `*g' and `*b' are filled with the RGB values
+of the desired colour (if printing in colour), or all filled with the
+grey-scale value (if printing in black and white).
+
+4. The API provided by the mid-end
+----------------------------------
+
+This chapter documents the API provided by the mid-end to be called by
+the front end. You probably only need to read this if you are a front
+end implementor, i.e. you are porting Puzzles to a new platform. If
+you're only interested in writing new puzzles, you can safely skip this
+chapter.
+
+All the persistent state in the mid-end is encapsulated within a
+`midend' structure, to facilitate having multiple mid-ends in any
+port which supports multiple puzzle windows open simultaneously. Each
+`midend' is intended to handle the contents of a single puzzle window.
+
+4.1. midend_new()
+-----------------
+
+  midend *midend_new(frontend *fe, const game *ourgame,
+                     const drawing_api *drapi, void *drhandle)
+
+Allocates and returns a new mid-end structure.
+
+The `fe' argument is stored in the mid-end. It will be used when calling
+back to functions such as activate_timer() (section 4.37), and will be
+passed on to the back end function colours() (section 2.8.6).
+
+The parameters `drapi' and `drhandle' are passed to drawing_new()
+(section 3.3.1) to construct a drawing object which will be passed to
+the back end function redraw() (section 2.8.10). Hence, all drawing-
+related function pointers defined in `drapi' can expect to be called
+with `drhandle' as their first argument.
+
+The `ourgame' argument points to a container structure describing a game
+back end. The mid-end thus created will only be capable of handling that
+one game. (So even in a monolithic front end containing all the games,
+this imposes the constraint that any individual puzzle window is tied to
+a single game. Unless, of course, you feel brave enough to change the
+mid-end for the window without closing the window...)
+
+4.2. midend_free()
+------------------
+
+  void midend_free(midend *me);
+
+Frees a mid-end structure and all its associated data.
+
+4.3. midend_tilesize()
+----------------------
+
+  int midend_tilesize(midend *me);
+
+Returns the `tilesize' parameter being used to display the current
+puzzle (section 2.8.3).
+
+4.4. midend_set_params()
+------------------------
+
+  void midend_set_params(midend *me, game_params *params);
+
+Sets the current game parameters for a mid-end. Subsequent games
+generated by midend_new_game() (section 4.8) will use these parameters
+until further notice.
+
+The usual way in which the front end will have an actual `game_params'
+structure to pass to this function is if it had previously got it from
+midend_fetch_preset() (section 4.16). Thus, this function is usually
+called in response to the user making a selection from the presets menu.
+
+4.5. midend_get_params()
+------------------------
+
+  game_params *midend_get_params(midend *me);
+
+Returns the current game parameters stored in this mid-end.
+
+The returned value is dynamically allocated, and should be freed when
+finished with by passing it to the game's own free_params() function
+(see section 2.3.5).
+
+4.6. midend_size()
+------------------
+
+  void midend_size(midend *me, int *x, int *y, int user_size);
+
+Tells the mid-end to figure out its window size.
+
+On input, `*x' and `*y' should contain the maximum or requested size
+for the window. (Typically this will be the size of the screen that the
+window has to fit on, or similar.) The mid-end will repeatedly call the
+back end function compute_size() (section 2.8.4), searching for a tile
+size that best satisfies the requirements. On exit, `*x' and `*y' will
+contain the size needed for the puzzle window's drawing area. (It is
+of course up to the front end to adjust this for any additional window
+furniture such as menu bars and window borders, if necessary. The status
+bar is also not included in this size.)
+
+Use `user_size' to indicate whether `*x' and `*y' are a requested size,
+or just a maximum size.
+
+If `user_size' is set to TRUE, the mid-end will treat the input size as
+a request, and will pick a tile size which approximates it _as closely
+as possible_, going over the game's preferred tile size if necessary to
+achieve this. The mid-end will also use the resulting tile size as its
+preferred one until further notice, on the assumption that this size was
+explicitly requested by the user. Use this option if you want your front
+end to support dynamic resizing of the puzzle window with automatic
+scaling of the puzzle to fit.
+
+If `user_size' is set to FALSE, then the game's tile size will never go
+over its preferred one, although it may go under in order to fit within
+the maximum bounds specified by `*x' and `*y'. This is the recommended
+approach when opening a new window at default size: the game will use
+its preferred size unless it has to use a smaller one to fit on the
+screen. If the tile size is shrunk for this reason, the change will not
+persist; if a smaller grid is subsequently chosen, the tile size will
+recover.
+
+The mid-end will try as hard as it can to return a size which is
+less than or equal to the input size, in both dimensions. In extreme
+circumstances it may fail (if even the lowest possible tile size gives
+window dimensions greater than the input), in which case it will return
+a size greater than the input size. Front ends should be prepared
+for this to happen (i.e. don't crash or fail an assertion), but may
+handle it in any way they see fit: by rejecting the game parameters
+which caused the problem, by opening a window larger than the screen
+regardless of inconvenience, by introducing scroll bars on the window,
+by drawing on a large bitmap and scaling it into a smaller window, or by
+any other means you can think of. It is likely that when the tile size
+is that small the game will be unplayable anyway, so don't put _too_
+much effort into handling it creatively.
+
+If your platform has no limit on window size (or if you're planning to
+use scroll bars for large puzzles), you can pass dimensions of INT_MAX
+as input to this function. You should probably not do that _and_ set the
+`user_size' flag, though!
+
+The midend relies on the frontend calling midend_new_game() (section
+4.8) before calling midend_size().
+
+4.7. midend_reset_tilesize()
+----------------------------
+
+  void midend_reset_tilesize(midend *me);
+
+This function resets the midend's preferred tile size to that of the
+standard puzzle.
+
+As discussed in section 4.6, puzzle resizes are typically 'sticky',
+in that once the user has dragged the puzzle to a different window
+size, the resulting tile size will be remembered and used when the
+puzzle configuration changes. If you _don't_ want that, e.g. if you
+want to provide a command to explicitly reset the puzzle size back to
+its default, then you can call this just before calling midend_size()
+(which, in turn, you would probably call with `user_size' set to FALSE).
+
+4.8. midend_new_game()
+----------------------
+
+  void midend_new_game(midend *me);
+
+Causes the mid-end to begin a new game. Normally the game will be a
+new randomly generated puzzle. However, if you have previously called
+midend_game_id() or midend_set_config(), the game generated might be
+dictated by the results of those functions. (In particular, you _must_
+call midend_new_game() after calling either of those functions, or else
+no immediate effect will be visible.)
+
+You will probably need to call midend_size() after calling this
+function, because if the game parameters have been changed since the
+last new game then the window size might need to change. (If you know
+the parameters _haven't_ changed, you don't need to do this.)
+
+This function will create a new `game_drawstate', but does not actually
+perform a redraw (since you often need to call midend_size() before
+the redraw can be done). So after calling this function and after
+calling midend_size(), you should then call midend_redraw(). (It is not
+necessary to call midend_force_redraw(); that will discard the draw
+state and create a fresh one, which is unnecessary in this case since
+there's a fresh one already. It would work, but it's usually excessive.)
+
+4.9. midend_restart_game()
+--------------------------
+
+  void midend_restart_game(midend *me);
+
+This function causes the current game to be restarted. This is done by
+placing a new copy of the original game state on the end of the undo
+list (so that an accidental restart can be undone).
+
+This function automatically causes a redraw, i.e. the front end can
+expect its drawing API to be called from _within_ a call to this
+function. Some back ends require that midend_size() (section 4.6) is
+called before midend_restart_game().
+
+4.10. midend_force_redraw()
+---------------------------
+
+  void midend_force_redraw(midend *me);
+
+Forces a complete redraw of the puzzle window, by means of discarding
+the current `game_drawstate' and creating a new one from scratch before
+calling the game's redraw() function.
+
+The front end can expect its drawing API to be called from within a call
+to this function. Some back ends require that midend_size() (section
+4.6) is called before midend_force_redraw().
+
+4.11. midend_redraw()
+---------------------
+
+  void midend_redraw(midend *me);
+
+Causes a partial redraw of the puzzle window, by means of simply calling
+the game's redraw() function. (That is, the only things redrawn will be
+things that have changed since the last redraw.)
+
+The front end can expect its drawing API to be called from within a call
+to this function. Some back ends require that midend_size() (section
+4.6) is called before midend_redraw().
+
+4.12. midend_process_key()
+--------------------------
+
+  int midend_process_key(midend *me, int x, int y, int button);
+
+The front end calls this function to report a mouse or keyboard event.
+The parameters `x', `y' and `button' are almost identical to the ones
+passed to the back end function interpret_move() (section 2.7.1), except
+that the front end is _not_ required to provide the guarantees about
+mouse event ordering. The mid-end will sort out multiple simultaneous
+button presses and changes of button; the front end's responsibility
+is simply to pass on the mouse events it receives as accurately as
+possible.
+
+(Some platforms may need to emulate absent mouse buttons by means of
+using a modifier key such as Shift with another mouse button. This tends
+to mean that if Shift is pressed or released in the middle of a mouse
+drag, the mid-end will suddenly stop receiving, say, LEFT_DRAG events
+and start receiving RIGHT_DRAGs, with no intervening button release or
+press events. This too is something which the mid-end will sort out for
+you; the front end has no obligation to maintain sanity in this area.)
+
+The front end _should_, however, always eventually send some kind of
+button release. On some platforms this requires special effort: Windows,
+for example, requires a call to the system API function SetCapture() in
+order to ensure that your window receives a mouse-up event even if the
+pointer has left the window by the time the mouse button is released.
+On any platform that requires this sort of thing, the front end _is_
+responsible for doing it.
+
+Calling this function is very likely to result in calls back to the
+front end's drawing API and/or activate_timer() (section 4.37).
+
+The return value from midend_process_key() is non-zero, unless the
+effect of the keypress was to request termination of the program. A
+front end should shut down the puzzle in response to a zero return.
+
+4.13. midend_colours()
+----------------------
+
+  float *midend_colours(midend *me, int *ncolours);
+
+Returns an array of the colours required by the game, in exactly
+the same format as that returned by the back end function colours()
+(section 2.8.6). Front ends should call this function rather than
+calling the back end's version directly, since the mid-end adds standard
+customisation facilities. (At the time of writing, those customisation
+facilities are implemented hackily by means of environment variables,
+but it's not impossible that they may become more full and formal in
+future.)
+
+4.14. midend_timer()
+--------------------
+
+  void midend_timer(midend *me, float tplus);
+
+If the mid-end has called activate_timer() (section 4.37) to request
+regular callbacks for purposes of animation or timing, this is the
+function the front end should call on a regular basis. The argument
+`tplus' gives the time, in seconds, since the last time either this
+function was called or activate_timer() was invoked.
+
+One of the major purposes of timing in the mid-end is to perform move
+animation. Therefore, calling this function is very likely to result in
+calls back to the front end's drawing API.
+
+4.15. midend_num_presets()
+--------------------------
+
+  int midend_num_presets(midend *me);
+
+Returns the number of game parameter presets supplied by this game.
+Front ends should use this function and midend_fetch_preset() to
+configure their presets menu rather than calling the back end directly,
+since the mid-end adds standard customisation facilities. (At the time
+of writing, those customisation facilities are implemented hackily by
+means of environment variables, but it's not impossible that they may
+become more full and formal in future.)
+
+4.16. midend_fetch_preset()
+---------------------------
+
+  void midend_fetch_preset(midend *me, int n,
+                           char **name, game_params **params);
+
+Returns one of the preset game parameter structures for the game.
+On input `n' must be a non-negative integer and less than the value
+returned from midend_num_presets(). On output, `*name' is set to an
+ASCII string suitable for entering in the game's presets menu, and
+`*params' is set to the corresponding `game_params' structure.
+
+Both of the two output values are dynamically allocated, but they are
+owned by the mid-end structure: the front end should not ever free them
+directly, because they will be freed automatically during midend_free().
+
+4.17. midend_which_preset()
+---------------------------
+
+  int midend_which_preset(midend *me);
+
+Returns the numeric index of the preset game parameter structure which
+matches the current game parameters, or a negative number if no preset
+matches. Front ends could use this to maintain a tick beside one of the
+items in the menu (or tick the `Custom' option if the return value is
+less than zero).
+
+4.18. midend_wants_statusbar()
+------------------------------
+
+  int midend_wants_statusbar(midend *me);
+
+This function returns TRUE if the puzzle has a use for a textual status
+line (to display score, completion status, currently active tiles, time,
+or anything else).
+
+Front ends should call this function rather than talking directly to the
+back end.
+
+4.19. midend_get_config()
+-------------------------
+
+  config_item *midend_get_config(midend *me, int which,
+                                 char **wintitle);
+
+Returns a dialog box description for user configuration.
+
+On input, which should be set to one of three values, which select which
+of the various dialog box descriptions is returned:
+
+CFG_SETTINGS
+
+    Requests the GUI parameter configuration box generated by the puzzle
+    itself. This should be used when the user selects `Custom' from the
+    game types menu (or equivalent). The mid-end passes this request on
+    to the back end function configure() (section 2.3.8).
+
+CFG_DESC
+
+    Requests a box suitable for entering a descriptive game ID (and
+    viewing the existing one). The mid-end generates this dialog box
+    description itself. This should be used when the user selects
+    `Specific' from the game menu (or equivalent).
+
+CFG_SEED
+
+    Requests a box suitable for entering a random-seed game ID (and
+    viewing the existing one). The mid-end generates this dialog box
+    description itself. This should be used when the user selects
+    `Random Seed' from the game menu (or equivalent).
+
+The returned value is an array of config_items, exactly as described
+in section 2.3.8. Another returned value is an ASCII string giving a
+suitable title for the configuration window, in `*wintitle'.
+
+Both returned values are dynamically allocated and will need to be
+freed. The window title can be freed in the obvious way; the config_item
+array is a slightly complex structure, so a utility function free_cfg()
+is provided to free it for you. See section 5.2.6.
+
+(Of course, you will probably not want to free the config_item array
+until the dialog box is dismissed, because before then you will probably
+need to pass it to midend_set_config.)
+
+4.20. midend_set_config()
+-------------------------
+
+  char *midend_set_config(midend *me, int which,
+                          config_item *cfg);
+
+Passes the mid-end the results of a configuration dialog box. `which'
+should have the same value which it had when midend_get_config() was
+called; `cfg' should be the array of `config_item's returned from
+midend_get_config(), modified to contain the results of the user's
+editing operations.
+
+This function returns NULL on success, or otherwise (if the
+configuration data was in some way invalid) an ASCII string containing
+an error message suitable for showing to the user.
+
+If the function succeeds, it is likely that the game parameters will
+have been changed and it is certain that a new game will be requested.
+The front end should therefore call midend_new_game(), and probably also
+re-think the window size using midend_size() and eventually perform a
+refresh using midend_redraw().
+
+4.21. midend_game_id()
+----------------------
+
+  char *midend_game_id(midend *me, char *id);
+
+Passes the mid-end a string game ID (of any of the valid forms `params',
+`params:description' or `params#seed') which the mid-end will process
+and use for the next generated game.
+
+This function returns NULL on success, or otherwise (if the
+configuration data was in some way invalid) an ASCII string containing
+an error message (not dynamically allocated) suitable for showing to the
+user. In the event of an error, the mid-end's internal state will be
+left exactly as it was before the call.
+
+If the function succeeds, it is likely that the game parameters will
+have been changed and it is certain that a new game will be requested.
+The front end should therefore call midend_new_game(), and probably
+also re-think the window size using midend_size() and eventually case a
+refresh using midend_redraw().
+
+4.22. midend_get_game_id()
+--------------------------
+
+  char *midend_get_game_id(midend *me)
+
+Returns a descriptive game ID (i.e. one in the form
+`params:description') describing the game currently active in the mid-
+end. The returned string is dynamically allocated.
+
+4.23. midend_get_random_seed()
+------------------------------
+
+  char *midend_get_random_seed(midend *me)
+
+Returns a random game ID (i.e. one in the form `params#seedstring')
+describing the game currently active in the mid-end, if there is one.
+If the game was created by entering a description, no random seed will
+currently exist and this function will return NULL.
+
+The returned string, if it is non-NULL, is dynamically allocated.
+
+4.24. midend_can_format_as_text_now()
+-------------------------------------
+
+  int midend_can_format_as_text_now(midend *me);
+
+Returns TRUE if the game code is capable of formatting puzzles of the
+currently selected game type as ASCII.
+
+If this returns FALSE, then midend_text_format() (section 4.25) will
+return NULL.
+
+4.25. midend_text_format()
+--------------------------
+
+  char *midend_text_format(midend *me);
+
+Formats the current game's current state as ASCII text suitable for
+copying to the clipboard. The returned string is dynamically allocated.
+
+If the game's `can_format_as_text_ever' flag is FALSE, or if its
+can_format_as_text_now() function returns FALSE, then this function will
+return NULL.
+
+If the returned string contains multiple lines (which is likely), it
+will use the normal C line ending convention (\n only). On platforms
+which use a different line ending convention for data in the clipboard,
+it is the front end's responsibility to perform the conversion.
+
+4.26. midend_solve()
+--------------------
+
+  char *midend_solve(midend *me);
+
+Requests the mid-end to perform a Solve operation.
+
+On success, NULL is returned. On failure, an error message (not
+dynamically allocated) is returned, suitable for showing to the user.
+
+The front end can expect its drawing API and/or activate_timer() to be
+called from within a call to this function. Some back ends require that
+midend_size() (section 4.6) is called before midend_solve().
+
+4.27. midend_status()
+---------------------
+
+  int midend_status(midend *me);
+
+This function returns +1 if the midend is currently displaying a game
+in a solved state, -1 if the game is in a permanently lost state, or 0
+otherwise. This function just calls the back end's status() function.
+Front ends may wish to use this as a cue to proactively offer the option
+of starting a new game.
+
+(See section 2.8.9 for more detail about the back end's status()
+function and discussion of what should count as which status code.)
+
+4.28. midend_can_undo()
+-----------------------
+
+  int midend_can_undo(midend *me);
+
+Returns TRUE if the midend is currently in a state where the undo
+operation is meaningful (i.e. at least one position exists on the undo
+chain before the present one). Front ends may wish to use this to
+visually activate and deactivate an undo button.
+
+4.29. midend_can_redo()
+-----------------------
+
+  int midend_can_redo(midend *me);
+
+Returns TRUE if the midend is currently in a state where the redo
+operation is meaningful (i.e. at least one position exists on the
+redo chain after the present one). Front ends may wish to use this to
+visually activate and deactivate a redo button.
+
+4.30. midend_serialise()
+------------------------
+
+  void midend_serialise(midend *me,
+                        void (*write)(void *ctx, void *buf, int len),
+                        void *wctx);
+
+Calling this function causes the mid-end to convert its entire internal
+state into a long ASCII text string, and to pass that string (piece by
+piece) to the supplied `write' function.
+
+Desktop implementations can use this function to save a game in any
+state (including half-finished) to a disk file, by supplying a `write'
+function which is a wrapper on fwrite() (or local equivalent). Other
+implementations may find other uses for it, such as compressing the
+large and sprawling mid-end state into a manageable amount of memory
+when a palmtop application is suspended so that another one can run; in
+this case write might want to write to a memory buffer rather than a
+file. There may be other uses for it as well.
+
+This function will call back to the supplied `write' function a number
+of times, with the first parameter (`ctx') equal to `wctx', and the
+other two parameters pointing at a piece of the output string.
+
+4.31. midend_deserialise()
+--------------------------
+
+  char *midend_deserialise(midend *me,
+                           int (*read)(void *ctx, void *buf, int len),
+                           void *rctx);
+
+This function is the counterpart to midend_serialise(). It calls the
+supplied read function repeatedly to read a quantity of data, and
+attempts to interpret that data as a serialised mid-end as output by
+midend_serialise().
+
+The read function is called with the first parameter (`ctx') equal
+to `rctx', and should attempt to read `len' bytes of data into the
+buffer pointed to by `buf'. It should return FALSE on failure or TRUE
+on success. It should not report success unless it has filled the
+entire buffer; on platforms which might be reading from a pipe or other
+blocking data source, `read' is responsible for looping until the whole
+buffer has been filled.
+
+If the de-serialisation operation is successful, the mid-end's internal
+data structures will be replaced by the results of the load, and NULL
+will be returned. Otherwise, the mid-end's state will be completely
+unchanged and an error message (typically some variation on `save file
+is corrupt') will be returned. As usual, the error message string is not
+dynamically allocated.
+
+If this function succeeds, it is likely that the game parameters will
+have been changed. The front end should therefore probably re-think the
+window size using midend_size(), and probably cause a refresh using
+midend_redraw().
+
+Because each mid-end is tied to a specific game back end, this function
+will fail if you attempt to read in a save file generated by a different
+game from the one configured in this mid-end, even if your application
+is a monolithic one containing all the puzzles. See section 4.32 for a
+helper function which will allow you to identify a save file before you
+instantiate your mid-end in the first place.
+
+4.32. identify_game()
+---------------------
+
+  char *identify_game(char **name,
+                      int (*read)(void *ctx, void *buf, int len),
+                      void *rctx);
+
+This function examines a serialised midend stream, of the same kind used
+by midend_serialise() and midend_deserialise(), and returns the name
+field of the game back end from which it was saved.
+
+You might want this if your front end was a monolithic one containing
+all the puzzles, and you wanted to be able to load an arbitrary save
+file and automatically switch to the right game. Probably your next step
+would be to iterate through gamelist (section 4.34) looking for a game
+structure whose name field matched the returned string, and give an
+error if you didn't find one.
+
+On success, the return value of this function is NULL, and the game name
+string is written into *name. The caller should free that string after
+using it.
+
+On failure, *name is NULL, and the return value is an error message
+(which does not need freeing at all).
+
+(This isn't strictly speaking a midend function, since it doesn't accept
+or return a pointer to a midend. You'd probably call it just _before_
+deciding what kind of midend you wanted to instantiate.)
+
+4.33. midend_request_id_changes()
+---------------------------------
+
+  void midend_request_id_changes(midend *me,
+                                 void (*notify)(void *), void *ctx);
+
+This function is called by the front end to request notification by the
+mid-end when the current game IDs (either descriptive or random-seed)
+change. This can occur as a result of keypresses ('n' for New Game, for
+example) or when a puzzle supersedes its game description (see section
+2.11.2). After this function is called, any change of the game ids will
+cause the mid-end to call notify(ctx) after the change.
+
+This is for use by puzzles which want to present the game description to
+the user constantly (e.g. as an HTML hyperlink) instead of only showing
+it when the user explicitly requests it.
+
+This is a function I anticipate few front ends needing to implement, so
+I make it a callback rather than a static function in order to relieve
+most front ends of the need to provide an empty implementation.
+
+4.34. Direct reference to the back end structure by the front end
+-----------------------------------------------------------------
+
+Although _most_ things the front end needs done should be done by
+calling the mid-end, there are a few situations in which the front end
+needs to refer directly to the game back end structure.
+
+The most obvious of these is
+
+ -  passing the game back end as a parameter to midend_new().
+
+There are a few other back end features which are not wrapped by the
+mid-end because there didn't seem much point in doing so:
+
+ -  fetching the `name' field to use in window titles and similar
+
+ -  reading the `can_configure', `can_solve' and
+    `can_format_as_text_ever' fields to decide whether to add those
+    items to the menu bar or equivalent
+
+ -  reading the `winhelp_topic' field (Windows only)
+
+ -  the GTK front end provides a `--generate' command-line option which
+    directly calls the back end to do most of its work. This is not
+    really part of the main front end code, though, and I'm not sure it
+    counts.
+
+In order to find the game back end structure, the front end does one of
+two things:
+
+ -  If the particular front end is compiling a separate binary per game,
+    then the back end structure is a global variable with the standard
+    name `thegame':
+
+      extern const game thegame;
+
+ -  If the front end is compiled as a monolithic application containing
+    all the puzzles together (in which case the preprocessor symbol
+    COMBINED must be defined when compiling most of the code base), then
+    there will be two global variables defined:
+
+      extern const game *gamelist[];
+      extern const int gamecount;
+
+    `gamelist' will be an array of `gamecount' game structures, declared
+    in the automatically constructed source module `list.c'. The
+    application should search that array for the game it wants, probably
+    by reaching into each game structure and looking at its `name'
+    field.
+
+4.35. Mid-end to front-end calls
+--------------------------------
+
+This section describes the small number of functions which a front end
+must provide to be called by the mid-end or other standard utility
+modules.
+
+4.36. get_random_seed()
+-----------------------
+
+  void get_random_seed(void **randseed, int *randseedsize);
+
+This function is called by a new mid-end, and also occasionally by game
+back ends. Its job is to return a piece of data suitable for using as a
+seed for initialisation of a new `random_state'.
+
+On exit, `*randseed' should be set to point at a newly allocated piece
+of memory containing some seed data, and `*randseedsize' should be set
+to the length of that data.
+
+A simple and entirely adequate implementation is to return a piece of
+data containing the current system time at the highest conveniently
+available resolution.
+
+4.37. activate_timer()
+----------------------
+
+  void activate_timer(frontend *fe);
+
+This is called by the mid-end to request that the front end begin
+calling it back at regular intervals.
+
+The timeout interval is left up to the front end; the finer it is, the
+smoother move animations will be, but the more CPU time will be used.
+Current front ends use values around 20ms (i.e. 50Hz).
+
+After this function is called, the mid-end will expect to receive calls
+to midend_timer() on a regular basis.
+
+4.38. deactivate_timer()
+------------------------
+
+  void deactivate_timer(frontend *fe);
+
+This is called by the mid-end to request that the front end stop calling
+midend_timer().
+
+4.39. fatal()
+-------------
+
+  void fatal(char *fmt, ...);
+
+This is called by some utility functions if they encounter a genuinely
+fatal error such as running out of memory. It is a variadic function
+in the style of printf(), and is expected to show the formatted error
+message to the user any way it can and then terminate the application.
+It must not return.
+
+4.40. frontend_default_colour()
+-------------------------------
+
+  void frontend_default_colour(frontend *fe, float *output);
+
+This function expects to be passed a pointer to an array of three
+floats. It returns the platform's local preferred background colour
+in those three floats, as red, green and blue values (in that order)
+ranging from 0.0 to 1.0.
+
+This function should only ever be called by the back end function
+colours() (section 2.8.6). (Thus, it isn't a _midend_-to-frontend
+function as such, but there didn't seem to be anywhere else particularly
+good to put it. Sorry.)
+
+5. Utility APIs
+---------------
+
+This chapter documents a variety of utility APIs provided for the
+general use of the rest of the Puzzles code.
+
+5.1. Random number generation
+-----------------------------
+
+Platforms' local random number generators vary widely in quality and
+seed size. Puzzles therefore supplies its own high-quality random number
+generator, with the additional advantage of giving the same results if
+fed the same seed data on different platforms. This allows game random
+seeds to be exchanged between different ports of Puzzles and still
+generate the same games.
+
+Unlike the ANSI C rand() function, the Puzzles random number generator
+has an _explicit_ state object called a `random_state'. One of these
+is managed by each mid-end, for example, and passed to the back end to
+generate a game with.
+
+5.1.1. random_new()
+-------------------
+
+  random_state *random_new(char *seed, int len);
+
+Allocates, initialises and returns a new `random_state'. The input data
+is used as the seed for the random number stream (i.e. using the same
+seed at a later time will generate the same stream).
+
+The seed data can be any data at all; there is no requirement to use
+printable ASCII, or NUL-terminated strings, or anything like that.
+
+5.1.2. random_copy()
+--------------------
+
+  random_state *random_copy(random_state *tocopy);
+
+Allocates a new `random_state', copies the contents of another
+`random_state' into it, and returns the new state. If exactly the
+same sequence of functions is subseqently called on both the copy and
+the original, the results will be identical. This may be useful for
+speculatively performing some operation using a given random state, and
+later replaying that operation precisely.
+
+5.1.3. random_free()
+--------------------
+
+  void random_free(random_state *state);
+
+Frees a `random_state'.
+
+5.1.4. random_bits()
+--------------------
+
+  unsigned long random_bits(random_state *state, int bits);
+
+Returns a random number from 0 to 2^bits-1 inclusive. `bits' should be
+between 1 and 32 inclusive.
+
+5.1.5. random_upto()
+--------------------
+
+  unsigned long random_upto(random_state *state, unsigned long limit);
+
+Returns a random number from 0 to limit-1 inclusive.
+
+5.1.6. random_state_encode()
+----------------------------
+
+  char *random_state_encode(random_state *state);
+
+Encodes the entire contents of a `random_state' in printable ASCII.
+Returns a dynamically allocated string containing that encoding. This
+can subsequently be passed to random_state_decode() to reconstruct the
+same `random_state'.
+
+5.1.7. random_state_decode()
+----------------------------
+
+  random_state *random_state_decode(char *input);
+
+Decodes a string generated by random_state_encode() and reconstructs an
+equivalent `random_state' to the one encoded, i.e. it should produce the
+same stream of random numbers.
+
+This function has no error reporting; if you pass it an invalid string
+it will simply generate an arbitrary random state, which may turn out to
+be noticeably non-random.
+
+5.1.8. shuffle()
+----------------
+
+  void shuffle(void *array, int nelts, int eltsize, random_state *rs);
+
+Shuffles an array into a random order. The interface is much like ANSI C
+qsort(), except that there's no need for a compare function.
+
+`array' is a pointer to the first element of the array. `nelts' is the
+number of elements in the array; `eltsize' is the size of a single
+element (typically measured using `sizeof'). `rs' is a `random_state'
+used to generate all the random numbers for the shuffling process.
+
+5.2. Memory allocation
+----------------------
+
+Puzzles has some central wrappers on the standard memory allocation
+functions, which provide compile-time type checking, and run-time error
+checking by means of quitting the application if it runs out of memory.
+This doesn't provide the best possible recovery from memory shortage,
+but on the other hand it greatly simplifies the rest of the code,
+because nothing else anywhere needs to worry about NULL returns from
+allocation.
+
+5.2.1. snew()
+-------------
+
+  var = snew(type);
+
+This macro takes a single argument which is a _type name_. It allocates
+space for one object of that type. If allocation fails it will call
+fatal() and not return; so if it does return, you can be confident that
+its return value is non-NULL.
+
+The return value is cast to the specified type, so that the compiler
+will type-check it against the variable you assign it into. Thus, this
+ensures you don't accidentally allocate memory the size of the wrong
+type and assign it into a variable of the right one (or vice versa!).
+
+5.2.2. snewn()
+--------------
+
+  var = snewn(n, type);
+
+This macro is the array form of snew(). It takes two arguments; the
+first is a number, and the second is a type name. It allocates space
+for that many objects of that type, and returns a type-checked non-NULL
+pointer just as snew() does.
+
+5.2.3. sresize()
+----------------
+
+  var = sresize(var, n, type);
+
+This macro is a type-checked form of realloc(). It takes three
+arguments: an input memory block, a new size in elements, and a type.
+It re-sizes the input memory block to a size sufficient to contain that
+many elements of that type. It returns a type-checked non-NULL pointer,
+like snew() and snewn().
+
+The input memory block can be NULL, in which case this function will
+behave exactly like snewn(). (In principle any ANSI-compliant realloc()
+implementation ought to cope with this, but I've never quite trusted it
+to work everywhere.)
+
+5.2.4. sfree()
+--------------
+
+  void sfree(void *p);
+
+This function is pretty much equivalent to free(). It is provided with a
+dynamically allocated block, and frees it.
+
+The input memory block can be NULL, in which case this function will do
+nothing. (In principle any ANSI-compliant free() implementation ought to
+cope with this, but I've never quite trusted it to work everywhere.)
+
+5.2.5. dupstr()
+---------------
+
+  char *dupstr(const char *s);
+
+This function dynamically allocates a duplicate of a C string. Like the
+snew() functions, it guarantees to return non-NULL or not return at all.
+
+(Many platforms provide the function strdup(). As well as guaranteeing
+never to return NULL, my version has the advantage of being defined
+_everywhere_, rather than inconveniently not quite everywhere.)
+
+5.2.6. free_cfg()
+-----------------
+
+  void free_cfg(config_item *cfg);
+
+This function correctly frees an array of `config_item's, including
+walking the array until it gets to the end and freeing precisely those
+`sval' fields which are expected to be dynamically allocated.
+
+(See section 2.3.8 for details of the `config_item' structure.)
+
+5.3. Sorted and counted tree functions
+--------------------------------------
+
+Many games require complex algorithms for generating random puzzles, and
+some require moderately complex algorithms even during play. A common
+requirement during these algorithms is for a means of maintaining sorted
+or unsorted lists of items, such that items can be removed and added
+conveniently.
+
+For general use, Puzzles provides the following set of functions which
+maintain 2-3-4 trees in memory. (A 2-3-4 tree is a balanced tree
+structure, with the property that all lookups, insertions, deletions,
+splits and joins can be done in O(log N) time.)
+
+All these functions expect you to be storing a tree of `void *'
+pointers. You can put anything you like in those pointers.
+
+By the use of per-node element counts, these tree structures have the
+slightly unusual ability to look elements up by their numeric index
+within the list represented by the tree. This means that they can be
+used to store an unsorted list (in which case, every time you insert a
+new element, you must explicitly specify the position where you wish to
+insert it). They can also do numeric lookups in a sorted tree, which
+might be useful for (for example) tracking the median of a changing data
+set.
+
+As well as storing sorted lists, these functions can be used for storing
+`maps' (associative arrays), by defining each element of a tree to be a
+(key, value) pair.
+
+5.3.1. newtree234()
+-------------------
+
+  tree234 *newtree234(cmpfn234 cmp);
+
+Creates a new empty tree, and returns a pointer to it.
+
+The parameter `cmp' determines the sorting criterion on the tree. Its
+prototype is
+
+  typedef int (*cmpfn234)(void *, void *);
+
+If you want a sorted tree, you should provide a function matching this
+prototype, which returns like strcmp() does (negative if the first
+argument is smaller than the second, positive if it is bigger, zero if
+they compare equal). In this case, the function addpos234() will not be
+usable on your tree (because all insertions must respect the sorting
+order).
+
+If you want an unsorted tree, pass NULL. In this case you will not be
+able to use either add234() or del234(), or any other function such
+as find234() which depends on a sorting order. Your tree will become
+something more like an array, except that it will efficiently support
+insertion and deletion as well as lookups by numeric index.
+
+5.3.2. freetree234()
+--------------------
+
+  void freetree234(tree234 *t);
+
+Frees a tree. This function will not free the _elements_ of the tree
+(because they might not be dynamically allocated, or you might be
+storing the same set of elements in more than one tree); it will just
+free the tree structure itself. If you want to free all the elements of
+a tree, you should empty it before passing it to freetree234(), by means
+of code along the lines of
+
+  while ((element = delpos234(tree, 0)) != NULL)
+      sfree(element); /* or some more complicated free function */
+
+5.3.3. add234()
+---------------
+
+  void *add234(tree234 *t, void *e);
+
+Inserts a new element `e' into the tree `t'. This function expects the
+tree to be sorted; the new element is inserted according to the sort
+order.
+
+If an element comparing equal to `e' is already in the tree, then the
+insertion will fail, and the return value will be the existing element.
+Otherwise, the insertion succeeds, and `e' is returned.
+
+5.3.4. addpos234()
+------------------
+
+  void *addpos234(tree234 *t, void *e, int index);
+
+Inserts a new element into an unsorted tree. Since there is no sorting
+order to dictate where the new element goes, you must specify where you
+want it to go. Setting `index' to zero puts the new element right at the
+start of the list; setting `index' to the current number of elements in
+the tree puts the new element at the end.
+
+Return value is `e', in line with add234() (although this function
+cannot fail except by running out of memory, in which case it will bomb
+out and die rather than returning an error indication).
+
+5.3.5. index234()
+-----------------
+
+  void *index234(tree234 *t, int index);
+
+Returns a pointer to the `index'th element of the tree, or NULL if
+`index' is out of range. Elements of the tree are numbered from zero.
+
+5.3.6. find234()
+----------------
+
+  void *find234(tree234 *t, void *e, cmpfn234 cmp);
+
+Searches for an element comparing equal to `e' in a sorted tree.
+
+If `cmp' is NULL, the tree's ordinary comparison function will be used
+to perform the search. However, sometimes you don't want that; suppose,
+for example, each of your elements is a big structure containing a
+`char *' name field, and you want to find the element with a given name.
+You _could_ achieve this by constructing a fake element structure,
+setting its name field appropriately, and passing it to find234(),
+but you might find it more convenient to pass _just_ a name string to
+find234(), supplying an alternative comparison function which expects
+one of its arguments to be a bare name and the other to be a large
+structure containing a name field.
+
+Therefore, if `cmp' is not NULL, then it will be used to compare `e' to
+elements of the tree. The first argument passed to `cmp' will always be
+`e'; the second will be an element of the tree.
+
+(See section 5.3.1 for the definition of the `cmpfn234' function pointer
+type.)
+
+The returned value is the element found, or NULL if the search is
+unsuccessful.
+
+5.3.7. findrel234()
+-------------------
+
+  void *findrel234(tree234 *t, void *e, cmpfn234 cmp, int relation);
+
+This function is like find234(), but has the additional ability to do a
+_relative_ search. The additional parameter `relation' can be one of the
+following values:
+
+REL234_EQ
+
+    Find only an element that compares equal to `e'. This is exactly the
+    behaviour of find234().
+
+REL234_LT
+
+    Find the greatest element that compares strictly less than `e'. `e'
+    may be NULL, in which case it finds the greatest element in the
+    whole tree (which could also be done by index234(t, count234(t)-1)).
+
+REL234_LE
+
+    Find the greatest element that compares less than or equal to `e'.
+    (That is, find an element that compares equal to `e' if possible,
+    but failing that settle for something just less than it.)
+
+REL234_GT
+
+    Find the smallest element that compares strictly greater than `e'.
+    `e' may be NULL, in which case it finds the smallest element in the
+    whole tree (which could also be done by index234(t, 0)).
+
+REL234_GE
+
+    Find the smallest element that compares greater than or equal
+    to `e'. (That is, find an element that compares equal to `e' if
+    possible, but failing that settle for something just bigger than
+    it.)
+
+Return value, as before, is the element found or NULL if no element
+satisfied the search criterion.
+
+5.3.8. findpos234()
+-------------------
+
+  void *findpos234(tree234 *t, void *e, cmpfn234 cmp, int *index);
+
+This function is like find234(), but has the additional feature of
+returning the index of the element found in the tree; that index is
+written to `*index' in the event of a successful search (a non-NULL
+return value).
+
+`index' may be NULL, in which case this function behaves exactly like
+find234().
+
+5.3.9. findrelpos234()
+----------------------
+
+  void *findrelpos234(tree234 *t, void *e, cmpfn234 cmp, int relation,
+                      int *index);
+
+This function combines all the features of findrel234() and
+findpos234().
+
+5.3.10. del234()
+----------------
+
+  void *del234(tree234 *t, void *e);
+
+Finds an element comparing equal to `e' in the tree, deletes it, and
+returns it.
+
+The input tree must be sorted.
+
+The element found might be `e' itself, or might merely compare equal to
+it.
+
+Return value is NULL if no such element is found.
+
+5.3.11. delpos234()
+-------------------
+
+  void *delpos234(tree234 *t, int index);
+
+Deletes the element at position `index' in the tree, and returns it.
+
+Return value is NULL if the index is out of range.
+
+5.3.12. count234()
+------------------
+
+  int count234(tree234 *t);
+
+Returns the number of elements currently in the tree.
+
+5.3.13. splitpos234()
+---------------------
+
+  tree234 *splitpos234(tree234 *t, int index, int before);
+
+Splits the input tree into two pieces at a given position, and creates a
+new tree containing all the elements on one side of that position.
+
+If `before' is TRUE, then all the items at or after position `index' are
+left in the input tree, and the items before that point are returned in
+the new tree. Otherwise, the reverse happens: all the items at or after
+`index' are moved into the new tree, and those before that point are
+left in the old one.
+
+If `index' is equal to 0 or to the number of elements in the input tree,
+then one of the two trees will end up empty (and this is not an error
+condition). If `index' is further out of range in either direction, the
+operation will fail completely and return NULL.
+
+This operation completes in O(log N) time, no matter how large the tree
+or how balanced or unbalanced the split.
+
+5.3.14. split234()
+------------------
+
+  tree234 *split234(tree234 *t, void *e, cmpfn234 cmp, int rel);
+
+Splits a sorted tree according to its sort order.
+
+`rel' can be any of the relation constants described in section 5.3.7,
+_except_ for REL234_EQ. All the elements having that relation to `e'
+will be transferred into the new tree; the rest will be left in the old
+one.
+
+The parameter `cmp' has the same semantics as it does in find234(): if
+it is not NULL, it will be used in place of the tree's own comparison
+function when comparing elements to `e', in such a way that `e' itself
+is always the first of its two operands.
+
+Again, this operation completes in O(log N) time, no matter how large
+the tree or how balanced or unbalanced the split.
+
+5.3.15. join234()
+-----------------
+
+  tree234 *join234(tree234 *t1, tree234 *t2);
+
+Joins two trees together by concatenating the lists they represent. All
+the elements of `t2' are moved into `t1', in such a way that they appear
+_after_ the elements of `t1'. The tree `t2' is freed; the return value
+is `t1'.
+
+If you apply this function to a sorted tree and it violates the sort
+order (i.e. the smallest element in `t2' is smaller than or equal to the
+largest element in `t1'), the operation will fail and return NULL.
+
+This operation completes in O(log N) time, no matter how large the trees
+being joined together.
+
+5.3.16. join234r()
+------------------
+
+  tree234 *join234r(tree234 *t1, tree234 *t2);
+
+Joins two trees together in exactly the same way as join234(), but this
+time the combined tree is returned in `t2', and `t1' is destroyed. The
+elements in `t1' still appear before those in `t2'.
+
+Again, this operation completes in O(log N) time, no matter how large
+the trees being joined together.
+
+5.3.17. copytree234()
+---------------------
+
+  tree234 *copytree234(tree234 *t, copyfn234 copyfn,
+                       void *copyfnstate);
+
+Makes a copy of an entire tree.
+
+If `copyfn' is NULL, the tree will be copied but the elements will not
+be; i.e. the new tree will contain pointers to exactly the same physical
+elements as the old one.
+
+If you want to copy each actual element during the operation, you can
+instead pass a function in `copyfn' which makes a copy of each element.
+That function has the prototype
+
+  typedef void *(*copyfn234)(void *state, void *element);
+
+and every time it is called, the `state' parameter will be set to the
+value you passed in as `copyfnstate'.
+
+5.4. Miscellaneous utility functions and macros
+-----------------------------------------------
+
+This section contains all the utility functions which didn't sensibly
+fit anywhere else.
+
+5.4.1. TRUE and FALSE
+---------------------
+
+The main Puzzles header file defines the macros TRUE and FALSE, which
+are used throughout the code in place of 1 and 0 (respectively) to
+indicate that the values are in a boolean context. For code base
+consistency, I'd prefer it if submissions of new code followed this
+convention as well.
+
+5.4.2. max() and min()
+----------------------
+
+The main Puzzles header file defines the pretty standard macros max()
+and min(), each of which is given two arguments and returns the one
+which compares greater or less respectively.
+
+These macros may evaluate their arguments multiple times. Avoid side
+effects.
+
+5.4.3. PI
+---------
+
+The main Puzzles header file defines a macro PI which expands to a
+floating-point constant representing pi.
+
+(I've never understood why ANSI's <math.h> doesn't define this. It'd be
+so useful!)
+
+5.4.4. obfuscate_bitmap()
+-------------------------
+
+  void obfuscate_bitmap(unsigned char *bmp, int bits, int decode);
+
+This function obscures the contents of a piece of data, by cryptographic
+methods. It is useful for games of hidden information (such as Mines,
+Guess or Black Box), in which the game ID theoretically reveals all the
+information the player is supposed to be trying to guess. So in order
+that players should be able to send game IDs to one another without
+accidentally spoiling the resulting game by looking at them, these games
+obfuscate their game IDs using this function.
+
+Although the obfuscation function is cryptographic, it cannot properly
+be called encryption because it has no key. Therefore, anybody motivated
+enough can re-implement it, or hack it out of the Puzzles source,
+and strip the obfuscation off one of these game IDs to see what lies
+beneath. (Indeed, they could usually do it much more easily than that,
+by entering the game ID into their own copy of the puzzle and hitting
+Solve.) The aim is not to protect against a determined attacker; the aim
+is simply to protect people who wanted to play the game honestly from
+_accidentally_ spoiling their own fun.
+
+The input argument `bmp' points at a piece of memory to be obfuscated.
+`bits' gives the length of the data. Note that that length is in _bits_
+rather than bytes: if you ask for obfuscation of a partial number of
+bytes, then you will get it. Bytes are considered to be used from the
+top down: thus, for example, setting `bits' to 10 will cover the whole
+of bmp[0] and the _top two_ bits of bmp[1]. The remainder of a partially
+used byte is undefined (i.e. it may be corrupted by the function).
+
+The parameter `decode' is FALSE for an encoding operation, and TRUE
+for a decoding operation. Each is the inverse of the other. (There's
+no particular reason you shouldn't obfuscate by decoding and restore
+cleartext by encoding, if you really wanted to; it should still work.)
+
+The input bitmap is processed in place.
+
+5.4.5. bin2hex()
+----------------
+
+  char *bin2hex(const unsigned char *in, int inlen);
+
+This function takes an input byte array and converts it into an
+ASCII string encoding those bytes in (lower-case) hex. It returns a
+dynamically allocated string containing that encoding.
+
+This function is useful for encoding the result of obfuscate_bitmap() in
+printable ASCII for use in game IDs.
+
+5.4.6. hex2bin()
+----------------
+
+  unsigned char *hex2bin(const char *in, int outlen);
+
+This function takes an ASCII string containing hex digits, and converts
+it back into a byte array of length `outlen'. If there aren't enough
+hex digits in the string, the contents of the resulting array will be
+undefined.
+
+This function is the inverse of bin2hex().
+
+5.4.7. game_mkhighlight()
+-------------------------
+
+  void game_mkhighlight(frontend *fe, float *ret,
+                        int background, int highlight, int lowlight);
+
+It's reasonably common for a puzzle game's graphics to use highlights
+and lowlights to indicate `raised' or `lowered' sections. Fifteen,
+Sixteen and Twiddle are good examples of this.
+
+Puzzles using this graphical style are running a risk if they just use
+whatever background colour is supplied to them by the front end, because
+that background colour might be too light to see any highlights on at
+all. (In particular, it's not unheard of for the front end to specify a
+default background colour of white.)
+
+Therefore, such puzzles can call this utility function from their
+colours() routine (section 2.8.6). You pass it your front end handle, a
+pointer to the start of your return array, and three colour indices. It
+will:
+
+ -  call frontend_default_colour() (section 4.40) to fetch the front
+    end's default background colour
+
+ -  alter the brightness of that colour if it's unsuitable
+
+ -  define brighter and darker variants of the colour to be used as
+    highlights and lowlights
+
+ -  write those results into the relevant positions in the `ret' array.
+
+Thus, ret[background*3] to ret[background*3+2] will be set to RGB values
+defining a sensible background colour, and similary `highlight' and
+`lowlight' will be set to sensible colours.
+
+6. How to write a new puzzle
+----------------------------
+
+This chapter gives a guide to how to actually write a new puzzle: where
+to start, what to do first, how to solve common problems.
+
+The previous chapters have been largely composed of facts. This one is
+mostly advice.
+
+6.1. Choosing a puzzle
+----------------------
+
+Before you start writing a puzzle, you have to choose one. Your taste
+in puzzle games is up to you, of course; and, in fact, you're probably
+reading this guide because you've _already_ thought of a game you want
+to write. But if you want to get it accepted into the official Puzzles
+distribution, then there's a criterion it has to meet.
+
+The current Puzzles editorial policy is that all games should be _fair_.
+A fair game is one which a player can only fail to complete through
+demonstrable lack of skill - that is, such that a better player in the
+same situation would have _known_ to do something different.
+
+For a start, that means every game presented to the user must have _at
+least one solution_. Giving the unsuspecting user a puzzle which is
+actually impossible is not acceptable. (There is an exception: if the
+user has selected some non-default option which is clearly labelled as
+potentially unfair, _then_ you're allowed to generate possibly insoluble
+puzzles, because the user isn't unsuspecting any more. Same Game and
+Mines both have options of this type.)
+
+Also, this actually _rules out_ games such as Klondike, or the normal
+form of Mahjong Solitaire. Those games have the property that even if
+there is a solution (i.e. some sequence of moves which will get from
+the start state to the solved state), the player doesn't necessarily
+have enough information to _find_ that solution. In both games, it is
+possible to reach a dead end because you had an arbitrary choice to make
+and made it the wrong way. This violates the fairness criterion, because
+a better player couldn't have known they needed to make the other
+choice.
+
+(GNOME has a variant on Mahjong Solitaire which makes it fair: there
+is a Shuffle operation which randomly permutes all the remaining tiles
+without changing their positions, which allows you to get out of a
+sticky situation. Using this operation adds a 60-second penalty to your
+solution time, so it's to the player's advantage to try to minimise
+the chance of having to use it. It's still possible to render the game
+uncompletable if you end up with only two tiles vertically stacked,
+but that's easy to foresee and avoid using a shuffle operation. This
+form of the game _is_ fair. Implementing it in Puzzles would require
+an infrastructure change so that the back end could communicate time
+penalties to the mid-end, but that would be easy enough.)
+
+Providing a _unique_ solution is a little more negotiable; it depends
+on the puzzle. Solo would have been of unacceptably low quality if it
+didn't always have a unique solution, whereas Twiddle inherently has
+multiple solutions by its very nature and it would have been meaningless
+to even _suggest_ making it uniquely soluble. Somewhere in between, Flip
+could reasonably be made to have unique solutions (by enforcing a zero-
+dimension kernel in every generated matrix) but it doesn't seem like a
+serious quality problem that it doesn't.
+
+Of course, you don't _have_ to care about all this. There's nothing
+stopping you implementing any puzzle you want to if you're happy to
+maintain your puzzle yourself, distribute it from your own web site,
+fork the Puzzles code completely, or anything like that. It's free
+software; you can do what you like with it. But any game that you want
+to be accepted into _my_ Puzzles code base has to satisfy the fairness
+criterion, which means all randomly generated puzzles must have a
+solution (unless the user has deliberately chosen otherwise) and it must
+be possible _in theory_ to find that solution without having to guess.
+
+6.2. Getting started
+--------------------
+
+The simplest way to start writing a new puzzle is to copy `nullgame.c'.
+This is a template puzzle source file which does almost nothing, but
+which contains all the back end function prototypes and declares the
+back end data structure correctly. It is built every time the rest of
+Puzzles is built, to ensure that it doesn't get out of sync with the
+code and remains buildable.
+
+So start by copying `nullgame.c' into your new source file. Then you'll
+gradually add functionality until the very boring Null Game turns into
+your real game.
+
+Next you'll need to add your puzzle to the Makefiles, in order to
+compile it conveniently. _Do not edit the Makefiles_: they are created
+automatically by the script `mkfiles.pl', from the file called `Recipe'.
+Edit `Recipe', and then re-run `mkfiles.pl'.
+
+Also, don't forget to add your puzzle to `list.c': if you don't, then it
+will still run fine on platforms which build each puzzle separately, but
+Mac OS X and other monolithic platforms will not include your new puzzle
+in their single binary.
+
+Once your source file is building, you can move on to the fun bit.
+
+6.2.1. Puzzle generation
+------------------------
+
+Randomly generating instances of your puzzle is almost certain to be
+the most difficult part of the code, and also the task with the highest
+chance of turning out to be completely infeasible. Therefore I strongly
+recommend doing it _first_, so that if it all goes horribly wrong you
+haven't wasted any more time than you absolutely had to. What I usually
+do is to take an unmodified `nullgame.c', and start adding code to
+new_game_desc() which tries to generate a puzzle instance and print it
+out using printf(). Once that's working, _then_ I start connecting it up
+to the return value of new_game_desc(), populating other structures like
+`game_params', and generally writing the rest of the source file.
+
+There are many ways to generate a puzzle which is known to be soluble.
+In this section I list all the methods I currently know of, in case any
+of them can be applied to your puzzle. (Not all of these methods will
+work, or in some cases even make sense, for all puzzles.)
+
+Some puzzles are mathematically tractable, meaning you can work out in
+advance which instances are soluble. Sixteen, for example, has a parity
+constraint in some settings which renders exactly half the game space
+unreachable, but it can be mathematically proved that any position
+not in that half _is_ reachable. Therefore, Sixteen's grid generation
+simply consists of selecting at random from a well defined subset of the
+game space. Cube in its default state is even easier: _every_ possible
+arrangement of the blue squares and the cube's starting position is
+soluble!
+
+Another option is to redefine what you mean by `soluble'. Black Box
+takes this approach. There are layouts of balls in the box which are
+completely indistinguishable from one another no matter how many beams
+you fire into the box from which angles, which would normally be grounds
+for declaring those layouts unfair; but fortunately, detecting that
+indistinguishability is computationally easy. So Black Box doesn't
+demand that your ball placements match its own; it merely demands
+that your ball placements be _indistinguishable_ from the ones it was
+thinking of. If you have an ambiguous puzzle, then any of the possible
+answers is considered to be a solution. Having redefined the rules in
+that way, any puzzle is soluble again.
+
+Those are the simple techniques. If they don't work, you have to get
+cleverer.
+
+One way to generate a soluble puzzle is to start from the solved state
+and make inverse moves until you reach a starting state. Then you know
+there's a solution, because you can just list the inverse moves you made
+and make them in the opposite order to return to the solved state.
+
+This method can be simple and effective for puzzles where you get to
+decide what's a starting state and what's not. In Pegs, for example,
+the generator begins with one peg in the centre of the board and makes
+inverse moves until it gets bored; in this puzzle, valid inverse moves
+are easy to detect, and _any_ state that's reachable from the solved
+state by inverse moves is a reasonable starting position. So Pegs just
+continues making inverse moves until the board satisfies some criteria
+about extent and density, and then stops and declares itself done.
+
+For other puzzles, it can be a lot more difficult. Same Game uses
+this strategy too, and it's lucky to get away with it at all: valid
+inverse moves aren't easy to find (because although it's easy to insert
+additional squares in a Same Game position, it's difficult to arrange
+that _after_ the insertion they aren't adjacent to any other squares of
+the same colour), so you're constantly at risk of running out of options
+and having to backtrack or start again. Also, Same Game grids never
+start off half-empty, which means you can't just stop when you run out
+of moves - you have to find a way to fill the grid up _completely_.
+
+The other way to generate a puzzle that's soluble is to start from the
+other end, and actually write a _solver_. This tends to ensure that a
+puzzle has a _unique_ solution over and above having a solution at all,
+so it's a good technique to apply to puzzles for which that's important.
+
+One theoretical drawback of generating soluble puzzles by using a solver
+is that your puzzles are restricted in difficulty to those which the
+solver can handle. (Most solvers are not fully general: many sets of
+puzzle rules are NP-complete or otherwise nasty, so most solvers can
+only handle a subset of the theoretically soluble puzzles.) It's been
+my experience in practice, however, that this usually isn't a problem;
+computers are good at very different things from humans, and what the
+computer thinks is nice and easy might still be pleasantly challenging
+for a human. For example, when solving Dominosa puzzles I frequently
+find myself using a variety of reasoning techniques that my solver
+doesn't know about; in principle, therefore, I should be able to solve
+the puzzle using only those techniques it _does_ know about, but this
+would involve repeatedly searching the entire grid for the one simple
+deduction I can make. Computers are good at this sort of exhaustive
+search, but it's been my experience that human solvers prefer to do more
+complex deductions than to spend ages searching for simple ones. So in
+many cases I don't find my own playing experience to be limited by the
+restrictions on the solver.
+
+(This isn't _always_ the case. Solo is a counter-example; generating
+Solo puzzles using a simple solver does lead to qualitatively easier
+puzzles. Therefore I had to make the Solo solver rather more advanced
+than most of them.)
+
+There are several different ways to apply a solver to the problem of
+generating a soluble puzzle. I list a few of them below.
+
+The simplest approach is brute force: randomly generate a puzzle, use
+the solver to see if it's soluble, and if not, throw it away and try
+again until you get lucky. This is often a viable technique if all
+else fails, but it tends not to scale well: for many puzzle types, the
+probability of finding a uniquely soluble instance decreases sharply
+as puzzle size goes up, so this technique might work reasonably fast
+for small puzzles but take (almost) forever at larger sizes. Still, if
+there's no other alternative it can be usable: Pattern and Dominosa
+both use this technique. (However, Dominosa has a means of tweaking the
+randomly generated grids to increase the _probability_ of them being
+soluble, by ruling out one of the most common ambiguous cases. This
+improved generation speed by over a factor of 10 on the highest preset!)
+
+An approach which can be more scalable involves generating a grid and
+then tweaking it to make it soluble. This is the technique used by Mines
+and also by Net: first a random puzzle is generated, and then the solver
+is run to see how far it gets. Sometimes the solver will get stuck;
+when that happens, examine the area it's having trouble with, and make
+a small random change in that area to allow it to make more progress.
+Continue solving (possibly even without restarting the solver), tweaking
+as necessary, until the solver finishes. Then restart the solver from
+the beginning to ensure that the tweaks haven't caused new problems in
+the process of solving old ones (which can sometimes happen).
+
+This strategy works well in situations where the usual solver failure
+mode is to get stuck in an easily localised spot. Thus it works well
+for Net and Mines, whose most common failure mode tends to be that most
+of the grid is fine but there are a few widely separated ambiguous
+sections; but it would work less well for Dominosa, in which the way you
+get stuck is to have scoured the whole grid and not found anything you
+can deduce _anywhere_. Also, it relies on there being a low probability
+that tweaking the grid introduces a new problem at the same time as
+solving the old one; Mines and Net also have the property that most of
+their deductions are local, so that it's very unlikely for a tweak to
+affect something half way across the grid from the location where it was
+applied. In Dominosa, by contrast, a lot of deductions use information
+about half the grid (`out of all the sixes, only one is next to a
+three', which can depend on the values of up to 32 of the 56 squares in
+the default setting!), so this tweaking strategy would be rather less
+likely to work well.
+
+A more specialised strategy is that used in Solo and Slant. These
+puzzles have the property that they derive their difficulty from not
+presenting all the available clues. (In Solo's case, if all the possible
+clues were provided then the puzzle would already be solved; in Slant
+it would still require user action to fill in the lines, but it would
+present no challenge at all). Therefore, a simple generation technique
+is to leave the decision of which clues to provide until the last
+minute. In other words, first generate a random _filled_ grid with all
+possible clues present, and then gradually remove clues for as long as
+the solver reports that it's still soluble. Unlike the methods described
+above, this technique _cannot_ fail - once you've got a filled grid,
+nothing can stop you from being able to convert it into a viable puzzle.
+However, it wouldn't even be meaningful to apply this technique to (say)
+Pattern, in which clues can never be left out, so the only way to affect
+the set of clues is by altering the solution.
+
+(Unfortunately, Solo is complicated by the need to provide puzzles at
+varying difficulty levels. It's easy enough to generate a puzzle of
+_at most_ a given level of difficulty; you just have a solver with
+configurable intelligence, and you set it to a given level and apply the
+above technique, thus guaranteeing that the resulting grid is solvable
+by someone with at most that much intelligence. However, generating a
+puzzle of _at least_ a given level of difficulty is rather harder; if
+you go for _at most_ Intermediate level, you're likely to find that
+you've accidentally generated a Trivial grid a lot of the time, because
+removing just one number is sufficient to take the puzzle from Trivial
+straight to Ambiguous. In that situation Solo has no remaining options
+but to throw the puzzle away and start again.)
+
+A final strategy is to use the solver _during_ puzzle construction:
+lay out a bit of the grid, run the solver to see what it allows you to
+deduce, and then lay out a bit more to allow the solver to make more
+progress. There are articles on the web that recommend constructing
+Sudoku puzzles by this method (which is completely the opposite way
+round to how Solo does it); for Sudoku it has the advantage that you
+get to specify your clue squares in advance (so you can have them make
+pretty patterns).
+
+Rectangles uses a strategy along these lines. First it generates a grid
+by placing the actual rectangles; then it has to decide where in each
+rectangle to place a number. It uses a solver to help it place the
+numbers in such a way as to ensure a unique solution. It does this by
+means of running a test solver, but it runs the solver _before_ it's
+placed any of the numbers - which means the solver must be capable of
+coping with uncertainty about exactly where the numbers are! It runs
+the solver as far as it can until it gets stuck; then it narrows down
+the possible positions of a number in order to allow the solver to make
+more progress, and so on. Most of the time this process terminates with
+the grid fully solved, at which point any remaining number-placement
+decisions can be made at random from the options not so far ruled out.
+Note that unlike the Net/Mines tweaking strategy described above, this
+algorithm does not require a checking run after it completes: if it
+finishes successfully at all, then it has definitely produced a uniquely
+soluble puzzle.
+
+Most of the strategies described above are not 100% reliable. Each
+one has a failure rate: every so often it has to throw out the whole
+grid and generate a fresh one from scratch. (Solo's strategy would
+be the exception, if it weren't for the need to provide configurable
+difficulty levels.) Occasional failures are not a fundamental problem in
+this sort of work, however: it's just a question of dividing the grid
+generation time by the success rate (if it takes 10ms to generate a
+candidate grid and 1/5 of them work, then it will take 50ms on average
+to generate a viable one), and seeing whether the expected time taken
+to _successfully_ generate a puzzle is unacceptably slow. Dominosa's
+generator has a very low success rate (about 1 out of 20 candidate grids
+turn out to be usable, and if you think _that's_ bad then go and look
+at the source code and find the comment showing what the figures were
+before the generation-time tweaks!), but the generator itself is very
+fast so this doesn't matter. Rectangles has a slower generator, but
+fails well under 50% of the time.
+
+So don't be discouraged if you have an algorithm that doesn't always
+work: if it _nearly_ always works, that's probably good enough. The one
+place where reliability is important is that your algorithm must never
+produce false positives: it must not claim a puzzle is soluble when it
+isn't. It can produce false negatives (failing to notice that a puzzle
+is soluble), and it can fail to generate a puzzle at all, provided it
+doesn't do either so often as to become slow.
+
+One last piece of advice: for grid-based puzzles, when writing and
+testing your generation algorithm, it's almost always a good idea _not_
+to test it initially on a grid that's square (i.e. w==h), because if the
+grid is square then you won't notice if you mistakenly write `h' instead
+of `w' (or vice versa) somewhere in the code. Use a rectangular grid for
+testing, and any size of grid will be likely to work after that.
+
+6.2.2. Designing textual description formats
+--------------------------------------------
+
+Another aspect of writing a puzzle which is worth putting some thought
+into is the design of the various text description formats: the format
+of the game parameter encoding, the game description encoding, and the
+move encoding.
+
+The first two of these should be reasonably intuitive for a user to type
+in; so provide some flexibility where possible. Suppose, for example,
+your parameter format consists of two numbers separated by an `x' to
+specify the grid dimensions (`10x10' or `20x15'), and then has some
+suffixes to specify other aspects of the game type. It's almost always a
+good idea in this situation to arrange that decode_params() can handle
+the suffixes appearing in any order, even if encode_params() only ever
+generates them in one order.
+
+These formats will also be expected to be reasonably stable: users will
+expect to be able to exchange game IDs with other users who aren't
+running exactly the same version of your game. So make them robust and
+stable: don't build too many assumptions into the game ID format which
+will have to be changed every time something subtle changes in the
+puzzle code.
+
+6.3. Common how-to questions
+----------------------------
+
+This section lists some common things people want to do when writing a
+puzzle, and describes how to achieve them within the Puzzles framework.
+
+6.3.1. Drawing objects at only one position
+-------------------------------------------
+
+A common phenomenon is to have an object described in the `game_state'
+or the `game_ui' which can only be at one position. A cursor - probably
+specified in the `game_ui' - is a good example.
+
+In the `game_ui', it would _obviously_ be silly to have an array
+covering the whole game grid with a boolean flag stating whether the
+cursor was at each position. Doing that would waste space, would make
+it difficult to find the cursor in order to do anything with it, and
+would introduce the potential for synchronisation bugs in which you
+ended up with two cursors or none. The obviously sensible way to store a
+cursor in the `game_ui' is to have fields directly encoding the cursor's
+coordinates.
+
+However, it is a mistake to assume that the same logic applies to the
+`game_drawstate'. If you replicate the cursor position fields in the
+draw state, the redraw code will get very complicated. In the draw
+state, in fact, it _is_ probably the right thing to have a cursor flag
+for every position in the grid. You probably have an array for the whole
+grid in the drawstate already (stating what is currently displayed in
+the window at each position); the sensible approach is to add a `cursor'
+flag to each element of that array. Then the main redraw loop will look
+something like this (pseudo-code):
+
+  for (y = 0; y < h; y++) {
+      for (x = 0; x < w; x++) {
+          int value = state->symbol_at_position[y][x];
+          if (x == ui->cursor_x && y == ui->cursor_y)
+              value |= CURSOR;
+          if (ds->symbol_at_position[y][x] != value) {
+              symbol_drawing_subroutine(dr, ds, x, y, value);
+              ds->symbol_at_position[y][x] = value;
+          }
+      }
+  }
+
+This loop is very simple, pretty hard to get wrong, and _automatically_
+deals both with erasing the previous cursor and drawing the new one,
+with no special case code required.
+
+This type of loop is generally a sensible way to write a redraw
+function, in fact. The best thing is to ensure that the information
+stored in the draw state for each position tells you _everything_ about
+what was drawn there. A good way to ensure that is to pass precisely
+the same information, and _only_ that information, to a subroutine that
+does the actual drawing; then you know there's no additional information
+which affects the drawing but which you don't notice changes in.
+
+6.3.2. Implementing a keyboard-controlled cursor
+------------------------------------------------
+
+It is often useful to provide a keyboard control method in a basically
+mouse-controlled game. A keyboard-controlled cursor is best implemented
+by storing its location in the `game_ui' (since if it were in the
+`game_state' then the user would have to separately undo every cursor
+move operation). So the procedure would be:
+
+ -  Put cursor position fields in the `game_ui'.
+
+ -  interpret_move() responds to arrow keys by modifying the cursor
+    position fields and returning "".
+
+ -  interpret_move() responds to some sort of fire button by actually
+    performing a move based on the current cursor location.
+
+ -  You might want an additional `game_ui' field stating whether the
+    cursor is currently visible, and having it disappear when a mouse
+    action occurs (so that it doesn't clutter the display when not
+    actually in use).
+
+ -  You might also want to automatically hide the cursor in
+    changed_state() when the current game state changes to one in
+    which there is no move to make (which is the case in some types of
+    completed game).
+
+ -  redraw() draws the cursor using the technique described in section
+    6.3.1.
+
+6.3.3. Implementing draggable sprites
+-------------------------------------
+
+Some games have a user interface which involves dragging some sort of
+game element around using the mouse. If you need to show a graphic
+moving smoothly over the top of other graphics, use a blitter (see
+section 3.1.13 for the blitter API) to save the background underneath
+it. The typical scenario goes:
+
+ -  Have a blitter field in the `game_drawstate'.
+
+ -  Set the blitter field to NULL in the game's new_drawstate()
+    function, since you don't yet know how big the piece of saved
+    background needs to be.
+
+ -  In the game's set_size() function, once you know the size of the
+    object you'll be dragging around the display and hence the required
+    size of the blitter, actually allocate the blitter.
+
+ -  In free_drawstate(), free the blitter if it's not NULL.
+
+ -  In interpret_move(), respond to mouse-down and mouse-drag events by
+    updating some fields in the game_ui which indicate that a drag is in
+    progress.
+
+ -  At the _very end_ of redraw(), after all other drawing has been
+    done, draw the moving object if there is one. First save the
+    background under the object in the blitter; then set a clip
+    rectangle covering precisely the area you just saved (just in case
+    anti-aliasing or some other error causes your drawing to go beyond
+    the area you saved). Then draw the object, and call unclip().
+    Finally, set a flag in the game_drawstate that indicates that the
+    blitter needs restoring.
+
+ -  At the very start of redraw(), before doing anything else at all,
+    check the flag in the game_drawstate, and if it says the blitter
+    needs restoring then restore it. (Then clear the flag, so that this
+    won't happen again in the next redraw if no moving object is drawn
+    this time.)
+
+This way, you will be able to write the rest of the redraw function
+completely ignoring the dragged object, as if it were floating above
+your bitmap and being completely separate.
+
+6.3.4. Sharing large invariant data between all game states
+-----------------------------------------------------------
+
+In some puzzles, there is a large amount of data which never changes
+between game states. The array of numbers in Dominosa is a good example.
+
+You _could_ dynamically allocate a copy of that array in every
+`game_state', and have dup_game() make a fresh copy of it for every new
+`game_state'; but it would waste memory and time. A more efficient way
+is to use a reference-counted structure.
+
+ -  Define a structure type containing the data in question, and also
+    containing an integer reference count.
+
+ -  Have a field in `game_state' which is a pointer to this structure.
+
+ -  In new_game(), when creating a fresh game state at the start of a
+    new game, create an instance of this structure, initialise it with
+    the invariant data, and set its reference count to 1.
+
+ -  In dup_game(), rather than making a copy of the structure for the
+    new game state, simply set the new game state to point at the same
+    copy of the structure, and increment its reference count.
+
+ -  In free_game(), decrement the reference count in the structure
+    pointed to by the game state; if the count reaches zero, free the
+    structure.
+
+This way, the invariant data will persist for only as long as it's
+genuinely needed; _as soon_ as the last game state for a particular
+puzzle instance is freed, the invariant data for that puzzle will
+vanish as well. Reference counting is a very efficient form of garbage
+collection, when it works at all. (Which it does in this instance, of
+course, because there's no possibility of circular references.)
+
+6.3.5. Implementing multiple types of flash
+-------------------------------------------
+
+In some games you need to flash in more than one different way. Mines,
+for example, flashes white when you win, and flashes red when you tread
+on a mine and die.
+
+The simple way to do this is:
+
+ -  Have a field in the `game_ui' which describes the type of flash.
+
+ -  In flash_length(), examine the old and new game states to decide
+    whether a flash is required and what type. Write the type of flash
+    to the `game_ui' field whenever you return non-zero.
+
+ -  In redraw(), when you detect that `flash_time' is non-zero, examine
+    the field in `game_ui' to decide which type of flash to draw.
+
+redraw() will never be called with `flash_time' non-zero unless
+flash_length() was first called to tell the mid-end that a flash was
+required; so whenever redraw() notices that `flash_time' is non-zero,
+you can be sure that the field in `game_ui' is correctly set.
+
+6.3.6. Animating game moves
+---------------------------
+
+A number of puzzle types benefit from a quick animation of each move you
+make.
+
+For some games, such as Fifteen, this is particularly easy. Whenever
+redraw() is called with `oldstate' non-NULL, Fifteen simply compares the
+position of each tile in the two game states, and if the tile is not in
+the same place then it draws it some fraction of the way from its old
+position to its new position. This method copes automatically with undo.
+
+Other games are less obvious. In Sixteen, for example, you can't just
+draw each tile a fraction of the way from its old to its new position:
+if you did that, the end tile would zip very rapidly past all the others
+to get to the other end and that would look silly. (Worse, it would look
+inconsistent if the end tile was drawn on top going one way and on the
+bottom going the other way.)
+
+A useful trick here is to define a field or two in the game state that
+indicates what the last move was.
+
+ -  Add a `last move' field to the `game_state' (or two or more fields
+    if the move is complex enough to need them).
+
+ -  new_game() initialises this field to a null value for a new game
+    state.
+
+ -  execute_move() sets up the field to reflect the move it just
+    performed.
+
+ -  redraw() now needs to examine its `dir' parameter. If `dir' is
+    positive, it determines the move being animated by looking at the
+    last-move field in `newstate'; but if `dir' is negative, it has to
+    look at the last-move field in `oldstate', and invert whatever move
+    it finds there.
+
+Note also that Sixteen needs to store the _direction_ of the move,
+because you can't quite determine it by examining the row or column in
+question. You can in almost all cases, but when the row is precisely
+two squares long it doesn't work since a move in either direction looks
+the same. (You could argue that since moving a 2-element row left and
+right has the same effect, it doesn't matter which one you animate; but
+in fact it's very disorienting to click the arrow left and find the row
+moving right, and almost as bad to undo a move to the right and find the
+game animating _another_ move to the right.)
+
+6.3.7. Animating drag operations
+--------------------------------
+
+In Untangle, moves are made by dragging a node from an old position to a
+new position. Therefore, at the time when the move is initially made, it
+should not be animated, because the node has already been dragged to the
+right place and doesn't need moving there. However, it's nice to animate
+the same move if it's later undone or redone. This requires a bit of
+fiddling.
+
+The obvious approach is to have a flag in the `game_ui' which inhibits
+move animation, and to set that flag in interpret_move(). The question
+is, when would the flag be reset again? The obvious place to do so
+is changed_state(), which will be called once per move. But it will
+be called _before_ anim_length(), so if it resets the flag then
+anim_length() will never see the flag set at all.
+
+The solution is to have _two_ flags in a queue.
+
+ -  Define two flags in `game_ui'; let's call them `current' and `next'.
+
+ -  Set both to FALSE in `new_ui()'.
+
+ -  When a drag operation completes in interpret_move(), set the `next'
+    flag to TRUE.
+
+ -  Every time changed_state() is called, set the value of `current' to
+    the value in `next', and then set the value of `next' to FALSE.
+
+ -  That way, `current' will be TRUE _after_ a call to changed_state()
+    if and only if that call to changed_state() was the result of a
+    drag operation processed by interpret_move(). Any other call to
+    changed_state(), due to an Undo or a Redo or a Restart or a Solve,
+    will leave `current' FALSE.
+
+ -  So now anim_length() can request a move animation if and only if the
+    `current' flag is _not_ set.
+
+6.3.8. Inhibiting the victory flash when Solve is used
+------------------------------------------------------
+
+Many games flash when you complete them, as a visual congratulation for
+having got to the end of the puzzle. It often seems like a good idea to
+disable that flash when the puzzle is brought to a solved state by means
+of the Solve operation.
+
+This is easily done:
+
+ -  Add a `cheated' flag to the `game_state'.
+
+ -  Set this flag to FALSE in new_game().
+
+ -  Have solve() return a move description string which clearly
+    identifies the move as a solve operation.
+
+ -  Have execute_move() respond to that clear identification by setting
+    the `cheated' flag in the returned `game_state'. The flag will
+    then be propagated to all subsequent game states, even if the user
+    continues fiddling with the game after it is solved.
+
+ -  flash_length() now returns non-zero if `oldstate' is not completed
+    and `newstate' is, _and_ neither state has the `cheated' flag set.
+
+6.4. Things to test once your puzzle is written
+-----------------------------------------------
+
+Puzzle implementations written in this framework are self-testing as far
+as I could make them.
+
+Textual game and move descriptions, for example, are generated and
+parsed as part of the normal process of play. Therefore, if you can make
+moves in the game _at all_ you can be reasonably confident that the
+mid-end serialisation interface will function correctly and you will
+be able to save your game. (By contrast, if I'd stuck with a single
+make_move() function performing the jobs of both interpret_move() and
+execute_move(), and had separate functions to encode and decode a game
+state in string form, then those functions would not be used during
+normal play; so they could have been completely broken, and you'd never
+know it until you tried to save the game - which would have meant you'd
+have to test game saving _extensively_ and make sure to test every
+possible type of game state. As an added bonus, doing it the way I did
+leads to smaller save files.)
+
+There is one exception to this, which is the string encoding of the
+`game_ui'. Most games do not store anything permanent in the `game_ui',
+and hence do not need to put anything in its encode and decode
+functions; but if there is anything in there, you do need to test game
+loading and saving to ensure those functions work properly.
+
+It's also worth testing undo and redo of all operations, to ensure that
+the redraw and the animations (if any) work properly. Failing to animate
+undo properly seems to be a common error.
+
+Other than that, just use your common sense.
+
diff --git a/LICENCE b/LICENCE
new file mode 100644 (file)
index 0000000..4235005
--- /dev/null
+++ b/LICENCE
@@ -0,0 +1,25 @@
+This software is copyright (c) 2004-2014 Simon Tatham.
+
+Portions copyright Richard Boulton, James Harvey, Mike Pinna, Jonas
+Kölker, Dariusz Olszewski, Michael Schierl, Lambros Lambrou, Bernd
+Schmidt, Steffen Bauer, Lennard Sprong and Rogier Goossens.
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation files
+(the "Software"), to deal in the Software without restriction,
+including without limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of the Software,
+and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Makefile.am b/Makefile.am
new file mode 100644 (file)
index 0000000..b91fb63
--- /dev/null
@@ -0,0 +1,469 @@
+# Makefile.am for puzzles under Unix with Autoconf/Automake.
+#
+# This file was created by `mkfiles.pl' from the `Recipe' file.
+# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.
+
+GAMES =
+noinst_PROGRAMS = blackbox bridges cube dominosa fifteen fifteensolver \
+               filling fillingsolver flip flood galaxies galaxiespicture \
+               galaxiessolver guess inertia keen keensolver latincheck \
+               lightup lightupsolver loopy loopysolver magnets \
+               magnetssolver map mapsolver mineobfusc mines net netslide \
+               nullgame obfusc palisade pattern patternpicture \
+               patternsolver pearl pearlbench pegs range rect samegame \
+               signpost signpostsolver singles singlessolver sixteen slant \
+               slantsolver solo solosolver tents tentssolver towers \
+               towerssolver tracks twiddle undead unequal unequalsolver \
+               unruly unrulysolver untangle
+AUTOMAKE_OPTIONS = subdir-objects
+
+allsources = ./blackbox.c ./bridges.c ./combi.c ./cube.c ./divvy.c \
+               ./dominosa.c ./drawing.c ./dsf.c ./fifteen.c ./filling.c \
+               ./findloop.c ./flip.c ./flood.c ./galaxies.c ./grid.c \
+               ./grid.h ./gtk.c ./guess.c ./inertia.c ./keen.c ./latin.c \
+               ./latin.h ./laydomino.c ./lightup.c ./list.c ./loopgen.c \
+               ./loopgen.h ./loopy.c ./magnets.c ./malloc.c ./map.c \
+               ./maxflow.c ./maxflow.h ./midend.c ./mines.c ./misc.c \
+               ./net.c ./netslide.c ./no-icon.c ./nullfe.c ./nullgame.c \
+               ./obfusc.c ./osx.m ./palisade.c ./pattern.c ./pearl.c \
+               ./pegs.c ./penrose.c ./penrose.h ./printing.c ./ps.c \
+               ./puzzles.h ./random.c ./range.c ./rect.c ./resource.h \
+               ./samegame.c ./signpost.c ./singles.c ./sixteen.c ./slant.c \
+               ./solo.c ./tdq.c ./tents.c ./towers.c ./tracks.c ./tree234.c \
+               ./tree234.h ./twiddle.c ./undead.c ./unequal.c ./unruly.c \
+               ./untangle.c ./version.c ./version.h ./windows.c \
+               icons/blackbox-icon.c icons/bridges-icon.c icons/cube-icon.c \
+               icons/dominosa-icon.c icons/fifteen-icon.c \
+               icons/filling-icon.c icons/flip-icon.c icons/flood-icon.c \
+               icons/galaxies-icon.c icons/guess-icon.c \
+               icons/inertia-icon.c icons/keen-icon.c icons/lightup-icon.c \
+               icons/loopy-icon.c icons/magnets-icon.c icons/map-icon.c \
+               icons/mines-icon.c icons/net-icon.c icons/netslide-icon.c \
+               icons/palisade-icon.c icons/pattern-icon.c \
+               icons/pearl-icon.c icons/pegs-icon.c icons/range-icon.c \
+               icons/rect-icon.c icons/samegame-icon.c \
+               icons/signpost-icon.c icons/singles-icon.c \
+               icons/sixteen-icon.c icons/slant-icon.c icons/solo-icon.c \
+               icons/tents-icon.c icons/towers-icon.c icons/tracks-icon.c \
+               icons/twiddle-icon.c icons/undead-icon.c \
+               icons/unequal-icon.c icons/unruly-icon.c \
+               icons/untangle-icon.c
+
+AM_CPPFLAGS = -I$(srcdir)/./ -I$(srcdir)/icons/ 
+AM_CFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)
+blackbox_SOURCES = ./blackbox.c ./drawing.c ./gtk.c ./malloc.c ./midend.c \
+               ./misc.c ./printing.c ./ps.c ./puzzles.h ./random.c \
+               ./version.c ./version.h icons/blackbox-icon.c
+blackbox_LDADD = $(GTK_LIBS) -lm
+
+bridges_SOURCES = ./bridges.c ./drawing.c ./dsf.c ./findloop.c ./gtk.c \
+               ./malloc.c ./midend.c ./misc.c ./printing.c ./ps.c \
+               ./puzzles.h ./random.c ./version.c ./version.h \
+               icons/bridges-icon.c
+bridges_LDADD = $(GTK_LIBS) -lm
+
+cube_SOURCES = ./cube.c ./drawing.c ./gtk.c ./malloc.c ./midend.c ./misc.c \
+               ./printing.c ./ps.c ./puzzles.h ./random.c ./version.c \
+               ./version.h icons/cube-icon.c
+cube_LDADD = $(GTK_LIBS) -lm
+
+dominosa_SOURCES = ./dominosa.c ./drawing.c ./gtk.c ./laydomino.c ./malloc.c \
+               ./midend.c ./misc.c ./printing.c ./ps.c ./puzzles.h \
+               ./random.c ./version.c ./version.h icons/dominosa-icon.c
+dominosa_LDADD = $(GTK_LIBS) -lm
+
+fifteen_SOURCES = ./drawing.c ./fifteen.c ./gtk.c ./malloc.c ./midend.c \
+               ./misc.c ./printing.c ./ps.c ./puzzles.h ./random.c \
+               ./version.c ./version.h icons/fifteen-icon.c
+fifteen_LDADD = $(GTK_LIBS) -lm
+
+fifteensolver_SOURCES = ./malloc.c ./misc.c ./nullfe.c ./puzzles.h \
+               ./random.c
+fifteensolver_LDADD = libfifteen2_a-fifteen.$(OBJEXT) -lm
+
+filling_SOURCES = ./drawing.c ./dsf.c ./filling.c ./gtk.c ./malloc.c \
+               ./midend.c ./misc.c ./printing.c ./ps.c ./puzzles.h \
+               ./random.c ./version.c ./version.h icons/filling-icon.c
+filling_LDADD = $(GTK_LIBS) -lm
+
+fillingsolver_SOURCES = ./dsf.c ./malloc.c ./misc.c ./nullfe.c ./puzzles.h \
+               ./random.c
+fillingsolver_LDADD = libfilling2_a-filling.$(OBJEXT) -lm
+
+flip_SOURCES = ./drawing.c ./flip.c ./gtk.c ./malloc.c ./midend.c ./misc.c \
+               ./printing.c ./ps.c ./puzzles.h ./random.c ./tree234.c \
+               ./tree234.h ./version.c ./version.h icons/flip-icon.c
+flip_LDADD = $(GTK_LIBS) -lm
+
+flood_SOURCES = ./drawing.c ./flood.c ./gtk.c ./malloc.c ./midend.c ./misc.c \
+               ./printing.c ./ps.c ./puzzles.h ./random.c ./version.c \
+               ./version.h icons/flood-icon.c
+flood_LDADD = $(GTK_LIBS) -lm
+
+galaxies_SOURCES = ./drawing.c ./dsf.c ./galaxies.c ./gtk.c ./malloc.c \
+               ./midend.c ./misc.c ./printing.c ./ps.c ./puzzles.h \
+               ./random.c ./version.c ./version.h icons/galaxies-icon.c
+galaxies_LDADD = $(GTK_LIBS) -lm
+
+galaxiespicture_SOURCES = ./dsf.c ./malloc.c ./misc.c ./nullfe.c ./puzzles.h \
+               ./random.c
+galaxiespicture_LDADD = libgalaxie4_a-galaxies.$(OBJEXT) -lm
+
+galaxiessolver_SOURCES = ./dsf.c ./malloc.c ./misc.c ./nullfe.c ./puzzles.h \
+               ./random.c
+galaxiessolver_LDADD = libgalaxie2_a-galaxies.$(OBJEXT) -lm
+
+guess_SOURCES = ./drawing.c ./gtk.c ./guess.c ./malloc.c ./midend.c ./misc.c \
+               ./printing.c ./ps.c ./puzzles.h ./random.c ./version.c \
+               ./version.h icons/guess-icon.c
+guess_LDADD = $(GTK_LIBS) -lm
+
+inertia_SOURCES = ./drawing.c ./gtk.c ./inertia.c ./malloc.c ./midend.c \
+               ./misc.c ./printing.c ./ps.c ./puzzles.h ./random.c \
+               ./version.c ./version.h icons/inertia-icon.c
+inertia_LDADD = $(GTK_LIBS) -lm
+
+keen_SOURCES = ./drawing.c ./dsf.c ./gtk.c ./keen.c ./latin.c ./latin.h \
+               ./malloc.c ./maxflow.c ./maxflow.h ./midend.c ./misc.c \
+               ./printing.c ./ps.c ./puzzles.h ./random.c ./tree234.c \
+               ./tree234.h ./version.c ./version.h icons/keen-icon.c
+keen_LDADD = $(GTK_LIBS) -lm
+
+keensolver_SOURCES = ./dsf.c ./malloc.c ./maxflow.c ./maxflow.h ./misc.c \
+               ./nullfe.c ./puzzles.h ./random.c ./tree234.c ./tree234.h
+keensolver_LDADD = libkeen2_a-keen.$(OBJEXT) liblatin6_a-latin.$(OBJEXT) -lm
+
+latincheck_SOURCES = ./malloc.c ./maxflow.c ./maxflow.h ./misc.c ./nullfe.c \
+               ./puzzles.h ./random.c ./tree234.c ./tree234.h
+latincheck_LDADD = liblatin8_a-latin.$(OBJEXT) -lm
+
+lightup_SOURCES = ./combi.c ./drawing.c ./gtk.c ./lightup.c ./malloc.c \
+               ./midend.c ./misc.c ./printing.c ./ps.c ./puzzles.h \
+               ./random.c ./version.c ./version.h icons/lightup-icon.c
+lightup_LDADD = $(GTK_LIBS) -lm
+
+lightupsolver_SOURCES = ./combi.c ./malloc.c ./misc.c ./nullfe.c ./puzzles.h \
+               ./random.c
+lightupsolver_LDADD = liblightup2_a-lightup.$(OBJEXT) -lm
+
+loopy_SOURCES = ./drawing.c ./dsf.c ./grid.c ./grid.h ./gtk.c ./loopgen.c \
+               ./loopgen.h ./loopy.c ./malloc.c ./midend.c ./misc.c \
+               ./penrose.c ./penrose.h ./printing.c ./ps.c ./puzzles.h \
+               ./random.c ./tree234.c ./tree234.h ./version.c ./version.h \
+               icons/loopy-icon.c
+loopy_LDADD = $(GTK_LIBS) -lm
+
+loopysolver_SOURCES = ./dsf.c ./grid.c ./grid.h ./loopgen.c ./loopgen.h \
+               ./malloc.c ./misc.c ./nullfe.c ./penrose.c ./penrose.h \
+               ./puzzles.h ./random.c ./tree234.c ./tree234.h
+loopysolver_LDADD = libloopy2_a-loopy.$(OBJEXT) -lm
+
+magnets_SOURCES = ./drawing.c ./gtk.c ./laydomino.c ./magnets.c ./malloc.c \
+               ./midend.c ./misc.c ./printing.c ./ps.c ./puzzles.h \
+               ./random.c ./version.c ./version.h icons/magnets-icon.c
+magnets_LDADD = $(GTK_LIBS) -lm
+
+magnetssolver_SOURCES = ./laydomino.c ./malloc.c ./misc.c ./nullfe.c \
+               ./puzzles.h ./random.c
+magnetssolver_LDADD = libmagnets2_a-magnets.$(OBJEXT) -lm
+
+map_SOURCES = ./drawing.c ./dsf.c ./gtk.c ./malloc.c ./map.c ./midend.c \
+               ./misc.c ./printing.c ./ps.c ./puzzles.h ./random.c \
+               ./version.c ./version.h icons/map-icon.c
+map_LDADD = $(GTK_LIBS) -lm
+
+mapsolver_SOURCES = ./dsf.c ./malloc.c ./misc.c ./nullfe.c ./puzzles.h \
+               ./random.c
+mapsolver_LDADD = libmap2_a-map.$(OBJEXT) -lm
+
+mineobfusc_SOURCES = ./malloc.c ./misc.c ./nullfe.c ./puzzles.h ./random.c \
+               ./tree234.c ./tree234.h
+mineobfusc_LDADD = libmines2_a-mines.$(OBJEXT) -lm
+
+mines_SOURCES = ./drawing.c ./gtk.c ./malloc.c ./midend.c ./mines.c ./misc.c \
+               ./printing.c ./ps.c ./puzzles.h ./random.c ./tree234.c \
+               ./tree234.h ./version.c ./version.h icons/mines-icon.c
+mines_LDADD = $(GTK_LIBS) -lm
+
+net_SOURCES = ./drawing.c ./dsf.c ./findloop.c ./gtk.c ./malloc.c ./midend.c \
+               ./misc.c ./net.c ./printing.c ./ps.c ./puzzles.h ./random.c \
+               ./tree234.c ./tree234.h ./version.c ./version.h \
+               icons/net-icon.c
+net_LDADD = $(GTK_LIBS) -lm
+
+netslide_SOURCES = ./drawing.c ./gtk.c ./malloc.c ./midend.c ./misc.c \
+               ./netslide.c ./printing.c ./ps.c ./puzzles.h ./random.c \
+               ./tree234.c ./tree234.h ./version.c ./version.h \
+               icons/netslide-icon.c
+netslide_LDADD = $(GTK_LIBS) -lm
+
+nullgame_SOURCES = ./drawing.c ./gtk.c ./malloc.c ./midend.c ./misc.c \
+               ./no-icon.c ./nullgame.c ./printing.c ./ps.c ./puzzles.h \
+               ./random.c ./version.c ./version.h
+nullgame_LDADD = $(GTK_LIBS) -lm
+
+obfusc_SOURCES = ./malloc.c ./misc.c ./nullfe.c ./obfusc.c ./puzzles.h \
+               ./random.c
+obfusc_LDADD = -lm
+
+palisade_SOURCES = ./divvy.c ./drawing.c ./dsf.c ./gtk.c ./malloc.c \
+               ./midend.c ./misc.c ./palisade.c ./printing.c ./ps.c \
+               ./puzzles.h ./random.c ./version.c ./version.h \
+               icons/palisade-icon.c
+palisade_LDADD = $(GTK_LIBS) -lm
+
+pattern_SOURCES = ./drawing.c ./gtk.c ./malloc.c ./midend.c ./misc.c \
+               ./pattern.c ./printing.c ./ps.c ./puzzles.h ./random.c \
+               ./version.c ./version.h icons/pattern-icon.c
+pattern_LDADD = $(GTK_LIBS) -lm
+
+patternpicture_SOURCES = ./malloc.c ./misc.c ./nullfe.c ./puzzles.h \
+               ./random.c
+patternpicture_LDADD = libpattern4_a-pattern.$(OBJEXT) -lm
+
+patternsolver_SOURCES = ./malloc.c ./misc.c ./nullfe.c ./puzzles.h \
+               ./random.c
+patternsolver_LDADD = libpattern2_a-pattern.$(OBJEXT) -lm
+
+pearl_SOURCES = ./drawing.c ./dsf.c ./grid.c ./grid.h ./gtk.c ./loopgen.c \
+               ./loopgen.h ./malloc.c ./midend.c ./misc.c ./pearl.c \
+               ./penrose.c ./penrose.h ./printing.c ./ps.c ./puzzles.h \
+               ./random.c ./tdq.c ./tree234.c ./tree234.h ./version.c \
+               ./version.h icons/pearl-icon.c
+pearl_LDADD = $(GTK_LIBS) -lm
+
+pearlbench_SOURCES = ./dsf.c ./grid.c ./grid.h ./loopgen.c ./loopgen.h \
+               ./malloc.c ./misc.c ./nullfe.c ./penrose.c ./penrose.h \
+               ./puzzles.h ./random.c ./tdq.c ./tree234.c ./tree234.h
+pearlbench_LDADD = libpearl2_a-pearl.$(OBJEXT) -lm
+
+pegs_SOURCES = ./drawing.c ./gtk.c ./malloc.c ./midend.c ./misc.c ./pegs.c \
+               ./printing.c ./ps.c ./puzzles.h ./random.c ./tree234.c \
+               ./tree234.h ./version.c ./version.h icons/pegs-icon.c
+pegs_LDADD = $(GTK_LIBS) -lm
+
+range_SOURCES = ./drawing.c ./dsf.c ./gtk.c ./malloc.c ./midend.c ./misc.c \
+               ./printing.c ./ps.c ./puzzles.h ./random.c ./range.c \
+               ./version.c ./version.h icons/range-icon.c
+range_LDADD = $(GTK_LIBS) -lm
+
+rect_SOURCES = ./drawing.c ./gtk.c ./malloc.c ./midend.c ./misc.c \
+               ./printing.c ./ps.c ./puzzles.h ./random.c ./rect.c \
+               ./version.c ./version.h icons/rect-icon.c
+rect_LDADD = $(GTK_LIBS) -lm
+
+samegame_SOURCES = ./drawing.c ./gtk.c ./malloc.c ./midend.c ./misc.c \
+               ./printing.c ./ps.c ./puzzles.h ./random.c ./samegame.c \
+               ./version.c ./version.h icons/samegame-icon.c
+samegame_LDADD = $(GTK_LIBS) -lm
+
+signpost_SOURCES = ./drawing.c ./dsf.c ./gtk.c ./malloc.c ./midend.c \
+               ./misc.c ./printing.c ./ps.c ./puzzles.h ./random.c \
+               ./signpost.c ./version.c ./version.h icons/signpost-icon.c
+signpost_LDADD = $(GTK_LIBS) -lm
+
+signpostsolver_SOURCES = ./dsf.c ./malloc.c ./misc.c ./nullfe.c ./puzzles.h \
+               ./random.c
+signpostsolver_LDADD = libsignpos2_a-signpost.$(OBJEXT) -lm
+
+singles_SOURCES = ./drawing.c ./dsf.c ./gtk.c ./latin.c ./latin.h ./malloc.c \
+               ./maxflow.c ./maxflow.h ./midend.c ./misc.c ./printing.c \
+               ./ps.c ./puzzles.h ./random.c ./singles.c ./tree234.c \
+               ./tree234.h ./version.c ./version.h icons/singles-icon.c
+singles_LDADD = $(GTK_LIBS) -lm
+
+singlessolver_SOURCES = ./dsf.c ./latin.c ./latin.h ./malloc.c ./maxflow.c \
+               ./maxflow.h ./misc.c ./nullfe.c ./puzzles.h ./random.c \
+               ./tree234.c ./tree234.h
+singlessolver_LDADD = libsingles3_a-singles.$(OBJEXT) -lm
+
+sixteen_SOURCES = ./drawing.c ./gtk.c ./malloc.c ./midend.c ./misc.c \
+               ./printing.c ./ps.c ./puzzles.h ./random.c ./sixteen.c \
+               ./version.c ./version.h icons/sixteen-icon.c
+sixteen_LDADD = $(GTK_LIBS) -lm
+
+slant_SOURCES = ./drawing.c ./dsf.c ./findloop.c ./gtk.c ./malloc.c \
+               ./midend.c ./misc.c ./printing.c ./ps.c ./puzzles.h \
+               ./random.c ./slant.c ./version.c ./version.h \
+               icons/slant-icon.c
+slant_LDADD = $(GTK_LIBS) -lm
+
+slantsolver_SOURCES = ./dsf.c ./findloop.c ./malloc.c ./misc.c ./nullfe.c \
+               ./puzzles.h ./random.c
+slantsolver_LDADD = libslant2_a-slant.$(OBJEXT) -lm
+
+solo_SOURCES = ./divvy.c ./drawing.c ./dsf.c ./gtk.c ./malloc.c ./midend.c \
+               ./misc.c ./printing.c ./ps.c ./puzzles.h ./random.c ./solo.c \
+               ./version.c ./version.h icons/solo-icon.c
+solo_LDADD = $(GTK_LIBS) -lm
+
+solosolver_SOURCES = ./divvy.c ./dsf.c ./malloc.c ./misc.c ./nullfe.c \
+               ./puzzles.h ./random.c
+solosolver_LDADD = libsolo2_a-solo.$(OBJEXT) -lm
+
+tents_SOURCES = ./drawing.c ./dsf.c ./gtk.c ./malloc.c ./maxflow.c \
+               ./maxflow.h ./midend.c ./misc.c ./printing.c ./ps.c \
+               ./puzzles.h ./random.c ./tents.c ./version.c ./version.h \
+               icons/tents-icon.c
+tents_LDADD = $(GTK_LIBS) -lm
+
+tentssolver_SOURCES = ./dsf.c ./malloc.c ./maxflow.c ./maxflow.h ./misc.c \
+               ./nullfe.c ./puzzles.h ./random.c
+tentssolver_LDADD = libtents3_a-tents.$(OBJEXT) -lm
+
+towers_SOURCES = ./drawing.c ./gtk.c ./latin.c ./latin.h ./malloc.c \
+               ./maxflow.c ./maxflow.h ./midend.c ./misc.c ./printing.c \
+               ./ps.c ./puzzles.h ./random.c ./towers.c ./tree234.c \
+               ./tree234.h ./version.c ./version.h icons/towers-icon.c
+towers_LDADD = $(GTK_LIBS) -lm
+
+towerssolver_SOURCES = ./malloc.c ./maxflow.c ./maxflow.h ./misc.c \
+               ./nullfe.c ./puzzles.h ./random.c ./tree234.c ./tree234.h
+towerssolver_LDADD = liblatin6_a-latin.$(OBJEXT) \
+               libtowers2_a-towers.$(OBJEXT) -lm
+
+tracks_SOURCES = ./drawing.c ./dsf.c ./findloop.c ./gtk.c ./malloc.c \
+               ./midend.c ./misc.c ./printing.c ./ps.c ./puzzles.h \
+               ./random.c ./tracks.c ./version.c ./version.h \
+               icons/tracks-icon.c
+tracks_LDADD = $(GTK_LIBS) -lm
+
+twiddle_SOURCES = ./drawing.c ./gtk.c ./malloc.c ./midend.c ./misc.c \
+               ./printing.c ./ps.c ./puzzles.h ./random.c ./twiddle.c \
+               ./version.c ./version.h icons/twiddle-icon.c
+twiddle_LDADD = $(GTK_LIBS) -lm
+
+undead_SOURCES = ./drawing.c ./gtk.c ./malloc.c ./midend.c ./misc.c \
+               ./printing.c ./ps.c ./puzzles.h ./random.c ./undead.c \
+               ./version.c ./version.h icons/undead-icon.c
+undead_LDADD = $(GTK_LIBS) -lm
+
+unequal_SOURCES = ./drawing.c ./gtk.c ./latin.c ./latin.h ./malloc.c \
+               ./maxflow.c ./maxflow.h ./midend.c ./misc.c ./printing.c \
+               ./ps.c ./puzzles.h ./random.c ./tree234.c ./tree234.h \
+               ./unequal.c ./version.c ./version.h icons/unequal-icon.c
+unequal_LDADD = $(GTK_LIBS) -lm
+
+unequalsolver_SOURCES = ./malloc.c ./maxflow.c ./maxflow.h ./misc.c \
+               ./nullfe.c ./puzzles.h ./random.c ./tree234.c ./tree234.h
+unequalsolver_LDADD = liblatin6_a-latin.$(OBJEXT) \
+               libunequal2_a-unequal.$(OBJEXT) -lm
+
+unruly_SOURCES = ./drawing.c ./gtk.c ./malloc.c ./midend.c ./misc.c \
+               ./printing.c ./ps.c ./puzzles.h ./random.c ./unruly.c \
+               ./version.c ./version.h icons/unruly-icon.c
+unruly_LDADD = $(GTK_LIBS) -lm
+
+unrulysolver_SOURCES = ./malloc.c ./misc.c ./nullfe.c ./puzzles.h ./random.c
+unrulysolver_LDADD = libunruly2_a-unruly.$(OBJEXT) -lm
+
+untangle_SOURCES = ./drawing.c ./gtk.c ./malloc.c ./midend.c ./misc.c \
+               ./printing.c ./ps.c ./puzzles.h ./random.c ./tree234.c \
+               ./tree234.h ./untangle.c ./version.c ./version.h \
+               icons/untangle-icon.c
+untangle_LDADD = $(GTK_LIBS) -lm
+
+libfifteen2_a_SOURCES = ./fifteen.c ./puzzles.h
+libfifteen2_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+libfilling2_a_SOURCES = ./filling.c ./puzzles.h
+libfilling2_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+libgalaxie2_a_SOURCES = ./galaxies.c ./puzzles.h
+libgalaxie2_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+libgalaxie4_a_SOURCES = ./galaxies.c ./puzzles.h
+libgalaxie4_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  \
+               -DSTANDALONE_PICTURE_GENERATOR
+libkeen2_a_SOURCES = ./keen.c ./puzzles.h ./latin.h
+libkeen2_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+liblatin6_a_SOURCES = ./latin.c ./puzzles.h ./tree234.h ./maxflow.h \
+               ./latin.h
+liblatin6_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+liblatin8_a_SOURCES = ./latin.c ./puzzles.h ./tree234.h ./maxflow.h \
+               ./latin.h
+liblatin8_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_LATIN_TEST
+liblightup2_a_SOURCES = ./lightup.c ./puzzles.h
+liblightup2_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+libloopy2_a_SOURCES = ./loopy.c ./puzzles.h ./tree234.h ./grid.h ./loopgen.h
+libloopy2_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+libmagnets2_a_SOURCES = ./magnets.c ./puzzles.h
+libmagnets2_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+libmap2_a_SOURCES = ./map.c ./puzzles.h
+libmap2_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+libmines2_a_SOURCES = ./mines.c ./tree234.h ./puzzles.h
+libmines2_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_OBFUSCATOR
+libpattern2_a_SOURCES = ./pattern.c ./puzzles.h
+libpattern2_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+libpattern4_a_SOURCES = ./pattern.c ./puzzles.h
+libpattern4_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  \
+               -DSTANDALONE_PICTURE_GENERATOR
+libpearl2_a_SOURCES = ./pearl.c ./puzzles.h ./grid.h ./loopgen.h
+libpearl2_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+libsignpos2_a_SOURCES = ./signpost.c ./puzzles.h
+libsignpos2_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+libsingles3_a_SOURCES = ./singles.c ./puzzles.h ./latin.h
+libsingles3_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+libslant2_a_SOURCES = ./slant.c ./puzzles.h
+libslant2_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+libsolo2_a_SOURCES = ./solo.c ./puzzles.h
+libsolo2_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+libtents3_a_SOURCES = ./tents.c ./puzzles.h ./maxflow.h
+libtents3_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+libtowers2_a_SOURCES = ./towers.c ./puzzles.h ./latin.h
+libtowers2_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+libunequal2_a_SOURCES = ./unequal.c ./puzzles.h ./latin.h
+libunequal2_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+libunruly2_a_SOURCES = ./unruly.c ./puzzles.h
+libunruly2_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+noinst_LIBRARIES = libfifteen2.a libfilling2.a libgalaxie2.a libgalaxie4.a \
+               libkeen2.a liblatin6.a liblatin8.a liblightup2.a libloopy2.a \
+               libmagnets2.a libmap2.a libmines2.a libpattern2.a \
+               libpattern4.a libpearl2.a libsignpos2.a libsingles3.a \
+               libslant2.a libsolo2.a libtents3.a libtowers2.a \
+               libunequal2.a libunruly2.a
+
+GAMES += blackbox
+GAMES += bridges
+GAMES += cube
+GAMES += dominosa
+GAMES += fifteen
+GAMES += filling
+GAMES += flip
+GAMES += flood
+GAMES += galaxies
+GAMES += guess
+GAMES += inertia
+GAMES += keen
+GAMES += lightup
+GAMES += loopy
+GAMES += magnets
+GAMES += map
+GAMES += mines
+GAMES += net
+GAMES += netslide
+GAMES += palisade
+GAMES += pattern
+GAMES += pearl
+GAMES += pegs
+GAMES += range
+GAMES += rect
+GAMES += samegame
+GAMES += signpost
+GAMES += singles
+GAMES += sixteen
+GAMES += slant
+GAMES += solo
+GAMES += tents
+GAMES += towers
+GAMES += tracks
+GAMES += twiddle
+GAMES += undead
+GAMES += unequal
+GAMES += unruly
+GAMES += untangle
+bin_PROGRAMS = $(GAMES)
+test: benchmark.html benchmark.txt
+
+benchmark.html: benchmark.txt benchmark.pl
+       ./benchmark.pl benchmark.txt > $@
+
+benchmark.txt: benchmark.sh $(GAMES)
+       ./benchmark.sh > $@
diff --git a/Makefile.cyg b/Makefile.cyg
new file mode 100644 (file)
index 0000000..168fcf2
--- /dev/null
@@ -0,0 +1,879 @@
+# Makefile for puzzles under cygwin.
+#
+# This file was created by `mkfiles.pl' from the `Recipe' file.
+# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.
+
+# You can define this path to point at your tools if you need to
+# TOOLPATH = c:\cygwin\bin\ # or similar, if you're running Windows
+# TOOLPATH = /pkg/mingw32msvc/i386-mingw32msvc/bin/
+CC = $(TOOLPATH)gcc
+RC = $(TOOLPATH)windres
+# Uncomment the following two lines to compile under Winelib
+# CC = winegcc
+# RC = wrc
+# You may also need to tell windres where to find include files:
+# RCINC = --include-dir c:\cygwin\include\
+
+CFLAGS = -mno-cygwin -Wall -O2 -D_WINDOWS -DDEBUG -DWIN32S_COMPAT \
+               -D_NO_OLDNAMES -DNO_MULTIMON -DNO_HTMLHELP -I./ -Iicons/
+LDFLAGS = -mno-cygwin -s
+RCFLAGS = $(RCINC) --define WIN32=1 --define _WIN32=1 --define WINVER=0x0400 \
+               --define MINGW32_FIX=1 --include ./ --include icons/
+
+all: blackbox.exe bridges.exe cube.exe dominosa.exe fifteen.exe \
+               fifteensolver.exe filling.exe fillingsolver.exe flip.exe \
+               flood.exe galaxies.exe galaxiespicture.exe \
+               galaxiessolver.exe guess.exe inertia.exe keen.exe \
+               keensolver.exe latincheck.exe lightup.exe lightupsolver.exe \
+               loopy.exe loopysolver.exe magnets.exe magnetssolver.exe \
+               map.exe mapsolver.exe mineobfusc.exe mines.exe netgame.exe \
+               netslide.exe nullgame.exe palisade.exe pattern.exe \
+               patternpicture.exe patternsolver.exe pearl.exe \
+               pearlbench.exe pegs.exe puzzles.exe range.exe rect.exe \
+               samegame.exe signpost.exe signpostsolver.exe singles.exe \
+               singlessolver.exe sixteen.exe slant.exe slantsolver.exe \
+               solo.exe solosolver.exe tents.exe tentssolver.exe towers.exe \
+               towerssolver.exe tracks.exe twiddle.exe undead.exe \
+               unequal.exe unequalsolver.exe unruly.exe unrulysolver.exe \
+               untangle.exe
+
+blackbox.exe: blackbox.o blackbox.res.o drawing.o malloc.o midend.o misc.o \
+               printing.o random.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,blackbox.map blackbox.o \
+               blackbox.res.o drawing.o malloc.o midend.o misc.o printing.o \
+               random.o version.o windows.o -lcomctl32 -lcomdlg32 -lgdi32 \
+               -luser32 -lwinspool
+
+bridges.exe: bridges.o bridges.res.o drawing.o dsf.o findloop.o malloc.o \
+               midend.o misc.o printing.o random.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,bridges.map bridges.o \
+               bridges.res.o drawing.o dsf.o findloop.o malloc.o midend.o \
+               misc.o printing.o random.o version.o windows.o -lcomctl32 \
+               -lcomdlg32 -lgdi32 -luser32 -lwinspool
+
+cube.exe: cube.o cube.res.o drawing.o malloc.o midend.o misc.o printing.o \
+               random.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,cube.map cube.o cube.res.o \
+               drawing.o malloc.o midend.o misc.o printing.o random.o \
+               version.o windows.o -lcomctl32 -lcomdlg32 -lgdi32 -luser32 \
+               -lwinspool
+
+dominosa.exe: dominosa.o dominosa.res.o drawing.o laydomino.o malloc.o \
+               midend.o misc.o printing.o random.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,dominosa.map dominosa.o \
+               dominosa.res.o drawing.o laydomino.o malloc.o midend.o \
+               misc.o printing.o random.o version.o windows.o -lcomctl32 \
+               -lcomdlg32 -lgdi32 -luser32 -lwinspool
+
+fifteen.exe: drawing.o fifteen.o fifteen.res.o malloc.o midend.o misc.o \
+               printing.o random.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,fifteen.map drawing.o \
+               fifteen.o fifteen.res.o malloc.o midend.o misc.o printing.o \
+               random.o version.o windows.o -lcomctl32 -lcomdlg32 -lgdi32 \
+               -luser32 -lwinspool
+
+fifteensolver.exe: fifteen2.o malloc.o misc.o nullfe.o random.o
+       $(CC) $(LDFLAGS) -o $@ -Wl,-Map,fifteensolver.map fifteen2.o \
+               malloc.o misc.o nullfe.o random.o 
+
+filling.exe: drawing.o dsf.o filling.o filling.res.o malloc.o midend.o \
+               misc.o printing.o random.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,filling.map drawing.o \
+               dsf.o filling.o filling.res.o malloc.o midend.o misc.o \
+               printing.o random.o version.o windows.o -lcomctl32 \
+               -lcomdlg32 -lgdi32 -luser32 -lwinspool
+
+fillingsolver.exe: dsf.o filling2.o malloc.o misc.o nullfe.o random.o
+       $(CC) $(LDFLAGS) -o $@ -Wl,-Map,fillingsolver.map dsf.o filling2.o \
+               malloc.o misc.o nullfe.o random.o 
+
+flip.exe: drawing.o flip.o flip.res.o malloc.o midend.o misc.o printing.o \
+               random.o tree234.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,flip.map drawing.o flip.o \
+               flip.res.o malloc.o midend.o misc.o printing.o random.o \
+               tree234.o version.o windows.o -lcomctl32 -lcomdlg32 -lgdi32 \
+               -luser32 -lwinspool
+
+flood.exe: drawing.o flood.o flood.res.o malloc.o midend.o misc.o printing.o \
+               random.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,flood.map drawing.o \
+               flood.o flood.res.o malloc.o midend.o misc.o printing.o \
+               random.o version.o windows.o -lcomctl32 -lcomdlg32 -lgdi32 \
+               -luser32 -lwinspool
+
+galaxies.exe: drawing.o dsf.o galaxies.o galaxies.res.o malloc.o midend.o \
+               misc.o printing.o random.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,galaxies.map drawing.o \
+               dsf.o galaxies.o galaxies.res.o malloc.o midend.o misc.o \
+               printing.o random.o version.o windows.o -lcomctl32 \
+               -lcomdlg32 -lgdi32 -luser32 -lwinspool
+
+galaxiespicture.exe: dsf.o galaxie4.o malloc.o misc.o nullfe.o random.o
+       $(CC) $(LDFLAGS) -o $@ -Wl,-Map,galaxiespicture.map dsf.o galaxie4.o \
+               malloc.o misc.o nullfe.o random.o 
+
+galaxiessolver.exe: dsf.o galaxie2.o malloc.o misc.o nullfe.o random.o
+       $(CC) $(LDFLAGS) -o $@ -Wl,-Map,galaxiessolver.map dsf.o galaxie2.o \
+               malloc.o misc.o nullfe.o random.o 
+
+guess.exe: drawing.o guess.o guess.res.o malloc.o midend.o misc.o printing.o \
+               random.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,guess.map drawing.o \
+               guess.o guess.res.o malloc.o midend.o misc.o printing.o \
+               random.o version.o windows.o -lcomctl32 -lcomdlg32 -lgdi32 \
+               -luser32 -lwinspool
+
+inertia.exe: drawing.o inertia.o inertia.res.o malloc.o midend.o misc.o \
+               printing.o random.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,inertia.map drawing.o \
+               inertia.o inertia.res.o malloc.o midend.o misc.o printing.o \
+               random.o version.o windows.o -lcomctl32 -lcomdlg32 -lgdi32 \
+               -luser32 -lwinspool
+
+keen.exe: drawing.o dsf.o keen.o keen.res.o latin.o malloc.o maxflow.o \
+               midend.o misc.o printing.o random.o tree234.o version.o \
+               windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,keen.map drawing.o dsf.o \
+               keen.o keen.res.o latin.o malloc.o maxflow.o midend.o misc.o \
+               printing.o random.o tree234.o version.o windows.o -lcomctl32 \
+               -lcomdlg32 -lgdi32 -luser32 -lwinspool
+
+keensolver.exe: dsf.o keen2.o latin6.o malloc.o maxflow.o misc.o nullfe.o \
+               random.o tree234.o
+       $(CC) $(LDFLAGS) -o $@ -Wl,-Map,keensolver.map dsf.o keen2.o \
+               latin6.o malloc.o maxflow.o misc.o nullfe.o random.o \
+               tree234.o 
+
+latincheck.exe: latin8.o malloc.o maxflow.o misc.o nullfe.o random.o \
+               tree234.o
+       $(CC) $(LDFLAGS) -o $@ -Wl,-Map,latincheck.map latin8.o malloc.o \
+               maxflow.o misc.o nullfe.o random.o tree234.o 
+
+lightup.exe: combi.o drawing.o lightup.o lightup.res.o malloc.o midend.o \
+               misc.o printing.o random.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,lightup.map combi.o \
+               drawing.o lightup.o lightup.res.o malloc.o midend.o misc.o \
+               printing.o random.o version.o windows.o -lcomctl32 \
+               -lcomdlg32 -lgdi32 -luser32 -lwinspool
+
+lightupsolver.exe: combi.o lightup2.o malloc.o misc.o nullfe.o random.o
+       $(CC) $(LDFLAGS) -o $@ -Wl,-Map,lightupsolver.map combi.o lightup2.o \
+               malloc.o misc.o nullfe.o random.o 
+
+loopy.exe: drawing.o dsf.o grid.o loopgen.o loopy.o loopy.res.o malloc.o \
+               midend.o misc.o penrose.o printing.o random.o tree234.o \
+               version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,loopy.map drawing.o dsf.o \
+               grid.o loopgen.o loopy.o loopy.res.o malloc.o midend.o \
+               misc.o penrose.o printing.o random.o tree234.o version.o \
+               windows.o -lcomctl32 -lcomdlg32 -lgdi32 -luser32 -lwinspool
+
+loopysolver.exe: dsf.o grid.o loopgen.o loopy2.o malloc.o misc.o nullfe.o \
+               penrose.o random.o tree234.o
+       $(CC) $(LDFLAGS) -o $@ -Wl,-Map,loopysolver.map dsf.o grid.o \
+               loopgen.o loopy2.o malloc.o misc.o nullfe.o penrose.o \
+               random.o tree234.o 
+
+magnets.exe: drawing.o laydomino.o magnets.o magnets.res.o malloc.o midend.o \
+               misc.o printing.o random.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,magnets.map drawing.o \
+               laydomino.o magnets.o magnets.res.o malloc.o midend.o misc.o \
+               printing.o random.o version.o windows.o -lcomctl32 \
+               -lcomdlg32 -lgdi32 -luser32 -lwinspool
+
+magnetssolver.exe: laydomino.o magnets2.o malloc.o misc.o nullfe.o random.o
+       $(CC) $(LDFLAGS) -o $@ -Wl,-Map,magnetssolver.map laydomino.o \
+               magnets2.o malloc.o misc.o nullfe.o random.o 
+
+map.exe: drawing.o dsf.o malloc.o map.o map.res.o midend.o misc.o printing.o \
+               random.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,map.map drawing.o dsf.o \
+               malloc.o map.o map.res.o midend.o misc.o printing.o random.o \
+               version.o windows.o -lcomctl32 -lcomdlg32 -lgdi32 -luser32 \
+               -lwinspool
+
+mapsolver.exe: dsf.o malloc.o map2.o misc.o nullfe.o random.o
+       $(CC) $(LDFLAGS) -o $@ -Wl,-Map,mapsolver.map dsf.o malloc.o map2.o \
+               misc.o nullfe.o random.o 
+
+mineobfusc.exe: malloc.o mines2.o misc.o nullfe.o random.o tree234.o
+       $(CC) $(LDFLAGS) -o $@ -Wl,-Map,mineobfusc.map malloc.o mines2.o \
+               misc.o nullfe.o random.o tree234.o 
+
+mines.exe: drawing.o malloc.o midend.o mines.o mines.res.o misc.o printing.o \
+               random.o tree234.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,mines.map drawing.o \
+               malloc.o midend.o mines.o mines.res.o misc.o printing.o \
+               random.o tree234.o version.o windows.o -lcomctl32 -lcomdlg32 \
+               -lgdi32 -luser32 -lwinspool
+
+netgame.exe: drawing.o dsf.o findloop.o malloc.o midend.o misc.o net.o \
+               net.res.o printing.o random.o tree234.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,netgame.map drawing.o \
+               dsf.o findloop.o malloc.o midend.o misc.o net.o net.res.o \
+               printing.o random.o tree234.o version.o windows.o -lcomctl32 \
+               -lcomdlg32 -lgdi32 -luser32 -lwinspool
+
+netslide.exe: drawing.o malloc.o midend.o misc.o netslide.o netslide.res.o \
+               printing.o random.o tree234.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,netslide.map drawing.o \
+               malloc.o midend.o misc.o netslide.o netslide.res.o \
+               printing.o random.o tree234.o version.o windows.o -lcomctl32 \
+               -lcomdlg32 -lgdi32 -luser32 -lwinspool
+
+nullgame.exe: drawing.o malloc.o midend.o misc.o noicon.res.o nullgame.o \
+               printing.o random.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,nullgame.map drawing.o \
+               malloc.o midend.o misc.o noicon.res.o nullgame.o printing.o \
+               random.o version.o windows.o -lcomctl32 -lcomdlg32 -lgdi32 \
+               -luser32 -lwinspool
+
+palisade.exe: divvy.o drawing.o dsf.o malloc.o midend.o misc.o palisade.o \
+               palisade.res.o printing.o random.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,palisade.map divvy.o \
+               drawing.o dsf.o malloc.o midend.o misc.o palisade.o \
+               palisade.res.o printing.o random.o version.o windows.o \
+               -lcomctl32 -lcomdlg32 -lgdi32 -luser32 -lwinspool
+
+pattern.exe: drawing.o malloc.o midend.o misc.o pattern.o pattern.res.o \
+               printing.o random.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,pattern.map drawing.o \
+               malloc.o midend.o misc.o pattern.o pattern.res.o printing.o \
+               random.o version.o windows.o -lcomctl32 -lcomdlg32 -lgdi32 \
+               -luser32 -lwinspool
+
+patternpicture.exe: malloc.o misc.o nullfe.o pattern4.o random.o
+       $(CC) $(LDFLAGS) -o $@ -Wl,-Map,patternpicture.map malloc.o misc.o \
+               nullfe.o pattern4.o random.o 
+
+patternsolver.exe: malloc.o misc.o nullfe.o pattern2.o random.o
+       $(CC) $(LDFLAGS) -o $@ -Wl,-Map,patternsolver.map malloc.o misc.o \
+               nullfe.o pattern2.o random.o 
+
+pearl.exe: drawing.o dsf.o grid.o loopgen.o malloc.o midend.o misc.o pearl.o \
+               pearl.res.o penrose.o printing.o random.o tdq.o tree234.o \
+               version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,pearl.map drawing.o dsf.o \
+               grid.o loopgen.o malloc.o midend.o misc.o pearl.o \
+               pearl.res.o penrose.o printing.o random.o tdq.o tree234.o \
+               version.o windows.o -lcomctl32 -lcomdlg32 -lgdi32 -luser32 \
+               -lwinspool
+
+pearlbench.exe: dsf.o grid.o loopgen.o malloc.o misc.o nullfe.o pearl2.o \
+               penrose.o random.o tdq.o tree234.o
+       $(CC) $(LDFLAGS) -o $@ -Wl,-Map,pearlbench.map dsf.o grid.o \
+               loopgen.o malloc.o misc.o nullfe.o pearl2.o penrose.o \
+               random.o tdq.o tree234.o 
+
+pegs.exe: drawing.o malloc.o midend.o misc.o pegs.o pegs.res.o printing.o \
+               random.o tree234.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,pegs.map drawing.o \
+               malloc.o midend.o misc.o pegs.o pegs.res.o printing.o \
+               random.o tree234.o version.o windows.o -lcomctl32 -lcomdlg32 \
+               -lgdi32 -luser32 -lwinspool
+
+puzzles.exe: blackbo3.o bridges3.o combi.o cube3.o divvy.o dominos3.o \
+               drawing.o dsf.o fifteen5.o filling5.o findloop.o flip3.o \
+               flood3.o galaxie7.o grid.o guess3.o inertia3.o keen5.o \
+               latin.o laydomino.o lightup5.o list.o loopgen.o loopy5.o \
+               magnets5.o malloc.o map5.o maxflow.o midend.o mines5.o \
+               misc.o net3.o netslid3.o noicon.res.o palisad3.o pattern7.o \
+               pearl5.o pegs3.o penrose.o printing.o random.o range3.o \
+               rect3.o samegam3.o signpos5.o singles5.o sixteen3.o slant5.o \
+               solo5.o tdq.o tents5.o towers5.o tracks3.o tree234.o \
+               twiddle3.o undead3.o unequal5.o unruly5.o untangl3.o \
+               version.o windows1.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,puzzles.map blackbo3.o \
+               bridges3.o combi.o cube3.o divvy.o dominos3.o drawing.o \
+               dsf.o fifteen5.o filling5.o findloop.o flip3.o flood3.o \
+               galaxie7.o grid.o guess3.o inertia3.o keen5.o latin.o \
+               laydomino.o lightup5.o list.o loopgen.o loopy5.o magnets5.o \
+               malloc.o map5.o maxflow.o midend.o mines5.o misc.o net3.o \
+               netslid3.o noicon.res.o palisad3.o pattern7.o pearl5.o \
+               pegs3.o penrose.o printing.o random.o range3.o rect3.o \
+               samegam3.o signpos5.o singles5.o sixteen3.o slant5.o solo5.o \
+               tdq.o tents5.o towers5.o tracks3.o tree234.o twiddle3.o \
+               undead3.o unequal5.o unruly5.o untangl3.o version.o \
+               windows1.o -lcomctl32 -lcomdlg32 -lgdi32 -luser32 -lwinspool
+
+range.exe: drawing.o dsf.o malloc.o midend.o misc.o printing.o random.o \
+               range.o range.res.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,range.map drawing.o dsf.o \
+               malloc.o midend.o misc.o printing.o random.o range.o \
+               range.res.o version.o windows.o -lcomctl32 -lcomdlg32 \
+               -lgdi32 -luser32 -lwinspool
+
+rect.exe: drawing.o malloc.o midend.o misc.o printing.o random.o rect.o \
+               rect.res.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,rect.map drawing.o \
+               malloc.o midend.o misc.o printing.o random.o rect.o \
+               rect.res.o version.o windows.o -lcomctl32 -lcomdlg32 -lgdi32 \
+               -luser32 -lwinspool
+
+samegame.exe: drawing.o malloc.o midend.o misc.o printing.o random.o \
+               samegame.o samegame.res.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,samegame.map drawing.o \
+               malloc.o midend.o misc.o printing.o random.o samegame.o \
+               samegame.res.o version.o windows.o -lcomctl32 -lcomdlg32 \
+               -lgdi32 -luser32 -lwinspool
+
+signpost.exe: drawing.o dsf.o malloc.o midend.o misc.o printing.o random.o \
+               signpost.o signpost.res.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,signpost.map drawing.o \
+               dsf.o malloc.o midend.o misc.o printing.o random.o \
+               signpost.o signpost.res.o version.o windows.o -lcomctl32 \
+               -lcomdlg32 -lgdi32 -luser32 -lwinspool
+
+signpostsolver.exe: dsf.o malloc.o misc.o nullfe.o random.o signpos2.o
+       $(CC) $(LDFLAGS) -o $@ -Wl,-Map,signpostsolver.map dsf.o malloc.o \
+               misc.o nullfe.o random.o signpos2.o 
+
+singles.exe: drawing.o dsf.o latin.o malloc.o maxflow.o midend.o misc.o \
+               printing.o random.o singles.o singles.res.o tree234.o \
+               version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,singles.map drawing.o \
+               dsf.o latin.o malloc.o maxflow.o midend.o misc.o printing.o \
+               random.o singles.o singles.res.o tree234.o version.o \
+               windows.o -lcomctl32 -lcomdlg32 -lgdi32 -luser32 -lwinspool
+
+singlessolver.exe: dsf.o latin.o malloc.o maxflow.o misc.o nullfe.o random.o \
+               singles3.o tree234.o
+       $(CC) $(LDFLAGS) -o $@ -Wl,-Map,singlessolver.map dsf.o latin.o \
+               malloc.o maxflow.o misc.o nullfe.o random.o singles3.o \
+               tree234.o 
+
+sixteen.exe: drawing.o malloc.o midend.o misc.o printing.o random.o \
+               sixteen.o sixteen.res.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,sixteen.map drawing.o \
+               malloc.o midend.o misc.o printing.o random.o sixteen.o \
+               sixteen.res.o version.o windows.o -lcomctl32 -lcomdlg32 \
+               -lgdi32 -luser32 -lwinspool
+
+slant.exe: drawing.o dsf.o findloop.o malloc.o midend.o misc.o printing.o \
+               random.o slant.o slant.res.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,slant.map drawing.o dsf.o \
+               findloop.o malloc.o midend.o misc.o printing.o random.o \
+               slant.o slant.res.o version.o windows.o -lcomctl32 \
+               -lcomdlg32 -lgdi32 -luser32 -lwinspool
+
+slantsolver.exe: dsf.o findloop.o malloc.o misc.o nullfe.o random.o slant2.o
+       $(CC) $(LDFLAGS) -o $@ -Wl,-Map,slantsolver.map dsf.o findloop.o \
+               malloc.o misc.o nullfe.o random.o slant2.o 
+
+solo.exe: divvy.o drawing.o dsf.o malloc.o midend.o misc.o printing.o \
+               random.o solo.o solo.res.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,solo.map divvy.o drawing.o \
+               dsf.o malloc.o midend.o misc.o printing.o random.o solo.o \
+               solo.res.o version.o windows.o -lcomctl32 -lcomdlg32 -lgdi32 \
+               -luser32 -lwinspool
+
+solosolver.exe: divvy.o dsf.o malloc.o misc.o nullfe.o random.o solo2.o
+       $(CC) $(LDFLAGS) -o $@ -Wl,-Map,solosolver.map divvy.o dsf.o \
+               malloc.o misc.o nullfe.o random.o solo2.o 
+
+tents.exe: drawing.o dsf.o malloc.o maxflow.o midend.o misc.o printing.o \
+               random.o tents.o tents.res.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,tents.map drawing.o dsf.o \
+               malloc.o maxflow.o midend.o misc.o printing.o random.o \
+               tents.o tents.res.o version.o windows.o -lcomctl32 \
+               -lcomdlg32 -lgdi32 -luser32 -lwinspool
+
+tentssolver.exe: dsf.o malloc.o maxflow.o misc.o nullfe.o random.o tents3.o
+       $(CC) $(LDFLAGS) -o $@ -Wl,-Map,tentssolver.map dsf.o malloc.o \
+               maxflow.o misc.o nullfe.o random.o tents3.o 
+
+towers.exe: drawing.o latin.o malloc.o maxflow.o midend.o misc.o printing.o \
+               random.o towers.o towers.res.o tree234.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,towers.map drawing.o \
+               latin.o malloc.o maxflow.o midend.o misc.o printing.o \
+               random.o towers.o towers.res.o tree234.o version.o windows.o \
+               -lcomctl32 -lcomdlg32 -lgdi32 -luser32 -lwinspool
+
+towerssolver.exe: latin6.o malloc.o maxflow.o misc.o nullfe.o random.o \
+               towers2.o tree234.o
+       $(CC) $(LDFLAGS) -o $@ -Wl,-Map,towerssolver.map latin6.o malloc.o \
+               maxflow.o misc.o nullfe.o random.o towers2.o tree234.o 
+
+tracks.exe: drawing.o dsf.o findloop.o malloc.o midend.o misc.o printing.o \
+               random.o tracks.o tracks.res.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,tracks.map drawing.o dsf.o \
+               findloop.o malloc.o midend.o misc.o printing.o random.o \
+               tracks.o tracks.res.o version.o windows.o -lcomctl32 \
+               -lcomdlg32 -lgdi32 -luser32 -lwinspool
+
+twiddle.exe: drawing.o malloc.o midend.o misc.o printing.o random.o \
+               twiddle.o twiddle.res.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,twiddle.map drawing.o \
+               malloc.o midend.o misc.o printing.o random.o twiddle.o \
+               twiddle.res.o version.o windows.o -lcomctl32 -lcomdlg32 \
+               -lgdi32 -luser32 -lwinspool
+
+undead.exe: drawing.o malloc.o midend.o misc.o printing.o random.o undead.o \
+               undead.res.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,undead.map drawing.o \
+               malloc.o midend.o misc.o printing.o random.o undead.o \
+               undead.res.o version.o windows.o -lcomctl32 -lcomdlg32 \
+               -lgdi32 -luser32 -lwinspool
+
+unequal.exe: drawing.o latin.o malloc.o maxflow.o midend.o misc.o printing.o \
+               random.o tree234.o unequal.o unequal.res.o version.o \
+               windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,unequal.map drawing.o \
+               latin.o malloc.o maxflow.o midend.o misc.o printing.o \
+               random.o tree234.o unequal.o unequal.res.o version.o \
+               windows.o -lcomctl32 -lcomdlg32 -lgdi32 -luser32 -lwinspool
+
+unequalsolver.exe: latin6.o malloc.o maxflow.o misc.o nullfe.o random.o \
+               tree234.o unequal2.o
+       $(CC) $(LDFLAGS) -o $@ -Wl,-Map,unequalsolver.map latin6.o malloc.o \
+               maxflow.o misc.o nullfe.o random.o tree234.o unequal2.o 
+
+unruly.exe: drawing.o malloc.o midend.o misc.o printing.o random.o unruly.o \
+               unruly.res.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,unruly.map drawing.o \
+               malloc.o midend.o misc.o printing.o random.o unruly.o \
+               unruly.res.o version.o windows.o -lcomctl32 -lcomdlg32 \
+               -lgdi32 -luser32 -lwinspool
+
+unrulysolver.exe: malloc.o misc.o nullfe.o random.o unruly2.o
+       $(CC) $(LDFLAGS) -o $@ -Wl,-Map,unrulysolver.map malloc.o misc.o \
+               nullfe.o random.o unruly2.o 
+
+untangle.exe: drawing.o malloc.o midend.o misc.o printing.o random.o \
+               tree234.o untangle.o untangle.res.o version.o windows.o
+       $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,untangle.map drawing.o \
+               malloc.o midend.o misc.o printing.o random.o tree234.o \
+               untangle.o untangle.res.o version.o windows.o -lcomctl32 \
+               -lcomdlg32 -lgdi32 -luser32 -lwinspool
+
+blackbox.o: ./blackbox.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+blackbox-icon.o: icons/blackbox-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+blackbox.res.o: icons/blackbox.rc ./puzzles.rc2 icons/blackbox.ico \
+               ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+blackbo3.o: ./blackbox.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+bridges.o: ./bridges.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+bridges-icon.o: icons/bridges-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+bridges.res.o: icons/bridges.rc ./puzzles.rc2 icons/bridges.ico ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+bridges3.o: ./bridges.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+combi.o: ./combi.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+cube.o: ./cube.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+cube-icon.o: icons/cube-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+cube.res.o: icons/cube.rc ./puzzles.rc2 icons/cube.ico ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+cube3.o: ./cube.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+divvy.o: ./divvy.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+dominosa.o: ./dominosa.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+dominosa-icon.o: icons/dominosa-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+dominosa.res.o: icons/dominosa.rc ./puzzles.rc2 icons/dominosa.ico \
+               ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+dominos3.o: ./dominosa.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+drawing.o: ./drawing.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+dsf.o: ./dsf.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+fifteen.o: ./fifteen.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+fifteen-icon.o: icons/fifteen-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+fifteen.res.o: icons/fifteen.rc ./puzzles.rc2 icons/fifteen.ico ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+fifteen5.o: ./fifteen.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+fifteen2.o: ./fifteen.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+filling.o: ./filling.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+filling-icon.o: icons/filling-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+filling.res.o: icons/filling.rc ./puzzles.rc2 icons/filling.ico ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+filling5.o: ./filling.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+filling2.o: ./filling.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+findloop.o: ./findloop.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+flip.o: ./flip.c ./puzzles.h ./tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+flip-icon.o: icons/flip-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+flip.res.o: icons/flip.rc ./puzzles.rc2 icons/flip.ico ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+flip3.o: ./flip.c ./puzzles.h ./tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+flood.o: ./flood.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+flood-icon.o: icons/flood-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+flood.res.o: icons/flood.rc ./puzzles.rc2 icons/flood.ico ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+flood3.o: ./flood.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+galaxies.o: ./galaxies.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+galaxies-icon.o: icons/galaxies-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+galaxies.res.o: icons/galaxies.rc ./puzzles.rc2 icons/galaxies.ico \
+               ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+galaxie7.o: ./galaxies.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+galaxie4.o: ./galaxies.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_PICTURE_GENERATOR -c $< -o $@
+galaxie2.o: ./galaxies.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+grid.o: ./grid.c ./puzzles.h ./tree234.h ./grid.h ./penrose.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+gtk.o: ./gtk.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+guess.o: ./guess.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+guess-icon.o: icons/guess-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+guess.res.o: icons/guess.rc ./puzzles.rc2 icons/guess.ico ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+guess3.o: ./guess.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+inertia.o: ./inertia.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+inertia-icon.o: icons/inertia-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+inertia.res.o: icons/inertia.rc ./puzzles.rc2 icons/inertia.ico ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+inertia3.o: ./inertia.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+keen.o: ./keen.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+keen-icon.o: icons/keen-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+keen.res.o: icons/keen.rc ./puzzles.rc2 icons/keen.ico ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+keen5.o: ./keen.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+keen2.o: ./keen.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+latin.o: ./latin.c ./puzzles.h ./tree234.h ./maxflow.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+latin8.o: ./latin.c ./puzzles.h ./tree234.h ./maxflow.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_LATIN_TEST -c $< -o $@
+latin6.o: ./latin.c ./puzzles.h ./tree234.h ./maxflow.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+laydomino.o: ./laydomino.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+lightup.o: ./lightup.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+lightup-icon.o: icons/lightup-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+lightup.res.o: icons/lightup.rc ./puzzles.rc2 icons/lightup.ico ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+lightup5.o: ./lightup.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+lightup2.o: ./lightup.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+list.o: ./list.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+loopgen.o: ./loopgen.c ./puzzles.h ./tree234.h ./grid.h ./loopgen.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+loopy.o: ./loopy.c ./puzzles.h ./tree234.h ./grid.h ./loopgen.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+loopy-icon.o: icons/loopy-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+loopy.res.o: icons/loopy.rc ./puzzles.rc2 icons/loopy.ico ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+loopy5.o: ./loopy.c ./puzzles.h ./tree234.h ./grid.h ./loopgen.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+loopy2.o: ./loopy.c ./puzzles.h ./tree234.h ./grid.h ./loopgen.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+magnets.o: ./magnets.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+magnets-icon.o: icons/magnets-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+magnets.res.o: icons/magnets.rc ./puzzles.rc2 icons/magnets.ico ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+magnets5.o: ./magnets.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+magnets2.o: ./magnets.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+malloc.o: ./malloc.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+map.o: ./map.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+map-icon.o: icons/map-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+map.res.o: icons/map.rc ./puzzles.rc2 icons/map.ico ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+map5.o: ./map.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+map2.o: ./map.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+maxflow.o: ./maxflow.c ./maxflow.h ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+midend.o: ./midend.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+mines.o: ./mines.c ./tree234.h ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+mines-icon.o: icons/mines-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+mines.res.o: icons/mines.rc ./puzzles.rc2 icons/mines.ico ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+mines5.o: ./mines.c ./tree234.h ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+mines2.o: ./mines.c ./tree234.h ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_OBFUSCATOR -c $< -o $@
+misc.o: ./misc.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+net.o: ./net.c ./puzzles.h ./tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+net-icon.o: icons/net-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+net.res.o: icons/net.rc ./puzzles.rc2 icons/net.ico ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+net3.o: ./net.c ./puzzles.h ./tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+netslide.o: ./netslide.c ./puzzles.h ./tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+netslide-icon.o: icons/netslide-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+netslide.res.o: icons/netslide.rc ./puzzles.rc2 icons/netslide.ico \
+               ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+netslid3.o: ./netslide.c ./puzzles.h ./tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+no-icon.o: ./no-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+noicon.res.o: ./noicon.rc ./puzzles.rc2 ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+nullfe.o: ./nullfe.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+nullgame.o: ./nullgame.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+obfusc.o: ./obfusc.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+osx.o: ./osx.m ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+palisade.o: ./palisade.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+palisade-icon.o: icons/palisade-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+palisade.res.o: icons/palisade.rc ./puzzles.rc2 icons/palisade.ico \
+               ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+palisad3.o: ./palisade.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+pattern.o: ./pattern.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pattern-icon.o: icons/pattern-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pattern.res.o: icons/pattern.rc ./puzzles.rc2 icons/pattern.ico ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+pattern7.o: ./pattern.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+pattern4.o: ./pattern.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_PICTURE_GENERATOR -c $< -o $@
+pattern2.o: ./pattern.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+pearl.o: ./pearl.c ./puzzles.h ./grid.h ./loopgen.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pearl-icon.o: icons/pearl-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pearl.res.o: icons/pearl.rc ./puzzles.rc2 icons/pearl.ico ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+pearl5.o: ./pearl.c ./puzzles.h ./grid.h ./loopgen.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+pearl2.o: ./pearl.c ./puzzles.h ./grid.h ./loopgen.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+pegs.o: ./pegs.c ./puzzles.h ./tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pegs-icon.o: icons/pegs-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pegs.res.o: icons/pegs.rc ./puzzles.rc2 icons/pegs.ico ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+pegs3.o: ./pegs.c ./puzzles.h ./tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+penrose.o: ./penrose.c ./puzzles.h ./penrose.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+printing.o: ./printing.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+ps.o: ./ps.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+random.o: ./random.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+range.o: ./range.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+range-icon.o: icons/range-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+range.res.o: icons/range.rc ./puzzles.rc2 icons/range.ico ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+range3.o: ./range.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+rect.o: ./rect.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+rect-icon.o: icons/rect-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+rect.res.o: icons/rect.rc ./puzzles.rc2 icons/rect.ico ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+rect3.o: ./rect.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+samegame.o: ./samegame.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+samegame-icon.o: icons/samegame-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+samegame.res.o: icons/samegame.rc ./puzzles.rc2 icons/samegame.ico \
+               ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+samegam3.o: ./samegame.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+signpost.o: ./signpost.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+signpost-icon.o: icons/signpost-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+signpost.res.o: icons/signpost.rc ./puzzles.rc2 icons/signpost.ico \
+               ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+signpos5.o: ./signpost.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+signpos2.o: ./signpost.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+singles.o: ./singles.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+singles-icon.o: icons/singles-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+singles.res.o: icons/singles.rc ./puzzles.rc2 icons/singles.ico ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+singles5.o: ./singles.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+singles3.o: ./singles.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+sixteen.o: ./sixteen.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+sixteen-icon.o: icons/sixteen-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+sixteen.res.o: icons/sixteen.rc ./puzzles.rc2 icons/sixteen.ico ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+sixteen3.o: ./sixteen.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+slant.o: ./slant.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+slant-icon.o: icons/slant-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+slant.res.o: icons/slant.rc ./puzzles.rc2 icons/slant.ico ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+slant5.o: ./slant.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+slant2.o: ./slant.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+solo.o: ./solo.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+solo-icon.o: icons/solo-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+solo.res.o: icons/solo.rc ./puzzles.rc2 icons/solo.ico ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+solo5.o: ./solo.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+solo2.o: ./solo.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+tdq.o: ./tdq.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+tents.o: ./tents.c ./puzzles.h ./maxflow.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+tents-icon.o: icons/tents-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+tents.res.o: icons/tents.rc ./puzzles.rc2 icons/tents.ico ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+tents5.o: ./tents.c ./puzzles.h ./maxflow.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+tents3.o: ./tents.c ./puzzles.h ./maxflow.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+towers.o: ./towers.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+towers-icon.o: icons/towers-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+towers.res.o: icons/towers.rc ./puzzles.rc2 icons/towers.ico ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+towers5.o: ./towers.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+towers2.o: ./towers.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+tracks.o: ./tracks.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+tracks-icon.o: icons/tracks-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+tracks.res.o: icons/tracks.rc ./puzzles.rc2 icons/tracks.ico ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+tracks3.o: ./tracks.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+tree234.o: ./tree234.c ./tree234.h ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+twiddle.o: ./twiddle.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+twiddle-icon.o: icons/twiddle-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+twiddle.res.o: icons/twiddle.rc ./puzzles.rc2 icons/twiddle.ico ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+twiddle3.o: ./twiddle.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+undead.o: ./undead.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+undead-icon.o: icons/undead-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+undead.res.o: icons/undead.rc ./puzzles.rc2 icons/undead.ico ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+undead3.o: ./undead.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+unequal.o: ./unequal.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+unequal-icon.o: icons/unequal-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+unequal.res.o: icons/unequal.rc ./puzzles.rc2 icons/unequal.ico ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+unequal5.o: ./unequal.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+unequal2.o: ./unequal.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+unruly.o: ./unruly.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+unruly-icon.o: icons/unruly-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+unruly.res.o: icons/unruly.rc ./puzzles.rc2 icons/unruly.ico ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+unruly5.o: ./unruly.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+unruly2.o: ./unruly.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+untangle.o: ./untangle.c ./puzzles.h ./tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+untangle-icon.o: icons/untangle-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+untangle.res.o: icons/untangle.rc ./puzzles.rc2 icons/untangle.ico \
+               ./resource.h
+       $(RC) $(FWHACK) $(RCFL) $(RCFLAGS) $< $@
+untangl3.o: ./untangle.c ./puzzles.h ./tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+version.o: ./version.c ./version.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+windows.o: ./windows.c ./puzzles.h ./resource.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+windows1.o: ./windows.c ./puzzles.h ./resource.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+
+
+clean:
+       rm -f *.o *.exe *.res.o *.map
+
diff --git a/Makefile.doc b/Makefile.doc
new file mode 100644 (file)
index 0000000..c7d6946
--- /dev/null
@@ -0,0 +1,17 @@
+all: puzzles.hlp puzzles.txt HACKING
+
+preprocessed.but: puzzles.but
+       sed 's/PREFIX-/$(BINPREFIX)/g' puzzles.but > preprocessed.but
+
+puzzles.hlp puzzles.txt: preprocessed.but
+       halibut --winhelp=puzzles.hlp --text=puzzles.txt preprocessed.but
+
+HACKING: devel.but
+       halibut --text=HACKING devel.but
+
+chm: puzzles.hhp
+puzzles.hhp: puzzles.but chm.but
+       halibut --html puzzles.but chm.but
+
+clean:
+       rm -f puzzles.hlp puzzles.txt preprocessed.but HACKING *.html *.hh[pck]
diff --git a/Makefile.emcc b/Makefile.emcc
new file mode 100644 (file)
index 0000000..f248c85
--- /dev/null
@@ -0,0 +1,576 @@
+# Makefile for puzzles using Emscripten. Requires GNU make.
+#
+# This file was created by `mkfiles.pl' from the `Recipe' file.
+# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.
+
+# This can be set on the command line to point at the emcc command,
+# if it is not on your PATH.
+EMCC = emcc
+
+CFLAGS = -DSLOW_SYSTEM -I./ -Iicons/
+
+all: $(OUTPREFIX)blackbox.js $(OUTPREFIX)bridges.js $(OUTPREFIX)cube.js \
+               $(OUTPREFIX)dominosa.js $(OUTPREFIX)fifteen.js \
+               $(OUTPREFIX)filling.js $(OUTPREFIX)flip.js \
+               $(OUTPREFIX)flood.js $(OUTPREFIX)galaxies.js \
+               $(OUTPREFIX)guess.js $(OUTPREFIX)inertia.js \
+               $(OUTPREFIX)keen.js $(OUTPREFIX)lightup.js \
+               $(OUTPREFIX)loopy.js $(OUTPREFIX)magnets.js \
+               $(OUTPREFIX)map.js $(OUTPREFIX)mines.js $(OUTPREFIX)net.js \
+               $(OUTPREFIX)netslide.js $(OUTPREFIX)nullgame.js \
+               $(OUTPREFIX)palisade.js $(OUTPREFIX)pattern.js \
+               $(OUTPREFIX)pearl.js $(OUTPREFIX)pegs.js \
+               $(OUTPREFIX)range.js $(OUTPREFIX)rect.js \
+               $(OUTPREFIX)samegame.js $(OUTPREFIX)signpost.js \
+               $(OUTPREFIX)singles.js $(OUTPREFIX)sixteen.js \
+               $(OUTPREFIX)slant.js $(OUTPREFIX)solo.js \
+               $(OUTPREFIX)tents.js $(OUTPREFIX)towers.js \
+               $(OUTPREFIX)tracks.js $(OUTPREFIX)twiddle.js \
+               $(OUTPREFIX)undead.js $(OUTPREFIX)unequal.js \
+               $(OUTPREFIX)unruly.js $(OUTPREFIX)untangle.js
+
+$(OUTPREFIX)blackbox.js: blackbox.o blackbox-icon.o drawing.o emcc.o \
+               malloc.o midend.o misc.o printing.o ps.o random.o version.o \
+               emccpre.js emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)blackbox.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" blackbox.o blackbox-icon.o drawing.o emcc.o malloc.o midend.o misc.o printing.o ps.o random.o version.o
+
+$(OUTPREFIX)bridges.js: bridges.o bridges-icon.o drawing.o dsf.o findloop.o \
+               emcc.o malloc.o midend.o misc.o printing.o ps.o random.o \
+               version.o emccpre.js emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)bridges.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" bridges.o bridges-icon.o drawing.o dsf.o findloop.o emcc.o malloc.o midend.o misc.o printing.o ps.o random.o version.o
+
+$(OUTPREFIX)cube.js: cube.o cube-icon.o drawing.o emcc.o malloc.o midend.o \
+               misc.o printing.o ps.o random.o version.o emccpre.js \
+               emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)cube.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" cube.o cube-icon.o drawing.o emcc.o malloc.o midend.o misc.o printing.o ps.o random.o version.o
+
+$(OUTPREFIX)dominosa.js: dominosa.o dominosa-icon.o drawing.o emcc.o \
+               laydomino.o malloc.o midend.o misc.o printing.o ps.o \
+               random.o version.o emccpre.js emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)dominosa.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" dominosa.o dominosa-icon.o drawing.o emcc.o laydomino.o malloc.o midend.o misc.o printing.o ps.o random.o version.o
+
+$(OUTPREFIX)fifteen.js: drawing.o fifteen.o fifteen-icon.o emcc.o malloc.o \
+               midend.o misc.o printing.o ps.o random.o version.o \
+               emccpre.js emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)fifteen.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" drawing.o fifteen.o fifteen-icon.o emcc.o malloc.o midend.o misc.o printing.o ps.o random.o version.o
+
+$(OUTPREFIX)filling.js: drawing.o dsf.o filling.o filling-icon.o emcc.o \
+               malloc.o midend.o misc.o printing.o ps.o random.o version.o \
+               emccpre.js emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)filling.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" drawing.o dsf.o filling.o filling-icon.o emcc.o malloc.o midend.o misc.o printing.o ps.o random.o version.o
+
+$(OUTPREFIX)flip.js: drawing.o flip.o flip-icon.o emcc.o malloc.o midend.o \
+               misc.o printing.o ps.o random.o tree234.o version.o \
+               emccpre.js emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)flip.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" drawing.o flip.o flip-icon.o emcc.o malloc.o midend.o misc.o printing.o ps.o random.o tree234.o version.o
+
+$(OUTPREFIX)flood.js: drawing.o flood.o flood-icon.o emcc.o malloc.o \
+               midend.o misc.o printing.o ps.o random.o version.o \
+               emccpre.js emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)flood.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" drawing.o flood.o flood-icon.o emcc.o malloc.o midend.o misc.o printing.o ps.o random.o version.o
+
+$(OUTPREFIX)galaxies.js: drawing.o dsf.o galaxies.o galaxies-icon.o emcc.o \
+               malloc.o midend.o misc.o printing.o ps.o random.o version.o \
+               emccpre.js emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)galaxies.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" drawing.o dsf.o galaxies.o galaxies-icon.o emcc.o malloc.o midend.o misc.o printing.o ps.o random.o version.o
+
+$(OUTPREFIX)guess.js: drawing.o emcc.o guess.o guess-icon.o malloc.o \
+               midend.o misc.o printing.o ps.o random.o version.o \
+               emccpre.js emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)guess.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" drawing.o emcc.o guess.o guess-icon.o malloc.o midend.o misc.o printing.o ps.o random.o version.o
+
+$(OUTPREFIX)inertia.js: drawing.o emcc.o inertia.o inertia-icon.o malloc.o \
+               midend.o misc.o printing.o ps.o random.o version.o \
+               emccpre.js emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)inertia.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" drawing.o emcc.o inertia.o inertia-icon.o malloc.o midend.o misc.o printing.o ps.o random.o version.o
+
+$(OUTPREFIX)keen.js: drawing.o dsf.o emcc.o keen.o keen-icon.o latin.o \
+               malloc.o maxflow.o midend.o misc.o printing.o ps.o random.o \
+               tree234.o version.o emccpre.js emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)keen.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" drawing.o dsf.o emcc.o keen.o keen-icon.o latin.o malloc.o maxflow.o midend.o misc.o printing.o ps.o random.o tree234.o version.o
+
+$(OUTPREFIX)lightup.js: combi.o drawing.o emcc.o lightup.o lightup-icon.o \
+               malloc.o midend.o misc.o printing.o ps.o random.o version.o \
+               emccpre.js emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)lightup.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" combi.o drawing.o emcc.o lightup.o lightup-icon.o malloc.o midend.o misc.o printing.o ps.o random.o version.o
+
+$(OUTPREFIX)loopy.js: drawing.o dsf.o grid.o emcc.o loopgen.o loopy.o \
+               loopy-icon.o malloc.o midend.o misc.o penrose.o printing.o \
+               ps.o random.o tree234.o version.o emccpre.js emcclib.js \
+               emccx.json
+       $(EMCC) -o $(OUTPREFIX)loopy.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" drawing.o dsf.o grid.o emcc.o loopgen.o loopy.o loopy-icon.o malloc.o midend.o misc.o penrose.o printing.o ps.o random.o tree234.o version.o
+
+$(OUTPREFIX)magnets.js: drawing.o emcc.o laydomino.o magnets.o \
+               magnets-icon.o malloc.o midend.o misc.o printing.o ps.o \
+               random.o version.o emccpre.js emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)magnets.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" drawing.o emcc.o laydomino.o magnets.o magnets-icon.o malloc.o midend.o misc.o printing.o ps.o random.o version.o
+
+$(OUTPREFIX)map.js: drawing.o dsf.o emcc.o malloc.o map.o map-icon.o \
+               midend.o misc.o printing.o ps.o random.o version.o \
+               emccpre.js emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)map.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" drawing.o dsf.o emcc.o malloc.o map.o map-icon.o midend.o misc.o printing.o ps.o random.o version.o
+
+$(OUTPREFIX)mines.js: drawing.o emcc.o malloc.o midend.o mines.o \
+               mines-icon.o misc.o printing.o ps.o random.o tree234.o \
+               version.o emccpre.js emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)mines.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" drawing.o emcc.o malloc.o midend.o mines.o mines-icon.o misc.o printing.o ps.o random.o tree234.o version.o
+
+$(OUTPREFIX)net.js: drawing.o dsf.o findloop.o emcc.o malloc.o midend.o \
+               misc.o net.o net-icon.o printing.o ps.o random.o tree234.o \
+               version.o emccpre.js emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)net.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" drawing.o dsf.o findloop.o emcc.o malloc.o midend.o misc.o net.o net-icon.o printing.o ps.o random.o tree234.o version.o
+
+$(OUTPREFIX)netslide.js: drawing.o emcc.o malloc.o midend.o misc.o \
+               netslide.o netslide-icon.o printing.o ps.o random.o \
+               tree234.o version.o emccpre.js emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)netslide.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" drawing.o emcc.o malloc.o midend.o misc.o netslide.o netslide-icon.o printing.o ps.o random.o tree234.o version.o
+
+$(OUTPREFIX)nullgame.js: drawing.o emcc.o malloc.o midend.o misc.o no-icon.o \
+               nullgame.o printing.o ps.o random.o version.o emccpre.js \
+               emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)nullgame.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" drawing.o emcc.o malloc.o midend.o misc.o no-icon.o nullgame.o printing.o ps.o random.o version.o
+
+$(OUTPREFIX)palisade.js: divvy.o drawing.o dsf.o emcc.o malloc.o midend.o \
+               misc.o palisade.o palisade-icon.o printing.o ps.o random.o \
+               version.o emccpre.js emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)palisade.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" divvy.o drawing.o dsf.o emcc.o malloc.o midend.o misc.o palisade.o palisade-icon.o printing.o ps.o random.o version.o
+
+$(OUTPREFIX)pattern.js: drawing.o emcc.o malloc.o midend.o misc.o pattern.o \
+               pattern-icon.o printing.o ps.o random.o version.o emccpre.js \
+               emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)pattern.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" drawing.o emcc.o malloc.o midend.o misc.o pattern.o pattern-icon.o printing.o ps.o random.o version.o
+
+$(OUTPREFIX)pearl.js: drawing.o dsf.o grid.o emcc.o loopgen.o malloc.o \
+               midend.o misc.o pearl.o pearl-icon.o penrose.o printing.o \
+               ps.o random.o tdq.o tree234.o version.o emccpre.js \
+               emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)pearl.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" drawing.o dsf.o grid.o emcc.o loopgen.o malloc.o midend.o misc.o pearl.o pearl-icon.o penrose.o printing.o ps.o random.o tdq.o tree234.o version.o
+
+$(OUTPREFIX)pegs.js: drawing.o emcc.o malloc.o midend.o misc.o pegs.o \
+               pegs-icon.o printing.o ps.o random.o tree234.o version.o \
+               emccpre.js emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)pegs.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" drawing.o emcc.o malloc.o midend.o misc.o pegs.o pegs-icon.o printing.o ps.o random.o tree234.o version.o
+
+$(OUTPREFIX)range.js: drawing.o dsf.o emcc.o malloc.o midend.o misc.o \
+               printing.o ps.o random.o range.o range-icon.o version.o \
+               emccpre.js emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)range.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" drawing.o dsf.o emcc.o malloc.o midend.o misc.o printing.o ps.o random.o range.o range-icon.o version.o
+
+$(OUTPREFIX)rect.js: drawing.o emcc.o malloc.o midend.o misc.o printing.o \
+               ps.o random.o rect.o rect-icon.o version.o emccpre.js \
+               emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)rect.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" drawing.o emcc.o malloc.o midend.o misc.o printing.o ps.o random.o rect.o rect-icon.o version.o
+
+$(OUTPREFIX)samegame.js: drawing.o emcc.o malloc.o midend.o misc.o \
+               printing.o ps.o random.o samegame.o samegame-icon.o \
+               version.o emccpre.js emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)samegame.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" drawing.o emcc.o malloc.o midend.o misc.o printing.o ps.o random.o samegame.o samegame-icon.o version.o
+
+$(OUTPREFIX)signpost.js: drawing.o dsf.o emcc.o malloc.o midend.o misc.o \
+               printing.o ps.o random.o signpost.o signpost-icon.o \
+               version.o emccpre.js emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)signpost.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" drawing.o dsf.o emcc.o malloc.o midend.o misc.o printing.o ps.o random.o signpost.o signpost-icon.o version.o
+
+$(OUTPREFIX)singles.js: drawing.o dsf.o emcc.o latin.o malloc.o maxflow.o \
+               midend.o misc.o printing.o ps.o random.o singles.o \
+               singles-icon.o tree234.o version.o emccpre.js emcclib.js \
+               emccx.json
+       $(EMCC) -o $(OUTPREFIX)singles.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" drawing.o dsf.o emcc.o latin.o malloc.o maxflow.o midend.o misc.o printing.o ps.o random.o singles.o singles-icon.o tree234.o version.o
+
+$(OUTPREFIX)sixteen.js: drawing.o emcc.o malloc.o midend.o misc.o printing.o \
+               ps.o random.o sixteen.o sixteen-icon.o version.o emccpre.js \
+               emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)sixteen.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" drawing.o emcc.o malloc.o midend.o misc.o printing.o ps.o random.o sixteen.o sixteen-icon.o version.o
+
+$(OUTPREFIX)slant.js: drawing.o dsf.o findloop.o emcc.o malloc.o midend.o \
+               misc.o printing.o ps.o random.o slant.o slant-icon.o \
+               version.o emccpre.js emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)slant.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" drawing.o dsf.o findloop.o emcc.o malloc.o midend.o misc.o printing.o ps.o random.o slant.o slant-icon.o version.o
+
+$(OUTPREFIX)solo.js: divvy.o drawing.o dsf.o emcc.o malloc.o midend.o misc.o \
+               printing.o ps.o random.o solo.o solo-icon.o version.o \
+               emccpre.js emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)solo.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" divvy.o drawing.o dsf.o emcc.o malloc.o midend.o misc.o printing.o ps.o random.o solo.o solo-icon.o version.o
+
+$(OUTPREFIX)tents.js: drawing.o dsf.o emcc.o malloc.o maxflow.o midend.o \
+               misc.o printing.o ps.o random.o tents.o tents-icon.o \
+               version.o emccpre.js emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)tents.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" drawing.o dsf.o emcc.o malloc.o maxflow.o midend.o misc.o printing.o ps.o random.o tents.o tents-icon.o version.o
+
+$(OUTPREFIX)towers.js: drawing.o emcc.o latin.o malloc.o maxflow.o midend.o \
+               misc.o printing.o ps.o random.o towers.o towers-icon.o \
+               tree234.o version.o emccpre.js emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)towers.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" drawing.o emcc.o latin.o malloc.o maxflow.o midend.o misc.o printing.o ps.o random.o towers.o towers-icon.o tree234.o version.o
+
+$(OUTPREFIX)tracks.js: drawing.o dsf.o findloop.o emcc.o malloc.o midend.o \
+               misc.o printing.o ps.o random.o tracks.o tracks-icon.o \
+               version.o emccpre.js emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)tracks.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" drawing.o dsf.o findloop.o emcc.o malloc.o midend.o misc.o printing.o ps.o random.o tracks.o tracks-icon.o version.o
+
+$(OUTPREFIX)twiddle.js: drawing.o emcc.o malloc.o midend.o misc.o printing.o \
+               ps.o random.o twiddle.o twiddle-icon.o version.o emccpre.js \
+               emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)twiddle.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" drawing.o emcc.o malloc.o midend.o misc.o printing.o ps.o random.o twiddle.o twiddle-icon.o version.o
+
+$(OUTPREFIX)undead.js: drawing.o emcc.o malloc.o midend.o misc.o printing.o \
+               ps.o random.o undead.o undead-icon.o version.o emccpre.js \
+               emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)undead.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" drawing.o emcc.o malloc.o midend.o misc.o printing.o ps.o random.o undead.o undead-icon.o version.o
+
+$(OUTPREFIX)unequal.js: drawing.o emcc.o latin.o malloc.o maxflow.o midend.o \
+               misc.o printing.o ps.o random.o tree234.o unequal.o \
+               unequal-icon.o version.o emccpre.js emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)unequal.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" drawing.o emcc.o latin.o malloc.o maxflow.o midend.o misc.o printing.o ps.o random.o tree234.o unequal.o unequal-icon.o version.o
+
+$(OUTPREFIX)unruly.js: drawing.o emcc.o malloc.o midend.o misc.o printing.o \
+               ps.o random.o unruly.o unruly-icon.o version.o emccpre.js \
+               emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)unruly.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" drawing.o emcc.o malloc.o midend.o misc.o printing.o ps.o random.o unruly.o unruly-icon.o version.o
+
+$(OUTPREFIX)untangle.js: drawing.o emcc.o malloc.o midend.o misc.o \
+               printing.o ps.o random.o tree234.o untangle.o \
+               untangle-icon.o version.o emccpre.js emcclib.js emccx.json
+       $(EMCC) -o $(OUTPREFIX)untangle.js -O2 -s ASM_JS=1 --pre-js emccpre.js --js-library emcclib.js -s EXPORTED_FUNCTIONS="`sed 's://.*::' emccx.json | tr -d ' \n'`" drawing.o emcc.o malloc.o midend.o misc.o printing.o ps.o random.o tree234.o untangle.o untangle-icon.o version.o
+
+blackbox.o: ./blackbox.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+blackbox-icon.o: icons/blackbox-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+blackbo3.o: ./blackbox.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+bridges.o: ./bridges.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+bridges-icon.o: icons/bridges-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+bridges3.o: ./bridges.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+combi.o: ./combi.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+cube.o: ./cube.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+cube-icon.o: icons/cube-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+cube3.o: ./cube.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+divvy.o: ./divvy.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+dominosa.o: ./dominosa.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+dominosa-icon.o: icons/dominosa-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+dominos3.o: ./dominosa.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+drawing.o: ./drawing.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+dsf.o: ./dsf.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+fifteen.o: ./fifteen.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+fifteen-icon.o: icons/fifteen-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+fifteen5.o: ./fifteen.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+fifteen2.o: ./fifteen.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+filling.o: ./filling.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+filling-icon.o: icons/filling-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+filling5.o: ./filling.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+filling2.o: ./filling.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+findloop.o: ./findloop.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+flip.o: ./flip.c ./puzzles.h ./tree234.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+flip-icon.o: icons/flip-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+flip3.o: ./flip.c ./puzzles.h ./tree234.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+flood.o: ./flood.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+flood-icon.o: icons/flood-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+flood3.o: ./flood.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+galaxies.o: ./galaxies.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+galaxies-icon.o: icons/galaxies-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+galaxie7.o: ./galaxies.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+galaxie4.o: ./galaxies.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DSTANDALONE_PICTURE_GENERATOR -c $< -o $@
+galaxie2.o: ./galaxies.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+grid.o: ./grid.c ./puzzles.h ./tree234.h ./grid.h ./penrose.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+emcc.o: ./emcc.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+guess.o: ./guess.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+guess-icon.o: icons/guess-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+guess3.o: ./guess.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+inertia.o: ./inertia.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+inertia-icon.o: icons/inertia-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+inertia3.o: ./inertia.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+keen.o: ./keen.c ./puzzles.h ./latin.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+keen-icon.o: icons/keen-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+keen5.o: ./keen.c ./puzzles.h ./latin.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+keen2.o: ./keen.c ./puzzles.h ./latin.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+latin.o: ./latin.c ./puzzles.h ./tree234.h ./maxflow.h ./latin.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+latin8.o: ./latin.c ./puzzles.h ./tree234.h ./maxflow.h ./latin.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DSTANDALONE_LATIN_TEST -c $< -o $@
+latin6.o: ./latin.c ./puzzles.h ./tree234.h ./maxflow.h ./latin.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+laydomino.o: ./laydomino.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+lightup.o: ./lightup.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+lightup-icon.o: icons/lightup-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+lightup5.o: ./lightup.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+lightup2.o: ./lightup.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+list.o: ./list.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+loopgen.o: ./loopgen.c ./puzzles.h ./tree234.h ./grid.h ./loopgen.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+loopy.o: ./loopy.c ./puzzles.h ./tree234.h ./grid.h ./loopgen.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+loopy-icon.o: icons/loopy-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+loopy5.o: ./loopy.c ./puzzles.h ./tree234.h ./grid.h ./loopgen.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+loopy2.o: ./loopy.c ./puzzles.h ./tree234.h ./grid.h ./loopgen.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+magnets.o: ./magnets.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+magnets-icon.o: icons/magnets-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+magnets5.o: ./magnets.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+magnets2.o: ./magnets.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+malloc.o: ./malloc.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+map.o: ./map.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+map-icon.o: icons/map-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+map5.o: ./map.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+map2.o: ./map.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+maxflow.o: ./maxflow.c ./maxflow.h ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+midend.o: ./midend.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+mines.o: ./mines.c ./tree234.h ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+mines-icon.o: icons/mines-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+mines5.o: ./mines.c ./tree234.h ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+mines2.o: ./mines.c ./tree234.h ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DSTANDALONE_OBFUSCATOR -c $< -o $@
+misc.o: ./misc.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+net.o: ./net.c ./puzzles.h ./tree234.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+net-icon.o: icons/net-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+net3.o: ./net.c ./puzzles.h ./tree234.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+netslide.o: ./netslide.c ./puzzles.h ./tree234.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+netslide-icon.o: icons/netslide-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+netslid3.o: ./netslide.c ./puzzles.h ./tree234.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+no-icon.o: ./no-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+nullfe.o: ./nullfe.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+nullgame.o: ./nullgame.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+obfusc.o: ./obfusc.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+osx.o: ./osx.m ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+palisade.o: ./palisade.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+palisade-icon.o: icons/palisade-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+palisad3.o: ./palisade.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+pattern.o: ./pattern.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pattern-icon.o: icons/pattern-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pattern7.o: ./pattern.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+pattern4.o: ./pattern.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DSTANDALONE_PICTURE_GENERATOR -c $< -o $@
+pattern2.o: ./pattern.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+pearl.o: ./pearl.c ./puzzles.h ./grid.h ./loopgen.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pearl-icon.o: icons/pearl-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pearl5.o: ./pearl.c ./puzzles.h ./grid.h ./loopgen.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+pearl2.o: ./pearl.c ./puzzles.h ./grid.h ./loopgen.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+pegs.o: ./pegs.c ./puzzles.h ./tree234.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pegs-icon.o: icons/pegs-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pegs3.o: ./pegs.c ./puzzles.h ./tree234.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+penrose.o: ./penrose.c ./puzzles.h ./penrose.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+printing.o: ./printing.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+ps.o: ./ps.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+random.o: ./random.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+range.o: ./range.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+range-icon.o: icons/range-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+range3.o: ./range.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+rect.o: ./rect.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+rect-icon.o: icons/rect-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+rect3.o: ./rect.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+samegame.o: ./samegame.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+samegame-icon.o: icons/samegame-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+samegam3.o: ./samegame.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+signpost.o: ./signpost.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+signpost-icon.o: icons/signpost-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+signpos5.o: ./signpost.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+signpos2.o: ./signpost.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+singles.o: ./singles.c ./puzzles.h ./latin.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+singles-icon.o: icons/singles-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+singles5.o: ./singles.c ./puzzles.h ./latin.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+singles3.o: ./singles.c ./puzzles.h ./latin.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+sixteen.o: ./sixteen.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+sixteen-icon.o: icons/sixteen-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+sixteen3.o: ./sixteen.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+slant.o: ./slant.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+slant-icon.o: icons/slant-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+slant5.o: ./slant.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+slant2.o: ./slant.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+solo.o: ./solo.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+solo-icon.o: icons/solo-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+solo5.o: ./solo.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+solo2.o: ./solo.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+tdq.o: ./tdq.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+tents.o: ./tents.c ./puzzles.h ./maxflow.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+tents-icon.o: icons/tents-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+tents5.o: ./tents.c ./puzzles.h ./maxflow.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+tents3.o: ./tents.c ./puzzles.h ./maxflow.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+towers.o: ./towers.c ./puzzles.h ./latin.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+towers-icon.o: icons/towers-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+towers5.o: ./towers.c ./puzzles.h ./latin.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+towers2.o: ./towers.c ./puzzles.h ./latin.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+tracks.o: ./tracks.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+tracks-icon.o: icons/tracks-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+tracks3.o: ./tracks.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+tree234.o: ./tree234.c ./tree234.h ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+twiddle.o: ./twiddle.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+twiddle-icon.o: icons/twiddle-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+twiddle3.o: ./twiddle.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+undead.o: ./undead.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+undead-icon.o: icons/undead-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+undead3.o: ./undead.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+unequal.o: ./unequal.c ./puzzles.h ./latin.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+unequal-icon.o: icons/unequal-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+unequal5.o: ./unequal.c ./puzzles.h ./latin.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+unequal2.o: ./unequal.c ./puzzles.h ./latin.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+unruly.o: ./unruly.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+unruly-icon.o: icons/unruly-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+unruly5.o: ./unruly.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+unruly2.o: ./unruly.c ./puzzles.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+untangle.o: ./untangle.c ./puzzles.h ./tree234.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+untangle-icon.o: icons/untangle-icon.c
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+untangl3.o: ./untangle.c ./puzzles.h ./tree234.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+version.o: ./version.c ./version.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+windows.o: ./windows.c ./puzzles.h ./resource.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -c $< -o $@
+windows1.o: ./windows.c ./puzzles.h ./resource.h
+       $(EMCC) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+
+
+clean:
+       rm -rf *.o  $(OUTPREFIX)blackbox.js $(OUTPREFIX)bridges.js $(OUTPREFIX)cube.js $(OUTPREFIX)dominosa.js $(OUTPREFIX)fifteen.js $(OUTPREFIX)filling.js $(OUTPREFIX)flip.js $(OUTPREFIX)flood.js $(OUTPREFIX)galaxies.js $(OUTPREFIX)guess.js $(OUTPREFIX)inertia.js $(OUTPREFIX)keen.js $(OUTPREFIX)lightup.js $(OUTPREFIX)loopy.js $(OUTPREFIX)magnets.js $(OUTPREFIX)map.js $(OUTPREFIX)mines.js $(OUTPREFIX)net.js $(OUTPREFIX)netslide.js $(OUTPREFIX)nullgame.js $(OUTPREFIX)palisade.js $(OUTPREFIX)pattern.js $(OUTPREFIX)pearl.js $(OUTPREFIX)pegs.js $(OUTPREFIX)range.js $(OUTPREFIX)rect.js $(OUTPREFIX)samegame.js $(OUTPREFIX)signpost.js $(OUTPREFIX)singles.js $(OUTPREFIX)sixteen.js $(OUTPREFIX)slant.js $(OUTPREFIX)solo.js $(OUTPREFIX)tents.js $(OUTPREFIX)towers.js $(OUTPREFIX)tracks.js $(OUTPREFIX)twiddle.js $(OUTPREFIX)undead.js $(OUTPREFIX)unequal.js $(OUTPREFIX)unruly.js $(OUTPREFIX)untangle.js
diff --git a/Makefile.gnustep b/Makefile.gnustep
new file mode 100644 (file)
index 0000000..0ab2548
--- /dev/null
@@ -0,0 +1,490 @@
+# Makefile for puzzles under GNUstep.
+#
+# This file was created by `mkfiles.pl' from the `Recipe' file.
+# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.
+NEEDS_GUI=yes
+include $(GNUSTEP_MAKEFILES)/common.make
+include $(GNUSTEP_MAKEFILES)/rules.make
+include $(GNUSTEP_MAKEFILES)/Instance/rules.make
+
+all:: Puzzles fifteensolver fillingsolver galaxiespicture galaxiessolver \
+               keensolver latincheck lightupsolver loopysolver \
+               magnetssolver mapsolver mineobfusc obfusc patternpicture \
+               patternsolver pearlbench signpostsolver singlessolver \
+               slantsolver solosolver tentssolver towerssolver \
+               unequalsolver unrulysolver
+
+.SUFFIXES: .o .c .m
+
+
+
+Puzzles.app:
+       mkdir -p $@
+Puzzles.app/Resources: Puzzles.app
+       mkdir -p $@
+Puzzles.app/Resources/Puzzles.icns: Puzzles.app/Resources osx.icns
+       cp osx.icns $@
+Puzzles.app/Info.plist: Puzzles.app osx-info.plist
+       cp osx-info.plist $@
+Puzzles: Puzzles.app Puzzles.app/Puzzles \
+               Puzzles.app/Resources/Puzzles.icns Puzzles.app/Info.plist \
+               $(Puzzles_extra)
+
+Puzzles.app/Puzzles: blackbo3.o bridges3.o combi.o cube3.o divvy.o \
+               dominos3.o drawing.o dsf.o fifteen5.o filling5.o findloop.o \
+               flip3.o flood3.o galaxie7.o grid.o guess3.o inertia3.o \
+               keen5.o latin.o laydomino.o lightup5.o list.o loopgen.o \
+               loopy5.o magnets5.o malloc.o map5.o maxflow.o midend.o \
+               mines5.o misc.o net3.o netslid3.o osx.o palisad3.o \
+               pattern7.o pearl5.o pegs3.o penrose.o random.o range3.o \
+               rect3.o samegam3.o signpos5.o singles5.o sixteen3.o slant5.o \
+               solo5.o tdq.o tents5.o towers5.o tracks3.o tree234.o \
+               twiddle3.o undead3.o unequal5.o unruly5.o untangl3.o \
+               version.o
+       $(CC) $(ALL_LDFLAGS) -o $@ blackbo3.o bridges3.o combi.o cube3.o \
+               divvy.o dominos3.o drawing.o dsf.o fifteen5.o filling5.o \
+               findloop.o flip3.o flood3.o galaxie7.o grid.o guess3.o \
+               inertia3.o keen5.o latin.o laydomino.o lightup5.o list.o \
+               loopgen.o loopy5.o magnets5.o malloc.o map5.o maxflow.o \
+               midend.o mines5.o misc.o net3.o netslid3.o osx.o palisad3.o \
+               pattern7.o pearl5.o pegs3.o penrose.o random.o range3.o \
+               rect3.o samegam3.o signpos5.o singles5.o sixteen3.o slant5.o \
+               solo5.o tdq.o tents5.o towers5.o tracks3.o tree234.o \
+               twiddle3.o undead3.o unequal5.o unruly5.o untangl3.o \
+               version.o $(ALL_LIB_DIRS)  $(ALL_LIBS)
+
+fifteensolver: fifteen2.o malloc.o misc.o nullfe.o random.o
+       $(CC) $(ULDFLAGS) -o $@ fifteen2.o malloc.o misc.o nullfe.o random.o
+
+fillingsolver: dsf.o filling2.o malloc.o misc.o nullfe.o random.o
+       $(CC) $(ULDFLAGS) -o $@ dsf.o filling2.o malloc.o misc.o nullfe.o \
+               random.o 
+
+galaxiespicture: dsf.o galaxie4.o malloc.o misc.o nullfe.o random.o
+       $(CC) $(ULDFLAGS) -o $@ dsf.o galaxie4.o malloc.o misc.o nullfe.o \
+               random.o -lm
+
+galaxiessolver: dsf.o galaxie2.o malloc.o misc.o nullfe.o random.o
+       $(CC) $(ULDFLAGS) -o $@ dsf.o galaxie2.o malloc.o misc.o nullfe.o \
+               random.o -lm
+
+keensolver: dsf.o keen2.o latin6.o malloc.o maxflow.o misc.o nullfe.o \
+               random.o tree234.o
+       $(CC) $(ULDFLAGS) -o $@ dsf.o keen2.o latin6.o malloc.o maxflow.o \
+               misc.o nullfe.o random.o tree234.o 
+
+latincheck: latin8.o malloc.o maxflow.o misc.o nullfe.o random.o tree234.o
+       $(CC) $(ULDFLAGS) -o $@ latin8.o malloc.o maxflow.o misc.o nullfe.o \
+               random.o tree234.o 
+
+lightupsolver: combi.o lightup2.o malloc.o misc.o nullfe.o random.o
+       $(CC) $(ULDFLAGS) -o $@ combi.o lightup2.o malloc.o misc.o nullfe.o \
+               random.o 
+
+loopysolver: dsf.o grid.o loopgen.o loopy2.o malloc.o misc.o nullfe.o \
+               penrose.o random.o tree234.o
+       $(CC) $(ULDFLAGS) -o $@ dsf.o grid.o loopgen.o loopy2.o malloc.o \
+               misc.o nullfe.o penrose.o random.o tree234.o -lm
+
+magnetssolver: laydomino.o magnets2.o malloc.o misc.o nullfe.o random.o
+       $(CC) $(ULDFLAGS) -o $@ laydomino.o magnets2.o malloc.o misc.o \
+               nullfe.o random.o -lm
+
+mapsolver: dsf.o malloc.o map2.o misc.o nullfe.o random.o
+       $(CC) $(ULDFLAGS) -o $@ dsf.o malloc.o map2.o misc.o nullfe.o \
+               random.o -lm
+
+mineobfusc: malloc.o mines2.o misc.o nullfe.o random.o tree234.o
+       $(CC) $(ULDFLAGS) -o $@ malloc.o mines2.o misc.o nullfe.o random.o \
+               tree234.o 
+
+obfusc: malloc.o misc.o nullfe.o obfusc.o random.o
+       $(CC) $(ULDFLAGS) -o $@ malloc.o misc.o nullfe.o obfusc.o random.o 
+
+patternpicture: malloc.o misc.o nullfe.o pattern4.o random.o
+       $(CC) $(ULDFLAGS) -o $@ malloc.o misc.o nullfe.o pattern4.o random.o
+
+patternsolver: malloc.o misc.o nullfe.o pattern2.o random.o
+       $(CC) $(ULDFLAGS) -o $@ malloc.o misc.o nullfe.o pattern2.o random.o
+
+pearlbench: dsf.o grid.o loopgen.o malloc.o misc.o nullfe.o pearl2.o \
+               penrose.o random.o tdq.o tree234.o
+       $(CC) $(ULDFLAGS) -o $@ dsf.o grid.o loopgen.o malloc.o misc.o \
+               nullfe.o pearl2.o penrose.o random.o tdq.o tree234.o -lm
+
+signpostsolver: dsf.o malloc.o misc.o nullfe.o random.o signpos2.o
+       $(CC) $(ULDFLAGS) -o $@ dsf.o malloc.o misc.o nullfe.o random.o \
+               signpos2.o -lm
+
+singlessolver: dsf.o latin.o malloc.o maxflow.o misc.o nullfe.o random.o \
+               singles3.o tree234.o
+       $(CC) $(ULDFLAGS) -o $@ dsf.o latin.o malloc.o maxflow.o misc.o \
+               nullfe.o random.o singles3.o tree234.o 
+
+slantsolver: dsf.o findloop.o malloc.o misc.o nullfe.o random.o slant2.o
+       $(CC) $(ULDFLAGS) -o $@ dsf.o findloop.o malloc.o misc.o nullfe.o \
+               random.o slant2.o 
+
+solosolver: divvy.o dsf.o malloc.o misc.o nullfe.o random.o solo2.o
+       $(CC) $(ULDFLAGS) -o $@ divvy.o dsf.o malloc.o misc.o nullfe.o \
+               random.o solo2.o 
+
+tentssolver: dsf.o malloc.o maxflow.o misc.o nullfe.o random.o tents3.o
+       $(CC) $(ULDFLAGS) -o $@ dsf.o malloc.o maxflow.o misc.o nullfe.o \
+               random.o tents3.o 
+
+towerssolver: latin6.o malloc.o maxflow.o misc.o nullfe.o random.o towers2.o \
+               tree234.o
+       $(CC) $(ULDFLAGS) -o $@ latin6.o malloc.o maxflow.o misc.o nullfe.o \
+               random.o towers2.o tree234.o 
+
+unequalsolver: latin6.o malloc.o maxflow.o misc.o nullfe.o random.o \
+               tree234.o unequal2.o
+       $(CC) $(ULDFLAGS) -o $@ latin6.o malloc.o maxflow.o misc.o nullfe.o \
+               random.o tree234.o unequal2.o 
+
+unrulysolver: malloc.o misc.o nullfe.o random.o unruly2.o
+       $(CC) $(ULDFLAGS) -o $@ malloc.o misc.o nullfe.o random.o unruly2.o 
+
+blackbox.o: ./blackbox.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+blackbox-icon.o: icons/blackbox-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+blackbo3.o: ./blackbox.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+bridges.o: ./bridges.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+bridges-icon.o: icons/bridges-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+bridges3.o: ./bridges.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+combi.o: ./combi.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+cube.o: ./cube.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+cube-icon.o: icons/cube-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+cube3.o: ./cube.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+divvy.o: ./divvy.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+dominosa.o: ./dominosa.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+dominosa-icon.o: icons/dominosa-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+dominos3.o: ./dominosa.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+drawing.o: ./drawing.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+dsf.o: ./dsf.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+fifteen.o: ./fifteen.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+fifteen-icon.o: icons/fifteen-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+fifteen5.o: ./fifteen.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+fifteen2.o: ./fifteen.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+filling.o: ./filling.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+filling-icon.o: icons/filling-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+filling5.o: ./filling.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+filling2.o: ./filling.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+findloop.o: ./findloop.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+flip.o: ./flip.c ./puzzles.h ./tree234.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+flip-icon.o: icons/flip-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+flip3.o: ./flip.c ./puzzles.h ./tree234.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+flood.o: ./flood.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+flood-icon.o: icons/flood-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+flood3.o: ./flood.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+galaxies.o: ./galaxies.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+galaxies-icon.o: icons/galaxies-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+galaxie7.o: ./galaxies.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+galaxie4.o: ./galaxies.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_PICTURE_GENERATOR -c $< -o $@
+galaxie2.o: ./galaxies.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+grid.o: ./grid.c ./puzzles.h ./tree234.h ./grid.h ./penrose.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+gtk.o: ./gtk.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+guess.o: ./guess.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+guess-icon.o: icons/guess-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+guess3.o: ./guess.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+inertia.o: ./inertia.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+inertia-icon.o: icons/inertia-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+inertia3.o: ./inertia.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+keen.o: ./keen.c ./puzzles.h ./latin.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+keen-icon.o: icons/keen-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+keen5.o: ./keen.c ./puzzles.h ./latin.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+keen2.o: ./keen.c ./puzzles.h ./latin.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+latin.o: ./latin.c ./puzzles.h ./tree234.h ./maxflow.h ./latin.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+latin8.o: ./latin.c ./puzzles.h ./tree234.h ./maxflow.h ./latin.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_LATIN_TEST -c $< -o $@
+latin6.o: ./latin.c ./puzzles.h ./tree234.h ./maxflow.h ./latin.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+laydomino.o: ./laydomino.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+lightup.o: ./lightup.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+lightup-icon.o: icons/lightup-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+lightup5.o: ./lightup.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+lightup2.o: ./lightup.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+list.o: ./list.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+loopgen.o: ./loopgen.c ./puzzles.h ./tree234.h ./grid.h ./loopgen.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+loopy.o: ./loopy.c ./puzzles.h ./tree234.h ./grid.h ./loopgen.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+loopy-icon.o: icons/loopy-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+loopy5.o: ./loopy.c ./puzzles.h ./tree234.h ./grid.h ./loopgen.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+loopy2.o: ./loopy.c ./puzzles.h ./tree234.h ./grid.h ./loopgen.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+magnets.o: ./magnets.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+magnets-icon.o: icons/magnets-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+magnets5.o: ./magnets.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+magnets2.o: ./magnets.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+malloc.o: ./malloc.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+map.o: ./map.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+map-icon.o: icons/map-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+map5.o: ./map.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+map2.o: ./map.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+maxflow.o: ./maxflow.c ./maxflow.h ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+midend.o: ./midend.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+mines.o: ./mines.c ./tree234.h ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+mines-icon.o: icons/mines-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+mines5.o: ./mines.c ./tree234.h ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+mines2.o: ./mines.c ./tree234.h ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_OBFUSCATOR -c $< -o $@
+misc.o: ./misc.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+net.o: ./net.c ./puzzles.h ./tree234.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+net-icon.o: icons/net-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+net3.o: ./net.c ./puzzles.h ./tree234.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+netslide.o: ./netslide.c ./puzzles.h ./tree234.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+netslide-icon.o: icons/netslide-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+netslid3.o: ./netslide.c ./puzzles.h ./tree234.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+no-icon.o: ./no-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+nullfe.o: ./nullfe.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+nullgame.o: ./nullgame.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+obfusc.o: ./obfusc.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+osx.o: ./osx.m ./puzzles.h
+       $(CC) -DGNUSTEP $(ALL_OBJCFLAGS) $(COMPAT) $(FWHACK) $(OBJCFLAGS) $(XFLAGS) -c $< -o $@
+palisade.o: ./palisade.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+palisade-icon.o: icons/palisade-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+palisad3.o: ./palisade.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+pattern.o: ./pattern.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pattern-icon.o: icons/pattern-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pattern7.o: ./pattern.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+pattern4.o: ./pattern.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_PICTURE_GENERATOR -c $< -o $@
+pattern2.o: ./pattern.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+pearl.o: ./pearl.c ./puzzles.h ./grid.h ./loopgen.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pearl-icon.o: icons/pearl-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pearl5.o: ./pearl.c ./puzzles.h ./grid.h ./loopgen.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+pearl2.o: ./pearl.c ./puzzles.h ./grid.h ./loopgen.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+pegs.o: ./pegs.c ./puzzles.h ./tree234.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pegs-icon.o: icons/pegs-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pegs3.o: ./pegs.c ./puzzles.h ./tree234.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+penrose.o: ./penrose.c ./puzzles.h ./penrose.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+printing.o: ./printing.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+ps.o: ./ps.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+random.o: ./random.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+range.o: ./range.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+range-icon.o: icons/range-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+range3.o: ./range.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+rect.o: ./rect.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+rect-icon.o: icons/rect-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+rect3.o: ./rect.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+samegame.o: ./samegame.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+samegame-icon.o: icons/samegame-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+samegam3.o: ./samegame.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+signpost.o: ./signpost.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+signpost-icon.o: icons/signpost-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+signpos5.o: ./signpost.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+signpos2.o: ./signpost.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+singles.o: ./singles.c ./puzzles.h ./latin.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+singles-icon.o: icons/singles-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+singles5.o: ./singles.c ./puzzles.h ./latin.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+singles3.o: ./singles.c ./puzzles.h ./latin.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+sixteen.o: ./sixteen.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+sixteen-icon.o: icons/sixteen-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+sixteen3.o: ./sixteen.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+slant.o: ./slant.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+slant-icon.o: icons/slant-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+slant5.o: ./slant.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+slant2.o: ./slant.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+solo.o: ./solo.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+solo-icon.o: icons/solo-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+solo5.o: ./solo.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+solo2.o: ./solo.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+tdq.o: ./tdq.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+tents.o: ./tents.c ./puzzles.h ./maxflow.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+tents-icon.o: icons/tents-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+tents5.o: ./tents.c ./puzzles.h ./maxflow.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+tents3.o: ./tents.c ./puzzles.h ./maxflow.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+towers.o: ./towers.c ./puzzles.h ./latin.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+towers-icon.o: icons/towers-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+towers5.o: ./towers.c ./puzzles.h ./latin.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+towers2.o: ./towers.c ./puzzles.h ./latin.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+tracks.o: ./tracks.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+tracks-icon.o: icons/tracks-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+tracks3.o: ./tracks.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+tree234.o: ./tree234.c ./tree234.h ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+twiddle.o: ./twiddle.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+twiddle-icon.o: icons/twiddle-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+twiddle3.o: ./twiddle.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+undead.o: ./undead.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+undead-icon.o: icons/undead-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+undead3.o: ./undead.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+unequal.o: ./unequal.c ./puzzles.h ./latin.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+unequal-icon.o: icons/unequal-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+unequal5.o: ./unequal.c ./puzzles.h ./latin.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+unequal2.o: ./unequal.c ./puzzles.h ./latin.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+unruly.o: ./unruly.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+unruly-icon.o: icons/unruly-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+unruly5.o: ./unruly.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+unruly2.o: ./unruly.c ./puzzles.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+untangle.o: ./untangle.c ./puzzles.h ./tree234.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+untangle-icon.o: icons/untangle-icon.c
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+untangl3.o: ./untangle.c ./puzzles.h ./tree234.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+version.o: ./version.c ./version.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+windows.o: ./windows.c ./puzzles.h ./resource.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+windows1.o: ./windows.c ./puzzles.h ./resource.h
+       $(CC) $(ALL_CFLAGS) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+
+clean::
+       rm -f *.o fifteensolver fillingsolver galaxiespicture galaxiessolver keensolver latincheck lightupsolver loopysolver magnetssolver mapsolver mineobfusc obfusc patternpicture patternsolver pearlbench signpostsolver singlessolver slantsolver solosolver tentssolver towerssolver unequalsolver unrulysolver
+       rm -rf *.app
diff --git a/Makefile.gtk b/Makefile.gtk
new file mode 100644 (file)
index 0000000..e6a76d5
--- /dev/null
@@ -0,0 +1,809 @@
+# Makefile for puzzles under X/GTK and Unix.
+#
+# This file was created by `mkfiles.pl' from the `Recipe' file.
+# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.
+
+# You can define this path to point at your tools if you need to
+# TOOLPATH = /opt/gcc/bin
+CC := $(TOOLPATH)$(CC)
+# You can manually set this to `gtk-config' or `pkg-config gtk+-1.2'
+# (depending on what works on your system) if you want to enforce
+# building with GTK 1.2, or you can set it to `pkg-config gtk+-2.0'
+# if you want to enforce 2.0. The default is to try 2.0 and fall back
+# to 1.2 if it isn't found.
+GTK_CONFIG = sh -c 'pkg-config gtk+-2.0 $$0 2>/dev/null || gtk-config $$0'
+
+CFLAGS := -O2 -Wall -Werror -ansi -pedantic -g -I./ -Iicons/ `$(GTK_CONFIG) \
+               --cflags` $(CFLAGS)
+XLIBS = `$(GTK_CONFIG) --libs` -lm
+ULIBS = -lm#
+INSTALL=install
+INSTALL_PROGRAM=$(INSTALL)
+INSTALL_DATA=$(INSTALL)
+prefix=/usr/local
+exec_prefix=$(prefix)
+bindir=$(exec_prefix)/bin
+gamesdir=$(exec_prefix)/games
+mandir=$(prefix)/man
+man1dir=$(mandir)/man1
+
+all: $(BINPREFIX)blackbox $(BINPREFIX)bridges $(BINPREFIX)cube \
+               $(BINPREFIX)dominosa $(BINPREFIX)fifteen \
+               $(BINPREFIX)fifteensolver $(BINPREFIX)filling \
+               $(BINPREFIX)fillingsolver $(BINPREFIX)flip $(BINPREFIX)flood \
+               $(BINPREFIX)galaxies $(BINPREFIX)galaxiespicture \
+               $(BINPREFIX)galaxiessolver $(BINPREFIX)guess \
+               $(BINPREFIX)inertia $(BINPREFIX)keen $(BINPREFIX)keensolver \
+               $(BINPREFIX)latincheck $(BINPREFIX)lightup \
+               $(BINPREFIX)lightupsolver $(BINPREFIX)loopy \
+               $(BINPREFIX)loopysolver $(BINPREFIX)magnets \
+               $(BINPREFIX)magnetssolver $(BINPREFIX)map \
+               $(BINPREFIX)mapsolver $(BINPREFIX)mineobfusc \
+               $(BINPREFIX)mines $(BINPREFIX)net $(BINPREFIX)netslide \
+               $(BINPREFIX)nullgame $(BINPREFIX)obfusc $(BINPREFIX)palisade \
+               $(BINPREFIX)pattern $(BINPREFIX)patternpicture \
+               $(BINPREFIX)patternsolver $(BINPREFIX)pearl \
+               $(BINPREFIX)pearlbench $(BINPREFIX)pegs $(BINPREFIX)range \
+               $(BINPREFIX)rect $(BINPREFIX)samegame $(BINPREFIX)signpost \
+               $(BINPREFIX)signpostsolver $(BINPREFIX)singles \
+               $(BINPREFIX)singlessolver $(BINPREFIX)sixteen \
+               $(BINPREFIX)slant $(BINPREFIX)slantsolver $(BINPREFIX)solo \
+               $(BINPREFIX)solosolver $(BINPREFIX)tents \
+               $(BINPREFIX)tentssolver $(BINPREFIX)towers \
+               $(BINPREFIX)towerssolver $(BINPREFIX)tracks \
+               $(BINPREFIX)twiddle $(BINPREFIX)undead $(BINPREFIX)unequal \
+               $(BINPREFIX)unequalsolver $(BINPREFIX)unruly \
+               $(BINPREFIX)unrulysolver $(BINPREFIX)untangle
+
+$(BINPREFIX)blackbox: blackbox.o blackbox-icon.o drawing.o gtk.o malloc.o \
+               midend.o misc.o printing.o ps.o random.o version.o
+       $(CC) -o $@ blackbox.o blackbox-icon.o drawing.o gtk.o malloc.o \
+               midend.o misc.o printing.o ps.o random.o version.o  \
+               $(XLFLAGS) $(XLIBS)
+
+$(BINPREFIX)bridges: bridges.o bridges-icon.o drawing.o dsf.o findloop.o \
+               gtk.o malloc.o midend.o misc.o printing.o ps.o random.o \
+               version.o
+       $(CC) -o $@ bridges.o bridges-icon.o drawing.o dsf.o findloop.o \
+               gtk.o malloc.o midend.o misc.o printing.o ps.o random.o \
+               version.o  $(XLFLAGS) $(XLIBS)
+
+$(BINPREFIX)cube: cube.o cube-icon.o drawing.o gtk.o malloc.o midend.o \
+               misc.o printing.o ps.o random.o version.o
+       $(CC) -o $@ cube.o cube-icon.o drawing.o gtk.o malloc.o midend.o \
+               misc.o printing.o ps.o random.o version.o  $(XLFLAGS) \
+               $(XLIBS)
+
+$(BINPREFIX)dominosa: dominosa.o dominosa-icon.o drawing.o gtk.o laydomino.o \
+               malloc.o midend.o misc.o printing.o ps.o random.o version.o
+       $(CC) -o $@ dominosa.o dominosa-icon.o drawing.o gtk.o laydomino.o \
+               malloc.o midend.o misc.o printing.o ps.o random.o version.o  \
+               $(XLFLAGS) $(XLIBS)
+
+$(BINPREFIX)fifteen: drawing.o fifteen.o fifteen-icon.o gtk.o malloc.o \
+               midend.o misc.o printing.o ps.o random.o version.o
+       $(CC) -o $@ drawing.o fifteen.o fifteen-icon.o gtk.o malloc.o \
+               midend.o misc.o printing.o ps.o random.o version.o  \
+               $(XLFLAGS) $(XLIBS)
+
+$(BINPREFIX)fifteensolver: fifteen2.o malloc.o misc.o nullfe.o random.o
+       $(CC) -o $@ fifteen2.o malloc.o misc.o nullfe.o random.o  $(XLFLAGS) \
+               $(ULIBS)
+
+$(BINPREFIX)filling: drawing.o dsf.o filling.o filling-icon.o gtk.o malloc.o \
+               midend.o misc.o printing.o ps.o random.o version.o
+       $(CC) -o $@ drawing.o dsf.o filling.o filling-icon.o gtk.o malloc.o \
+               midend.o misc.o printing.o ps.o random.o version.o  \
+               $(XLFLAGS) $(XLIBS)
+
+$(BINPREFIX)fillingsolver: dsf.o filling2.o malloc.o misc.o nullfe.o \
+               random.o
+       $(CC) -o $@ dsf.o filling2.o malloc.o misc.o nullfe.o random.o  \
+               $(XLFLAGS) $(ULIBS)
+
+$(BINPREFIX)flip: drawing.o flip.o flip-icon.o gtk.o malloc.o midend.o \
+               misc.o printing.o ps.o random.o tree234.o version.o
+       $(CC) -o $@ drawing.o flip.o flip-icon.o gtk.o malloc.o midend.o \
+               misc.o printing.o ps.o random.o tree234.o version.o  \
+               $(XLFLAGS) $(XLIBS)
+
+$(BINPREFIX)flood: drawing.o flood.o flood-icon.o gtk.o malloc.o midend.o \
+               misc.o printing.o ps.o random.o version.o
+       $(CC) -o $@ drawing.o flood.o flood-icon.o gtk.o malloc.o midend.o \
+               misc.o printing.o ps.o random.o version.o  $(XLFLAGS) \
+               $(XLIBS)
+
+$(BINPREFIX)galaxies: drawing.o dsf.o galaxies.o galaxies-icon.o gtk.o \
+               malloc.o midend.o misc.o printing.o ps.o random.o version.o
+       $(CC) -o $@ drawing.o dsf.o galaxies.o galaxies-icon.o gtk.o \
+               malloc.o midend.o misc.o printing.o ps.o random.o version.o  \
+               $(XLFLAGS) $(XLIBS)
+
+$(BINPREFIX)galaxiespicture: dsf.o galaxie4.o malloc.o misc.o nullfe.o \
+               random.o
+       $(CC) -o $@ dsf.o galaxie4.o malloc.o misc.o nullfe.o random.o -lm \
+               $(XLFLAGS) $(ULIBS)
+
+$(BINPREFIX)galaxiessolver: dsf.o galaxie2.o malloc.o misc.o nullfe.o \
+               random.o
+       $(CC) -o $@ dsf.o galaxie2.o malloc.o misc.o nullfe.o random.o -lm \
+               $(XLFLAGS) $(ULIBS)
+
+$(BINPREFIX)guess: drawing.o gtk.o guess.o guess-icon.o malloc.o midend.o \
+               misc.o printing.o ps.o random.o version.o
+       $(CC) -o $@ drawing.o gtk.o guess.o guess-icon.o malloc.o midend.o \
+               misc.o printing.o ps.o random.o version.o  $(XLFLAGS) \
+               $(XLIBS)
+
+$(BINPREFIX)inertia: drawing.o gtk.o inertia.o inertia-icon.o malloc.o \
+               midend.o misc.o printing.o ps.o random.o version.o
+       $(CC) -o $@ drawing.o gtk.o inertia.o inertia-icon.o malloc.o \
+               midend.o misc.o printing.o ps.o random.o version.o  \
+               $(XLFLAGS) $(XLIBS)
+
+$(BINPREFIX)keen: drawing.o dsf.o gtk.o keen.o keen-icon.o latin.o malloc.o \
+               maxflow.o midend.o misc.o printing.o ps.o random.o tree234.o \
+               version.o
+       $(CC) -o $@ drawing.o dsf.o gtk.o keen.o keen-icon.o latin.o \
+               malloc.o maxflow.o midend.o misc.o printing.o ps.o random.o \
+               tree234.o version.o  $(XLFLAGS) $(XLIBS)
+
+$(BINPREFIX)keensolver: dsf.o keen2.o latin6.o malloc.o maxflow.o misc.o \
+               nullfe.o random.o tree234.o
+       $(CC) -o $@ dsf.o keen2.o latin6.o malloc.o maxflow.o misc.o \
+               nullfe.o random.o tree234.o  $(XLFLAGS) $(ULIBS)
+
+$(BINPREFIX)latincheck: latin8.o malloc.o maxflow.o misc.o nullfe.o random.o \
+               tree234.o
+       $(CC) -o $@ latin8.o malloc.o maxflow.o misc.o nullfe.o random.o \
+               tree234.o  $(XLFLAGS) $(ULIBS)
+
+$(BINPREFIX)lightup: combi.o drawing.o gtk.o lightup.o lightup-icon.o \
+               malloc.o midend.o misc.o printing.o ps.o random.o version.o
+       $(CC) -o $@ combi.o drawing.o gtk.o lightup.o lightup-icon.o \
+               malloc.o midend.o misc.o printing.o ps.o random.o version.o  \
+               $(XLFLAGS) $(XLIBS)
+
+$(BINPREFIX)lightupsolver: combi.o lightup2.o malloc.o misc.o nullfe.o \
+               random.o
+       $(CC) -o $@ combi.o lightup2.o malloc.o misc.o nullfe.o random.o  \
+               $(XLFLAGS) $(ULIBS)
+
+$(BINPREFIX)loopy: drawing.o dsf.o grid.o gtk.o loopgen.o loopy.o \
+               loopy-icon.o malloc.o midend.o misc.o penrose.o printing.o \
+               ps.o random.o tree234.o version.o
+       $(CC) -o $@ drawing.o dsf.o grid.o gtk.o loopgen.o loopy.o \
+               loopy-icon.o malloc.o midend.o misc.o penrose.o printing.o \
+               ps.o random.o tree234.o version.o  $(XLFLAGS) $(XLIBS)
+
+$(BINPREFIX)loopysolver: dsf.o grid.o loopgen.o loopy2.o malloc.o misc.o \
+               nullfe.o penrose.o random.o tree234.o
+       $(CC) -o $@ dsf.o grid.o loopgen.o loopy2.o malloc.o misc.o nullfe.o \
+               penrose.o random.o tree234.o -lm $(XLFLAGS) $(ULIBS)
+
+$(BINPREFIX)magnets: drawing.o gtk.o laydomino.o magnets.o magnets-icon.o \
+               malloc.o midend.o misc.o printing.o ps.o random.o version.o
+       $(CC) -o $@ drawing.o gtk.o laydomino.o magnets.o magnets-icon.o \
+               malloc.o midend.o misc.o printing.o ps.o random.o version.o  \
+               $(XLFLAGS) $(XLIBS)
+
+$(BINPREFIX)magnetssolver: laydomino.o magnets2.o malloc.o misc.o nullfe.o \
+               random.o
+       $(CC) -o $@ laydomino.o magnets2.o malloc.o misc.o nullfe.o random.o \
+               -lm $(XLFLAGS) $(ULIBS)
+
+$(BINPREFIX)map: drawing.o dsf.o gtk.o malloc.o map.o map-icon.o midend.o \
+               misc.o printing.o ps.o random.o version.o
+       $(CC) -o $@ drawing.o dsf.o gtk.o malloc.o map.o map-icon.o midend.o \
+               misc.o printing.o ps.o random.o version.o  $(XLFLAGS) \
+               $(XLIBS)
+
+$(BINPREFIX)mapsolver: dsf.o malloc.o map2.o misc.o nullfe.o random.o
+       $(CC) -o $@ dsf.o malloc.o map2.o misc.o nullfe.o random.o -lm \
+               $(XLFLAGS) $(ULIBS)
+
+$(BINPREFIX)mineobfusc: malloc.o mines2.o misc.o nullfe.o random.o tree234.o
+       $(CC) -o $@ malloc.o mines2.o misc.o nullfe.o random.o tree234.o  \
+               $(XLFLAGS) $(ULIBS)
+
+$(BINPREFIX)mines: drawing.o gtk.o malloc.o midend.o mines.o mines-icon.o \
+               misc.o printing.o ps.o random.o tree234.o version.o
+       $(CC) -o $@ drawing.o gtk.o malloc.o midend.o mines.o mines-icon.o \
+               misc.o printing.o ps.o random.o tree234.o version.o  \
+               $(XLFLAGS) $(XLIBS)
+
+$(BINPREFIX)net: drawing.o dsf.o findloop.o gtk.o malloc.o midend.o misc.o \
+               net.o net-icon.o printing.o ps.o random.o tree234.o \
+               version.o
+       $(CC) -o $@ drawing.o dsf.o findloop.o gtk.o malloc.o midend.o \
+               misc.o net.o net-icon.o printing.o ps.o random.o tree234.o \
+               version.o  $(XLFLAGS) $(XLIBS)
+
+$(BINPREFIX)netslide: drawing.o gtk.o malloc.o midend.o misc.o netslide.o \
+               netslide-icon.o printing.o ps.o random.o tree234.o version.o
+       $(CC) -o $@ drawing.o gtk.o malloc.o midend.o misc.o netslide.o \
+               netslide-icon.o printing.o ps.o random.o tree234.o version.o \
+                $(XLFLAGS) $(XLIBS)
+
+$(BINPREFIX)nullgame: drawing.o gtk.o malloc.o midend.o misc.o no-icon.o \
+               nullgame.o printing.o ps.o random.o version.o
+       $(CC) -o $@ drawing.o gtk.o malloc.o midend.o misc.o no-icon.o \
+               nullgame.o printing.o ps.o random.o version.o  $(XLFLAGS) \
+               $(XLIBS)
+
+$(BINPREFIX)obfusc: malloc.o misc.o nullfe.o obfusc.o random.o
+       $(CC) -o $@ malloc.o misc.o nullfe.o obfusc.o random.o  $(XLFLAGS) \
+               $(ULIBS)
+
+$(BINPREFIX)palisade: divvy.o drawing.o dsf.o gtk.o malloc.o midend.o misc.o \
+               palisade.o palisade-icon.o printing.o ps.o random.o \
+               version.o
+       $(CC) -o $@ divvy.o drawing.o dsf.o gtk.o malloc.o midend.o misc.o \
+               palisade.o palisade-icon.o printing.o ps.o random.o \
+               version.o  $(XLFLAGS) $(XLIBS)
+
+$(BINPREFIX)pattern: drawing.o gtk.o malloc.o midend.o misc.o pattern.o \
+               pattern-icon.o printing.o ps.o random.o version.o
+       $(CC) -o $@ drawing.o gtk.o malloc.o midend.o misc.o pattern.o \
+               pattern-icon.o printing.o ps.o random.o version.o  \
+               $(XLFLAGS) $(XLIBS)
+
+$(BINPREFIX)patternpicture: malloc.o misc.o nullfe.o pattern4.o random.o
+       $(CC) -o $@ malloc.o misc.o nullfe.o pattern4.o random.o  $(XLFLAGS) \
+               $(ULIBS)
+
+$(BINPREFIX)patternsolver: malloc.o misc.o nullfe.o pattern2.o random.o
+       $(CC) -o $@ malloc.o misc.o nullfe.o pattern2.o random.o  $(XLFLAGS) \
+               $(ULIBS)
+
+$(BINPREFIX)pearl: drawing.o dsf.o grid.o gtk.o loopgen.o malloc.o midend.o \
+               misc.o pearl.o pearl-icon.o penrose.o printing.o ps.o \
+               random.o tdq.o tree234.o version.o
+       $(CC) -o $@ drawing.o dsf.o grid.o gtk.o loopgen.o malloc.o midend.o \
+               misc.o pearl.o pearl-icon.o penrose.o printing.o ps.o \
+               random.o tdq.o tree234.o version.o  $(XLFLAGS) $(XLIBS)
+
+$(BINPREFIX)pearlbench: dsf.o grid.o loopgen.o malloc.o misc.o nullfe.o \
+               pearl2.o penrose.o random.o tdq.o tree234.o
+       $(CC) -o $@ dsf.o grid.o loopgen.o malloc.o misc.o nullfe.o pearl2.o \
+               penrose.o random.o tdq.o tree234.o -lm $(XLFLAGS) $(ULIBS)
+
+$(BINPREFIX)pegs: drawing.o gtk.o malloc.o midend.o misc.o pegs.o \
+               pegs-icon.o printing.o ps.o random.o tree234.o version.o
+       $(CC) -o $@ drawing.o gtk.o malloc.o midend.o misc.o pegs.o \
+               pegs-icon.o printing.o ps.o random.o tree234.o version.o  \
+               $(XLFLAGS) $(XLIBS)
+
+$(BINPREFIX)range: drawing.o dsf.o gtk.o malloc.o midend.o misc.o printing.o \
+               ps.o random.o range.o range-icon.o version.o
+       $(CC) -o $@ drawing.o dsf.o gtk.o malloc.o midend.o misc.o \
+               printing.o ps.o random.o range.o range-icon.o version.o  \
+               $(XLFLAGS) $(XLIBS)
+
+$(BINPREFIX)rect: drawing.o gtk.o malloc.o midend.o misc.o printing.o ps.o \
+               random.o rect.o rect-icon.o version.o
+       $(CC) -o $@ drawing.o gtk.o malloc.o midend.o misc.o printing.o ps.o \
+               random.o rect.o rect-icon.o version.o  $(XLFLAGS) $(XLIBS)
+
+$(BINPREFIX)samegame: drawing.o gtk.o malloc.o midend.o misc.o printing.o \
+               ps.o random.o samegame.o samegame-icon.o version.o
+       $(CC) -o $@ drawing.o gtk.o malloc.o midend.o misc.o printing.o ps.o \
+               random.o samegame.o samegame-icon.o version.o  $(XLFLAGS) \
+               $(XLIBS)
+
+$(BINPREFIX)signpost: drawing.o dsf.o gtk.o malloc.o midend.o misc.o \
+               printing.o ps.o random.o signpost.o signpost-icon.o \
+               version.o
+       $(CC) -o $@ drawing.o dsf.o gtk.o malloc.o midend.o misc.o \
+               printing.o ps.o random.o signpost.o signpost-icon.o \
+               version.o  $(XLFLAGS) $(XLIBS)
+
+$(BINPREFIX)signpostsolver: dsf.o malloc.o misc.o nullfe.o random.o \
+               signpos2.o
+       $(CC) -o $@ dsf.o malloc.o misc.o nullfe.o random.o signpos2.o -lm \
+               $(XLFLAGS) $(ULIBS)
+
+$(BINPREFIX)singles: drawing.o dsf.o gtk.o latin.o malloc.o maxflow.o \
+               midend.o misc.o printing.o ps.o random.o singles.o \
+               singles-icon.o tree234.o version.o
+       $(CC) -o $@ drawing.o dsf.o gtk.o latin.o malloc.o maxflow.o \
+               midend.o misc.o printing.o ps.o random.o singles.o \
+               singles-icon.o tree234.o version.o  $(XLFLAGS) $(XLIBS)
+
+$(BINPREFIX)singlessolver: dsf.o latin.o malloc.o maxflow.o misc.o nullfe.o \
+               random.o singles3.o tree234.o
+       $(CC) -o $@ dsf.o latin.o malloc.o maxflow.o misc.o nullfe.o \
+               random.o singles3.o tree234.o  $(XLFLAGS) $(ULIBS)
+
+$(BINPREFIX)sixteen: drawing.o gtk.o malloc.o midend.o misc.o printing.o \
+               ps.o random.o sixteen.o sixteen-icon.o version.o
+       $(CC) -o $@ drawing.o gtk.o malloc.o midend.o misc.o printing.o ps.o \
+               random.o sixteen.o sixteen-icon.o version.o  $(XLFLAGS) \
+               $(XLIBS)
+
+$(BINPREFIX)slant: drawing.o dsf.o findloop.o gtk.o malloc.o midend.o misc.o \
+               printing.o ps.o random.o slant.o slant-icon.o version.o
+       $(CC) -o $@ drawing.o dsf.o findloop.o gtk.o malloc.o midend.o \
+               misc.o printing.o ps.o random.o slant.o slant-icon.o \
+               version.o  $(XLFLAGS) $(XLIBS)
+
+$(BINPREFIX)slantsolver: dsf.o findloop.o malloc.o misc.o nullfe.o random.o \
+               slant2.o
+       $(CC) -o $@ dsf.o findloop.o malloc.o misc.o nullfe.o random.o \
+               slant2.o  $(XLFLAGS) $(ULIBS)
+
+$(BINPREFIX)solo: divvy.o drawing.o dsf.o gtk.o malloc.o midend.o misc.o \
+               printing.o ps.o random.o solo.o solo-icon.o version.o
+       $(CC) -o $@ divvy.o drawing.o dsf.o gtk.o malloc.o midend.o misc.o \
+               printing.o ps.o random.o solo.o solo-icon.o version.o  \
+               $(XLFLAGS) $(XLIBS)
+
+$(BINPREFIX)solosolver: divvy.o dsf.o malloc.o misc.o nullfe.o random.o \
+               solo2.o
+       $(CC) -o $@ divvy.o dsf.o malloc.o misc.o nullfe.o random.o solo2.o  \
+               $(XLFLAGS) $(ULIBS)
+
+$(BINPREFIX)tents: drawing.o dsf.o gtk.o malloc.o maxflow.o midend.o misc.o \
+               printing.o ps.o random.o tents.o tents-icon.o version.o
+       $(CC) -o $@ drawing.o dsf.o gtk.o malloc.o maxflow.o midend.o misc.o \
+               printing.o ps.o random.o tents.o tents-icon.o version.o  \
+               $(XLFLAGS) $(XLIBS)
+
+$(BINPREFIX)tentssolver: dsf.o malloc.o maxflow.o misc.o nullfe.o random.o \
+               tents3.o
+       $(CC) -o $@ dsf.o malloc.o maxflow.o misc.o nullfe.o random.o \
+               tents3.o  $(XLFLAGS) $(ULIBS)
+
+$(BINPREFIX)towers: drawing.o gtk.o latin.o malloc.o maxflow.o midend.o \
+               misc.o printing.o ps.o random.o towers.o towers-icon.o \
+               tree234.o version.o
+       $(CC) -o $@ drawing.o gtk.o latin.o malloc.o maxflow.o midend.o \
+               misc.o printing.o ps.o random.o towers.o towers-icon.o \
+               tree234.o version.o  $(XLFLAGS) $(XLIBS)
+
+$(BINPREFIX)towerssolver: latin6.o malloc.o maxflow.o misc.o nullfe.o \
+               random.o towers2.o tree234.o
+       $(CC) -o $@ latin6.o malloc.o maxflow.o misc.o nullfe.o random.o \
+               towers2.o tree234.o  $(XLFLAGS) $(ULIBS)
+
+$(BINPREFIX)tracks: drawing.o dsf.o findloop.o gtk.o malloc.o midend.o \
+               misc.o printing.o ps.o random.o tracks.o tracks-icon.o \
+               version.o
+       $(CC) -o $@ drawing.o dsf.o findloop.o gtk.o malloc.o midend.o \
+               misc.o printing.o ps.o random.o tracks.o tracks-icon.o \
+               version.o  $(XLFLAGS) $(XLIBS)
+
+$(BINPREFIX)twiddle: drawing.o gtk.o malloc.o midend.o misc.o printing.o \
+               ps.o random.o twiddle.o twiddle-icon.o version.o
+       $(CC) -o $@ drawing.o gtk.o malloc.o midend.o misc.o printing.o ps.o \
+               random.o twiddle.o twiddle-icon.o version.o  $(XLFLAGS) \
+               $(XLIBS)
+
+$(BINPREFIX)undead: drawing.o gtk.o malloc.o midend.o misc.o printing.o ps.o \
+               random.o undead.o undead-icon.o version.o
+       $(CC) -o $@ drawing.o gtk.o malloc.o midend.o misc.o printing.o ps.o \
+               random.o undead.o undead-icon.o version.o  $(XLFLAGS) \
+               $(XLIBS)
+
+$(BINPREFIX)unequal: drawing.o gtk.o latin.o malloc.o maxflow.o midend.o \
+               misc.o printing.o ps.o random.o tree234.o unequal.o \
+               unequal-icon.o version.o
+       $(CC) -o $@ drawing.o gtk.o latin.o malloc.o maxflow.o midend.o \
+               misc.o printing.o ps.o random.o tree234.o unequal.o \
+               unequal-icon.o version.o  $(XLFLAGS) $(XLIBS)
+
+$(BINPREFIX)unequalsolver: latin6.o malloc.o maxflow.o misc.o nullfe.o \
+               random.o tree234.o unequal2.o
+       $(CC) -o $@ latin6.o malloc.o maxflow.o misc.o nullfe.o random.o \
+               tree234.o unequal2.o  $(XLFLAGS) $(ULIBS)
+
+$(BINPREFIX)unruly: drawing.o gtk.o malloc.o midend.o misc.o printing.o ps.o \
+               random.o unruly.o unruly-icon.o version.o
+       $(CC) -o $@ drawing.o gtk.o malloc.o midend.o misc.o printing.o ps.o \
+               random.o unruly.o unruly-icon.o version.o  $(XLFLAGS) \
+               $(XLIBS)
+
+$(BINPREFIX)unrulysolver: malloc.o misc.o nullfe.o random.o unruly2.o
+       $(CC) -o $@ malloc.o misc.o nullfe.o random.o unruly2.o  $(XLFLAGS) \
+               $(ULIBS)
+
+$(BINPREFIX)untangle: drawing.o gtk.o malloc.o midend.o misc.o printing.o \
+               ps.o random.o tree234.o untangle.o untangle-icon.o version.o
+       $(CC) -o $@ drawing.o gtk.o malloc.o midend.o misc.o printing.o ps.o \
+               random.o tree234.o untangle.o untangle-icon.o version.o  \
+               $(XLFLAGS) $(XLIBS)
+
+blackbox.o: ./blackbox.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+blackbox-icon.o: icons/blackbox-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+blackbo3.o: ./blackbox.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+bridges.o: ./bridges.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+bridges-icon.o: icons/bridges-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+bridges3.o: ./bridges.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+combi.o: ./combi.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+cube.o: ./cube.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+cube-icon.o: icons/cube-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+cube3.o: ./cube.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+divvy.o: ./divvy.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+dominosa.o: ./dominosa.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+dominosa-icon.o: icons/dominosa-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+dominos3.o: ./dominosa.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+drawing.o: ./drawing.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+dsf.o: ./dsf.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+fifteen.o: ./fifteen.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+fifteen-icon.o: icons/fifteen-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+fifteen5.o: ./fifteen.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+fifteen2.o: ./fifteen.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+filling.o: ./filling.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+filling-icon.o: icons/filling-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+filling5.o: ./filling.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+filling2.o: ./filling.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+findloop.o: ./findloop.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+flip.o: ./flip.c ./puzzles.h ./tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+flip-icon.o: icons/flip-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+flip3.o: ./flip.c ./puzzles.h ./tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+flood.o: ./flood.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+flood-icon.o: icons/flood-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+flood3.o: ./flood.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+galaxies.o: ./galaxies.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+galaxies-icon.o: icons/galaxies-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+galaxie7.o: ./galaxies.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+galaxie4.o: ./galaxies.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_PICTURE_GENERATOR -c $< -o $@
+galaxie2.o: ./galaxies.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+grid.o: ./grid.c ./puzzles.h ./tree234.h ./grid.h ./penrose.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+gtk.o: ./gtk.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+guess.o: ./guess.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+guess-icon.o: icons/guess-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+guess3.o: ./guess.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+inertia.o: ./inertia.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+inertia-icon.o: icons/inertia-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+inertia3.o: ./inertia.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+keen.o: ./keen.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+keen-icon.o: icons/keen-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+keen5.o: ./keen.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+keen2.o: ./keen.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+latin.o: ./latin.c ./puzzles.h ./tree234.h ./maxflow.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+latin8.o: ./latin.c ./puzzles.h ./tree234.h ./maxflow.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_LATIN_TEST -c $< -o $@
+latin6.o: ./latin.c ./puzzles.h ./tree234.h ./maxflow.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+laydomino.o: ./laydomino.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+lightup.o: ./lightup.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+lightup-icon.o: icons/lightup-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+lightup5.o: ./lightup.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+lightup2.o: ./lightup.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+list.o: ./list.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+loopgen.o: ./loopgen.c ./puzzles.h ./tree234.h ./grid.h ./loopgen.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+loopy.o: ./loopy.c ./puzzles.h ./tree234.h ./grid.h ./loopgen.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+loopy-icon.o: icons/loopy-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+loopy5.o: ./loopy.c ./puzzles.h ./tree234.h ./grid.h ./loopgen.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+loopy2.o: ./loopy.c ./puzzles.h ./tree234.h ./grid.h ./loopgen.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+magnets.o: ./magnets.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+magnets-icon.o: icons/magnets-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+magnets5.o: ./magnets.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+magnets2.o: ./magnets.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+malloc.o: ./malloc.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+map.o: ./map.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+map-icon.o: icons/map-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+map5.o: ./map.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+map2.o: ./map.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+maxflow.o: ./maxflow.c ./maxflow.h ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+midend.o: ./midend.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+mines.o: ./mines.c ./tree234.h ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+mines-icon.o: icons/mines-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+mines5.o: ./mines.c ./tree234.h ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+mines2.o: ./mines.c ./tree234.h ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_OBFUSCATOR -c $< -o $@
+misc.o: ./misc.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+net.o: ./net.c ./puzzles.h ./tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+net-icon.o: icons/net-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+net3.o: ./net.c ./puzzles.h ./tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+netslide.o: ./netslide.c ./puzzles.h ./tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+netslide-icon.o: icons/netslide-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+netslid3.o: ./netslide.c ./puzzles.h ./tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+no-icon.o: ./no-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+nullfe.o: ./nullfe.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+nullgame.o: ./nullgame.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+obfusc.o: ./obfusc.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+osx.o: ./osx.m ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+palisade.o: ./palisade.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+palisade-icon.o: icons/palisade-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+palisad3.o: ./palisade.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+pattern.o: ./pattern.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pattern-icon.o: icons/pattern-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pattern7.o: ./pattern.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+pattern4.o: ./pattern.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_PICTURE_GENERATOR -c $< -o $@
+pattern2.o: ./pattern.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+pearl.o: ./pearl.c ./puzzles.h ./grid.h ./loopgen.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pearl-icon.o: icons/pearl-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pearl5.o: ./pearl.c ./puzzles.h ./grid.h ./loopgen.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+pearl2.o: ./pearl.c ./puzzles.h ./grid.h ./loopgen.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+pegs.o: ./pegs.c ./puzzles.h ./tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pegs-icon.o: icons/pegs-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pegs3.o: ./pegs.c ./puzzles.h ./tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+penrose.o: ./penrose.c ./puzzles.h ./penrose.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+printing.o: ./printing.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+ps.o: ./ps.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+random.o: ./random.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+range.o: ./range.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+range-icon.o: icons/range-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+range3.o: ./range.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+rect.o: ./rect.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+rect-icon.o: icons/rect-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+rect3.o: ./rect.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+samegame.o: ./samegame.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+samegame-icon.o: icons/samegame-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+samegam3.o: ./samegame.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+signpost.o: ./signpost.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+signpost-icon.o: icons/signpost-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+signpos5.o: ./signpost.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+signpos2.o: ./signpost.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+singles.o: ./singles.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+singles-icon.o: icons/singles-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+singles5.o: ./singles.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+singles3.o: ./singles.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+sixteen.o: ./sixteen.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+sixteen-icon.o: icons/sixteen-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+sixteen3.o: ./sixteen.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+slant.o: ./slant.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+slant-icon.o: icons/slant-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+slant5.o: ./slant.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+slant2.o: ./slant.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+solo.o: ./solo.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+solo-icon.o: icons/solo-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+solo5.o: ./solo.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+solo2.o: ./solo.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+tdq.o: ./tdq.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+tents.o: ./tents.c ./puzzles.h ./maxflow.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+tents-icon.o: icons/tents-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+tents5.o: ./tents.c ./puzzles.h ./maxflow.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+tents3.o: ./tents.c ./puzzles.h ./maxflow.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+towers.o: ./towers.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+towers-icon.o: icons/towers-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+towers5.o: ./towers.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+towers2.o: ./towers.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+tracks.o: ./tracks.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+tracks-icon.o: icons/tracks-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+tracks3.o: ./tracks.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+tree234.o: ./tree234.c ./tree234.h ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+twiddle.o: ./twiddle.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+twiddle-icon.o: icons/twiddle-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+twiddle3.o: ./twiddle.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+undead.o: ./undead.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+undead-icon.o: icons/undead-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+undead3.o: ./undead.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+unequal.o: ./unequal.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+unequal-icon.o: icons/unequal-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+unequal5.o: ./unequal.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+unequal2.o: ./unequal.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+unruly.o: ./unruly.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+unruly-icon.o: icons/unruly-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+unruly5.o: ./unruly.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+unruly2.o: ./unruly.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+untangle.o: ./untangle.c ./puzzles.h ./tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+untangle-icon.o: icons/untangle-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+untangl3.o: ./untangle.c ./puzzles.h ./tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+version.o: ./version.c ./version.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+windows.o: ./windows.c ./puzzles.h ./resource.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+windows1.o: ./windows.c ./puzzles.h ./resource.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+
+GAMES += blackbox
+GAMES += bridges
+GAMES += cube
+GAMES += dominosa
+GAMES += fifteen
+GAMES += filling
+GAMES += flip
+GAMES += flood
+GAMES += galaxies
+GAMES += guess
+GAMES += inertia
+GAMES += keen
+GAMES += lightup
+GAMES += loopy
+GAMES += magnets
+GAMES += map
+GAMES += mines
+GAMES += net
+GAMES += netslide
+GAMES += palisade
+GAMES += pattern
+GAMES += pearl
+GAMES += pegs
+GAMES += range
+GAMES += rect
+GAMES += samegame
+GAMES += signpost
+GAMES += singles
+GAMES += sixteen
+GAMES += slant
+GAMES += solo
+GAMES += tents
+GAMES += towers
+GAMES += tracks
+GAMES += twiddle
+GAMES += undead
+GAMES += unequal
+GAMES += unruly
+GAMES += untangle
+install:
+       for i in $(GAMES); do \
+               $(INSTALL_PROGRAM) -m 755 $(BINPREFIX)$$i $(DESTDIR)$(gamesdir)/$(BINPREFIX)$$i \
+               || exit 1; \
+       done
+test: benchmark.html benchmark.txt
+
+benchmark.html: benchmark.txt benchmark.pl
+       ./benchmark.pl benchmark.txt > $@
+
+benchmark.txt: benchmark.sh $(GAMES)
+       ./benchmark.sh > $@
+
+
+clean:
+       rm -f *.o $(BINPREFIX)blackbox $(BINPREFIX)bridges $(BINPREFIX)cube $(BINPREFIX)dominosa $(BINPREFIX)fifteen $(BINPREFIX)fifteensolver $(BINPREFIX)filling $(BINPREFIX)fillingsolver $(BINPREFIX)flip $(BINPREFIX)flood $(BINPREFIX)galaxies $(BINPREFIX)galaxiespicture $(BINPREFIX)galaxiessolver $(BINPREFIX)guess $(BINPREFIX)inertia $(BINPREFIX)keen $(BINPREFIX)keensolver $(BINPREFIX)latincheck $(BINPREFIX)lightup $(BINPREFIX)lightupsolver $(BINPREFIX)loopy $(BINPREFIX)loopysolver $(BINPREFIX)magnets $(BINPREFIX)magnetssolver $(BINPREFIX)map $(BINPREFIX)mapsolver $(BINPREFIX)mineobfusc $(BINPREFIX)mines $(BINPREFIX)net $(BINPREFIX)netslide $(BINPREFIX)nullgame $(BINPREFIX)obfusc $(BINPREFIX)palisade $(BINPREFIX)pattern $(BINPREFIX)patternpicture $(BINPREFIX)patternsolver $(BINPREFIX)pearl $(BINPREFIX)pearlbench $(BINPREFIX)pegs $(BINPREFIX)range $(BINPREFIX)rect $(BINPREFIX)samegame $(BINPREFIX)signpost $(BINPREFIX)signpostsolver $(BINPREFIX)singles $(BINPREFIX)singlessolver $(BINPREFIX)sixteen $(BINPREFIX)slant $(BINPREFIX)slantsolver $(BINPREFIX)solo $(BINPREFIX)solosolver $(BINPREFIX)tents $(BINPREFIX)tentssolver $(BINPREFIX)towers $(BINPREFIX)towerssolver $(BINPREFIX)tracks $(BINPREFIX)twiddle $(BINPREFIX)undead $(BINPREFIX)unequal $(BINPREFIX)unequalsolver $(BINPREFIX)unruly $(BINPREFIX)unrulysolver $(BINPREFIX)untangle
diff --git a/Makefile.in b/Makefile.in
new file mode 100644 (file)
index 0000000..a49f795
--- /dev/null
@@ -0,0 +1,2780 @@
+# Makefile.in generated by automake 1.15 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2014 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+# Makefile.am for puzzles under Unix with Autoconf/Automake.
+#
+# This file was created by `mkfiles.pl' from the `Recipe' file.
+# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+  if test -z '$(MAKELEVEL)'; then \
+    false; \
+  elif test -n '$(MAKE_HOST)'; then \
+    true; \
+  elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+    true; \
+  else \
+    false; \
+  fi; \
+}
+am__make_running_with_option = \
+  case $${target_option-} in \
+      ?) ;; \
+      *) echo "am__make_running_with_option: internal error: invalid" \
+              "target option '$${target_option-}' specified" >&2; \
+         exit 1;; \
+  esac; \
+  has_opt=no; \
+  sane_makeflags=$$MAKEFLAGS; \
+  if $(am__is_gnu_make); then \
+    sane_makeflags=$$MFLAGS; \
+  else \
+    case $$MAKEFLAGS in \
+      *\\[\ \  ]*) \
+        bs=\\; \
+        sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+          | sed "s/$$bs$$bs[$$bs $$bs  ]*//g"`;; \
+    esac; \
+  fi; \
+  skip_next=no; \
+  strip_trailopt () \
+  { \
+    flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+  }; \
+  for flg in $$sane_makeflags; do \
+    test $$skip_next = yes && { skip_next=no; continue; }; \
+    case $$flg in \
+      *=*|--*) continue;; \
+        -*I) strip_trailopt 'I'; skip_next=yes;; \
+      -*I?*) strip_trailopt 'I';; \
+        -*O) strip_trailopt 'O'; skip_next=yes;; \
+      -*O?*) strip_trailopt 'O';; \
+        -*l) strip_trailopt 'l'; skip_next=yes;; \
+      -*l?*) strip_trailopt 'l';; \
+      -[dEDm]) skip_next=yes;; \
+      -[JT]) skip_next=yes;; \
+    esac; \
+    case $$flg in \
+      *$$target_option*) has_opt=yes; break;; \
+    esac; \
+  done; \
+  test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+noinst_PROGRAMS = blackbox$(EXEEXT) bridges$(EXEEXT) cube$(EXEEXT) \
+       dominosa$(EXEEXT) fifteen$(EXEEXT) fifteensolver$(EXEEXT) \
+       filling$(EXEEXT) fillingsolver$(EXEEXT) flip$(EXEEXT) \
+       flood$(EXEEXT) galaxies$(EXEEXT) galaxiespicture$(EXEEXT) \
+       galaxiessolver$(EXEEXT) guess$(EXEEXT) inertia$(EXEEXT) \
+       keen$(EXEEXT) keensolver$(EXEEXT) latincheck$(EXEEXT) \
+       lightup$(EXEEXT) lightupsolver$(EXEEXT) loopy$(EXEEXT) \
+       loopysolver$(EXEEXT) magnets$(EXEEXT) magnetssolver$(EXEEXT) \
+       map$(EXEEXT) mapsolver$(EXEEXT) mineobfusc$(EXEEXT) \
+       mines$(EXEEXT) net$(EXEEXT) netslide$(EXEEXT) \
+       nullgame$(EXEEXT) obfusc$(EXEEXT) palisade$(EXEEXT) \
+       pattern$(EXEEXT) patternpicture$(EXEEXT) \
+       patternsolver$(EXEEXT) pearl$(EXEEXT) pearlbench$(EXEEXT) \
+       pegs$(EXEEXT) range$(EXEEXT) rect$(EXEEXT) samegame$(EXEEXT) \
+       signpost$(EXEEXT) signpostsolver$(EXEEXT) singles$(EXEEXT) \
+       singlessolver$(EXEEXT) sixteen$(EXEEXT) slant$(EXEEXT) \
+       slantsolver$(EXEEXT) solo$(EXEEXT) solosolver$(EXEEXT) \
+       tents$(EXEEXT) tentssolver$(EXEEXT) towers$(EXEEXT) \
+       towerssolver$(EXEEXT) tracks$(EXEEXT) twiddle$(EXEEXT) \
+       undead$(EXEEXT) unequal$(EXEEXT) unequalsolver$(EXEEXT) \
+       unruly$(EXEEXT) unrulysolver$(EXEEXT) untangle$(EXEEXT)
+bin_PROGRAMS = $(am__EXEEXT_1)
+subdir = .
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+       $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(top_srcdir)/configure \
+       $(am__configure_deps) $(am__DIST_COMMON)
+am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \
+ configure.lineno config.status.lineno
+mkinstalldirs = $(install_sh) -d
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LIBRARIES = $(noinst_LIBRARIES)
+AR = ar
+ARFLAGS = cru
+AM_V_AR = $(am__v_AR_@AM_V@)
+am__v_AR_ = $(am__v_AR_@AM_DEFAULT_V@)
+am__v_AR_0 = @echo "  AR      " $@;
+am__v_AR_1 = 
+libfifteen2_a_AR = $(AR) $(ARFLAGS)
+libfifteen2_a_LIBADD =
+am__dirstamp = $(am__leading_dot)dirstamp
+am_libfifteen2_a_OBJECTS = ./libfifteen2_a-fifteen.$(OBJEXT)
+libfifteen2_a_OBJECTS = $(am_libfifteen2_a_OBJECTS)
+libfilling2_a_AR = $(AR) $(ARFLAGS)
+libfilling2_a_LIBADD =
+am_libfilling2_a_OBJECTS = ./libfilling2_a-filling.$(OBJEXT)
+libfilling2_a_OBJECTS = $(am_libfilling2_a_OBJECTS)
+libgalaxie2_a_AR = $(AR) $(ARFLAGS)
+libgalaxie2_a_LIBADD =
+am_libgalaxie2_a_OBJECTS = ./libgalaxie2_a-galaxies.$(OBJEXT)
+libgalaxie2_a_OBJECTS = $(am_libgalaxie2_a_OBJECTS)
+libgalaxie4_a_AR = $(AR) $(ARFLAGS)
+libgalaxie4_a_LIBADD =
+am_libgalaxie4_a_OBJECTS = ./libgalaxie4_a-galaxies.$(OBJEXT)
+libgalaxie4_a_OBJECTS = $(am_libgalaxie4_a_OBJECTS)
+libkeen2_a_AR = $(AR) $(ARFLAGS)
+libkeen2_a_LIBADD =
+am_libkeen2_a_OBJECTS = ./libkeen2_a-keen.$(OBJEXT)
+libkeen2_a_OBJECTS = $(am_libkeen2_a_OBJECTS)
+liblatin6_a_AR = $(AR) $(ARFLAGS)
+liblatin6_a_LIBADD =
+am_liblatin6_a_OBJECTS = ./liblatin6_a-latin.$(OBJEXT)
+liblatin6_a_OBJECTS = $(am_liblatin6_a_OBJECTS)
+liblatin8_a_AR = $(AR) $(ARFLAGS)
+liblatin8_a_LIBADD =
+am_liblatin8_a_OBJECTS = ./liblatin8_a-latin.$(OBJEXT)
+liblatin8_a_OBJECTS = $(am_liblatin8_a_OBJECTS)
+liblightup2_a_AR = $(AR) $(ARFLAGS)
+liblightup2_a_LIBADD =
+am_liblightup2_a_OBJECTS = ./liblightup2_a-lightup.$(OBJEXT)
+liblightup2_a_OBJECTS = $(am_liblightup2_a_OBJECTS)
+libloopy2_a_AR = $(AR) $(ARFLAGS)
+libloopy2_a_LIBADD =
+am_libloopy2_a_OBJECTS = ./libloopy2_a-loopy.$(OBJEXT)
+libloopy2_a_OBJECTS = $(am_libloopy2_a_OBJECTS)
+libmagnets2_a_AR = $(AR) $(ARFLAGS)
+libmagnets2_a_LIBADD =
+am_libmagnets2_a_OBJECTS = ./libmagnets2_a-magnets.$(OBJEXT)
+libmagnets2_a_OBJECTS = $(am_libmagnets2_a_OBJECTS)
+libmap2_a_AR = $(AR) $(ARFLAGS)
+libmap2_a_LIBADD =
+am_libmap2_a_OBJECTS = ./libmap2_a-map.$(OBJEXT)
+libmap2_a_OBJECTS = $(am_libmap2_a_OBJECTS)
+libmines2_a_AR = $(AR) $(ARFLAGS)
+libmines2_a_LIBADD =
+am_libmines2_a_OBJECTS = ./libmines2_a-mines.$(OBJEXT)
+libmines2_a_OBJECTS = $(am_libmines2_a_OBJECTS)
+libpattern2_a_AR = $(AR) $(ARFLAGS)
+libpattern2_a_LIBADD =
+am_libpattern2_a_OBJECTS = ./libpattern2_a-pattern.$(OBJEXT)
+libpattern2_a_OBJECTS = $(am_libpattern2_a_OBJECTS)
+libpattern4_a_AR = $(AR) $(ARFLAGS)
+libpattern4_a_LIBADD =
+am_libpattern4_a_OBJECTS = ./libpattern4_a-pattern.$(OBJEXT)
+libpattern4_a_OBJECTS = $(am_libpattern4_a_OBJECTS)
+libpearl2_a_AR = $(AR) $(ARFLAGS)
+libpearl2_a_LIBADD =
+am_libpearl2_a_OBJECTS = ./libpearl2_a-pearl.$(OBJEXT)
+libpearl2_a_OBJECTS = $(am_libpearl2_a_OBJECTS)
+libsignpos2_a_AR = $(AR) $(ARFLAGS)
+libsignpos2_a_LIBADD =
+am_libsignpos2_a_OBJECTS = ./libsignpos2_a-signpost.$(OBJEXT)
+libsignpos2_a_OBJECTS = $(am_libsignpos2_a_OBJECTS)
+libsingles3_a_AR = $(AR) $(ARFLAGS)
+libsingles3_a_LIBADD =
+am_libsingles3_a_OBJECTS = ./libsingles3_a-singles.$(OBJEXT)
+libsingles3_a_OBJECTS = $(am_libsingles3_a_OBJECTS)
+libslant2_a_AR = $(AR) $(ARFLAGS)
+libslant2_a_LIBADD =
+am_libslant2_a_OBJECTS = ./libslant2_a-slant.$(OBJEXT)
+libslant2_a_OBJECTS = $(am_libslant2_a_OBJECTS)
+libsolo2_a_AR = $(AR) $(ARFLAGS)
+libsolo2_a_LIBADD =
+am_libsolo2_a_OBJECTS = ./libsolo2_a-solo.$(OBJEXT)
+libsolo2_a_OBJECTS = $(am_libsolo2_a_OBJECTS)
+libtents3_a_AR = $(AR) $(ARFLAGS)
+libtents3_a_LIBADD =
+am_libtents3_a_OBJECTS = ./libtents3_a-tents.$(OBJEXT)
+libtents3_a_OBJECTS = $(am_libtents3_a_OBJECTS)
+libtowers2_a_AR = $(AR) $(ARFLAGS)
+libtowers2_a_LIBADD =
+am_libtowers2_a_OBJECTS = ./libtowers2_a-towers.$(OBJEXT)
+libtowers2_a_OBJECTS = $(am_libtowers2_a_OBJECTS)
+libunequal2_a_AR = $(AR) $(ARFLAGS)
+libunequal2_a_LIBADD =
+am_libunequal2_a_OBJECTS = ./libunequal2_a-unequal.$(OBJEXT)
+libunequal2_a_OBJECTS = $(am_libunequal2_a_OBJECTS)
+libunruly2_a_AR = $(AR) $(ARFLAGS)
+libunruly2_a_LIBADD =
+am_libunruly2_a_OBJECTS = ./libunruly2_a-unruly.$(OBJEXT)
+libunruly2_a_OBJECTS = $(am_libunruly2_a_OBJECTS)
+am__EXEEXT_1 = blackbox$(EXEEXT) bridges$(EXEEXT) cube$(EXEEXT) \
+       dominosa$(EXEEXT) fifteen$(EXEEXT) filling$(EXEEXT) \
+       flip$(EXEEXT) flood$(EXEEXT) galaxies$(EXEEXT) guess$(EXEEXT) \
+       inertia$(EXEEXT) keen$(EXEEXT) lightup$(EXEEXT) loopy$(EXEEXT) \
+       magnets$(EXEEXT) map$(EXEEXT) mines$(EXEEXT) net$(EXEEXT) \
+       netslide$(EXEEXT) palisade$(EXEEXT) pattern$(EXEEXT) \
+       pearl$(EXEEXT) pegs$(EXEEXT) range$(EXEEXT) rect$(EXEEXT) \
+       samegame$(EXEEXT) signpost$(EXEEXT) singles$(EXEEXT) \
+       sixteen$(EXEEXT) slant$(EXEEXT) solo$(EXEEXT) tents$(EXEEXT) \
+       towers$(EXEEXT) tracks$(EXEEXT) twiddle$(EXEEXT) \
+       undead$(EXEEXT) unequal$(EXEEXT) unruly$(EXEEXT) \
+       untangle$(EXEEXT)
+am__installdirs = "$(DESTDIR)$(bindir)"
+PROGRAMS = $(bin_PROGRAMS) $(noinst_PROGRAMS)
+am_blackbox_OBJECTS = ./blackbox.$(OBJEXT) ./drawing.$(OBJEXT) \
+       ./gtk.$(OBJEXT) ./malloc.$(OBJEXT) ./midend.$(OBJEXT) \
+       ./misc.$(OBJEXT) ./printing.$(OBJEXT) ./ps.$(OBJEXT) \
+       ./random.$(OBJEXT) ./version.$(OBJEXT) \
+       icons/blackbox-icon.$(OBJEXT)
+blackbox_OBJECTS = $(am_blackbox_OBJECTS)
+am__DEPENDENCIES_1 =
+blackbox_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_bridges_OBJECTS = ./bridges.$(OBJEXT) ./drawing.$(OBJEXT) \
+       ./dsf.$(OBJEXT) ./findloop.$(OBJEXT) ./gtk.$(OBJEXT) \
+       ./malloc.$(OBJEXT) ./midend.$(OBJEXT) ./misc.$(OBJEXT) \
+       ./printing.$(OBJEXT) ./ps.$(OBJEXT) ./random.$(OBJEXT) \
+       ./version.$(OBJEXT) icons/bridges-icon.$(OBJEXT)
+bridges_OBJECTS = $(am_bridges_OBJECTS)
+bridges_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_cube_OBJECTS = ./cube.$(OBJEXT) ./drawing.$(OBJEXT) ./gtk.$(OBJEXT) \
+       ./malloc.$(OBJEXT) ./midend.$(OBJEXT) ./misc.$(OBJEXT) \
+       ./printing.$(OBJEXT) ./ps.$(OBJEXT) ./random.$(OBJEXT) \
+       ./version.$(OBJEXT) icons/cube-icon.$(OBJEXT)
+cube_OBJECTS = $(am_cube_OBJECTS)
+cube_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_dominosa_OBJECTS = ./dominosa.$(OBJEXT) ./drawing.$(OBJEXT) \
+       ./gtk.$(OBJEXT) ./laydomino.$(OBJEXT) ./malloc.$(OBJEXT) \
+       ./midend.$(OBJEXT) ./misc.$(OBJEXT) ./printing.$(OBJEXT) \
+       ./ps.$(OBJEXT) ./random.$(OBJEXT) ./version.$(OBJEXT) \
+       icons/dominosa-icon.$(OBJEXT)
+dominosa_OBJECTS = $(am_dominosa_OBJECTS)
+dominosa_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_fifteen_OBJECTS = ./drawing.$(OBJEXT) ./fifteen.$(OBJEXT) \
+       ./gtk.$(OBJEXT) ./malloc.$(OBJEXT) ./midend.$(OBJEXT) \
+       ./misc.$(OBJEXT) ./printing.$(OBJEXT) ./ps.$(OBJEXT) \
+       ./random.$(OBJEXT) ./version.$(OBJEXT) \
+       icons/fifteen-icon.$(OBJEXT)
+fifteen_OBJECTS = $(am_fifteen_OBJECTS)
+fifteen_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_fifteensolver_OBJECTS = ./malloc.$(OBJEXT) ./misc.$(OBJEXT) \
+       ./nullfe.$(OBJEXT) ./random.$(OBJEXT)
+fifteensolver_OBJECTS = $(am_fifteensolver_OBJECTS)
+fifteensolver_DEPENDENCIES = libfifteen2_a-fifteen.$(OBJEXT)
+am_filling_OBJECTS = ./drawing.$(OBJEXT) ./dsf.$(OBJEXT) \
+       ./filling.$(OBJEXT) ./gtk.$(OBJEXT) ./malloc.$(OBJEXT) \
+       ./midend.$(OBJEXT) ./misc.$(OBJEXT) ./printing.$(OBJEXT) \
+       ./ps.$(OBJEXT) ./random.$(OBJEXT) ./version.$(OBJEXT) \
+       icons/filling-icon.$(OBJEXT)
+filling_OBJECTS = $(am_filling_OBJECTS)
+filling_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_fillingsolver_OBJECTS = ./dsf.$(OBJEXT) ./malloc.$(OBJEXT) \
+       ./misc.$(OBJEXT) ./nullfe.$(OBJEXT) ./random.$(OBJEXT)
+fillingsolver_OBJECTS = $(am_fillingsolver_OBJECTS)
+fillingsolver_DEPENDENCIES = libfilling2_a-filling.$(OBJEXT)
+am_flip_OBJECTS = ./drawing.$(OBJEXT) ./flip.$(OBJEXT) ./gtk.$(OBJEXT) \
+       ./malloc.$(OBJEXT) ./midend.$(OBJEXT) ./misc.$(OBJEXT) \
+       ./printing.$(OBJEXT) ./ps.$(OBJEXT) ./random.$(OBJEXT) \
+       ./tree234.$(OBJEXT) ./version.$(OBJEXT) \
+       icons/flip-icon.$(OBJEXT)
+flip_OBJECTS = $(am_flip_OBJECTS)
+flip_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_flood_OBJECTS = ./drawing.$(OBJEXT) ./flood.$(OBJEXT) \
+       ./gtk.$(OBJEXT) ./malloc.$(OBJEXT) ./midend.$(OBJEXT) \
+       ./misc.$(OBJEXT) ./printing.$(OBJEXT) ./ps.$(OBJEXT) \
+       ./random.$(OBJEXT) ./version.$(OBJEXT) \
+       icons/flood-icon.$(OBJEXT)
+flood_OBJECTS = $(am_flood_OBJECTS)
+flood_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_galaxies_OBJECTS = ./drawing.$(OBJEXT) ./dsf.$(OBJEXT) \
+       ./galaxies.$(OBJEXT) ./gtk.$(OBJEXT) ./malloc.$(OBJEXT) \
+       ./midend.$(OBJEXT) ./misc.$(OBJEXT) ./printing.$(OBJEXT) \
+       ./ps.$(OBJEXT) ./random.$(OBJEXT) ./version.$(OBJEXT) \
+       icons/galaxies-icon.$(OBJEXT)
+galaxies_OBJECTS = $(am_galaxies_OBJECTS)
+galaxies_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_galaxiespicture_OBJECTS = ./dsf.$(OBJEXT) ./malloc.$(OBJEXT) \
+       ./misc.$(OBJEXT) ./nullfe.$(OBJEXT) ./random.$(OBJEXT)
+galaxiespicture_OBJECTS = $(am_galaxiespicture_OBJECTS)
+galaxiespicture_DEPENDENCIES = libgalaxie4_a-galaxies.$(OBJEXT)
+am_galaxiessolver_OBJECTS = ./dsf.$(OBJEXT) ./malloc.$(OBJEXT) \
+       ./misc.$(OBJEXT) ./nullfe.$(OBJEXT) ./random.$(OBJEXT)
+galaxiessolver_OBJECTS = $(am_galaxiessolver_OBJECTS)
+galaxiessolver_DEPENDENCIES = libgalaxie2_a-galaxies.$(OBJEXT)
+am_guess_OBJECTS = ./drawing.$(OBJEXT) ./gtk.$(OBJEXT) \
+       ./guess.$(OBJEXT) ./malloc.$(OBJEXT) ./midend.$(OBJEXT) \
+       ./misc.$(OBJEXT) ./printing.$(OBJEXT) ./ps.$(OBJEXT) \
+       ./random.$(OBJEXT) ./version.$(OBJEXT) \
+       icons/guess-icon.$(OBJEXT)
+guess_OBJECTS = $(am_guess_OBJECTS)
+guess_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_inertia_OBJECTS = ./drawing.$(OBJEXT) ./gtk.$(OBJEXT) \
+       ./inertia.$(OBJEXT) ./malloc.$(OBJEXT) ./midend.$(OBJEXT) \
+       ./misc.$(OBJEXT) ./printing.$(OBJEXT) ./ps.$(OBJEXT) \
+       ./random.$(OBJEXT) ./version.$(OBJEXT) \
+       icons/inertia-icon.$(OBJEXT)
+inertia_OBJECTS = $(am_inertia_OBJECTS)
+inertia_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_keen_OBJECTS = ./drawing.$(OBJEXT) ./dsf.$(OBJEXT) ./gtk.$(OBJEXT) \
+       ./keen.$(OBJEXT) ./latin.$(OBJEXT) ./malloc.$(OBJEXT) \
+       ./maxflow.$(OBJEXT) ./midend.$(OBJEXT) ./misc.$(OBJEXT) \
+       ./printing.$(OBJEXT) ./ps.$(OBJEXT) ./random.$(OBJEXT) \
+       ./tree234.$(OBJEXT) ./version.$(OBJEXT) \
+       icons/keen-icon.$(OBJEXT)
+keen_OBJECTS = $(am_keen_OBJECTS)
+keen_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_keensolver_OBJECTS = ./dsf.$(OBJEXT) ./malloc.$(OBJEXT) \
+       ./maxflow.$(OBJEXT) ./misc.$(OBJEXT) ./nullfe.$(OBJEXT) \
+       ./random.$(OBJEXT) ./tree234.$(OBJEXT)
+keensolver_OBJECTS = $(am_keensolver_OBJECTS)
+keensolver_DEPENDENCIES = libkeen2_a-keen.$(OBJEXT) \
+       liblatin6_a-latin.$(OBJEXT)
+am_latincheck_OBJECTS = ./malloc.$(OBJEXT) ./maxflow.$(OBJEXT) \
+       ./misc.$(OBJEXT) ./nullfe.$(OBJEXT) ./random.$(OBJEXT) \
+       ./tree234.$(OBJEXT)
+latincheck_OBJECTS = $(am_latincheck_OBJECTS)
+latincheck_DEPENDENCIES = liblatin8_a-latin.$(OBJEXT)
+am_lightup_OBJECTS = ./combi.$(OBJEXT) ./drawing.$(OBJEXT) \
+       ./gtk.$(OBJEXT) ./lightup.$(OBJEXT) ./malloc.$(OBJEXT) \
+       ./midend.$(OBJEXT) ./misc.$(OBJEXT) ./printing.$(OBJEXT) \
+       ./ps.$(OBJEXT) ./random.$(OBJEXT) ./version.$(OBJEXT) \
+       icons/lightup-icon.$(OBJEXT)
+lightup_OBJECTS = $(am_lightup_OBJECTS)
+lightup_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_lightupsolver_OBJECTS = ./combi.$(OBJEXT) ./malloc.$(OBJEXT) \
+       ./misc.$(OBJEXT) ./nullfe.$(OBJEXT) ./random.$(OBJEXT)
+lightupsolver_OBJECTS = $(am_lightupsolver_OBJECTS)
+lightupsolver_DEPENDENCIES = liblightup2_a-lightup.$(OBJEXT)
+am_loopy_OBJECTS = ./drawing.$(OBJEXT) ./dsf.$(OBJEXT) \
+       ./grid.$(OBJEXT) ./gtk.$(OBJEXT) ./loopgen.$(OBJEXT) \
+       ./loopy.$(OBJEXT) ./malloc.$(OBJEXT) ./midend.$(OBJEXT) \
+       ./misc.$(OBJEXT) ./penrose.$(OBJEXT) ./printing.$(OBJEXT) \
+       ./ps.$(OBJEXT) ./random.$(OBJEXT) ./tree234.$(OBJEXT) \
+       ./version.$(OBJEXT) icons/loopy-icon.$(OBJEXT)
+loopy_OBJECTS = $(am_loopy_OBJECTS)
+loopy_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_loopysolver_OBJECTS = ./dsf.$(OBJEXT) ./grid.$(OBJEXT) \
+       ./loopgen.$(OBJEXT) ./malloc.$(OBJEXT) ./misc.$(OBJEXT) \
+       ./nullfe.$(OBJEXT) ./penrose.$(OBJEXT) ./random.$(OBJEXT) \
+       ./tree234.$(OBJEXT)
+loopysolver_OBJECTS = $(am_loopysolver_OBJECTS)
+loopysolver_DEPENDENCIES = libloopy2_a-loopy.$(OBJEXT)
+am_magnets_OBJECTS = ./drawing.$(OBJEXT) ./gtk.$(OBJEXT) \
+       ./laydomino.$(OBJEXT) ./magnets.$(OBJEXT) ./malloc.$(OBJEXT) \
+       ./midend.$(OBJEXT) ./misc.$(OBJEXT) ./printing.$(OBJEXT) \
+       ./ps.$(OBJEXT) ./random.$(OBJEXT) ./version.$(OBJEXT) \
+       icons/magnets-icon.$(OBJEXT)
+magnets_OBJECTS = $(am_magnets_OBJECTS)
+magnets_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_magnetssolver_OBJECTS = ./laydomino.$(OBJEXT) ./malloc.$(OBJEXT) \
+       ./misc.$(OBJEXT) ./nullfe.$(OBJEXT) ./random.$(OBJEXT)
+magnetssolver_OBJECTS = $(am_magnetssolver_OBJECTS)
+magnetssolver_DEPENDENCIES = libmagnets2_a-magnets.$(OBJEXT)
+am_map_OBJECTS = ./drawing.$(OBJEXT) ./dsf.$(OBJEXT) ./gtk.$(OBJEXT) \
+       ./malloc.$(OBJEXT) ./map.$(OBJEXT) ./midend.$(OBJEXT) \
+       ./misc.$(OBJEXT) ./printing.$(OBJEXT) ./ps.$(OBJEXT) \
+       ./random.$(OBJEXT) ./version.$(OBJEXT) \
+       icons/map-icon.$(OBJEXT)
+map_OBJECTS = $(am_map_OBJECTS)
+map_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_mapsolver_OBJECTS = ./dsf.$(OBJEXT) ./malloc.$(OBJEXT) \
+       ./misc.$(OBJEXT) ./nullfe.$(OBJEXT) ./random.$(OBJEXT)
+mapsolver_OBJECTS = $(am_mapsolver_OBJECTS)
+mapsolver_DEPENDENCIES = libmap2_a-map.$(OBJEXT)
+am_mineobfusc_OBJECTS = ./malloc.$(OBJEXT) ./misc.$(OBJEXT) \
+       ./nullfe.$(OBJEXT) ./random.$(OBJEXT) ./tree234.$(OBJEXT)
+mineobfusc_OBJECTS = $(am_mineobfusc_OBJECTS)
+mineobfusc_DEPENDENCIES = libmines2_a-mines.$(OBJEXT)
+am_mines_OBJECTS = ./drawing.$(OBJEXT) ./gtk.$(OBJEXT) \
+       ./malloc.$(OBJEXT) ./midend.$(OBJEXT) ./mines.$(OBJEXT) \
+       ./misc.$(OBJEXT) ./printing.$(OBJEXT) ./ps.$(OBJEXT) \
+       ./random.$(OBJEXT) ./tree234.$(OBJEXT) ./version.$(OBJEXT) \
+       icons/mines-icon.$(OBJEXT)
+mines_OBJECTS = $(am_mines_OBJECTS)
+mines_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_net_OBJECTS = ./drawing.$(OBJEXT) ./dsf.$(OBJEXT) \
+       ./findloop.$(OBJEXT) ./gtk.$(OBJEXT) ./malloc.$(OBJEXT) \
+       ./midend.$(OBJEXT) ./misc.$(OBJEXT) ./net.$(OBJEXT) \
+       ./printing.$(OBJEXT) ./ps.$(OBJEXT) ./random.$(OBJEXT) \
+       ./tree234.$(OBJEXT) ./version.$(OBJEXT) \
+       icons/net-icon.$(OBJEXT)
+net_OBJECTS = $(am_net_OBJECTS)
+net_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_netslide_OBJECTS = ./drawing.$(OBJEXT) ./gtk.$(OBJEXT) \
+       ./malloc.$(OBJEXT) ./midend.$(OBJEXT) ./misc.$(OBJEXT) \
+       ./netslide.$(OBJEXT) ./printing.$(OBJEXT) ./ps.$(OBJEXT) \
+       ./random.$(OBJEXT) ./tree234.$(OBJEXT) ./version.$(OBJEXT) \
+       icons/netslide-icon.$(OBJEXT)
+netslide_OBJECTS = $(am_netslide_OBJECTS)
+netslide_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_nullgame_OBJECTS = ./drawing.$(OBJEXT) ./gtk.$(OBJEXT) \
+       ./malloc.$(OBJEXT) ./midend.$(OBJEXT) ./misc.$(OBJEXT) \
+       ./no-icon.$(OBJEXT) ./nullgame.$(OBJEXT) ./printing.$(OBJEXT) \
+       ./ps.$(OBJEXT) ./random.$(OBJEXT) ./version.$(OBJEXT)
+nullgame_OBJECTS = $(am_nullgame_OBJECTS)
+nullgame_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_obfusc_OBJECTS = ./malloc.$(OBJEXT) ./misc.$(OBJEXT) \
+       ./nullfe.$(OBJEXT) ./obfusc.$(OBJEXT) ./random.$(OBJEXT)
+obfusc_OBJECTS = $(am_obfusc_OBJECTS)
+obfusc_DEPENDENCIES =
+am_palisade_OBJECTS = ./divvy.$(OBJEXT) ./drawing.$(OBJEXT) \
+       ./dsf.$(OBJEXT) ./gtk.$(OBJEXT) ./malloc.$(OBJEXT) \
+       ./midend.$(OBJEXT) ./misc.$(OBJEXT) ./palisade.$(OBJEXT) \
+       ./printing.$(OBJEXT) ./ps.$(OBJEXT) ./random.$(OBJEXT) \
+       ./version.$(OBJEXT) icons/palisade-icon.$(OBJEXT)
+palisade_OBJECTS = $(am_palisade_OBJECTS)
+palisade_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_pattern_OBJECTS = ./drawing.$(OBJEXT) ./gtk.$(OBJEXT) \
+       ./malloc.$(OBJEXT) ./midend.$(OBJEXT) ./misc.$(OBJEXT) \
+       ./pattern.$(OBJEXT) ./printing.$(OBJEXT) ./ps.$(OBJEXT) \
+       ./random.$(OBJEXT) ./version.$(OBJEXT) \
+       icons/pattern-icon.$(OBJEXT)
+pattern_OBJECTS = $(am_pattern_OBJECTS)
+pattern_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_patternpicture_OBJECTS = ./malloc.$(OBJEXT) ./misc.$(OBJEXT) \
+       ./nullfe.$(OBJEXT) ./random.$(OBJEXT)
+patternpicture_OBJECTS = $(am_patternpicture_OBJECTS)
+patternpicture_DEPENDENCIES = libpattern4_a-pattern.$(OBJEXT)
+am_patternsolver_OBJECTS = ./malloc.$(OBJEXT) ./misc.$(OBJEXT) \
+       ./nullfe.$(OBJEXT) ./random.$(OBJEXT)
+patternsolver_OBJECTS = $(am_patternsolver_OBJECTS)
+patternsolver_DEPENDENCIES = libpattern2_a-pattern.$(OBJEXT)
+am_pearl_OBJECTS = ./drawing.$(OBJEXT) ./dsf.$(OBJEXT) \
+       ./grid.$(OBJEXT) ./gtk.$(OBJEXT) ./loopgen.$(OBJEXT) \
+       ./malloc.$(OBJEXT) ./midend.$(OBJEXT) ./misc.$(OBJEXT) \
+       ./pearl.$(OBJEXT) ./penrose.$(OBJEXT) ./printing.$(OBJEXT) \
+       ./ps.$(OBJEXT) ./random.$(OBJEXT) ./tdq.$(OBJEXT) \
+       ./tree234.$(OBJEXT) ./version.$(OBJEXT) \
+       icons/pearl-icon.$(OBJEXT)
+pearl_OBJECTS = $(am_pearl_OBJECTS)
+pearl_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_pearlbench_OBJECTS = ./dsf.$(OBJEXT) ./grid.$(OBJEXT) \
+       ./loopgen.$(OBJEXT) ./malloc.$(OBJEXT) ./misc.$(OBJEXT) \
+       ./nullfe.$(OBJEXT) ./penrose.$(OBJEXT) ./random.$(OBJEXT) \
+       ./tdq.$(OBJEXT) ./tree234.$(OBJEXT)
+pearlbench_OBJECTS = $(am_pearlbench_OBJECTS)
+pearlbench_DEPENDENCIES = libpearl2_a-pearl.$(OBJEXT)
+am_pegs_OBJECTS = ./drawing.$(OBJEXT) ./gtk.$(OBJEXT) \
+       ./malloc.$(OBJEXT) ./midend.$(OBJEXT) ./misc.$(OBJEXT) \
+       ./pegs.$(OBJEXT) ./printing.$(OBJEXT) ./ps.$(OBJEXT) \
+       ./random.$(OBJEXT) ./tree234.$(OBJEXT) ./version.$(OBJEXT) \
+       icons/pegs-icon.$(OBJEXT)
+pegs_OBJECTS = $(am_pegs_OBJECTS)
+pegs_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_range_OBJECTS = ./drawing.$(OBJEXT) ./dsf.$(OBJEXT) ./gtk.$(OBJEXT) \
+       ./malloc.$(OBJEXT) ./midend.$(OBJEXT) ./misc.$(OBJEXT) \
+       ./printing.$(OBJEXT) ./ps.$(OBJEXT) ./random.$(OBJEXT) \
+       ./range.$(OBJEXT) ./version.$(OBJEXT) \
+       icons/range-icon.$(OBJEXT)
+range_OBJECTS = $(am_range_OBJECTS)
+range_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_rect_OBJECTS = ./drawing.$(OBJEXT) ./gtk.$(OBJEXT) \
+       ./malloc.$(OBJEXT) ./midend.$(OBJEXT) ./misc.$(OBJEXT) \
+       ./printing.$(OBJEXT) ./ps.$(OBJEXT) ./random.$(OBJEXT) \
+       ./rect.$(OBJEXT) ./version.$(OBJEXT) icons/rect-icon.$(OBJEXT)
+rect_OBJECTS = $(am_rect_OBJECTS)
+rect_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_samegame_OBJECTS = ./drawing.$(OBJEXT) ./gtk.$(OBJEXT) \
+       ./malloc.$(OBJEXT) ./midend.$(OBJEXT) ./misc.$(OBJEXT) \
+       ./printing.$(OBJEXT) ./ps.$(OBJEXT) ./random.$(OBJEXT) \
+       ./samegame.$(OBJEXT) ./version.$(OBJEXT) \
+       icons/samegame-icon.$(OBJEXT)
+samegame_OBJECTS = $(am_samegame_OBJECTS)
+samegame_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_signpost_OBJECTS = ./drawing.$(OBJEXT) ./dsf.$(OBJEXT) \
+       ./gtk.$(OBJEXT) ./malloc.$(OBJEXT) ./midend.$(OBJEXT) \
+       ./misc.$(OBJEXT) ./printing.$(OBJEXT) ./ps.$(OBJEXT) \
+       ./random.$(OBJEXT) ./signpost.$(OBJEXT) ./version.$(OBJEXT) \
+       icons/signpost-icon.$(OBJEXT)
+signpost_OBJECTS = $(am_signpost_OBJECTS)
+signpost_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_signpostsolver_OBJECTS = ./dsf.$(OBJEXT) ./malloc.$(OBJEXT) \
+       ./misc.$(OBJEXT) ./nullfe.$(OBJEXT) ./random.$(OBJEXT)
+signpostsolver_OBJECTS = $(am_signpostsolver_OBJECTS)
+signpostsolver_DEPENDENCIES = libsignpos2_a-signpost.$(OBJEXT)
+am_singles_OBJECTS = ./drawing.$(OBJEXT) ./dsf.$(OBJEXT) \
+       ./gtk.$(OBJEXT) ./latin.$(OBJEXT) ./malloc.$(OBJEXT) \
+       ./maxflow.$(OBJEXT) ./midend.$(OBJEXT) ./misc.$(OBJEXT) \
+       ./printing.$(OBJEXT) ./ps.$(OBJEXT) ./random.$(OBJEXT) \
+       ./singles.$(OBJEXT) ./tree234.$(OBJEXT) ./version.$(OBJEXT) \
+       icons/singles-icon.$(OBJEXT)
+singles_OBJECTS = $(am_singles_OBJECTS)
+singles_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_singlessolver_OBJECTS = ./dsf.$(OBJEXT) ./latin.$(OBJEXT) \
+       ./malloc.$(OBJEXT) ./maxflow.$(OBJEXT) ./misc.$(OBJEXT) \
+       ./nullfe.$(OBJEXT) ./random.$(OBJEXT) ./tree234.$(OBJEXT)
+singlessolver_OBJECTS = $(am_singlessolver_OBJECTS)
+singlessolver_DEPENDENCIES = libsingles3_a-singles.$(OBJEXT)
+am_sixteen_OBJECTS = ./drawing.$(OBJEXT) ./gtk.$(OBJEXT) \
+       ./malloc.$(OBJEXT) ./midend.$(OBJEXT) ./misc.$(OBJEXT) \
+       ./printing.$(OBJEXT) ./ps.$(OBJEXT) ./random.$(OBJEXT) \
+       ./sixteen.$(OBJEXT) ./version.$(OBJEXT) \
+       icons/sixteen-icon.$(OBJEXT)
+sixteen_OBJECTS = $(am_sixteen_OBJECTS)
+sixteen_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_slant_OBJECTS = ./drawing.$(OBJEXT) ./dsf.$(OBJEXT) \
+       ./findloop.$(OBJEXT) ./gtk.$(OBJEXT) ./malloc.$(OBJEXT) \
+       ./midend.$(OBJEXT) ./misc.$(OBJEXT) ./printing.$(OBJEXT) \
+       ./ps.$(OBJEXT) ./random.$(OBJEXT) ./slant.$(OBJEXT) \
+       ./version.$(OBJEXT) icons/slant-icon.$(OBJEXT)
+slant_OBJECTS = $(am_slant_OBJECTS)
+slant_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_slantsolver_OBJECTS = ./dsf.$(OBJEXT) ./findloop.$(OBJEXT) \
+       ./malloc.$(OBJEXT) ./misc.$(OBJEXT) ./nullfe.$(OBJEXT) \
+       ./random.$(OBJEXT)
+slantsolver_OBJECTS = $(am_slantsolver_OBJECTS)
+slantsolver_DEPENDENCIES = libslant2_a-slant.$(OBJEXT)
+am_solo_OBJECTS = ./divvy.$(OBJEXT) ./drawing.$(OBJEXT) \
+       ./dsf.$(OBJEXT) ./gtk.$(OBJEXT) ./malloc.$(OBJEXT) \
+       ./midend.$(OBJEXT) ./misc.$(OBJEXT) ./printing.$(OBJEXT) \
+       ./ps.$(OBJEXT) ./random.$(OBJEXT) ./solo.$(OBJEXT) \
+       ./version.$(OBJEXT) icons/solo-icon.$(OBJEXT)
+solo_OBJECTS = $(am_solo_OBJECTS)
+solo_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_solosolver_OBJECTS = ./divvy.$(OBJEXT) ./dsf.$(OBJEXT) \
+       ./malloc.$(OBJEXT) ./misc.$(OBJEXT) ./nullfe.$(OBJEXT) \
+       ./random.$(OBJEXT)
+solosolver_OBJECTS = $(am_solosolver_OBJECTS)
+solosolver_DEPENDENCIES = libsolo2_a-solo.$(OBJEXT)
+am_tents_OBJECTS = ./drawing.$(OBJEXT) ./dsf.$(OBJEXT) ./gtk.$(OBJEXT) \
+       ./malloc.$(OBJEXT) ./maxflow.$(OBJEXT) ./midend.$(OBJEXT) \
+       ./misc.$(OBJEXT) ./printing.$(OBJEXT) ./ps.$(OBJEXT) \
+       ./random.$(OBJEXT) ./tents.$(OBJEXT) ./version.$(OBJEXT) \
+       icons/tents-icon.$(OBJEXT)
+tents_OBJECTS = $(am_tents_OBJECTS)
+tents_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_tentssolver_OBJECTS = ./dsf.$(OBJEXT) ./malloc.$(OBJEXT) \
+       ./maxflow.$(OBJEXT) ./misc.$(OBJEXT) ./nullfe.$(OBJEXT) \
+       ./random.$(OBJEXT)
+tentssolver_OBJECTS = $(am_tentssolver_OBJECTS)
+tentssolver_DEPENDENCIES = libtents3_a-tents.$(OBJEXT)
+am_towers_OBJECTS = ./drawing.$(OBJEXT) ./gtk.$(OBJEXT) \
+       ./latin.$(OBJEXT) ./malloc.$(OBJEXT) ./maxflow.$(OBJEXT) \
+       ./midend.$(OBJEXT) ./misc.$(OBJEXT) ./printing.$(OBJEXT) \
+       ./ps.$(OBJEXT) ./random.$(OBJEXT) ./towers.$(OBJEXT) \
+       ./tree234.$(OBJEXT) ./version.$(OBJEXT) \
+       icons/towers-icon.$(OBJEXT)
+towers_OBJECTS = $(am_towers_OBJECTS)
+towers_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_towerssolver_OBJECTS = ./malloc.$(OBJEXT) ./maxflow.$(OBJEXT) \
+       ./misc.$(OBJEXT) ./nullfe.$(OBJEXT) ./random.$(OBJEXT) \
+       ./tree234.$(OBJEXT)
+towerssolver_OBJECTS = $(am_towerssolver_OBJECTS)
+towerssolver_DEPENDENCIES = liblatin6_a-latin.$(OBJEXT) \
+       libtowers2_a-towers.$(OBJEXT)
+am_tracks_OBJECTS = ./drawing.$(OBJEXT) ./dsf.$(OBJEXT) \
+       ./findloop.$(OBJEXT) ./gtk.$(OBJEXT) ./malloc.$(OBJEXT) \
+       ./midend.$(OBJEXT) ./misc.$(OBJEXT) ./printing.$(OBJEXT) \
+       ./ps.$(OBJEXT) ./random.$(OBJEXT) ./tracks.$(OBJEXT) \
+       ./version.$(OBJEXT) icons/tracks-icon.$(OBJEXT)
+tracks_OBJECTS = $(am_tracks_OBJECTS)
+tracks_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_twiddle_OBJECTS = ./drawing.$(OBJEXT) ./gtk.$(OBJEXT) \
+       ./malloc.$(OBJEXT) ./midend.$(OBJEXT) ./misc.$(OBJEXT) \
+       ./printing.$(OBJEXT) ./ps.$(OBJEXT) ./random.$(OBJEXT) \
+       ./twiddle.$(OBJEXT) ./version.$(OBJEXT) \
+       icons/twiddle-icon.$(OBJEXT)
+twiddle_OBJECTS = $(am_twiddle_OBJECTS)
+twiddle_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_undead_OBJECTS = ./drawing.$(OBJEXT) ./gtk.$(OBJEXT) \
+       ./malloc.$(OBJEXT) ./midend.$(OBJEXT) ./misc.$(OBJEXT) \
+       ./printing.$(OBJEXT) ./ps.$(OBJEXT) ./random.$(OBJEXT) \
+       ./undead.$(OBJEXT) ./version.$(OBJEXT) \
+       icons/undead-icon.$(OBJEXT)
+undead_OBJECTS = $(am_undead_OBJECTS)
+undead_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_unequal_OBJECTS = ./drawing.$(OBJEXT) ./gtk.$(OBJEXT) \
+       ./latin.$(OBJEXT) ./malloc.$(OBJEXT) ./maxflow.$(OBJEXT) \
+       ./midend.$(OBJEXT) ./misc.$(OBJEXT) ./printing.$(OBJEXT) \
+       ./ps.$(OBJEXT) ./random.$(OBJEXT) ./tree234.$(OBJEXT) \
+       ./unequal.$(OBJEXT) ./version.$(OBJEXT) \
+       icons/unequal-icon.$(OBJEXT)
+unequal_OBJECTS = $(am_unequal_OBJECTS)
+unequal_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_unequalsolver_OBJECTS = ./malloc.$(OBJEXT) ./maxflow.$(OBJEXT) \
+       ./misc.$(OBJEXT) ./nullfe.$(OBJEXT) ./random.$(OBJEXT) \
+       ./tree234.$(OBJEXT)
+unequalsolver_OBJECTS = $(am_unequalsolver_OBJECTS)
+unequalsolver_DEPENDENCIES = liblatin6_a-latin.$(OBJEXT) \
+       libunequal2_a-unequal.$(OBJEXT)
+am_unruly_OBJECTS = ./drawing.$(OBJEXT) ./gtk.$(OBJEXT) \
+       ./malloc.$(OBJEXT) ./midend.$(OBJEXT) ./misc.$(OBJEXT) \
+       ./printing.$(OBJEXT) ./ps.$(OBJEXT) ./random.$(OBJEXT) \
+       ./unruly.$(OBJEXT) ./version.$(OBJEXT) \
+       icons/unruly-icon.$(OBJEXT)
+unruly_OBJECTS = $(am_unruly_OBJECTS)
+unruly_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am_unrulysolver_OBJECTS = ./malloc.$(OBJEXT) ./misc.$(OBJEXT) \
+       ./nullfe.$(OBJEXT) ./random.$(OBJEXT)
+unrulysolver_OBJECTS = $(am_unrulysolver_OBJECTS)
+unrulysolver_DEPENDENCIES = libunruly2_a-unruly.$(OBJEXT)
+am_untangle_OBJECTS = ./drawing.$(OBJEXT) ./gtk.$(OBJEXT) \
+       ./malloc.$(OBJEXT) ./midend.$(OBJEXT) ./misc.$(OBJEXT) \
+       ./printing.$(OBJEXT) ./ps.$(OBJEXT) ./random.$(OBJEXT) \
+       ./tree234.$(OBJEXT) ./untangle.$(OBJEXT) ./version.$(OBJEXT) \
+       icons/untangle-icon.$(OBJEXT)
+untangle_OBJECTS = $(am_untangle_OBJECTS)
+untangle_DEPENDENCIES = $(am__DEPENDENCIES_1)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo "  GEN     " $@;
+am__v_GEN_1 = 
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 = 
+DEFAULT_INCLUDES = -I.@am__isrc@
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 = 
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+       $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo "  CC      " $@;
+am__v_CC_1 = 
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo "  CCLD    " $@;
+am__v_CCLD_1 = 
+SOURCES = $(libfifteen2_a_SOURCES) $(libfilling2_a_SOURCES) \
+       $(libgalaxie2_a_SOURCES) $(libgalaxie4_a_SOURCES) \
+       $(libkeen2_a_SOURCES) $(liblatin6_a_SOURCES) \
+       $(liblatin8_a_SOURCES) $(liblightup2_a_SOURCES) \
+       $(libloopy2_a_SOURCES) $(libmagnets2_a_SOURCES) \
+       $(libmap2_a_SOURCES) $(libmines2_a_SOURCES) \
+       $(libpattern2_a_SOURCES) $(libpattern4_a_SOURCES) \
+       $(libpearl2_a_SOURCES) $(libsignpos2_a_SOURCES) \
+       $(libsingles3_a_SOURCES) $(libslant2_a_SOURCES) \
+       $(libsolo2_a_SOURCES) $(libtents3_a_SOURCES) \
+       $(libtowers2_a_SOURCES) $(libunequal2_a_SOURCES) \
+       $(libunruly2_a_SOURCES) $(blackbox_SOURCES) $(bridges_SOURCES) \
+       $(cube_SOURCES) $(dominosa_SOURCES) $(fifteen_SOURCES) \
+       $(fifteensolver_SOURCES) $(filling_SOURCES) \
+       $(fillingsolver_SOURCES) $(flip_SOURCES) $(flood_SOURCES) \
+       $(galaxies_SOURCES) $(galaxiespicture_SOURCES) \
+       $(galaxiessolver_SOURCES) $(guess_SOURCES) $(inertia_SOURCES) \
+       $(keen_SOURCES) $(keensolver_SOURCES) $(latincheck_SOURCES) \
+       $(lightup_SOURCES) $(lightupsolver_SOURCES) $(loopy_SOURCES) \
+       $(loopysolver_SOURCES) $(magnets_SOURCES) \
+       $(magnetssolver_SOURCES) $(map_SOURCES) $(mapsolver_SOURCES) \
+       $(mineobfusc_SOURCES) $(mines_SOURCES) $(net_SOURCES) \
+       $(netslide_SOURCES) $(nullgame_SOURCES) $(obfusc_SOURCES) \
+       $(palisade_SOURCES) $(pattern_SOURCES) \
+       $(patternpicture_SOURCES) $(patternsolver_SOURCES) \
+       $(pearl_SOURCES) $(pearlbench_SOURCES) $(pegs_SOURCES) \
+       $(range_SOURCES) $(rect_SOURCES) $(samegame_SOURCES) \
+       $(signpost_SOURCES) $(signpostsolver_SOURCES) \
+       $(singles_SOURCES) $(singlessolver_SOURCES) $(sixteen_SOURCES) \
+       $(slant_SOURCES) $(slantsolver_SOURCES) $(solo_SOURCES) \
+       $(solosolver_SOURCES) $(tents_SOURCES) $(tentssolver_SOURCES) \
+       $(towers_SOURCES) $(towerssolver_SOURCES) $(tracks_SOURCES) \
+       $(twiddle_SOURCES) $(undead_SOURCES) $(unequal_SOURCES) \
+       $(unequalsolver_SOURCES) $(unruly_SOURCES) \
+       $(unrulysolver_SOURCES) $(untangle_SOURCES)
+DIST_SOURCES = $(libfifteen2_a_SOURCES) $(libfilling2_a_SOURCES) \
+       $(libgalaxie2_a_SOURCES) $(libgalaxie4_a_SOURCES) \
+       $(libkeen2_a_SOURCES) $(liblatin6_a_SOURCES) \
+       $(liblatin8_a_SOURCES) $(liblightup2_a_SOURCES) \
+       $(libloopy2_a_SOURCES) $(libmagnets2_a_SOURCES) \
+       $(libmap2_a_SOURCES) $(libmines2_a_SOURCES) \
+       $(libpattern2_a_SOURCES) $(libpattern4_a_SOURCES) \
+       $(libpearl2_a_SOURCES) $(libsignpos2_a_SOURCES) \
+       $(libsingles3_a_SOURCES) $(libslant2_a_SOURCES) \
+       $(libsolo2_a_SOURCES) $(libtents3_a_SOURCES) \
+       $(libtowers2_a_SOURCES) $(libunequal2_a_SOURCES) \
+       $(libunruly2_a_SOURCES) $(blackbox_SOURCES) $(bridges_SOURCES) \
+       $(cube_SOURCES) $(dominosa_SOURCES) $(fifteen_SOURCES) \
+       $(fifteensolver_SOURCES) $(filling_SOURCES) \
+       $(fillingsolver_SOURCES) $(flip_SOURCES) $(flood_SOURCES) \
+       $(galaxies_SOURCES) $(galaxiespicture_SOURCES) \
+       $(galaxiessolver_SOURCES) $(guess_SOURCES) $(inertia_SOURCES) \
+       $(keen_SOURCES) $(keensolver_SOURCES) $(latincheck_SOURCES) \
+       $(lightup_SOURCES) $(lightupsolver_SOURCES) $(loopy_SOURCES) \
+       $(loopysolver_SOURCES) $(magnets_SOURCES) \
+       $(magnetssolver_SOURCES) $(map_SOURCES) $(mapsolver_SOURCES) \
+       $(mineobfusc_SOURCES) $(mines_SOURCES) $(net_SOURCES) \
+       $(netslide_SOURCES) $(nullgame_SOURCES) $(obfusc_SOURCES) \
+       $(palisade_SOURCES) $(pattern_SOURCES) \
+       $(patternpicture_SOURCES) $(patternsolver_SOURCES) \
+       $(pearl_SOURCES) $(pearlbench_SOURCES) $(pegs_SOURCES) \
+       $(range_SOURCES) $(rect_SOURCES) $(samegame_SOURCES) \
+       $(signpost_SOURCES) $(signpostsolver_SOURCES) \
+       $(singles_SOURCES) $(singlessolver_SOURCES) $(sixteen_SOURCES) \
+       $(slant_SOURCES) $(slantsolver_SOURCES) $(solo_SOURCES) \
+       $(solosolver_SOURCES) $(tents_SOURCES) $(tentssolver_SOURCES) \
+       $(towers_SOURCES) $(towerssolver_SOURCES) $(tracks_SOURCES) \
+       $(twiddle_SOURCES) $(undead_SOURCES) $(unequal_SOURCES) \
+       $(unequalsolver_SOURCES) $(unruly_SOURCES) \
+       $(unrulysolver_SOURCES) $(untangle_SOURCES)
+am__can_run_installinfo = \
+  case $$AM_UPDATE_INFO_DIR in \
+    n|no|NO) false;; \
+    *) (install-info --version) >/dev/null 2>&1;; \
+  esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates.  Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+  BEGIN { nonempty = 0; } \
+  { items[$$0] = 1; nonempty = 1; } \
+  END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique.  This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+  list='$(am__tagged_files)'; \
+  unique=`for i in $$list; do \
+    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+  done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+CSCOPE = cscope
+AM_RECURSIVE_TARGETS = cscope
+am__DIST_COMMON = $(srcdir)/Makefile.in README compile depcomp \
+       install-sh missing
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+distdir = $(PACKAGE)-$(VERSION)
+top_distdir = $(distdir)
+am__remove_distdir = \
+  if test -d "$(distdir)"; then \
+    find "$(distdir)" -type d ! -perm -200 -exec chmod u+w {} ';' \
+      && rm -rf "$(distdir)" \
+      || { sleep 5 && rm -rf "$(distdir)"; }; \
+  else :; fi
+am__post_remove_distdir = $(am__remove_distdir)
+DIST_ARCHIVES = $(distdir).tar.gz
+GZIP_ENV = --best
+DIST_TARGETS = dist-gzip
+distuninstallcheck_listfiles = find . -type f -print
+am__distuninstallcheck_listfiles = $(distuninstallcheck_listfiles) \
+  | sed 's|^\./|$(prefix)/|' | grep -v '$(infodir)/dir$$'
+distcleancheck_listfiles = find . -type f -print
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EXEEXT = @EXEEXT@
+GTK_CFLAGS = @GTK_CFLAGS@
+GTK_LIBS = @GTK_LIBS@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+GAMES = blackbox bridges cube dominosa fifteen filling flip flood \
+       galaxies guess inertia keen lightup loopy magnets map mines \
+       net netslide palisade pattern pearl pegs range rect samegame \
+       signpost singles sixteen slant solo tents towers tracks \
+       twiddle undead unequal unruly untangle
+AUTOMAKE_OPTIONS = subdir-objects
+allsources = ./blackbox.c ./bridges.c ./combi.c ./cube.c ./divvy.c \
+               ./dominosa.c ./drawing.c ./dsf.c ./fifteen.c ./filling.c \
+               ./findloop.c ./flip.c ./flood.c ./galaxies.c ./grid.c \
+               ./grid.h ./gtk.c ./guess.c ./inertia.c ./keen.c ./latin.c \
+               ./latin.h ./laydomino.c ./lightup.c ./list.c ./loopgen.c \
+               ./loopgen.h ./loopy.c ./magnets.c ./malloc.c ./map.c \
+               ./maxflow.c ./maxflow.h ./midend.c ./mines.c ./misc.c \
+               ./net.c ./netslide.c ./no-icon.c ./nullfe.c ./nullgame.c \
+               ./obfusc.c ./osx.m ./palisade.c ./pattern.c ./pearl.c \
+               ./pegs.c ./penrose.c ./penrose.h ./printing.c ./ps.c \
+               ./puzzles.h ./random.c ./range.c ./rect.c ./resource.h \
+               ./samegame.c ./signpost.c ./singles.c ./sixteen.c ./slant.c \
+               ./solo.c ./tdq.c ./tents.c ./towers.c ./tracks.c ./tree234.c \
+               ./tree234.h ./twiddle.c ./undead.c ./unequal.c ./unruly.c \
+               ./untangle.c ./version.c ./version.h ./windows.c \
+               icons/blackbox-icon.c icons/bridges-icon.c icons/cube-icon.c \
+               icons/dominosa-icon.c icons/fifteen-icon.c \
+               icons/filling-icon.c icons/flip-icon.c icons/flood-icon.c \
+               icons/galaxies-icon.c icons/guess-icon.c \
+               icons/inertia-icon.c icons/keen-icon.c icons/lightup-icon.c \
+               icons/loopy-icon.c icons/magnets-icon.c icons/map-icon.c \
+               icons/mines-icon.c icons/net-icon.c icons/netslide-icon.c \
+               icons/palisade-icon.c icons/pattern-icon.c \
+               icons/pearl-icon.c icons/pegs-icon.c icons/range-icon.c \
+               icons/rect-icon.c icons/samegame-icon.c \
+               icons/signpost-icon.c icons/singles-icon.c \
+               icons/sixteen-icon.c icons/slant-icon.c icons/solo-icon.c \
+               icons/tents-icon.c icons/towers-icon.c icons/tracks-icon.c \
+               icons/twiddle-icon.c icons/undead-icon.c \
+               icons/unequal-icon.c icons/unruly-icon.c \
+               icons/untangle-icon.c
+
+AM_CPPFLAGS = -I$(srcdir)/./ -I$(srcdir)/icons/ 
+AM_CFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)
+blackbox_SOURCES = ./blackbox.c ./drawing.c ./gtk.c ./malloc.c ./midend.c \
+               ./misc.c ./printing.c ./ps.c ./puzzles.h ./random.c \
+               ./version.c ./version.h icons/blackbox-icon.c
+
+blackbox_LDADD = $(GTK_LIBS) -lm
+bridges_SOURCES = ./bridges.c ./drawing.c ./dsf.c ./findloop.c ./gtk.c \
+               ./malloc.c ./midend.c ./misc.c ./printing.c ./ps.c \
+               ./puzzles.h ./random.c ./version.c ./version.h \
+               icons/bridges-icon.c
+
+bridges_LDADD = $(GTK_LIBS) -lm
+cube_SOURCES = ./cube.c ./drawing.c ./gtk.c ./malloc.c ./midend.c ./misc.c \
+               ./printing.c ./ps.c ./puzzles.h ./random.c ./version.c \
+               ./version.h icons/cube-icon.c
+
+cube_LDADD = $(GTK_LIBS) -lm
+dominosa_SOURCES = ./dominosa.c ./drawing.c ./gtk.c ./laydomino.c ./malloc.c \
+               ./midend.c ./misc.c ./printing.c ./ps.c ./puzzles.h \
+               ./random.c ./version.c ./version.h icons/dominosa-icon.c
+
+dominosa_LDADD = $(GTK_LIBS) -lm
+fifteen_SOURCES = ./drawing.c ./fifteen.c ./gtk.c ./malloc.c ./midend.c \
+               ./misc.c ./printing.c ./ps.c ./puzzles.h ./random.c \
+               ./version.c ./version.h icons/fifteen-icon.c
+
+fifteen_LDADD = $(GTK_LIBS) -lm
+fifteensolver_SOURCES = ./malloc.c ./misc.c ./nullfe.c ./puzzles.h \
+               ./random.c
+
+fifteensolver_LDADD = libfifteen2_a-fifteen.$(OBJEXT) -lm
+filling_SOURCES = ./drawing.c ./dsf.c ./filling.c ./gtk.c ./malloc.c \
+               ./midend.c ./misc.c ./printing.c ./ps.c ./puzzles.h \
+               ./random.c ./version.c ./version.h icons/filling-icon.c
+
+filling_LDADD = $(GTK_LIBS) -lm
+fillingsolver_SOURCES = ./dsf.c ./malloc.c ./misc.c ./nullfe.c ./puzzles.h \
+               ./random.c
+
+fillingsolver_LDADD = libfilling2_a-filling.$(OBJEXT) -lm
+flip_SOURCES = ./drawing.c ./flip.c ./gtk.c ./malloc.c ./midend.c ./misc.c \
+               ./printing.c ./ps.c ./puzzles.h ./random.c ./tree234.c \
+               ./tree234.h ./version.c ./version.h icons/flip-icon.c
+
+flip_LDADD = $(GTK_LIBS) -lm
+flood_SOURCES = ./drawing.c ./flood.c ./gtk.c ./malloc.c ./midend.c ./misc.c \
+               ./printing.c ./ps.c ./puzzles.h ./random.c ./version.c \
+               ./version.h icons/flood-icon.c
+
+flood_LDADD = $(GTK_LIBS) -lm
+galaxies_SOURCES = ./drawing.c ./dsf.c ./galaxies.c ./gtk.c ./malloc.c \
+               ./midend.c ./misc.c ./printing.c ./ps.c ./puzzles.h \
+               ./random.c ./version.c ./version.h icons/galaxies-icon.c
+
+galaxies_LDADD = $(GTK_LIBS) -lm
+galaxiespicture_SOURCES = ./dsf.c ./malloc.c ./misc.c ./nullfe.c ./puzzles.h \
+               ./random.c
+
+galaxiespicture_LDADD = libgalaxie4_a-galaxies.$(OBJEXT) -lm
+galaxiessolver_SOURCES = ./dsf.c ./malloc.c ./misc.c ./nullfe.c ./puzzles.h \
+               ./random.c
+
+galaxiessolver_LDADD = libgalaxie2_a-galaxies.$(OBJEXT) -lm
+guess_SOURCES = ./drawing.c ./gtk.c ./guess.c ./malloc.c ./midend.c ./misc.c \
+               ./printing.c ./ps.c ./puzzles.h ./random.c ./version.c \
+               ./version.h icons/guess-icon.c
+
+guess_LDADD = $(GTK_LIBS) -lm
+inertia_SOURCES = ./drawing.c ./gtk.c ./inertia.c ./malloc.c ./midend.c \
+               ./misc.c ./printing.c ./ps.c ./puzzles.h ./random.c \
+               ./version.c ./version.h icons/inertia-icon.c
+
+inertia_LDADD = $(GTK_LIBS) -lm
+keen_SOURCES = ./drawing.c ./dsf.c ./gtk.c ./keen.c ./latin.c ./latin.h \
+               ./malloc.c ./maxflow.c ./maxflow.h ./midend.c ./misc.c \
+               ./printing.c ./ps.c ./puzzles.h ./random.c ./tree234.c \
+               ./tree234.h ./version.c ./version.h icons/keen-icon.c
+
+keen_LDADD = $(GTK_LIBS) -lm
+keensolver_SOURCES = ./dsf.c ./malloc.c ./maxflow.c ./maxflow.h ./misc.c \
+               ./nullfe.c ./puzzles.h ./random.c ./tree234.c ./tree234.h
+
+keensolver_LDADD = libkeen2_a-keen.$(OBJEXT) liblatin6_a-latin.$(OBJEXT) -lm
+latincheck_SOURCES = ./malloc.c ./maxflow.c ./maxflow.h ./misc.c ./nullfe.c \
+               ./puzzles.h ./random.c ./tree234.c ./tree234.h
+
+latincheck_LDADD = liblatin8_a-latin.$(OBJEXT) -lm
+lightup_SOURCES = ./combi.c ./drawing.c ./gtk.c ./lightup.c ./malloc.c \
+               ./midend.c ./misc.c ./printing.c ./ps.c ./puzzles.h \
+               ./random.c ./version.c ./version.h icons/lightup-icon.c
+
+lightup_LDADD = $(GTK_LIBS) -lm
+lightupsolver_SOURCES = ./combi.c ./malloc.c ./misc.c ./nullfe.c ./puzzles.h \
+               ./random.c
+
+lightupsolver_LDADD = liblightup2_a-lightup.$(OBJEXT) -lm
+loopy_SOURCES = ./drawing.c ./dsf.c ./grid.c ./grid.h ./gtk.c ./loopgen.c \
+               ./loopgen.h ./loopy.c ./malloc.c ./midend.c ./misc.c \
+               ./penrose.c ./penrose.h ./printing.c ./ps.c ./puzzles.h \
+               ./random.c ./tree234.c ./tree234.h ./version.c ./version.h \
+               icons/loopy-icon.c
+
+loopy_LDADD = $(GTK_LIBS) -lm
+loopysolver_SOURCES = ./dsf.c ./grid.c ./grid.h ./loopgen.c ./loopgen.h \
+               ./malloc.c ./misc.c ./nullfe.c ./penrose.c ./penrose.h \
+               ./puzzles.h ./random.c ./tree234.c ./tree234.h
+
+loopysolver_LDADD = libloopy2_a-loopy.$(OBJEXT) -lm
+magnets_SOURCES = ./drawing.c ./gtk.c ./laydomino.c ./magnets.c ./malloc.c \
+               ./midend.c ./misc.c ./printing.c ./ps.c ./puzzles.h \
+               ./random.c ./version.c ./version.h icons/magnets-icon.c
+
+magnets_LDADD = $(GTK_LIBS) -lm
+magnetssolver_SOURCES = ./laydomino.c ./malloc.c ./misc.c ./nullfe.c \
+               ./puzzles.h ./random.c
+
+magnetssolver_LDADD = libmagnets2_a-magnets.$(OBJEXT) -lm
+map_SOURCES = ./drawing.c ./dsf.c ./gtk.c ./malloc.c ./map.c ./midend.c \
+               ./misc.c ./printing.c ./ps.c ./puzzles.h ./random.c \
+               ./version.c ./version.h icons/map-icon.c
+
+map_LDADD = $(GTK_LIBS) -lm
+mapsolver_SOURCES = ./dsf.c ./malloc.c ./misc.c ./nullfe.c ./puzzles.h \
+               ./random.c
+
+mapsolver_LDADD = libmap2_a-map.$(OBJEXT) -lm
+mineobfusc_SOURCES = ./malloc.c ./misc.c ./nullfe.c ./puzzles.h ./random.c \
+               ./tree234.c ./tree234.h
+
+mineobfusc_LDADD = libmines2_a-mines.$(OBJEXT) -lm
+mines_SOURCES = ./drawing.c ./gtk.c ./malloc.c ./midend.c ./mines.c ./misc.c \
+               ./printing.c ./ps.c ./puzzles.h ./random.c ./tree234.c \
+               ./tree234.h ./version.c ./version.h icons/mines-icon.c
+
+mines_LDADD = $(GTK_LIBS) -lm
+net_SOURCES = ./drawing.c ./dsf.c ./findloop.c ./gtk.c ./malloc.c ./midend.c \
+               ./misc.c ./net.c ./printing.c ./ps.c ./puzzles.h ./random.c \
+               ./tree234.c ./tree234.h ./version.c ./version.h \
+               icons/net-icon.c
+
+net_LDADD = $(GTK_LIBS) -lm
+netslide_SOURCES = ./drawing.c ./gtk.c ./malloc.c ./midend.c ./misc.c \
+               ./netslide.c ./printing.c ./ps.c ./puzzles.h ./random.c \
+               ./tree234.c ./tree234.h ./version.c ./version.h \
+               icons/netslide-icon.c
+
+netslide_LDADD = $(GTK_LIBS) -lm
+nullgame_SOURCES = ./drawing.c ./gtk.c ./malloc.c ./midend.c ./misc.c \
+               ./no-icon.c ./nullgame.c ./printing.c ./ps.c ./puzzles.h \
+               ./random.c ./version.c ./version.h
+
+nullgame_LDADD = $(GTK_LIBS) -lm
+obfusc_SOURCES = ./malloc.c ./misc.c ./nullfe.c ./obfusc.c ./puzzles.h \
+               ./random.c
+
+obfusc_LDADD = -lm
+palisade_SOURCES = ./divvy.c ./drawing.c ./dsf.c ./gtk.c ./malloc.c \
+               ./midend.c ./misc.c ./palisade.c ./printing.c ./ps.c \
+               ./puzzles.h ./random.c ./version.c ./version.h \
+               icons/palisade-icon.c
+
+palisade_LDADD = $(GTK_LIBS) -lm
+pattern_SOURCES = ./drawing.c ./gtk.c ./malloc.c ./midend.c ./misc.c \
+               ./pattern.c ./printing.c ./ps.c ./puzzles.h ./random.c \
+               ./version.c ./version.h icons/pattern-icon.c
+
+pattern_LDADD = $(GTK_LIBS) -lm
+patternpicture_SOURCES = ./malloc.c ./misc.c ./nullfe.c ./puzzles.h \
+               ./random.c
+
+patternpicture_LDADD = libpattern4_a-pattern.$(OBJEXT) -lm
+patternsolver_SOURCES = ./malloc.c ./misc.c ./nullfe.c ./puzzles.h \
+               ./random.c
+
+patternsolver_LDADD = libpattern2_a-pattern.$(OBJEXT) -lm
+pearl_SOURCES = ./drawing.c ./dsf.c ./grid.c ./grid.h ./gtk.c ./loopgen.c \
+               ./loopgen.h ./malloc.c ./midend.c ./misc.c ./pearl.c \
+               ./penrose.c ./penrose.h ./printing.c ./ps.c ./puzzles.h \
+               ./random.c ./tdq.c ./tree234.c ./tree234.h ./version.c \
+               ./version.h icons/pearl-icon.c
+
+pearl_LDADD = $(GTK_LIBS) -lm
+pearlbench_SOURCES = ./dsf.c ./grid.c ./grid.h ./loopgen.c ./loopgen.h \
+               ./malloc.c ./misc.c ./nullfe.c ./penrose.c ./penrose.h \
+               ./puzzles.h ./random.c ./tdq.c ./tree234.c ./tree234.h
+
+pearlbench_LDADD = libpearl2_a-pearl.$(OBJEXT) -lm
+pegs_SOURCES = ./drawing.c ./gtk.c ./malloc.c ./midend.c ./misc.c ./pegs.c \
+               ./printing.c ./ps.c ./puzzles.h ./random.c ./tree234.c \
+               ./tree234.h ./version.c ./version.h icons/pegs-icon.c
+
+pegs_LDADD = $(GTK_LIBS) -lm
+range_SOURCES = ./drawing.c ./dsf.c ./gtk.c ./malloc.c ./midend.c ./misc.c \
+               ./printing.c ./ps.c ./puzzles.h ./random.c ./range.c \
+               ./version.c ./version.h icons/range-icon.c
+
+range_LDADD = $(GTK_LIBS) -lm
+rect_SOURCES = ./drawing.c ./gtk.c ./malloc.c ./midend.c ./misc.c \
+               ./printing.c ./ps.c ./puzzles.h ./random.c ./rect.c \
+               ./version.c ./version.h icons/rect-icon.c
+
+rect_LDADD = $(GTK_LIBS) -lm
+samegame_SOURCES = ./drawing.c ./gtk.c ./malloc.c ./midend.c ./misc.c \
+               ./printing.c ./ps.c ./puzzles.h ./random.c ./samegame.c \
+               ./version.c ./version.h icons/samegame-icon.c
+
+samegame_LDADD = $(GTK_LIBS) -lm
+signpost_SOURCES = ./drawing.c ./dsf.c ./gtk.c ./malloc.c ./midend.c \
+               ./misc.c ./printing.c ./ps.c ./puzzles.h ./random.c \
+               ./signpost.c ./version.c ./version.h icons/signpost-icon.c
+
+signpost_LDADD = $(GTK_LIBS) -lm
+signpostsolver_SOURCES = ./dsf.c ./malloc.c ./misc.c ./nullfe.c ./puzzles.h \
+               ./random.c
+
+signpostsolver_LDADD = libsignpos2_a-signpost.$(OBJEXT) -lm
+singles_SOURCES = ./drawing.c ./dsf.c ./gtk.c ./latin.c ./latin.h ./malloc.c \
+               ./maxflow.c ./maxflow.h ./midend.c ./misc.c ./printing.c \
+               ./ps.c ./puzzles.h ./random.c ./singles.c ./tree234.c \
+               ./tree234.h ./version.c ./version.h icons/singles-icon.c
+
+singles_LDADD = $(GTK_LIBS) -lm
+singlessolver_SOURCES = ./dsf.c ./latin.c ./latin.h ./malloc.c ./maxflow.c \
+               ./maxflow.h ./misc.c ./nullfe.c ./puzzles.h ./random.c \
+               ./tree234.c ./tree234.h
+
+singlessolver_LDADD = libsingles3_a-singles.$(OBJEXT) -lm
+sixteen_SOURCES = ./drawing.c ./gtk.c ./malloc.c ./midend.c ./misc.c \
+               ./printing.c ./ps.c ./puzzles.h ./random.c ./sixteen.c \
+               ./version.c ./version.h icons/sixteen-icon.c
+
+sixteen_LDADD = $(GTK_LIBS) -lm
+slant_SOURCES = ./drawing.c ./dsf.c ./findloop.c ./gtk.c ./malloc.c \
+               ./midend.c ./misc.c ./printing.c ./ps.c ./puzzles.h \
+               ./random.c ./slant.c ./version.c ./version.h \
+               icons/slant-icon.c
+
+slant_LDADD = $(GTK_LIBS) -lm
+slantsolver_SOURCES = ./dsf.c ./findloop.c ./malloc.c ./misc.c ./nullfe.c \
+               ./puzzles.h ./random.c
+
+slantsolver_LDADD = libslant2_a-slant.$(OBJEXT) -lm
+solo_SOURCES = ./divvy.c ./drawing.c ./dsf.c ./gtk.c ./malloc.c ./midend.c \
+               ./misc.c ./printing.c ./ps.c ./puzzles.h ./random.c ./solo.c \
+               ./version.c ./version.h icons/solo-icon.c
+
+solo_LDADD = $(GTK_LIBS) -lm
+solosolver_SOURCES = ./divvy.c ./dsf.c ./malloc.c ./misc.c ./nullfe.c \
+               ./puzzles.h ./random.c
+
+solosolver_LDADD = libsolo2_a-solo.$(OBJEXT) -lm
+tents_SOURCES = ./drawing.c ./dsf.c ./gtk.c ./malloc.c ./maxflow.c \
+               ./maxflow.h ./midend.c ./misc.c ./printing.c ./ps.c \
+               ./puzzles.h ./random.c ./tents.c ./version.c ./version.h \
+               icons/tents-icon.c
+
+tents_LDADD = $(GTK_LIBS) -lm
+tentssolver_SOURCES = ./dsf.c ./malloc.c ./maxflow.c ./maxflow.h ./misc.c \
+               ./nullfe.c ./puzzles.h ./random.c
+
+tentssolver_LDADD = libtents3_a-tents.$(OBJEXT) -lm
+towers_SOURCES = ./drawing.c ./gtk.c ./latin.c ./latin.h ./malloc.c \
+               ./maxflow.c ./maxflow.h ./midend.c ./misc.c ./printing.c \
+               ./ps.c ./puzzles.h ./random.c ./towers.c ./tree234.c \
+               ./tree234.h ./version.c ./version.h icons/towers-icon.c
+
+towers_LDADD = $(GTK_LIBS) -lm
+towerssolver_SOURCES = ./malloc.c ./maxflow.c ./maxflow.h ./misc.c \
+               ./nullfe.c ./puzzles.h ./random.c ./tree234.c ./tree234.h
+
+towerssolver_LDADD = liblatin6_a-latin.$(OBJEXT) \
+               libtowers2_a-towers.$(OBJEXT) -lm
+
+tracks_SOURCES = ./drawing.c ./dsf.c ./findloop.c ./gtk.c ./malloc.c \
+               ./midend.c ./misc.c ./printing.c ./ps.c ./puzzles.h \
+               ./random.c ./tracks.c ./version.c ./version.h \
+               icons/tracks-icon.c
+
+tracks_LDADD = $(GTK_LIBS) -lm
+twiddle_SOURCES = ./drawing.c ./gtk.c ./malloc.c ./midend.c ./misc.c \
+               ./printing.c ./ps.c ./puzzles.h ./random.c ./twiddle.c \
+               ./version.c ./version.h icons/twiddle-icon.c
+
+twiddle_LDADD = $(GTK_LIBS) -lm
+undead_SOURCES = ./drawing.c ./gtk.c ./malloc.c ./midend.c ./misc.c \
+               ./printing.c ./ps.c ./puzzles.h ./random.c ./undead.c \
+               ./version.c ./version.h icons/undead-icon.c
+
+undead_LDADD = $(GTK_LIBS) -lm
+unequal_SOURCES = ./drawing.c ./gtk.c ./latin.c ./latin.h ./malloc.c \
+               ./maxflow.c ./maxflow.h ./midend.c ./misc.c ./printing.c \
+               ./ps.c ./puzzles.h ./random.c ./tree234.c ./tree234.h \
+               ./unequal.c ./version.c ./version.h icons/unequal-icon.c
+
+unequal_LDADD = $(GTK_LIBS) -lm
+unequalsolver_SOURCES = ./malloc.c ./maxflow.c ./maxflow.h ./misc.c \
+               ./nullfe.c ./puzzles.h ./random.c ./tree234.c ./tree234.h
+
+unequalsolver_LDADD = liblatin6_a-latin.$(OBJEXT) \
+               libunequal2_a-unequal.$(OBJEXT) -lm
+
+unruly_SOURCES = ./drawing.c ./gtk.c ./malloc.c ./midend.c ./misc.c \
+               ./printing.c ./ps.c ./puzzles.h ./random.c ./unruly.c \
+               ./version.c ./version.h icons/unruly-icon.c
+
+unruly_LDADD = $(GTK_LIBS) -lm
+unrulysolver_SOURCES = ./malloc.c ./misc.c ./nullfe.c ./puzzles.h ./random.c
+unrulysolver_LDADD = libunruly2_a-unruly.$(OBJEXT) -lm
+untangle_SOURCES = ./drawing.c ./gtk.c ./malloc.c ./midend.c ./misc.c \
+               ./printing.c ./ps.c ./puzzles.h ./random.c ./tree234.c \
+               ./tree234.h ./untangle.c ./version.c ./version.h \
+               icons/untangle-icon.c
+
+untangle_LDADD = $(GTK_LIBS) -lm
+libfifteen2_a_SOURCES = ./fifteen.c ./puzzles.h
+libfifteen2_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+libfilling2_a_SOURCES = ./filling.c ./puzzles.h
+libfilling2_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+libgalaxie2_a_SOURCES = ./galaxies.c ./puzzles.h
+libgalaxie2_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+libgalaxie4_a_SOURCES = ./galaxies.c ./puzzles.h
+libgalaxie4_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  \
+               -DSTANDALONE_PICTURE_GENERATOR
+
+libkeen2_a_SOURCES = ./keen.c ./puzzles.h ./latin.h
+libkeen2_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+liblatin6_a_SOURCES = ./latin.c ./puzzles.h ./tree234.h ./maxflow.h \
+               ./latin.h
+
+liblatin6_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+liblatin8_a_SOURCES = ./latin.c ./puzzles.h ./tree234.h ./maxflow.h \
+               ./latin.h
+
+liblatin8_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_LATIN_TEST
+liblightup2_a_SOURCES = ./lightup.c ./puzzles.h
+liblightup2_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+libloopy2_a_SOURCES = ./loopy.c ./puzzles.h ./tree234.h ./grid.h ./loopgen.h
+libloopy2_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+libmagnets2_a_SOURCES = ./magnets.c ./puzzles.h
+libmagnets2_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+libmap2_a_SOURCES = ./map.c ./puzzles.h
+libmap2_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+libmines2_a_SOURCES = ./mines.c ./tree234.h ./puzzles.h
+libmines2_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_OBFUSCATOR
+libpattern2_a_SOURCES = ./pattern.c ./puzzles.h
+libpattern2_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+libpattern4_a_SOURCES = ./pattern.c ./puzzles.h
+libpattern4_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  \
+               -DSTANDALONE_PICTURE_GENERATOR
+
+libpearl2_a_SOURCES = ./pearl.c ./puzzles.h ./grid.h ./loopgen.h
+libpearl2_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+libsignpos2_a_SOURCES = ./signpost.c ./puzzles.h
+libsignpos2_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+libsingles3_a_SOURCES = ./singles.c ./puzzles.h ./latin.h
+libsingles3_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+libslant2_a_SOURCES = ./slant.c ./puzzles.h
+libslant2_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+libsolo2_a_SOURCES = ./solo.c ./puzzles.h
+libsolo2_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+libtents3_a_SOURCES = ./tents.c ./puzzles.h ./maxflow.h
+libtents3_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+libtowers2_a_SOURCES = ./towers.c ./puzzles.h ./latin.h
+libtowers2_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+libunequal2_a_SOURCES = ./unequal.c ./puzzles.h ./latin.h
+libunequal2_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+libunruly2_a_SOURCES = ./unruly.c ./puzzles.h
+libunruly2_a_CPPFLAGS = $(GTK_CFLAGS) $(WARNINGOPTS)  -DSTANDALONE_SOLVER
+noinst_LIBRARIES = libfifteen2.a libfilling2.a libgalaxie2.a libgalaxie4.a \
+               libkeen2.a liblatin6.a liblatin8.a liblightup2.a libloopy2.a \
+               libmagnets2.a libmap2.a libmines2.a libpattern2.a \
+               libpattern4.a libpearl2.a libsignpos2.a libsingles3.a \
+               libslant2.a libsolo2.a libtents3.a libtowers2.a \
+               libunequal2.a libunruly2.a
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+am--refresh: Makefile
+       @:
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+       @for dep in $?; do \
+         case '$(am__configure_deps)' in \
+           *$$dep*) \
+             echo ' cd $(srcdir) && $(AUTOMAKE) --foreign'; \
+             $(am__cd) $(srcdir) && $(AUTOMAKE) --foreign \
+               && exit 0; \
+             exit 1;; \
+         esac; \
+       done; \
+       echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign Makefile'; \
+       $(am__cd) $(top_srcdir) && \
+         $(AUTOMAKE) --foreign Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+       @case '$?' in \
+         *config.status*) \
+           echo ' $(SHELL) ./config.status'; \
+           $(SHELL) ./config.status;; \
+         *) \
+           echo ' cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe)'; \
+           cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe);; \
+       esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+       $(SHELL) ./config.status --recheck
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+       $(am__cd) $(srcdir) && $(AUTOCONF)
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+       $(am__cd) $(srcdir) && $(ACLOCAL) $(ACLOCAL_AMFLAGS)
+$(am__aclocal_m4_deps):
+
+clean-noinstLIBRARIES:
+       -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES)
+./$(am__dirstamp):
+       @$(MKDIR_P) .
+       @: > ./$(am__dirstamp)
+$(DEPDIR)/$(am__dirstamp):
+       @$(MKDIR_P) ./$(DEPDIR)
+       @: > $(DEPDIR)/$(am__dirstamp)
+./libfifteen2_a-fifteen.$(OBJEXT): ./$(am__dirstamp) \
+       $(DEPDIR)/$(am__dirstamp)
+
+libfifteen2.a: $(libfifteen2_a_OBJECTS) $(libfifteen2_a_DEPENDENCIES) $(EXTRA_libfifteen2_a_DEPENDENCIES) 
+       $(AM_V_at)-rm -f libfifteen2.a
+       $(AM_V_AR)$(libfifteen2_a_AR) libfifteen2.a $(libfifteen2_a_OBJECTS) $(libfifteen2_a_LIBADD)
+       $(AM_V_at)$(RANLIB) libfifteen2.a
+./libfilling2_a-filling.$(OBJEXT): ./$(am__dirstamp) \
+       $(DEPDIR)/$(am__dirstamp)
+
+libfilling2.a: $(libfilling2_a_OBJECTS) $(libfilling2_a_DEPENDENCIES) $(EXTRA_libfilling2_a_DEPENDENCIES) 
+       $(AM_V_at)-rm -f libfilling2.a
+       $(AM_V_AR)$(libfilling2_a_AR) libfilling2.a $(libfilling2_a_OBJECTS) $(libfilling2_a_LIBADD)
+       $(AM_V_at)$(RANLIB) libfilling2.a
+./libgalaxie2_a-galaxies.$(OBJEXT): ./$(am__dirstamp) \
+       $(DEPDIR)/$(am__dirstamp)
+
+libgalaxie2.a: $(libgalaxie2_a_OBJECTS) $(libgalaxie2_a_DEPENDENCIES) $(EXTRA_libgalaxie2_a_DEPENDENCIES) 
+       $(AM_V_at)-rm -f libgalaxie2.a
+       $(AM_V_AR)$(libgalaxie2_a_AR) libgalaxie2.a $(libgalaxie2_a_OBJECTS) $(libgalaxie2_a_LIBADD)
+       $(AM_V_at)$(RANLIB) libgalaxie2.a
+./libgalaxie4_a-galaxies.$(OBJEXT): ./$(am__dirstamp) \
+       $(DEPDIR)/$(am__dirstamp)
+
+libgalaxie4.a: $(libgalaxie4_a_OBJECTS) $(libgalaxie4_a_DEPENDENCIES) $(EXTRA_libgalaxie4_a_DEPENDENCIES) 
+       $(AM_V_at)-rm -f libgalaxie4.a
+       $(AM_V_AR)$(libgalaxie4_a_AR) libgalaxie4.a $(libgalaxie4_a_OBJECTS) $(libgalaxie4_a_LIBADD)
+       $(AM_V_at)$(RANLIB) libgalaxie4.a
+./libkeen2_a-keen.$(OBJEXT): ./$(am__dirstamp) \
+       $(DEPDIR)/$(am__dirstamp)
+
+libkeen2.a: $(libkeen2_a_OBJECTS) $(libkeen2_a_DEPENDENCIES) $(EXTRA_libkeen2_a_DEPENDENCIES) 
+       $(AM_V_at)-rm -f libkeen2.a
+       $(AM_V_AR)$(libkeen2_a_AR) libkeen2.a $(libkeen2_a_OBJECTS) $(libkeen2_a_LIBADD)
+       $(AM_V_at)$(RANLIB) libkeen2.a
+./liblatin6_a-latin.$(OBJEXT): ./$(am__dirstamp) \
+       $(DEPDIR)/$(am__dirstamp)
+
+liblatin6.a: $(liblatin6_a_OBJECTS) $(liblatin6_a_DEPENDENCIES) $(EXTRA_liblatin6_a_DEPENDENCIES) 
+       $(AM_V_at)-rm -f liblatin6.a
+       $(AM_V_AR)$(liblatin6_a_AR) liblatin6.a $(liblatin6_a_OBJECTS) $(liblatin6_a_LIBADD)
+       $(AM_V_at)$(RANLIB) liblatin6.a
+./liblatin8_a-latin.$(OBJEXT): ./$(am__dirstamp) \
+       $(DEPDIR)/$(am__dirstamp)
+
+liblatin8.a: $(liblatin8_a_OBJECTS) $(liblatin8_a_DEPENDENCIES) $(EXTRA_liblatin8_a_DEPENDENCIES) 
+       $(AM_V_at)-rm -f liblatin8.a
+       $(AM_V_AR)$(liblatin8_a_AR) liblatin8.a $(liblatin8_a_OBJECTS) $(liblatin8_a_LIBADD)
+       $(AM_V_at)$(RANLIB) liblatin8.a
+./liblightup2_a-lightup.$(OBJEXT): ./$(am__dirstamp) \
+       $(DEPDIR)/$(am__dirstamp)
+
+liblightup2.a: $(liblightup2_a_OBJECTS) $(liblightup2_a_DEPENDENCIES) $(EXTRA_liblightup2_a_DEPENDENCIES) 
+       $(AM_V_at)-rm -f liblightup2.a
+       $(AM_V_AR)$(liblightup2_a_AR) liblightup2.a $(liblightup2_a_OBJECTS) $(liblightup2_a_LIBADD)
+       $(AM_V_at)$(RANLIB) liblightup2.a
+./libloopy2_a-loopy.$(OBJEXT): ./$(am__dirstamp) \
+       $(DEPDIR)/$(am__dirstamp)
+
+libloopy2.a: $(libloopy2_a_OBJECTS) $(libloopy2_a_DEPENDENCIES) $(EXTRA_libloopy2_a_DEPENDENCIES) 
+       $(AM_V_at)-rm -f libloopy2.a
+       $(AM_V_AR)$(libloopy2_a_AR) libloopy2.a $(libloopy2_a_OBJECTS) $(libloopy2_a_LIBADD)
+       $(AM_V_at)$(RANLIB) libloopy2.a
+./libmagnets2_a-magnets.$(OBJEXT): ./$(am__dirstamp) \
+       $(DEPDIR)/$(am__dirstamp)
+
+libmagnets2.a: $(libmagnets2_a_OBJECTS) $(libmagnets2_a_DEPENDENCIES) $(EXTRA_libmagnets2_a_DEPENDENCIES) 
+       $(AM_V_at)-rm -f libmagnets2.a
+       $(AM_V_AR)$(libmagnets2_a_AR) libmagnets2.a $(libmagnets2_a_OBJECTS) $(libmagnets2_a_LIBADD)
+       $(AM_V_at)$(RANLIB) libmagnets2.a
+./libmap2_a-map.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+
+libmap2.a: $(libmap2_a_OBJECTS) $(libmap2_a_DEPENDENCIES) $(EXTRA_libmap2_a_DEPENDENCIES) 
+       $(AM_V_at)-rm -f libmap2.a
+       $(AM_V_AR)$(libmap2_a_AR) libmap2.a $(libmap2_a_OBJECTS) $(libmap2_a_LIBADD)
+       $(AM_V_at)$(RANLIB) libmap2.a
+./libmines2_a-mines.$(OBJEXT): ./$(am__dirstamp) \
+       $(DEPDIR)/$(am__dirstamp)
+
+libmines2.a: $(libmines2_a_OBJECTS) $(libmines2_a_DEPENDENCIES) $(EXTRA_libmines2_a_DEPENDENCIES) 
+       $(AM_V_at)-rm -f libmines2.a
+       $(AM_V_AR)$(libmines2_a_AR) libmines2.a $(libmines2_a_OBJECTS) $(libmines2_a_LIBADD)
+       $(AM_V_at)$(RANLIB) libmines2.a
+./libpattern2_a-pattern.$(OBJEXT): ./$(am__dirstamp) \
+       $(DEPDIR)/$(am__dirstamp)
+
+libpattern2.a: $(libpattern2_a_OBJECTS) $(libpattern2_a_DEPENDENCIES) $(EXTRA_libpattern2_a_DEPENDENCIES) 
+       $(AM_V_at)-rm -f libpattern2.a
+       $(AM_V_AR)$(libpattern2_a_AR) libpattern2.a $(libpattern2_a_OBJECTS) $(libpattern2_a_LIBADD)
+       $(AM_V_at)$(RANLIB) libpattern2.a
+./libpattern4_a-pattern.$(OBJEXT): ./$(am__dirstamp) \
+       $(DEPDIR)/$(am__dirstamp)
+
+libpattern4.a: $(libpattern4_a_OBJECTS) $(libpattern4_a_DEPENDENCIES) $(EXTRA_libpattern4_a_DEPENDENCIES) 
+       $(AM_V_at)-rm -f libpattern4.a
+       $(AM_V_AR)$(libpattern4_a_AR) libpattern4.a $(libpattern4_a_OBJECTS) $(libpattern4_a_LIBADD)
+       $(AM_V_at)$(RANLIB) libpattern4.a
+./libpearl2_a-pearl.$(OBJEXT): ./$(am__dirstamp) \
+       $(DEPDIR)/$(am__dirstamp)
+
+libpearl2.a: $(libpearl2_a_OBJECTS) $(libpearl2_a_DEPENDENCIES) $(EXTRA_libpearl2_a_DEPENDENCIES) 
+       $(AM_V_at)-rm -f libpearl2.a
+       $(AM_V_AR)$(libpearl2_a_AR) libpearl2.a $(libpearl2_a_OBJECTS) $(libpearl2_a_LIBADD)
+       $(AM_V_at)$(RANLIB) libpearl2.a
+./libsignpos2_a-signpost.$(OBJEXT): ./$(am__dirstamp) \
+       $(DEPDIR)/$(am__dirstamp)
+
+libsignpos2.a: $(libsignpos2_a_OBJECTS) $(libsignpos2_a_DEPENDENCIES) $(EXTRA_libsignpos2_a_DEPENDENCIES) 
+       $(AM_V_at)-rm -f libsignpos2.a
+       $(AM_V_AR)$(libsignpos2_a_AR) libsignpos2.a $(libsignpos2_a_OBJECTS) $(libsignpos2_a_LIBADD)
+       $(AM_V_at)$(RANLIB) libsignpos2.a
+./libsingles3_a-singles.$(OBJEXT): ./$(am__dirstamp) \
+       $(DEPDIR)/$(am__dirstamp)
+
+libsingles3.a: $(libsingles3_a_OBJECTS) $(libsingles3_a_DEPENDENCIES) $(EXTRA_libsingles3_a_DEPENDENCIES) 
+       $(AM_V_at)-rm -f libsingles3.a
+       $(AM_V_AR)$(libsingles3_a_AR) libsingles3.a $(libsingles3_a_OBJECTS) $(libsingles3_a_LIBADD)
+       $(AM_V_at)$(RANLIB) libsingles3.a
+./libslant2_a-slant.$(OBJEXT): ./$(am__dirstamp) \
+       $(DEPDIR)/$(am__dirstamp)
+
+libslant2.a: $(libslant2_a_OBJECTS) $(libslant2_a_DEPENDENCIES) $(EXTRA_libslant2_a_DEPENDENCIES) 
+       $(AM_V_at)-rm -f libslant2.a
+       $(AM_V_AR)$(libslant2_a_AR) libslant2.a $(libslant2_a_OBJECTS) $(libslant2_a_LIBADD)
+       $(AM_V_at)$(RANLIB) libslant2.a
+./libsolo2_a-solo.$(OBJEXT): ./$(am__dirstamp) \
+       $(DEPDIR)/$(am__dirstamp)
+
+libsolo2.a: $(libsolo2_a_OBJECTS) $(libsolo2_a_DEPENDENCIES) $(EXTRA_libsolo2_a_DEPENDENCIES) 
+       $(AM_V_at)-rm -f libsolo2.a
+       $(AM_V_AR)$(libsolo2_a_AR) libsolo2.a $(libsolo2_a_OBJECTS) $(libsolo2_a_LIBADD)
+       $(AM_V_at)$(RANLIB) libsolo2.a
+./libtents3_a-tents.$(OBJEXT): ./$(am__dirstamp) \
+       $(DEPDIR)/$(am__dirstamp)
+
+libtents3.a: $(libtents3_a_OBJECTS) $(libtents3_a_DEPENDENCIES) $(EXTRA_libtents3_a_DEPENDENCIES) 
+       $(AM_V_at)-rm -f libtents3.a
+       $(AM_V_AR)$(libtents3_a_AR) libtents3.a $(libtents3_a_OBJECTS) $(libtents3_a_LIBADD)
+       $(AM_V_at)$(RANLIB) libtents3.a
+./libtowers2_a-towers.$(OBJEXT): ./$(am__dirstamp) \
+       $(DEPDIR)/$(am__dirstamp)
+
+libtowers2.a: $(libtowers2_a_OBJECTS) $(libtowers2_a_DEPENDENCIES) $(EXTRA_libtowers2_a_DEPENDENCIES) 
+       $(AM_V_at)-rm -f libtowers2.a
+       $(AM_V_AR)$(libtowers2_a_AR) libtowers2.a $(libtowers2_a_OBJECTS) $(libtowers2_a_LIBADD)
+       $(AM_V_at)$(RANLIB) libtowers2.a
+./libunequal2_a-unequal.$(OBJEXT): ./$(am__dirstamp) \
+       $(DEPDIR)/$(am__dirstamp)
+
+libunequal2.a: $(libunequal2_a_OBJECTS) $(libunequal2_a_DEPENDENCIES) $(EXTRA_libunequal2_a_DEPENDENCIES) 
+       $(AM_V_at)-rm -f libunequal2.a
+       $(AM_V_AR)$(libunequal2_a_AR) libunequal2.a $(libunequal2_a_OBJECTS) $(libunequal2_a_LIBADD)
+       $(AM_V_at)$(RANLIB) libunequal2.a
+./libunruly2_a-unruly.$(OBJEXT): ./$(am__dirstamp) \
+       $(DEPDIR)/$(am__dirstamp)
+
+libunruly2.a: $(libunruly2_a_OBJECTS) $(libunruly2_a_DEPENDENCIES) $(EXTRA_libunruly2_a_DEPENDENCIES) 
+       $(AM_V_at)-rm -f libunruly2.a
+       $(AM_V_AR)$(libunruly2_a_AR) libunruly2.a $(libunruly2_a_OBJECTS) $(libunruly2_a_LIBADD)
+       $(AM_V_at)$(RANLIB) libunruly2.a
+install-binPROGRAMS: $(bin_PROGRAMS)
+       @$(NORMAL_INSTALL)
+       @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+       if test -n "$$list"; then \
+         echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \
+         $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \
+       fi; \
+       for p in $$list; do echo "$$p $$p"; done | \
+       sed 's/$(EXEEXT)$$//' | \
+       while read p p1; do if test -f $$p \
+         ; then echo "$$p"; echo "$$p"; else :; fi; \
+       done | \
+       sed -e 'p;s,.*/,,;n;h' \
+           -e 's|.*|.|' \
+           -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+       sed 'N;N;N;s,\n, ,g' | \
+       $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+         { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+           if ($$2 == $$4) files[d] = files[d] " " $$1; \
+           else { print "f", $$3 "/" $$4, $$1; } } \
+         END { for (d in files) print "f", d, files[d] }' | \
+       while read type dir files; do \
+           if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+           test -z "$$files" || { \
+             echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+             $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+           } \
+       ; done
+
+uninstall-binPROGRAMS:
+       @$(NORMAL_UNINSTALL)
+       @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+       files=`for p in $$list; do echo "$$p"; done | \
+         sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+             -e 's/$$/$(EXEEXT)/' \
+       `; \
+       test -n "$$list" || exit 0; \
+       echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
+       cd "$(DESTDIR)$(bindir)" && rm -f $$files
+
+clean-binPROGRAMS:
+       -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS)
+
+clean-noinstPROGRAMS:
+       -test -z "$(noinst_PROGRAMS)" || rm -f $(noinst_PROGRAMS)
+./blackbox.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+./drawing.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+./gtk.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+./malloc.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+./midend.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+./misc.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+./printing.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+./ps.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+./random.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+./version.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/$(am__dirstamp):
+       @$(MKDIR_P) icons
+       @: > icons/$(am__dirstamp)
+icons/$(DEPDIR)/$(am__dirstamp):
+       @$(MKDIR_P) icons/$(DEPDIR)
+       @: > icons/$(DEPDIR)/$(am__dirstamp)
+icons/blackbox-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+blackbox$(EXEEXT): $(blackbox_OBJECTS) $(blackbox_DEPENDENCIES) $(EXTRA_blackbox_DEPENDENCIES) 
+       @rm -f blackbox$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(blackbox_OBJECTS) $(blackbox_LDADD) $(LIBS)
+./bridges.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+./dsf.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+./findloop.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/bridges-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+bridges$(EXEEXT): $(bridges_OBJECTS) $(bridges_DEPENDENCIES) $(EXTRA_bridges_DEPENDENCIES) 
+       @rm -f bridges$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(bridges_OBJECTS) $(bridges_LDADD) $(LIBS)
+./cube.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/cube-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+cube$(EXEEXT): $(cube_OBJECTS) $(cube_DEPENDENCIES) $(EXTRA_cube_DEPENDENCIES) 
+       @rm -f cube$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(cube_OBJECTS) $(cube_LDADD) $(LIBS)
+./dominosa.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+./laydomino.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/dominosa-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+dominosa$(EXEEXT): $(dominosa_OBJECTS) $(dominosa_DEPENDENCIES) $(EXTRA_dominosa_DEPENDENCIES) 
+       @rm -f dominosa$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(dominosa_OBJECTS) $(dominosa_LDADD) $(LIBS)
+./fifteen.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/fifteen-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+fifteen$(EXEEXT): $(fifteen_OBJECTS) $(fifteen_DEPENDENCIES) $(EXTRA_fifteen_DEPENDENCIES) 
+       @rm -f fifteen$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(fifteen_OBJECTS) $(fifteen_LDADD) $(LIBS)
+./nullfe.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+
+fifteensolver$(EXEEXT): $(fifteensolver_OBJECTS) $(fifteensolver_DEPENDENCIES) $(EXTRA_fifteensolver_DEPENDENCIES) 
+       @rm -f fifteensolver$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(fifteensolver_OBJECTS) $(fifteensolver_LDADD) $(LIBS)
+./filling.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/filling-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+filling$(EXEEXT): $(filling_OBJECTS) $(filling_DEPENDENCIES) $(EXTRA_filling_DEPENDENCIES) 
+       @rm -f filling$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(filling_OBJECTS) $(filling_LDADD) $(LIBS)
+
+fillingsolver$(EXEEXT): $(fillingsolver_OBJECTS) $(fillingsolver_DEPENDENCIES) $(EXTRA_fillingsolver_DEPENDENCIES) 
+       @rm -f fillingsolver$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(fillingsolver_OBJECTS) $(fillingsolver_LDADD) $(LIBS)
+./flip.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+./tree234.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/flip-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+flip$(EXEEXT): $(flip_OBJECTS) $(flip_DEPENDENCIES) $(EXTRA_flip_DEPENDENCIES) 
+       @rm -f flip$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(flip_OBJECTS) $(flip_LDADD) $(LIBS)
+./flood.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/flood-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+flood$(EXEEXT): $(flood_OBJECTS) $(flood_DEPENDENCIES) $(EXTRA_flood_DEPENDENCIES) 
+       @rm -f flood$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(flood_OBJECTS) $(flood_LDADD) $(LIBS)
+./galaxies.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/galaxies-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+galaxies$(EXEEXT): $(galaxies_OBJECTS) $(galaxies_DEPENDENCIES) $(EXTRA_galaxies_DEPENDENCIES) 
+       @rm -f galaxies$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(galaxies_OBJECTS) $(galaxies_LDADD) $(LIBS)
+
+galaxiespicture$(EXEEXT): $(galaxiespicture_OBJECTS) $(galaxiespicture_DEPENDENCIES) $(EXTRA_galaxiespicture_DEPENDENCIES) 
+       @rm -f galaxiespicture$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(galaxiespicture_OBJECTS) $(galaxiespicture_LDADD) $(LIBS)
+
+galaxiessolver$(EXEEXT): $(galaxiessolver_OBJECTS) $(galaxiessolver_DEPENDENCIES) $(EXTRA_galaxiessolver_DEPENDENCIES) 
+       @rm -f galaxiessolver$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(galaxiessolver_OBJECTS) $(galaxiessolver_LDADD) $(LIBS)
+./guess.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/guess-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+guess$(EXEEXT): $(guess_OBJECTS) $(guess_DEPENDENCIES) $(EXTRA_guess_DEPENDENCIES) 
+       @rm -f guess$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(guess_OBJECTS) $(guess_LDADD) $(LIBS)
+./inertia.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/inertia-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+inertia$(EXEEXT): $(inertia_OBJECTS) $(inertia_DEPENDENCIES) $(EXTRA_inertia_DEPENDENCIES) 
+       @rm -f inertia$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(inertia_OBJECTS) $(inertia_LDADD) $(LIBS)
+./keen.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+./latin.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+./maxflow.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/keen-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+keen$(EXEEXT): $(keen_OBJECTS) $(keen_DEPENDENCIES) $(EXTRA_keen_DEPENDENCIES) 
+       @rm -f keen$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(keen_OBJECTS) $(keen_LDADD) $(LIBS)
+
+keensolver$(EXEEXT): $(keensolver_OBJECTS) $(keensolver_DEPENDENCIES) $(EXTRA_keensolver_DEPENDENCIES) 
+       @rm -f keensolver$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(keensolver_OBJECTS) $(keensolver_LDADD) $(LIBS)
+
+latincheck$(EXEEXT): $(latincheck_OBJECTS) $(latincheck_DEPENDENCIES) $(EXTRA_latincheck_DEPENDENCIES) 
+       @rm -f latincheck$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(latincheck_OBJECTS) $(latincheck_LDADD) $(LIBS)
+./combi.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+./lightup.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/lightup-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+lightup$(EXEEXT): $(lightup_OBJECTS) $(lightup_DEPENDENCIES) $(EXTRA_lightup_DEPENDENCIES) 
+       @rm -f lightup$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(lightup_OBJECTS) $(lightup_LDADD) $(LIBS)
+
+lightupsolver$(EXEEXT): $(lightupsolver_OBJECTS) $(lightupsolver_DEPENDENCIES) $(EXTRA_lightupsolver_DEPENDENCIES) 
+       @rm -f lightupsolver$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(lightupsolver_OBJECTS) $(lightupsolver_LDADD) $(LIBS)
+./grid.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+./loopgen.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+./loopy.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+./penrose.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/loopy-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+loopy$(EXEEXT): $(loopy_OBJECTS) $(loopy_DEPENDENCIES) $(EXTRA_loopy_DEPENDENCIES) 
+       @rm -f loopy$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(loopy_OBJECTS) $(loopy_LDADD) $(LIBS)
+
+loopysolver$(EXEEXT): $(loopysolver_OBJECTS) $(loopysolver_DEPENDENCIES) $(EXTRA_loopysolver_DEPENDENCIES) 
+       @rm -f loopysolver$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(loopysolver_OBJECTS) $(loopysolver_LDADD) $(LIBS)
+./magnets.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/magnets-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+magnets$(EXEEXT): $(magnets_OBJECTS) $(magnets_DEPENDENCIES) $(EXTRA_magnets_DEPENDENCIES) 
+       @rm -f magnets$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(magnets_OBJECTS) $(magnets_LDADD) $(LIBS)
+
+magnetssolver$(EXEEXT): $(magnetssolver_OBJECTS) $(magnetssolver_DEPENDENCIES) $(EXTRA_magnetssolver_DEPENDENCIES) 
+       @rm -f magnetssolver$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(magnetssolver_OBJECTS) $(magnetssolver_LDADD) $(LIBS)
+./map.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/map-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+map$(EXEEXT): $(map_OBJECTS) $(map_DEPENDENCIES) $(EXTRA_map_DEPENDENCIES) 
+       @rm -f map$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(map_OBJECTS) $(map_LDADD) $(LIBS)
+
+mapsolver$(EXEEXT): $(mapsolver_OBJECTS) $(mapsolver_DEPENDENCIES) $(EXTRA_mapsolver_DEPENDENCIES) 
+       @rm -f mapsolver$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(mapsolver_OBJECTS) $(mapsolver_LDADD) $(LIBS)
+
+mineobfusc$(EXEEXT): $(mineobfusc_OBJECTS) $(mineobfusc_DEPENDENCIES) $(EXTRA_mineobfusc_DEPENDENCIES) 
+       @rm -f mineobfusc$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(mineobfusc_OBJECTS) $(mineobfusc_LDADD) $(LIBS)
+./mines.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/mines-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+mines$(EXEEXT): $(mines_OBJECTS) $(mines_DEPENDENCIES) $(EXTRA_mines_DEPENDENCIES) 
+       @rm -f mines$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(mines_OBJECTS) $(mines_LDADD) $(LIBS)
+./net.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/net-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+net$(EXEEXT): $(net_OBJECTS) $(net_DEPENDENCIES) $(EXTRA_net_DEPENDENCIES) 
+       @rm -f net$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(net_OBJECTS) $(net_LDADD) $(LIBS)
+./netslide.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/netslide-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+netslide$(EXEEXT): $(netslide_OBJECTS) $(netslide_DEPENDENCIES) $(EXTRA_netslide_DEPENDENCIES) 
+       @rm -f netslide$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(netslide_OBJECTS) $(netslide_LDADD) $(LIBS)
+./no-icon.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+./nullgame.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+
+nullgame$(EXEEXT): $(nullgame_OBJECTS) $(nullgame_DEPENDENCIES) $(EXTRA_nullgame_DEPENDENCIES) 
+       @rm -f nullgame$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(nullgame_OBJECTS) $(nullgame_LDADD) $(LIBS)
+./obfusc.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+
+obfusc$(EXEEXT): $(obfusc_OBJECTS) $(obfusc_DEPENDENCIES) $(EXTRA_obfusc_DEPENDENCIES) 
+       @rm -f obfusc$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(obfusc_OBJECTS) $(obfusc_LDADD) $(LIBS)
+./divvy.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+./palisade.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/palisade-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+palisade$(EXEEXT): $(palisade_OBJECTS) $(palisade_DEPENDENCIES) $(EXTRA_palisade_DEPENDENCIES) 
+       @rm -f palisade$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(palisade_OBJECTS) $(palisade_LDADD) $(LIBS)
+./pattern.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/pattern-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+pattern$(EXEEXT): $(pattern_OBJECTS) $(pattern_DEPENDENCIES) $(EXTRA_pattern_DEPENDENCIES) 
+       @rm -f pattern$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(pattern_OBJECTS) $(pattern_LDADD) $(LIBS)
+
+patternpicture$(EXEEXT): $(patternpicture_OBJECTS) $(patternpicture_DEPENDENCIES) $(EXTRA_patternpicture_DEPENDENCIES) 
+       @rm -f patternpicture$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(patternpicture_OBJECTS) $(patternpicture_LDADD) $(LIBS)
+
+patternsolver$(EXEEXT): $(patternsolver_OBJECTS) $(patternsolver_DEPENDENCIES) $(EXTRA_patternsolver_DEPENDENCIES) 
+       @rm -f patternsolver$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(patternsolver_OBJECTS) $(patternsolver_LDADD) $(LIBS)
+./pearl.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+./tdq.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/pearl-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+pearl$(EXEEXT): $(pearl_OBJECTS) $(pearl_DEPENDENCIES) $(EXTRA_pearl_DEPENDENCIES) 
+       @rm -f pearl$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(pearl_OBJECTS) $(pearl_LDADD) $(LIBS)
+
+pearlbench$(EXEEXT): $(pearlbench_OBJECTS) $(pearlbench_DEPENDENCIES) $(EXTRA_pearlbench_DEPENDENCIES) 
+       @rm -f pearlbench$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(pearlbench_OBJECTS) $(pearlbench_LDADD) $(LIBS)
+./pegs.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/pegs-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+pegs$(EXEEXT): $(pegs_OBJECTS) $(pegs_DEPENDENCIES) $(EXTRA_pegs_DEPENDENCIES) 
+       @rm -f pegs$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(pegs_OBJECTS) $(pegs_LDADD) $(LIBS)
+./range.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/range-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+range$(EXEEXT): $(range_OBJECTS) $(range_DEPENDENCIES) $(EXTRA_range_DEPENDENCIES) 
+       @rm -f range$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(range_OBJECTS) $(range_LDADD) $(LIBS)
+./rect.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/rect-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+rect$(EXEEXT): $(rect_OBJECTS) $(rect_DEPENDENCIES) $(EXTRA_rect_DEPENDENCIES) 
+       @rm -f rect$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(rect_OBJECTS) $(rect_LDADD) $(LIBS)
+./samegame.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/samegame-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+samegame$(EXEEXT): $(samegame_OBJECTS) $(samegame_DEPENDENCIES) $(EXTRA_samegame_DEPENDENCIES) 
+       @rm -f samegame$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(samegame_OBJECTS) $(samegame_LDADD) $(LIBS)
+./signpost.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/signpost-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+signpost$(EXEEXT): $(signpost_OBJECTS) $(signpost_DEPENDENCIES) $(EXTRA_signpost_DEPENDENCIES) 
+       @rm -f signpost$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(signpost_OBJECTS) $(signpost_LDADD) $(LIBS)
+
+signpostsolver$(EXEEXT): $(signpostsolver_OBJECTS) $(signpostsolver_DEPENDENCIES) $(EXTRA_signpostsolver_DEPENDENCIES) 
+       @rm -f signpostsolver$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(signpostsolver_OBJECTS) $(signpostsolver_LDADD) $(LIBS)
+./singles.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/singles-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+singles$(EXEEXT): $(singles_OBJECTS) $(singles_DEPENDENCIES) $(EXTRA_singles_DEPENDENCIES) 
+       @rm -f singles$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(singles_OBJECTS) $(singles_LDADD) $(LIBS)
+
+singlessolver$(EXEEXT): $(singlessolver_OBJECTS) $(singlessolver_DEPENDENCIES) $(EXTRA_singlessolver_DEPENDENCIES) 
+       @rm -f singlessolver$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(singlessolver_OBJECTS) $(singlessolver_LDADD) $(LIBS)
+./sixteen.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/sixteen-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+sixteen$(EXEEXT): $(sixteen_OBJECTS) $(sixteen_DEPENDENCIES) $(EXTRA_sixteen_DEPENDENCIES) 
+       @rm -f sixteen$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(sixteen_OBJECTS) $(sixteen_LDADD) $(LIBS)
+./slant.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/slant-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+slant$(EXEEXT): $(slant_OBJECTS) $(slant_DEPENDENCIES) $(EXTRA_slant_DEPENDENCIES) 
+       @rm -f slant$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(slant_OBJECTS) $(slant_LDADD) $(LIBS)
+
+slantsolver$(EXEEXT): $(slantsolver_OBJECTS) $(slantsolver_DEPENDENCIES) $(EXTRA_slantsolver_DEPENDENCIES) 
+       @rm -f slantsolver$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(slantsolver_OBJECTS) $(slantsolver_LDADD) $(LIBS)
+./solo.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/solo-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+solo$(EXEEXT): $(solo_OBJECTS) $(solo_DEPENDENCIES) $(EXTRA_solo_DEPENDENCIES) 
+       @rm -f solo$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(solo_OBJECTS) $(solo_LDADD) $(LIBS)
+
+solosolver$(EXEEXT): $(solosolver_OBJECTS) $(solosolver_DEPENDENCIES) $(EXTRA_solosolver_DEPENDENCIES) 
+       @rm -f solosolver$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(solosolver_OBJECTS) $(solosolver_LDADD) $(LIBS)
+./tents.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/tents-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+tents$(EXEEXT): $(tents_OBJECTS) $(tents_DEPENDENCIES) $(EXTRA_tents_DEPENDENCIES) 
+       @rm -f tents$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(tents_OBJECTS) $(tents_LDADD) $(LIBS)
+
+tentssolver$(EXEEXT): $(tentssolver_OBJECTS) $(tentssolver_DEPENDENCIES) $(EXTRA_tentssolver_DEPENDENCIES) 
+       @rm -f tentssolver$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(tentssolver_OBJECTS) $(tentssolver_LDADD) $(LIBS)
+./towers.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/towers-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+towers$(EXEEXT): $(towers_OBJECTS) $(towers_DEPENDENCIES) $(EXTRA_towers_DEPENDENCIES) 
+       @rm -f towers$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(towers_OBJECTS) $(towers_LDADD) $(LIBS)
+
+towerssolver$(EXEEXT): $(towerssolver_OBJECTS) $(towerssolver_DEPENDENCIES) $(EXTRA_towerssolver_DEPENDENCIES) 
+       @rm -f towerssolver$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(towerssolver_OBJECTS) $(towerssolver_LDADD) $(LIBS)
+./tracks.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/tracks-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+tracks$(EXEEXT): $(tracks_OBJECTS) $(tracks_DEPENDENCIES) $(EXTRA_tracks_DEPENDENCIES) 
+       @rm -f tracks$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(tracks_OBJECTS) $(tracks_LDADD) $(LIBS)
+./twiddle.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/twiddle-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+twiddle$(EXEEXT): $(twiddle_OBJECTS) $(twiddle_DEPENDENCIES) $(EXTRA_twiddle_DEPENDENCIES) 
+       @rm -f twiddle$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(twiddle_OBJECTS) $(twiddle_LDADD) $(LIBS)
+./undead.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/undead-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+undead$(EXEEXT): $(undead_OBJECTS) $(undead_DEPENDENCIES) $(EXTRA_undead_DEPENDENCIES) 
+       @rm -f undead$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(undead_OBJECTS) $(undead_LDADD) $(LIBS)
+./unequal.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/unequal-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+unequal$(EXEEXT): $(unequal_OBJECTS) $(unequal_DEPENDENCIES) $(EXTRA_unequal_DEPENDENCIES) 
+       @rm -f unequal$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(unequal_OBJECTS) $(unequal_LDADD) $(LIBS)
+
+unequalsolver$(EXEEXT): $(unequalsolver_OBJECTS) $(unequalsolver_DEPENDENCIES) $(EXTRA_unequalsolver_DEPENDENCIES) 
+       @rm -f unequalsolver$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(unequalsolver_OBJECTS) $(unequalsolver_LDADD) $(LIBS)
+./unruly.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/unruly-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+unruly$(EXEEXT): $(unruly_OBJECTS) $(unruly_DEPENDENCIES) $(EXTRA_unruly_DEPENDENCIES) 
+       @rm -f unruly$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(unruly_OBJECTS) $(unruly_LDADD) $(LIBS)
+
+unrulysolver$(EXEEXT): $(unrulysolver_OBJECTS) $(unrulysolver_DEPENDENCIES) $(EXTRA_unrulysolver_DEPENDENCIES) 
+       @rm -f unrulysolver$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(unrulysolver_OBJECTS) $(unrulysolver_LDADD) $(LIBS)
+./untangle.$(OBJEXT): ./$(am__dirstamp) $(DEPDIR)/$(am__dirstamp)
+icons/untangle-icon.$(OBJEXT): icons/$(am__dirstamp) \
+       icons/$(DEPDIR)/$(am__dirstamp)
+
+untangle$(EXEEXT): $(untangle_OBJECTS) $(untangle_DEPENDENCIES) $(EXTRA_untangle_DEPENDENCIES) 
+       @rm -f untangle$(EXEEXT)
+       $(AM_V_CCLD)$(LINK) $(untangle_OBJECTS) $(untangle_LDADD) $(LIBS)
+
+mostlyclean-compile:
+       -rm -f *.$(OBJEXT)
+       -rm -f ./*.$(OBJEXT)
+       -rm -f icons/*.$(OBJEXT)
+
+distclean-compile:
+       -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/blackbox.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bridges.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/combi.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cube.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/divvy.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dominosa.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/drawing.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dsf.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fifteen.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/filling.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/findloop.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/flip.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/flood.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/galaxies.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/grid.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gtk.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/guess.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/inertia.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/keen.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/latin.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/laydomino.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libfifteen2_a-fifteen.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libfilling2_a-filling.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgalaxie2_a-galaxies.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgalaxie4_a-galaxies.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkeen2_a-keen.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblatin6_a-latin.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblatin8_a-latin.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblightup2_a-lightup.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libloopy2_a-loopy.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmagnets2_a-magnets.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmap2_a-map.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmines2_a-mines.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpattern2_a-pattern.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpattern4_a-pattern.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpearl2_a-pearl.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libsignpos2_a-signpost.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libsingles3_a-singles.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libslant2_a-slant.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libsolo2_a-solo.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtents3_a-tents.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libtowers2_a-towers.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libunequal2_a-unequal.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libunruly2_a-unruly.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lightup.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/loopgen.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/loopy.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/magnets.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/malloc.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/map.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/maxflow.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/midend.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mines.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/misc.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/net.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/netslide.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/no-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nullfe.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nullgame.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/obfusc.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/palisade.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pattern.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pearl.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pegs.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/penrose.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/printing.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ps.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/random.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/range.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rect.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/samegame.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/signpost.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/singles.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sixteen.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/slant.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/solo.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tdq.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tents.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/towers.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tracks.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tree234.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/twiddle.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/undead.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/unequal.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/unruly.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/untangle.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/version.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/blackbox-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/bridges-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/cube-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/dominosa-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/fifteen-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/filling-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/flip-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/flood-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/galaxies-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/guess-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/inertia-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/keen-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/lightup-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/loopy-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/magnets-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/map-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/mines-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/net-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/netslide-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/palisade-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/pattern-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/pearl-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/pegs-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/range-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/rect-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/samegame-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/signpost-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/singles-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/sixteen-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/slant-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/solo-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/tents-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/towers-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/tracks-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/twiddle-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/undead-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/unequal-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/unruly-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@icons/$(DEPDIR)/untangle-icon.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@   $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@   $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@   $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@   $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@   $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@   $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+./libfifteen2_a-fifteen.o: ./fifteen.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libfifteen2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libfifteen2_a-fifteen.o -MD -MP -MF $(DEPDIR)/libfifteen2_a-fifteen.Tpo -c -o ./libfifteen2_a-fifteen.o `test -f './fifteen.c' || echo '$(srcdir)/'`./fifteen.c
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libfifteen2_a-fifteen.Tpo $(DEPDIR)/libfifteen2_a-fifteen.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./fifteen.c' object='./libfifteen2_a-fifteen.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libfifteen2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libfifteen2_a-fifteen.o `test -f './fifteen.c' || echo '$(srcdir)/'`./fifteen.c
+
+./libfifteen2_a-fifteen.obj: ./fifteen.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libfifteen2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libfifteen2_a-fifteen.obj -MD -MP -MF $(DEPDIR)/libfifteen2_a-fifteen.Tpo -c -o ./libfifteen2_a-fifteen.obj `if test -f './fifteen.c'; then $(CYGPATH_W) './fifteen.c'; else $(CYGPATH_W) '$(srcdir)/./fifteen.c'; fi`
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libfifteen2_a-fifteen.Tpo $(DEPDIR)/libfifteen2_a-fifteen.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./fifteen.c' object='./libfifteen2_a-fifteen.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libfifteen2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libfifteen2_a-fifteen.obj `if test -f './fifteen.c'; then $(CYGPATH_W) './fifteen.c'; else $(CYGPATH_W) '$(srcdir)/./fifteen.c'; fi`
+
+./libfilling2_a-filling.o: ./filling.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libfilling2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libfilling2_a-filling.o -MD -MP -MF $(DEPDIR)/libfilling2_a-filling.Tpo -c -o ./libfilling2_a-filling.o `test -f './filling.c' || echo '$(srcdir)/'`./filling.c
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libfilling2_a-filling.Tpo $(DEPDIR)/libfilling2_a-filling.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./filling.c' object='./libfilling2_a-filling.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libfilling2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libfilling2_a-filling.o `test -f './filling.c' || echo '$(srcdir)/'`./filling.c
+
+./libfilling2_a-filling.obj: ./filling.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libfilling2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libfilling2_a-filling.obj -MD -MP -MF $(DEPDIR)/libfilling2_a-filling.Tpo -c -o ./libfilling2_a-filling.obj `if test -f './filling.c'; then $(CYGPATH_W) './filling.c'; else $(CYGPATH_W) '$(srcdir)/./filling.c'; fi`
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libfilling2_a-filling.Tpo $(DEPDIR)/libfilling2_a-filling.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./filling.c' object='./libfilling2_a-filling.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libfilling2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libfilling2_a-filling.obj `if test -f './filling.c'; then $(CYGPATH_W) './filling.c'; else $(CYGPATH_W) '$(srcdir)/./filling.c'; fi`
+
+./libgalaxie2_a-galaxies.o: ./galaxies.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgalaxie2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libgalaxie2_a-galaxies.o -MD -MP -MF $(DEPDIR)/libgalaxie2_a-galaxies.Tpo -c -o ./libgalaxie2_a-galaxies.o `test -f './galaxies.c' || echo '$(srcdir)/'`./galaxies.c
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libgalaxie2_a-galaxies.Tpo $(DEPDIR)/libgalaxie2_a-galaxies.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./galaxies.c' object='./libgalaxie2_a-galaxies.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgalaxie2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libgalaxie2_a-galaxies.o `test -f './galaxies.c' || echo '$(srcdir)/'`./galaxies.c
+
+./libgalaxie2_a-galaxies.obj: ./galaxies.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgalaxie2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libgalaxie2_a-galaxies.obj -MD -MP -MF $(DEPDIR)/libgalaxie2_a-galaxies.Tpo -c -o ./libgalaxie2_a-galaxies.obj `if test -f './galaxies.c'; then $(CYGPATH_W) './galaxies.c'; else $(CYGPATH_W) '$(srcdir)/./galaxies.c'; fi`
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libgalaxie2_a-galaxies.Tpo $(DEPDIR)/libgalaxie2_a-galaxies.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./galaxies.c' object='./libgalaxie2_a-galaxies.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgalaxie2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libgalaxie2_a-galaxies.obj `if test -f './galaxies.c'; then $(CYGPATH_W) './galaxies.c'; else $(CYGPATH_W) '$(srcdir)/./galaxies.c'; fi`
+
+./libgalaxie4_a-galaxies.o: ./galaxies.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgalaxie4_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libgalaxie4_a-galaxies.o -MD -MP -MF $(DEPDIR)/libgalaxie4_a-galaxies.Tpo -c -o ./libgalaxie4_a-galaxies.o `test -f './galaxies.c' || echo '$(srcdir)/'`./galaxies.c
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libgalaxie4_a-galaxies.Tpo $(DEPDIR)/libgalaxie4_a-galaxies.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./galaxies.c' object='./libgalaxie4_a-galaxies.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgalaxie4_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libgalaxie4_a-galaxies.o `test -f './galaxies.c' || echo '$(srcdir)/'`./galaxies.c
+
+./libgalaxie4_a-galaxies.obj: ./galaxies.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgalaxie4_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libgalaxie4_a-galaxies.obj -MD -MP -MF $(DEPDIR)/libgalaxie4_a-galaxies.Tpo -c -o ./libgalaxie4_a-galaxies.obj `if test -f './galaxies.c'; then $(CYGPATH_W) './galaxies.c'; else $(CYGPATH_W) '$(srcdir)/./galaxies.c'; fi`
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libgalaxie4_a-galaxies.Tpo $(DEPDIR)/libgalaxie4_a-galaxies.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./galaxies.c' object='./libgalaxie4_a-galaxies.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgalaxie4_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libgalaxie4_a-galaxies.obj `if test -f './galaxies.c'; then $(CYGPATH_W) './galaxies.c'; else $(CYGPATH_W) '$(srcdir)/./galaxies.c'; fi`
+
+./libkeen2_a-keen.o: ./keen.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkeen2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libkeen2_a-keen.o -MD -MP -MF $(DEPDIR)/libkeen2_a-keen.Tpo -c -o ./libkeen2_a-keen.o `test -f './keen.c' || echo '$(srcdir)/'`./keen.c
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libkeen2_a-keen.Tpo $(DEPDIR)/libkeen2_a-keen.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./keen.c' object='./libkeen2_a-keen.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkeen2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libkeen2_a-keen.o `test -f './keen.c' || echo '$(srcdir)/'`./keen.c
+
+./libkeen2_a-keen.obj: ./keen.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkeen2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libkeen2_a-keen.obj -MD -MP -MF $(DEPDIR)/libkeen2_a-keen.Tpo -c -o ./libkeen2_a-keen.obj `if test -f './keen.c'; then $(CYGPATH_W) './keen.c'; else $(CYGPATH_W) '$(srcdir)/./keen.c'; fi`
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libkeen2_a-keen.Tpo $(DEPDIR)/libkeen2_a-keen.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./keen.c' object='./libkeen2_a-keen.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkeen2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libkeen2_a-keen.obj `if test -f './keen.c'; then $(CYGPATH_W) './keen.c'; else $(CYGPATH_W) '$(srcdir)/./keen.c'; fi`
+
+./liblatin6_a-latin.o: ./latin.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblatin6_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./liblatin6_a-latin.o -MD -MP -MF $(DEPDIR)/liblatin6_a-latin.Tpo -c -o ./liblatin6_a-latin.o `test -f './latin.c' || echo '$(srcdir)/'`./latin.c
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/liblatin6_a-latin.Tpo $(DEPDIR)/liblatin6_a-latin.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./latin.c' object='./liblatin6_a-latin.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblatin6_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./liblatin6_a-latin.o `test -f './latin.c' || echo '$(srcdir)/'`./latin.c
+
+./liblatin6_a-latin.obj: ./latin.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblatin6_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./liblatin6_a-latin.obj -MD -MP -MF $(DEPDIR)/liblatin6_a-latin.Tpo -c -o ./liblatin6_a-latin.obj `if test -f './latin.c'; then $(CYGPATH_W) './latin.c'; else $(CYGPATH_W) '$(srcdir)/./latin.c'; fi`
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/liblatin6_a-latin.Tpo $(DEPDIR)/liblatin6_a-latin.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./latin.c' object='./liblatin6_a-latin.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblatin6_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./liblatin6_a-latin.obj `if test -f './latin.c'; then $(CYGPATH_W) './latin.c'; else $(CYGPATH_W) '$(srcdir)/./latin.c'; fi`
+
+./liblatin8_a-latin.o: ./latin.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblatin8_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./liblatin8_a-latin.o -MD -MP -MF $(DEPDIR)/liblatin8_a-latin.Tpo -c -o ./liblatin8_a-latin.o `test -f './latin.c' || echo '$(srcdir)/'`./latin.c
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/liblatin8_a-latin.Tpo $(DEPDIR)/liblatin8_a-latin.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./latin.c' object='./liblatin8_a-latin.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblatin8_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./liblatin8_a-latin.o `test -f './latin.c' || echo '$(srcdir)/'`./latin.c
+
+./liblatin8_a-latin.obj: ./latin.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblatin8_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./liblatin8_a-latin.obj -MD -MP -MF $(DEPDIR)/liblatin8_a-latin.Tpo -c -o ./liblatin8_a-latin.obj `if test -f './latin.c'; then $(CYGPATH_W) './latin.c'; else $(CYGPATH_W) '$(srcdir)/./latin.c'; fi`
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/liblatin8_a-latin.Tpo $(DEPDIR)/liblatin8_a-latin.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./latin.c' object='./liblatin8_a-latin.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblatin8_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./liblatin8_a-latin.obj `if test -f './latin.c'; then $(CYGPATH_W) './latin.c'; else $(CYGPATH_W) '$(srcdir)/./latin.c'; fi`
+
+./liblightup2_a-lightup.o: ./lightup.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblightup2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./liblightup2_a-lightup.o -MD -MP -MF $(DEPDIR)/liblightup2_a-lightup.Tpo -c -o ./liblightup2_a-lightup.o `test -f './lightup.c' || echo '$(srcdir)/'`./lightup.c
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/liblightup2_a-lightup.Tpo $(DEPDIR)/liblightup2_a-lightup.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./lightup.c' object='./liblightup2_a-lightup.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblightup2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./liblightup2_a-lightup.o `test -f './lightup.c' || echo '$(srcdir)/'`./lightup.c
+
+./liblightup2_a-lightup.obj: ./lightup.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblightup2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./liblightup2_a-lightup.obj -MD -MP -MF $(DEPDIR)/liblightup2_a-lightup.Tpo -c -o ./liblightup2_a-lightup.obj `if test -f './lightup.c'; then $(CYGPATH_W) './lightup.c'; else $(CYGPATH_W) '$(srcdir)/./lightup.c'; fi`
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/liblightup2_a-lightup.Tpo $(DEPDIR)/liblightup2_a-lightup.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./lightup.c' object='./liblightup2_a-lightup.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblightup2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./liblightup2_a-lightup.obj `if test -f './lightup.c'; then $(CYGPATH_W) './lightup.c'; else $(CYGPATH_W) '$(srcdir)/./lightup.c'; fi`
+
+./libloopy2_a-loopy.o: ./loopy.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloopy2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libloopy2_a-loopy.o -MD -MP -MF $(DEPDIR)/libloopy2_a-loopy.Tpo -c -o ./libloopy2_a-loopy.o `test -f './loopy.c' || echo '$(srcdir)/'`./loopy.c
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libloopy2_a-loopy.Tpo $(DEPDIR)/libloopy2_a-loopy.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./loopy.c' object='./libloopy2_a-loopy.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloopy2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libloopy2_a-loopy.o `test -f './loopy.c' || echo '$(srcdir)/'`./loopy.c
+
+./libloopy2_a-loopy.obj: ./loopy.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloopy2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libloopy2_a-loopy.obj -MD -MP -MF $(DEPDIR)/libloopy2_a-loopy.Tpo -c -o ./libloopy2_a-loopy.obj `if test -f './loopy.c'; then $(CYGPATH_W) './loopy.c'; else $(CYGPATH_W) '$(srcdir)/./loopy.c'; fi`
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libloopy2_a-loopy.Tpo $(DEPDIR)/libloopy2_a-loopy.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./loopy.c' object='./libloopy2_a-loopy.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libloopy2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libloopy2_a-loopy.obj `if test -f './loopy.c'; then $(CYGPATH_W) './loopy.c'; else $(CYGPATH_W) '$(srcdir)/./loopy.c'; fi`
+
+./libmagnets2_a-magnets.o: ./magnets.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmagnets2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libmagnets2_a-magnets.o -MD -MP -MF $(DEPDIR)/libmagnets2_a-magnets.Tpo -c -o ./libmagnets2_a-magnets.o `test -f './magnets.c' || echo '$(srcdir)/'`./magnets.c
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libmagnets2_a-magnets.Tpo $(DEPDIR)/libmagnets2_a-magnets.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./magnets.c' object='./libmagnets2_a-magnets.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmagnets2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libmagnets2_a-magnets.o `test -f './magnets.c' || echo '$(srcdir)/'`./magnets.c
+
+./libmagnets2_a-magnets.obj: ./magnets.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmagnets2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libmagnets2_a-magnets.obj -MD -MP -MF $(DEPDIR)/libmagnets2_a-magnets.Tpo -c -o ./libmagnets2_a-magnets.obj `if test -f './magnets.c'; then $(CYGPATH_W) './magnets.c'; else $(CYGPATH_W) '$(srcdir)/./magnets.c'; fi`
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libmagnets2_a-magnets.Tpo $(DEPDIR)/libmagnets2_a-magnets.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./magnets.c' object='./libmagnets2_a-magnets.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmagnets2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libmagnets2_a-magnets.obj `if test -f './magnets.c'; then $(CYGPATH_W) './magnets.c'; else $(CYGPATH_W) '$(srcdir)/./magnets.c'; fi`
+
+./libmap2_a-map.o: ./map.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmap2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libmap2_a-map.o -MD -MP -MF $(DEPDIR)/libmap2_a-map.Tpo -c -o ./libmap2_a-map.o `test -f './map.c' || echo '$(srcdir)/'`./map.c
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libmap2_a-map.Tpo $(DEPDIR)/libmap2_a-map.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./map.c' object='./libmap2_a-map.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmap2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libmap2_a-map.o `test -f './map.c' || echo '$(srcdir)/'`./map.c
+
+./libmap2_a-map.obj: ./map.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmap2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libmap2_a-map.obj -MD -MP -MF $(DEPDIR)/libmap2_a-map.Tpo -c -o ./libmap2_a-map.obj `if test -f './map.c'; then $(CYGPATH_W) './map.c'; else $(CYGPATH_W) '$(srcdir)/./map.c'; fi`
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libmap2_a-map.Tpo $(DEPDIR)/libmap2_a-map.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./map.c' object='./libmap2_a-map.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmap2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libmap2_a-map.obj `if test -f './map.c'; then $(CYGPATH_W) './map.c'; else $(CYGPATH_W) '$(srcdir)/./map.c'; fi`
+
+./libmines2_a-mines.o: ./mines.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmines2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libmines2_a-mines.o -MD -MP -MF $(DEPDIR)/libmines2_a-mines.Tpo -c -o ./libmines2_a-mines.o `test -f './mines.c' || echo '$(srcdir)/'`./mines.c
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libmines2_a-mines.Tpo $(DEPDIR)/libmines2_a-mines.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./mines.c' object='./libmines2_a-mines.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmines2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libmines2_a-mines.o `test -f './mines.c' || echo '$(srcdir)/'`./mines.c
+
+./libmines2_a-mines.obj: ./mines.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmines2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libmines2_a-mines.obj -MD -MP -MF $(DEPDIR)/libmines2_a-mines.Tpo -c -o ./libmines2_a-mines.obj `if test -f './mines.c'; then $(CYGPATH_W) './mines.c'; else $(CYGPATH_W) '$(srcdir)/./mines.c'; fi`
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libmines2_a-mines.Tpo $(DEPDIR)/libmines2_a-mines.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./mines.c' object='./libmines2_a-mines.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmines2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libmines2_a-mines.obj `if test -f './mines.c'; then $(CYGPATH_W) './mines.c'; else $(CYGPATH_W) '$(srcdir)/./mines.c'; fi`
+
+./libpattern2_a-pattern.o: ./pattern.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpattern2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libpattern2_a-pattern.o -MD -MP -MF $(DEPDIR)/libpattern2_a-pattern.Tpo -c -o ./libpattern2_a-pattern.o `test -f './pattern.c' || echo '$(srcdir)/'`./pattern.c
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libpattern2_a-pattern.Tpo $(DEPDIR)/libpattern2_a-pattern.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./pattern.c' object='./libpattern2_a-pattern.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpattern2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libpattern2_a-pattern.o `test -f './pattern.c' || echo '$(srcdir)/'`./pattern.c
+
+./libpattern2_a-pattern.obj: ./pattern.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpattern2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libpattern2_a-pattern.obj -MD -MP -MF $(DEPDIR)/libpattern2_a-pattern.Tpo -c -o ./libpattern2_a-pattern.obj `if test -f './pattern.c'; then $(CYGPATH_W) './pattern.c'; else $(CYGPATH_W) '$(srcdir)/./pattern.c'; fi`
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libpattern2_a-pattern.Tpo $(DEPDIR)/libpattern2_a-pattern.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./pattern.c' object='./libpattern2_a-pattern.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpattern2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libpattern2_a-pattern.obj `if test -f './pattern.c'; then $(CYGPATH_W) './pattern.c'; else $(CYGPATH_W) '$(srcdir)/./pattern.c'; fi`
+
+./libpattern4_a-pattern.o: ./pattern.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpattern4_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libpattern4_a-pattern.o -MD -MP -MF $(DEPDIR)/libpattern4_a-pattern.Tpo -c -o ./libpattern4_a-pattern.o `test -f './pattern.c' || echo '$(srcdir)/'`./pattern.c
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libpattern4_a-pattern.Tpo $(DEPDIR)/libpattern4_a-pattern.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./pattern.c' object='./libpattern4_a-pattern.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpattern4_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libpattern4_a-pattern.o `test -f './pattern.c' || echo '$(srcdir)/'`./pattern.c
+
+./libpattern4_a-pattern.obj: ./pattern.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpattern4_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libpattern4_a-pattern.obj -MD -MP -MF $(DEPDIR)/libpattern4_a-pattern.Tpo -c -o ./libpattern4_a-pattern.obj `if test -f './pattern.c'; then $(CYGPATH_W) './pattern.c'; else $(CYGPATH_W) '$(srcdir)/./pattern.c'; fi`
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libpattern4_a-pattern.Tpo $(DEPDIR)/libpattern4_a-pattern.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./pattern.c' object='./libpattern4_a-pattern.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpattern4_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libpattern4_a-pattern.obj `if test -f './pattern.c'; then $(CYGPATH_W) './pattern.c'; else $(CYGPATH_W) '$(srcdir)/./pattern.c'; fi`
+
+./libpearl2_a-pearl.o: ./pearl.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpearl2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libpearl2_a-pearl.o -MD -MP -MF $(DEPDIR)/libpearl2_a-pearl.Tpo -c -o ./libpearl2_a-pearl.o `test -f './pearl.c' || echo '$(srcdir)/'`./pearl.c
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libpearl2_a-pearl.Tpo $(DEPDIR)/libpearl2_a-pearl.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./pearl.c' object='./libpearl2_a-pearl.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpearl2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libpearl2_a-pearl.o `test -f './pearl.c' || echo '$(srcdir)/'`./pearl.c
+
+./libpearl2_a-pearl.obj: ./pearl.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpearl2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libpearl2_a-pearl.obj -MD -MP -MF $(DEPDIR)/libpearl2_a-pearl.Tpo -c -o ./libpearl2_a-pearl.obj `if test -f './pearl.c'; then $(CYGPATH_W) './pearl.c'; else $(CYGPATH_W) '$(srcdir)/./pearl.c'; fi`
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libpearl2_a-pearl.Tpo $(DEPDIR)/libpearl2_a-pearl.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./pearl.c' object='./libpearl2_a-pearl.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpearl2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libpearl2_a-pearl.obj `if test -f './pearl.c'; then $(CYGPATH_W) './pearl.c'; else $(CYGPATH_W) '$(srcdir)/./pearl.c'; fi`
+
+./libsignpos2_a-signpost.o: ./signpost.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libsignpos2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libsignpos2_a-signpost.o -MD -MP -MF $(DEPDIR)/libsignpos2_a-signpost.Tpo -c -o ./libsignpos2_a-signpost.o `test -f './signpost.c' || echo '$(srcdir)/'`./signpost.c
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libsignpos2_a-signpost.Tpo $(DEPDIR)/libsignpos2_a-signpost.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./signpost.c' object='./libsignpos2_a-signpost.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libsignpos2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libsignpos2_a-signpost.o `test -f './signpost.c' || echo '$(srcdir)/'`./signpost.c
+
+./libsignpos2_a-signpost.obj: ./signpost.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libsignpos2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libsignpos2_a-signpost.obj -MD -MP -MF $(DEPDIR)/libsignpos2_a-signpost.Tpo -c -o ./libsignpos2_a-signpost.obj `if test -f './signpost.c'; then $(CYGPATH_W) './signpost.c'; else $(CYGPATH_W) '$(srcdir)/./signpost.c'; fi`
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libsignpos2_a-signpost.Tpo $(DEPDIR)/libsignpos2_a-signpost.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./signpost.c' object='./libsignpos2_a-signpost.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libsignpos2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libsignpos2_a-signpost.obj `if test -f './signpost.c'; then $(CYGPATH_W) './signpost.c'; else $(CYGPATH_W) '$(srcdir)/./signpost.c'; fi`
+
+./libsingles3_a-singles.o: ./singles.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libsingles3_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libsingles3_a-singles.o -MD -MP -MF $(DEPDIR)/libsingles3_a-singles.Tpo -c -o ./libsingles3_a-singles.o `test -f './singles.c' || echo '$(srcdir)/'`./singles.c
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libsingles3_a-singles.Tpo $(DEPDIR)/libsingles3_a-singles.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./singles.c' object='./libsingles3_a-singles.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libsingles3_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libsingles3_a-singles.o `test -f './singles.c' || echo '$(srcdir)/'`./singles.c
+
+./libsingles3_a-singles.obj: ./singles.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libsingles3_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libsingles3_a-singles.obj -MD -MP -MF $(DEPDIR)/libsingles3_a-singles.Tpo -c -o ./libsingles3_a-singles.obj `if test -f './singles.c'; then $(CYGPATH_W) './singles.c'; else $(CYGPATH_W) '$(srcdir)/./singles.c'; fi`
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libsingles3_a-singles.Tpo $(DEPDIR)/libsingles3_a-singles.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./singles.c' object='./libsingles3_a-singles.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libsingles3_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libsingles3_a-singles.obj `if test -f './singles.c'; then $(CYGPATH_W) './singles.c'; else $(CYGPATH_W) '$(srcdir)/./singles.c'; fi`
+
+./libslant2_a-slant.o: ./slant.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libslant2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libslant2_a-slant.o -MD -MP -MF $(DEPDIR)/libslant2_a-slant.Tpo -c -o ./libslant2_a-slant.o `test -f './slant.c' || echo '$(srcdir)/'`./slant.c
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libslant2_a-slant.Tpo $(DEPDIR)/libslant2_a-slant.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./slant.c' object='./libslant2_a-slant.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libslant2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libslant2_a-slant.o `test -f './slant.c' || echo '$(srcdir)/'`./slant.c
+
+./libslant2_a-slant.obj: ./slant.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libslant2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libslant2_a-slant.obj -MD -MP -MF $(DEPDIR)/libslant2_a-slant.Tpo -c -o ./libslant2_a-slant.obj `if test -f './slant.c'; then $(CYGPATH_W) './slant.c'; else $(CYGPATH_W) '$(srcdir)/./slant.c'; fi`
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libslant2_a-slant.Tpo $(DEPDIR)/libslant2_a-slant.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./slant.c' object='./libslant2_a-slant.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libslant2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libslant2_a-slant.obj `if test -f './slant.c'; then $(CYGPATH_W) './slant.c'; else $(CYGPATH_W) '$(srcdir)/./slant.c'; fi`
+
+./libsolo2_a-solo.o: ./solo.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libsolo2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libsolo2_a-solo.o -MD -MP -MF $(DEPDIR)/libsolo2_a-solo.Tpo -c -o ./libsolo2_a-solo.o `test -f './solo.c' || echo '$(srcdir)/'`./solo.c
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libsolo2_a-solo.Tpo $(DEPDIR)/libsolo2_a-solo.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./solo.c' object='./libsolo2_a-solo.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libsolo2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libsolo2_a-solo.o `test -f './solo.c' || echo '$(srcdir)/'`./solo.c
+
+./libsolo2_a-solo.obj: ./solo.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libsolo2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libsolo2_a-solo.obj -MD -MP -MF $(DEPDIR)/libsolo2_a-solo.Tpo -c -o ./libsolo2_a-solo.obj `if test -f './solo.c'; then $(CYGPATH_W) './solo.c'; else $(CYGPATH_W) '$(srcdir)/./solo.c'; fi`
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libsolo2_a-solo.Tpo $(DEPDIR)/libsolo2_a-solo.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./solo.c' object='./libsolo2_a-solo.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libsolo2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libsolo2_a-solo.obj `if test -f './solo.c'; then $(CYGPATH_W) './solo.c'; else $(CYGPATH_W) '$(srcdir)/./solo.c'; fi`
+
+./libtents3_a-tents.o: ./tents.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtents3_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libtents3_a-tents.o -MD -MP -MF $(DEPDIR)/libtents3_a-tents.Tpo -c -o ./libtents3_a-tents.o `test -f './tents.c' || echo '$(srcdir)/'`./tents.c
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libtents3_a-tents.Tpo $(DEPDIR)/libtents3_a-tents.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./tents.c' object='./libtents3_a-tents.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtents3_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libtents3_a-tents.o `test -f './tents.c' || echo '$(srcdir)/'`./tents.c
+
+./libtents3_a-tents.obj: ./tents.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtents3_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libtents3_a-tents.obj -MD -MP -MF $(DEPDIR)/libtents3_a-tents.Tpo -c -o ./libtents3_a-tents.obj `if test -f './tents.c'; then $(CYGPATH_W) './tents.c'; else $(CYGPATH_W) '$(srcdir)/./tents.c'; fi`
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libtents3_a-tents.Tpo $(DEPDIR)/libtents3_a-tents.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./tents.c' object='./libtents3_a-tents.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtents3_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libtents3_a-tents.obj `if test -f './tents.c'; then $(CYGPATH_W) './tents.c'; else $(CYGPATH_W) '$(srcdir)/./tents.c'; fi`
+
+./libtowers2_a-towers.o: ./towers.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtowers2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libtowers2_a-towers.o -MD -MP -MF $(DEPDIR)/libtowers2_a-towers.Tpo -c -o ./libtowers2_a-towers.o `test -f './towers.c' || echo '$(srcdir)/'`./towers.c
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libtowers2_a-towers.Tpo $(DEPDIR)/libtowers2_a-towers.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./towers.c' object='./libtowers2_a-towers.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtowers2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libtowers2_a-towers.o `test -f './towers.c' || echo '$(srcdir)/'`./towers.c
+
+./libtowers2_a-towers.obj: ./towers.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtowers2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libtowers2_a-towers.obj -MD -MP -MF $(DEPDIR)/libtowers2_a-towers.Tpo -c -o ./libtowers2_a-towers.obj `if test -f './towers.c'; then $(CYGPATH_W) './towers.c'; else $(CYGPATH_W) '$(srcdir)/./towers.c'; fi`
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libtowers2_a-towers.Tpo $(DEPDIR)/libtowers2_a-towers.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./towers.c' object='./libtowers2_a-towers.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libtowers2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libtowers2_a-towers.obj `if test -f './towers.c'; then $(CYGPATH_W) './towers.c'; else $(CYGPATH_W) '$(srcdir)/./towers.c'; fi`
+
+./libunequal2_a-unequal.o: ./unequal.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libunequal2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libunequal2_a-unequal.o -MD -MP -MF $(DEPDIR)/libunequal2_a-unequal.Tpo -c -o ./libunequal2_a-unequal.o `test -f './unequal.c' || echo '$(srcdir)/'`./unequal.c
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libunequal2_a-unequal.Tpo $(DEPDIR)/libunequal2_a-unequal.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./unequal.c' object='./libunequal2_a-unequal.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libunequal2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libunequal2_a-unequal.o `test -f './unequal.c' || echo '$(srcdir)/'`./unequal.c
+
+./libunequal2_a-unequal.obj: ./unequal.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libunequal2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libunequal2_a-unequal.obj -MD -MP -MF $(DEPDIR)/libunequal2_a-unequal.Tpo -c -o ./libunequal2_a-unequal.obj `if test -f './unequal.c'; then $(CYGPATH_W) './unequal.c'; else $(CYGPATH_W) '$(srcdir)/./unequal.c'; fi`
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libunequal2_a-unequal.Tpo $(DEPDIR)/libunequal2_a-unequal.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./unequal.c' object='./libunequal2_a-unequal.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libunequal2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libunequal2_a-unequal.obj `if test -f './unequal.c'; then $(CYGPATH_W) './unequal.c'; else $(CYGPATH_W) '$(srcdir)/./unequal.c'; fi`
+
+./libunruly2_a-unruly.o: ./unruly.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libunruly2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libunruly2_a-unruly.o -MD -MP -MF $(DEPDIR)/libunruly2_a-unruly.Tpo -c -o ./libunruly2_a-unruly.o `test -f './unruly.c' || echo '$(srcdir)/'`./unruly.c
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libunruly2_a-unruly.Tpo $(DEPDIR)/libunruly2_a-unruly.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./unruly.c' object='./libunruly2_a-unruly.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libunruly2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libunruly2_a-unruly.o `test -f './unruly.c' || echo '$(srcdir)/'`./unruly.c
+
+./libunruly2_a-unruly.obj: ./unruly.c
+@am__fastdepCC_TRUE@   $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libunruly2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ./libunruly2_a-unruly.obj -MD -MP -MF $(DEPDIR)/libunruly2_a-unruly.Tpo -c -o ./libunruly2_a-unruly.obj `if test -f './unruly.c'; then $(CYGPATH_W) './unruly.c'; else $(CYGPATH_W) '$(srcdir)/./unruly.c'; fi`
+@am__fastdepCC_TRUE@   $(AM_V_at)$(am__mv) $(DEPDIR)/libunruly2_a-unruly.Tpo $(DEPDIR)/libunruly2_a-unruly.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      $(AM_V_CC)source='./unruly.c' object='./libunruly2_a-unruly.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@      DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@  $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libunruly2_a_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ./libunruly2_a-unruly.obj `if test -f './unruly.c'; then $(CYGPATH_W) './unruly.c'; else $(CYGPATH_W) '$(srcdir)/./unruly.c'; fi`
+
+ID: $(am__tagged_files)
+       $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+       set x; \
+       here=`pwd`; \
+       $(am__define_uniq_tagged_files); \
+       shift; \
+       if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+         test -n "$$unique" || unique=$$empty_fix; \
+         if test $$# -gt 0; then \
+           $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+             "$$@" $$unique; \
+         else \
+           $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+             $$unique; \
+         fi; \
+       fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+       $(am__define_uniq_tagged_files); \
+       test -z "$(CTAGS_ARGS)$$unique" \
+         || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+            $$unique
+
+GTAGS:
+       here=`$(am__cd) $(top_builddir) && pwd` \
+         && $(am__cd) $(top_srcdir) \
+         && gtags -i $(GTAGS_ARGS) "$$here"
+cscope: cscope.files
+       test ! -s cscope.files \
+         || $(CSCOPE) -b -q $(AM_CSCOPEFLAGS) $(CSCOPEFLAGS) -i cscope.files $(CSCOPE_ARGS)
+clean-cscope:
+       -rm -f cscope.files
+cscope.files: clean-cscope cscopelist
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+       list='$(am__tagged_files)'; \
+       case "$(srcdir)" in \
+         [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+         *) sdir=$(subdir)/$(srcdir) ;; \
+       esac; \
+       for i in $$list; do \
+         if test -f "$$i"; then \
+           echo "$(subdir)/$$i"; \
+         else \
+           echo "$$sdir/$$i"; \
+         fi; \
+       done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+       -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+       -rm -f cscope.out cscope.in.out cscope.po.out cscope.files
+
+distdir: $(DISTFILES)
+       $(am__remove_distdir)
+       test -d "$(distdir)" || mkdir "$(distdir)"
+       @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+       topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+       list='$(DISTFILES)'; \
+         dist_files=`for file in $$list; do echo $$file; done | \
+         sed -e "s|^$$srcdirstrip/||;t" \
+             -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+       case $$dist_files in \
+         */*) $(MKDIR_P) `echo "$$dist_files" | \
+                          sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+                          sort -u` ;; \
+       esac; \
+       for file in $$dist_files; do \
+         if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+         if test -d $$d/$$file; then \
+           dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+           if test -d "$(distdir)/$$file"; then \
+             find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+           fi; \
+           if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+             cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+             find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+           fi; \
+           cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+         else \
+           test -f "$(distdir)/$$file" \
+           || cp -p $$d/$$file "$(distdir)/$$file" \
+           || exit 1; \
+         fi; \
+       done
+       -test -n "$(am__skip_mode_fix)" \
+       || find "$(distdir)" -type d ! -perm -755 \
+               -exec chmod u+rwx,go+rx {} \; -o \
+         ! -type d ! -perm -444 -links 1 -exec chmod a+r {} \; -o \
+         ! -type d ! -perm -400 -exec chmod a+r {} \; -o \
+         ! -type d ! -perm -444 -exec $(install_sh) -c -m a+r {} {} \; \
+       || chmod -R a+r "$(distdir)"
+dist-gzip: distdir
+       tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz
+       $(am__post_remove_distdir)
+
+dist-bzip2: distdir
+       tardir=$(distdir) && $(am__tar) | BZIP2=$${BZIP2--9} bzip2 -c >$(distdir).tar.bz2
+       $(am__post_remove_distdir)
+
+dist-lzip: distdir
+       tardir=$(distdir) && $(am__tar) | lzip -c $${LZIP_OPT--9} >$(distdir).tar.lz
+       $(am__post_remove_distdir)
+
+dist-xz: distdir
+       tardir=$(distdir) && $(am__tar) | XZ_OPT=$${XZ_OPT--e} xz -c >$(distdir).tar.xz
+       $(am__post_remove_distdir)
+
+dist-tarZ: distdir
+       @echo WARNING: "Support for distribution archives compressed with" \
+                      "legacy program 'compress' is deprecated." >&2
+       @echo WARNING: "It will be removed altogether in Automake 2.0" >&2
+       tardir=$(distdir) && $(am__tar) | compress -c >$(distdir).tar.Z
+       $(am__post_remove_distdir)
+
+dist-shar: distdir
+       @echo WARNING: "Support for shar distribution archives is" \
+                      "deprecated." >&2
+       @echo WARNING: "It will be removed altogether in Automake 2.0" >&2
+       shar $(distdir) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).shar.gz
+       $(am__post_remove_distdir)
+
+dist-zip: distdir
+       -rm -f $(distdir).zip
+       zip -rq $(distdir).zip $(distdir)
+       $(am__post_remove_distdir)
+
+dist dist-all:
+       $(MAKE) $(AM_MAKEFLAGS) $(DIST_TARGETS) am__post_remove_distdir='@:'
+       $(am__post_remove_distdir)
+
+# This target untars the dist file and tries a VPATH configuration.  Then
+# it guarantees that the distribution is self-contained by making another
+# tarfile.
+distcheck: dist
+       case '$(DIST_ARCHIVES)' in \
+       *.tar.gz*) \
+         GZIP=$(GZIP_ENV) gzip -dc $(distdir).tar.gz | $(am__untar) ;;\
+       *.tar.bz2*) \
+         bzip2 -dc $(distdir).tar.bz2 | $(am__untar) ;;\
+       *.tar.lz*) \
+         lzip -dc $(distdir).tar.lz | $(am__untar) ;;\
+       *.tar.xz*) \
+         xz -dc $(distdir).tar.xz | $(am__untar) ;;\
+       *.tar.Z*) \
+         uncompress -c $(distdir).tar.Z | $(am__untar) ;;\
+       *.shar.gz*) \
+         GZIP=$(GZIP_ENV) gzip -dc $(distdir).shar.gz | unshar ;;\
+       *.zip*) \
+         unzip $(distdir).zip ;;\
+       esac
+       chmod -R a-w $(distdir)
+       chmod u+w $(distdir)
+       mkdir $(distdir)/_build $(distdir)/_build/sub $(distdir)/_inst
+       chmod a-w $(distdir)
+       test -d $(distdir)/_build || exit 0; \
+       dc_install_base=`$(am__cd) $(distdir)/_inst && pwd | sed -e 's,^[^:\\/]:[\\/],/,'` \
+         && dc_destdir="$${TMPDIR-/tmp}/am-dc-$$$$/" \
+         && am__cwd=`pwd` \
+         && $(am__cd) $(distdir)/_build/sub \
+         && ../../configure \
+           $(AM_DISTCHECK_CONFIGURE_FLAGS) \
+           $(DISTCHECK_CONFIGURE_FLAGS) \
+           --srcdir=../.. --prefix="$$dc_install_base" \
+         && $(MAKE) $(AM_MAKEFLAGS) \
+         && $(MAKE) $(AM_MAKEFLAGS) dvi \
+         && $(MAKE) $(AM_MAKEFLAGS) check \
+         && $(MAKE) $(AM_MAKEFLAGS) install \
+         && $(MAKE) $(AM_MAKEFLAGS) installcheck \
+         && $(MAKE) $(AM_MAKEFLAGS) uninstall \
+         && $(MAKE) $(AM_MAKEFLAGS) distuninstallcheck_dir="$$dc_install_base" \
+               distuninstallcheck \
+         && chmod -R a-w "$$dc_install_base" \
+         && ({ \
+              (cd ../.. && umask 077 && mkdir "$$dc_destdir") \
+              && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" install \
+              && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" uninstall \
+              && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" \
+                   distuninstallcheck_dir="$$dc_destdir" distuninstallcheck; \
+             } || { rm -rf "$$dc_destdir"; exit 1; }) \
+         && rm -rf "$$dc_destdir" \
+         && $(MAKE) $(AM_MAKEFLAGS) dist \
+         && rm -rf $(DIST_ARCHIVES) \
+         && $(MAKE) $(AM_MAKEFLAGS) distcleancheck \
+         && cd "$$am__cwd" \
+         || exit 1
+       $(am__post_remove_distdir)
+       @(echo "$(distdir) archives ready for distribution: "; \
+         list='$(DIST_ARCHIVES)'; for i in $$list; do echo $$i; done) | \
+         sed -e 1h -e 1s/./=/g -e 1p -e 1x -e '$$p' -e '$$x'
+distuninstallcheck:
+       @test -n '$(distuninstallcheck_dir)' || { \
+         echo 'ERROR: trying to run $@ with an empty' \
+              '$$(distuninstallcheck_dir)' >&2; \
+         exit 1; \
+       }; \
+       $(am__cd) '$(distuninstallcheck_dir)' || { \
+         echo 'ERROR: cannot chdir into $(distuninstallcheck_dir)' >&2; \
+         exit 1; \
+       }; \
+       test `$(am__distuninstallcheck_listfiles) | wc -l` -eq 0 \
+          || { echo "ERROR: files left after uninstall:" ; \
+               if test -n "$(DESTDIR)"; then \
+                 echo "  (check DESTDIR support)"; \
+               fi ; \
+               $(distuninstallcheck_listfiles) ; \
+               exit 1; } >&2
+distcleancheck: distclean
+       @if test '$(srcdir)' = . ; then \
+         echo "ERROR: distcleancheck can only run from a VPATH build" ; \
+         exit 1 ; \
+       fi
+       @test `$(distcleancheck_listfiles) | wc -l` -eq 0 \
+         || { echo "ERROR: files left in build directory after distclean:" ; \
+              $(distcleancheck_listfiles) ; \
+              exit 1; } >&2
+check-am: all-am
+check: check-am
+all-am: Makefile $(LIBRARIES) $(PROGRAMS)
+installdirs:
+       for dir in "$(DESTDIR)$(bindir)"; do \
+         test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+       done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+       @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+       if test -z '$(STRIP)'; then \
+         $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+           install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+             install; \
+       else \
+         $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+           install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+           "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+       fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+       -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+       -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+       -rm -f ./$(am__dirstamp)
+       -rm -f icons/$(DEPDIR)/$(am__dirstamp)
+       -rm -f icons/$(am__dirstamp)
+       -test -z "$(DEPDIR)/$(am__dirstamp)" || rm -f $(DEPDIR)/$(am__dirstamp)
+
+maintainer-clean-generic:
+       @echo "This command is intended for maintainers to use"
+       @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-binPROGRAMS clean-generic clean-noinstLIBRARIES \
+       clean-noinstPROGRAMS mostlyclean-am
+
+distclean: distclean-am
+       -rm -f $(am__CONFIG_DISTCLEAN_FILES)
+       -rm -rf ./$(DEPDIR) icons/$(DEPDIR)
+       -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+       distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-binPROGRAMS
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+       -rm -f $(am__CONFIG_DISTCLEAN_FILES)
+       -rm -rf $(top_srcdir)/autom4te.cache
+       -rm -rf ./$(DEPDIR) icons/$(DEPDIR)
+       -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-binPROGRAMS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--refresh check check-am clean \
+       clean-binPROGRAMS clean-cscope clean-generic \
+       clean-noinstLIBRARIES clean-noinstPROGRAMS cscope \
+       cscopelist-am ctags ctags-am dist dist-all dist-bzip2 \
+       dist-gzip dist-lzip dist-shar dist-tarZ dist-xz dist-zip \
+       distcheck distclean distclean-compile distclean-generic \
+       distclean-tags distcleancheck distdir distuninstallcheck dvi \
+       dvi-am html html-am info info-am install install-am \
+       install-binPROGRAMS install-data install-data-am install-dvi \
+       install-dvi-am install-exec install-exec-am install-html \
+       install-html-am install-info install-info-am install-man \
+       install-pdf install-pdf-am install-ps install-ps-am \
+       install-strip installcheck installcheck-am installdirs \
+       maintainer-clean maintainer-clean-generic mostlyclean \
+       mostlyclean-compile mostlyclean-generic pdf pdf-am ps ps-am \
+       tags tags-am uninstall uninstall-am uninstall-binPROGRAMS
+
+.PRECIOUS: Makefile
+
+test: benchmark.html benchmark.txt
+
+benchmark.html: benchmark.txt benchmark.pl
+       ./benchmark.pl benchmark.txt > $@
+
+benchmark.txt: benchmark.sh $(GAMES)
+       ./benchmark.sh > $@
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/Makefile.nestedvm b/Makefile.nestedvm
new file mode 100644 (file)
index 0000000..b163699
--- /dev/null
@@ -0,0 +1,637 @@
+# Makefile for puzzles under NestedVM.
+#
+# This file was created by `mkfiles.pl' from the `Recipe' file.
+# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.
+
+# This path points at the nestedvm root directory
+NESTEDVM = /opt/nestedvm
+# You can define this path to point at your tools if you need to
+TOOLPATH = $(NESTEDVM)/upstream/install/bin
+CC = $(TOOLPATH)/mips-unknown-elf-gcc
+
+CFLAGS = -O2 -Wall -Werror -DSLOW_SYSTEM -g -I./ -Iicons/
+
+all: blackbox.jar bridges.jar cube.jar dominosa.jar fifteen.jar filling.jar \
+               flip.jar flood.jar galaxies.jar guess.jar inertia.jar \
+               keen.jar lightup.jar loopy.jar magnets.jar map.jar mines.jar \
+               net.jar netslide.jar nullgame.jar palisade.jar pattern.jar \
+               pearl.jar pegs.jar range.jar rect.jar samegame.jar \
+               signpost.jar singles.jar sixteen.jar slant.jar solo.jar \
+               tents.jar towers.jar tracks.jar twiddle.jar undead.jar \
+               unequal.jar unruly.jar untangle.jar
+
+blackbox.mips: blackbox.o blackbox-icon.o drawing.o nestedvm.o malloc.o \
+               midend.o misc.o printing.o ps.o random.o version.o
+       $(CC) $(XLDFLAGS) -o $@ blackbox.o blackbox-icon.o drawing.o \
+               nestedvm.o malloc.o midend.o misc.o printing.o ps.o random.o \
+               version.o  -lm
+
+bridges.mips: bridges.o bridges-icon.o drawing.o dsf.o findloop.o nestedvm.o \
+               malloc.o midend.o misc.o printing.o ps.o random.o version.o
+       $(CC) $(XLDFLAGS) -o $@ bridges.o bridges-icon.o drawing.o dsf.o \
+               findloop.o nestedvm.o malloc.o midend.o misc.o printing.o \
+               ps.o random.o version.o  -lm
+
+cube.mips: cube.o cube-icon.o drawing.o nestedvm.o malloc.o midend.o misc.o \
+               printing.o ps.o random.o version.o
+       $(CC) $(XLDFLAGS) -o $@ cube.o cube-icon.o drawing.o nestedvm.o \
+               malloc.o midend.o misc.o printing.o ps.o random.o version.o  \
+               -lm
+
+dominosa.mips: dominosa.o dominosa-icon.o drawing.o nestedvm.o laydomino.o \
+               malloc.o midend.o misc.o printing.o ps.o random.o version.o
+       $(CC) $(XLDFLAGS) -o $@ dominosa.o dominosa-icon.o drawing.o \
+               nestedvm.o laydomino.o malloc.o midend.o misc.o printing.o \
+               ps.o random.o version.o  -lm
+
+fifteen.mips: drawing.o fifteen.o fifteen-icon.o nestedvm.o malloc.o \
+               midend.o misc.o printing.o ps.o random.o version.o
+       $(CC) $(XLDFLAGS) -o $@ drawing.o fifteen.o fifteen-icon.o \
+               nestedvm.o malloc.o midend.o misc.o printing.o ps.o random.o \
+               version.o  -lm
+
+filling.mips: drawing.o dsf.o filling.o filling-icon.o nestedvm.o malloc.o \
+               midend.o misc.o printing.o ps.o random.o version.o
+       $(CC) $(XLDFLAGS) -o $@ drawing.o dsf.o filling.o filling-icon.o \
+               nestedvm.o malloc.o midend.o misc.o printing.o ps.o random.o \
+               version.o  -lm
+
+flip.mips: drawing.o flip.o flip-icon.o nestedvm.o malloc.o midend.o misc.o \
+               printing.o ps.o random.o tree234.o version.o
+       $(CC) $(XLDFLAGS) -o $@ drawing.o flip.o flip-icon.o nestedvm.o \
+               malloc.o midend.o misc.o printing.o ps.o random.o tree234.o \
+               version.o  -lm
+
+flood.mips: drawing.o flood.o flood-icon.o nestedvm.o malloc.o midend.o \
+               misc.o printing.o ps.o random.o version.o
+       $(CC) $(XLDFLAGS) -o $@ drawing.o flood.o flood-icon.o nestedvm.o \
+               malloc.o midend.o misc.o printing.o ps.o random.o version.o  \
+               -lm
+
+galaxies.mips: drawing.o dsf.o galaxies.o galaxies-icon.o nestedvm.o \
+               malloc.o midend.o misc.o printing.o ps.o random.o version.o
+       $(CC) $(XLDFLAGS) -o $@ drawing.o dsf.o galaxies.o galaxies-icon.o \
+               nestedvm.o malloc.o midend.o misc.o printing.o ps.o random.o \
+               version.o  -lm
+
+guess.mips: drawing.o nestedvm.o guess.o guess-icon.o malloc.o midend.o \
+               misc.o printing.o ps.o random.o version.o
+       $(CC) $(XLDFLAGS) -o $@ drawing.o nestedvm.o guess.o guess-icon.o \
+               malloc.o midend.o misc.o printing.o ps.o random.o version.o  \
+               -lm
+
+inertia.mips: drawing.o nestedvm.o inertia.o inertia-icon.o malloc.o \
+               midend.o misc.o printing.o ps.o random.o version.o
+       $(CC) $(XLDFLAGS) -o $@ drawing.o nestedvm.o inertia.o \
+               inertia-icon.o malloc.o midend.o misc.o printing.o ps.o \
+               random.o version.o  -lm
+
+keen.mips: drawing.o dsf.o nestedvm.o keen.o keen-icon.o latin.o malloc.o \
+               maxflow.o midend.o misc.o printing.o ps.o random.o tree234.o \
+               version.o
+       $(CC) $(XLDFLAGS) -o $@ drawing.o dsf.o nestedvm.o keen.o \
+               keen-icon.o latin.o malloc.o maxflow.o midend.o misc.o \
+               printing.o ps.o random.o tree234.o version.o  -lm
+
+lightup.mips: combi.o drawing.o nestedvm.o lightup.o lightup-icon.o malloc.o \
+               midend.o misc.o printing.o ps.o random.o version.o
+       $(CC) $(XLDFLAGS) -o $@ combi.o drawing.o nestedvm.o lightup.o \
+               lightup-icon.o malloc.o midend.o misc.o printing.o ps.o \
+               random.o version.o  -lm
+
+loopy.mips: drawing.o dsf.o grid.o nestedvm.o loopgen.o loopy.o loopy-icon.o \
+               malloc.o midend.o misc.o penrose.o printing.o ps.o random.o \
+               tree234.o version.o
+       $(CC) $(XLDFLAGS) -o $@ drawing.o dsf.o grid.o nestedvm.o loopgen.o \
+               loopy.o loopy-icon.o malloc.o midend.o misc.o penrose.o \
+               printing.o ps.o random.o tree234.o version.o  -lm
+
+magnets.mips: drawing.o nestedvm.o laydomino.o magnets.o magnets-icon.o \
+               malloc.o midend.o misc.o printing.o ps.o random.o version.o
+       $(CC) $(XLDFLAGS) -o $@ drawing.o nestedvm.o laydomino.o magnets.o \
+               magnets-icon.o malloc.o midend.o misc.o printing.o ps.o \
+               random.o version.o  -lm
+
+map.mips: drawing.o dsf.o nestedvm.o malloc.o map.o map-icon.o midend.o \
+               misc.o printing.o ps.o random.o version.o
+       $(CC) $(XLDFLAGS) -o $@ drawing.o dsf.o nestedvm.o malloc.o map.o \
+               map-icon.o midend.o misc.o printing.o ps.o random.o \
+               version.o  -lm
+
+mines.mips: drawing.o nestedvm.o malloc.o midend.o mines.o mines-icon.o \
+               misc.o printing.o ps.o random.o tree234.o version.o
+       $(CC) $(XLDFLAGS) -o $@ drawing.o nestedvm.o malloc.o midend.o \
+               mines.o mines-icon.o misc.o printing.o ps.o random.o \
+               tree234.o version.o  -lm
+
+net.mips: drawing.o dsf.o findloop.o nestedvm.o malloc.o midend.o misc.o \
+               net.o net-icon.o printing.o ps.o random.o tree234.o \
+               version.o
+       $(CC) $(XLDFLAGS) -o $@ drawing.o dsf.o findloop.o nestedvm.o \
+               malloc.o midend.o misc.o net.o net-icon.o printing.o ps.o \
+               random.o tree234.o version.o  -lm
+
+netslide.mips: drawing.o nestedvm.o malloc.o midend.o misc.o netslide.o \
+               netslide-icon.o printing.o ps.o random.o tree234.o version.o
+       $(CC) $(XLDFLAGS) -o $@ drawing.o nestedvm.o malloc.o midend.o \
+               misc.o netslide.o netslide-icon.o printing.o ps.o random.o \
+               tree234.o version.o  -lm
+
+nullgame.mips: drawing.o nestedvm.o malloc.o midend.o misc.o no-icon.o \
+               nullgame.o printing.o ps.o random.o version.o
+       $(CC) $(XLDFLAGS) -o $@ drawing.o nestedvm.o malloc.o midend.o \
+               misc.o no-icon.o nullgame.o printing.o ps.o random.o \
+               version.o  -lm
+
+palisade.mips: divvy.o drawing.o dsf.o nestedvm.o malloc.o midend.o misc.o \
+               palisade.o palisade-icon.o printing.o ps.o random.o \
+               version.o
+       $(CC) $(XLDFLAGS) -o $@ divvy.o drawing.o dsf.o nestedvm.o malloc.o \
+               midend.o misc.o palisade.o palisade-icon.o printing.o ps.o \
+               random.o version.o  -lm
+
+pattern.mips: drawing.o nestedvm.o malloc.o midend.o misc.o pattern.o \
+               pattern-icon.o printing.o ps.o random.o version.o
+       $(CC) $(XLDFLAGS) -o $@ drawing.o nestedvm.o malloc.o midend.o \
+               misc.o pattern.o pattern-icon.o printing.o ps.o random.o \
+               version.o  -lm
+
+pearl.mips: drawing.o dsf.o grid.o nestedvm.o loopgen.o malloc.o midend.o \
+               misc.o pearl.o pearl-icon.o penrose.o printing.o ps.o \
+               random.o tdq.o tree234.o version.o
+       $(CC) $(XLDFLAGS) -o $@ drawing.o dsf.o grid.o nestedvm.o loopgen.o \
+               malloc.o midend.o misc.o pearl.o pearl-icon.o penrose.o \
+               printing.o ps.o random.o tdq.o tree234.o version.o  -lm
+
+pegs.mips: drawing.o nestedvm.o malloc.o midend.o misc.o pegs.o pegs-icon.o \
+               printing.o ps.o random.o tree234.o version.o
+       $(CC) $(XLDFLAGS) -o $@ drawing.o nestedvm.o malloc.o midend.o \
+               misc.o pegs.o pegs-icon.o printing.o ps.o random.o tree234.o \
+               version.o  -lm
+
+range.mips: drawing.o dsf.o nestedvm.o malloc.o midend.o misc.o printing.o \
+               ps.o random.o range.o range-icon.o version.o
+       $(CC) $(XLDFLAGS) -o $@ drawing.o dsf.o nestedvm.o malloc.o midend.o \
+               misc.o printing.o ps.o random.o range.o range-icon.o \
+               version.o  -lm
+
+rect.mips: drawing.o nestedvm.o malloc.o midend.o misc.o printing.o ps.o \
+               random.o rect.o rect-icon.o version.o
+       $(CC) $(XLDFLAGS) -o $@ drawing.o nestedvm.o malloc.o midend.o \
+               misc.o printing.o ps.o random.o rect.o rect-icon.o version.o \
+                -lm
+
+samegame.mips: drawing.o nestedvm.o malloc.o midend.o misc.o printing.o ps.o \
+               random.o samegame.o samegame-icon.o version.o
+       $(CC) $(XLDFLAGS) -o $@ drawing.o nestedvm.o malloc.o midend.o \
+               misc.o printing.o ps.o random.o samegame.o samegame-icon.o \
+               version.o  -lm
+
+signpost.mips: drawing.o dsf.o nestedvm.o malloc.o midend.o misc.o \
+               printing.o ps.o random.o signpost.o signpost-icon.o \
+               version.o
+       $(CC) $(XLDFLAGS) -o $@ drawing.o dsf.o nestedvm.o malloc.o midend.o \
+               misc.o printing.o ps.o random.o signpost.o signpost-icon.o \
+               version.o  -lm
+
+singles.mips: drawing.o dsf.o nestedvm.o latin.o malloc.o maxflow.o midend.o \
+               misc.o printing.o ps.o random.o singles.o singles-icon.o \
+               tree234.o version.o
+       $(CC) $(XLDFLAGS) -o $@ drawing.o dsf.o nestedvm.o latin.o malloc.o \
+               maxflow.o midend.o misc.o printing.o ps.o random.o singles.o \
+               singles-icon.o tree234.o version.o  -lm
+
+sixteen.mips: drawing.o nestedvm.o malloc.o midend.o misc.o printing.o ps.o \
+               random.o sixteen.o sixteen-icon.o version.o
+       $(CC) $(XLDFLAGS) -o $@ drawing.o nestedvm.o malloc.o midend.o \
+               misc.o printing.o ps.o random.o sixteen.o sixteen-icon.o \
+               version.o  -lm
+
+slant.mips: drawing.o dsf.o findloop.o nestedvm.o malloc.o midend.o misc.o \
+               printing.o ps.o random.o slant.o slant-icon.o version.o
+       $(CC) $(XLDFLAGS) -o $@ drawing.o dsf.o findloop.o nestedvm.o \
+               malloc.o midend.o misc.o printing.o ps.o random.o slant.o \
+               slant-icon.o version.o  -lm
+
+solo.mips: divvy.o drawing.o dsf.o nestedvm.o malloc.o midend.o misc.o \
+               printing.o ps.o random.o solo.o solo-icon.o version.o
+       $(CC) $(XLDFLAGS) -o $@ divvy.o drawing.o dsf.o nestedvm.o malloc.o \
+               midend.o misc.o printing.o ps.o random.o solo.o solo-icon.o \
+               version.o  -lm
+
+tents.mips: drawing.o dsf.o nestedvm.o malloc.o maxflow.o midend.o misc.o \
+               printing.o ps.o random.o tents.o tents-icon.o version.o
+       $(CC) $(XLDFLAGS) -o $@ drawing.o dsf.o nestedvm.o malloc.o \
+               maxflow.o midend.o misc.o printing.o ps.o random.o tents.o \
+               tents-icon.o version.o  -lm
+
+towers.mips: drawing.o nestedvm.o latin.o malloc.o maxflow.o midend.o misc.o \
+               printing.o ps.o random.o towers.o towers-icon.o tree234.o \
+               version.o
+       $(CC) $(XLDFLAGS) -o $@ drawing.o nestedvm.o latin.o malloc.o \
+               maxflow.o midend.o misc.o printing.o ps.o random.o towers.o \
+               towers-icon.o tree234.o version.o  -lm
+
+tracks.mips: drawing.o dsf.o findloop.o nestedvm.o malloc.o midend.o misc.o \
+               printing.o ps.o random.o tracks.o tracks-icon.o version.o
+       $(CC) $(XLDFLAGS) -o $@ drawing.o dsf.o findloop.o nestedvm.o \
+               malloc.o midend.o misc.o printing.o ps.o random.o tracks.o \
+               tracks-icon.o version.o  -lm
+
+twiddle.mips: drawing.o nestedvm.o malloc.o midend.o misc.o printing.o ps.o \
+               random.o twiddle.o twiddle-icon.o version.o
+       $(CC) $(XLDFLAGS) -o $@ drawing.o nestedvm.o malloc.o midend.o \
+               misc.o printing.o ps.o random.o twiddle.o twiddle-icon.o \
+               version.o  -lm
+
+undead.mips: drawing.o nestedvm.o malloc.o midend.o misc.o printing.o ps.o \
+               random.o undead.o undead-icon.o version.o
+       $(CC) $(XLDFLAGS) -o $@ drawing.o nestedvm.o malloc.o midend.o \
+               misc.o printing.o ps.o random.o undead.o undead-icon.o \
+               version.o  -lm
+
+unequal.mips: drawing.o nestedvm.o latin.o malloc.o maxflow.o midend.o \
+               misc.o printing.o ps.o random.o tree234.o unequal.o \
+               unequal-icon.o version.o
+       $(CC) $(XLDFLAGS) -o $@ drawing.o nestedvm.o latin.o malloc.o \
+               maxflow.o midend.o misc.o printing.o ps.o random.o tree234.o \
+               unequal.o unequal-icon.o version.o  -lm
+
+unruly.mips: drawing.o nestedvm.o malloc.o midend.o misc.o printing.o ps.o \
+               random.o unruly.o unruly-icon.o version.o
+       $(CC) $(XLDFLAGS) -o $@ drawing.o nestedvm.o malloc.o midend.o \
+               misc.o printing.o ps.o random.o unruly.o unruly-icon.o \
+               version.o  -lm
+
+untangle.mips: drawing.o nestedvm.o malloc.o midend.o misc.o printing.o ps.o \
+               random.o tree234.o untangle.o untangle-icon.o version.o
+       $(CC) $(XLDFLAGS) -o $@ drawing.o nestedvm.o malloc.o midend.o \
+               misc.o printing.o ps.o random.o tree234.o untangle.o \
+               untangle-icon.o version.o  -lm
+
+blackbox.o: ./blackbox.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+blackbox-icon.o: icons/blackbox-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+blackbo3.o: ./blackbox.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+bridges.o: ./bridges.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+bridges-icon.o: icons/bridges-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+bridges3.o: ./bridges.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+combi.o: ./combi.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+cube.o: ./cube.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+cube-icon.o: icons/cube-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+cube3.o: ./cube.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+divvy.o: ./divvy.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+dominosa.o: ./dominosa.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+dominosa-icon.o: icons/dominosa-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+dominos3.o: ./dominosa.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+drawing.o: ./drawing.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+dsf.o: ./dsf.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+fifteen.o: ./fifteen.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+fifteen-icon.o: icons/fifteen-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+fifteen5.o: ./fifteen.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+fifteen2.o: ./fifteen.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+filling.o: ./filling.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+filling-icon.o: icons/filling-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+filling5.o: ./filling.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+filling2.o: ./filling.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+findloop.o: ./findloop.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+flip.o: ./flip.c ./puzzles.h ./tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+flip-icon.o: icons/flip-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+flip3.o: ./flip.c ./puzzles.h ./tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+flood.o: ./flood.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+flood-icon.o: icons/flood-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+flood3.o: ./flood.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+galaxies.o: ./galaxies.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+galaxies-icon.o: icons/galaxies-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+galaxie7.o: ./galaxies.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+galaxie4.o: ./galaxies.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_PICTURE_GENERATOR -c $< -o $@
+galaxie2.o: ./galaxies.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+grid.o: ./grid.c ./puzzles.h ./tree234.h ./grid.h ./penrose.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+nestedvm.o: ./nestedvm.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+guess.o: ./guess.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+guess-icon.o: icons/guess-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+guess3.o: ./guess.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+inertia.o: ./inertia.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+inertia-icon.o: icons/inertia-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+inertia3.o: ./inertia.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+keen.o: ./keen.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+keen-icon.o: icons/keen-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+keen5.o: ./keen.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+keen2.o: ./keen.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+latin.o: ./latin.c ./puzzles.h ./tree234.h ./maxflow.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+latin8.o: ./latin.c ./puzzles.h ./tree234.h ./maxflow.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_LATIN_TEST -c $< -o $@
+latin6.o: ./latin.c ./puzzles.h ./tree234.h ./maxflow.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+laydomino.o: ./laydomino.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+lightup.o: ./lightup.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+lightup-icon.o: icons/lightup-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+lightup5.o: ./lightup.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+lightup2.o: ./lightup.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+list.o: ./list.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+loopgen.o: ./loopgen.c ./puzzles.h ./tree234.h ./grid.h ./loopgen.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+loopy.o: ./loopy.c ./puzzles.h ./tree234.h ./grid.h ./loopgen.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+loopy-icon.o: icons/loopy-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+loopy5.o: ./loopy.c ./puzzles.h ./tree234.h ./grid.h ./loopgen.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+loopy2.o: ./loopy.c ./puzzles.h ./tree234.h ./grid.h ./loopgen.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+magnets.o: ./magnets.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+magnets-icon.o: icons/magnets-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+magnets5.o: ./magnets.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+magnets2.o: ./magnets.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+malloc.o: ./malloc.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+map.o: ./map.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+map-icon.o: icons/map-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+map5.o: ./map.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+map2.o: ./map.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+maxflow.o: ./maxflow.c ./maxflow.h ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+midend.o: ./midend.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+mines.o: ./mines.c ./tree234.h ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+mines-icon.o: icons/mines-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+mines5.o: ./mines.c ./tree234.h ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+mines2.o: ./mines.c ./tree234.h ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_OBFUSCATOR -c $< -o $@
+misc.o: ./misc.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+net.o: ./net.c ./puzzles.h ./tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+net-icon.o: icons/net-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+net3.o: ./net.c ./puzzles.h ./tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+netslide.o: ./netslide.c ./puzzles.h ./tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+netslide-icon.o: icons/netslide-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+netslid3.o: ./netslide.c ./puzzles.h ./tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+no-icon.o: ./no-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+nullfe.o: ./nullfe.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+nullgame.o: ./nullgame.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+obfusc.o: ./obfusc.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+osx.o: ./osx.m ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+palisade.o: ./palisade.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+palisade-icon.o: icons/palisade-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+palisad3.o: ./palisade.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+pattern.o: ./pattern.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pattern-icon.o: icons/pattern-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pattern7.o: ./pattern.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+pattern4.o: ./pattern.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_PICTURE_GENERATOR -c $< -o $@
+pattern2.o: ./pattern.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+pearl.o: ./pearl.c ./puzzles.h ./grid.h ./loopgen.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pearl-icon.o: icons/pearl-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pearl5.o: ./pearl.c ./puzzles.h ./grid.h ./loopgen.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+pearl2.o: ./pearl.c ./puzzles.h ./grid.h ./loopgen.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+pegs.o: ./pegs.c ./puzzles.h ./tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pegs-icon.o: icons/pegs-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pegs3.o: ./pegs.c ./puzzles.h ./tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+penrose.o: ./penrose.c ./puzzles.h ./penrose.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+printing.o: ./printing.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+ps.o: ./ps.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+random.o: ./random.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+range.o: ./range.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+range-icon.o: icons/range-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+range3.o: ./range.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+rect.o: ./rect.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+rect-icon.o: icons/rect-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+rect3.o: ./rect.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+samegame.o: ./samegame.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+samegame-icon.o: icons/samegame-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+samegam3.o: ./samegame.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+signpost.o: ./signpost.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+signpost-icon.o: icons/signpost-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+signpos5.o: ./signpost.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+signpos2.o: ./signpost.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+singles.o: ./singles.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+singles-icon.o: icons/singles-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+singles5.o: ./singles.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+singles3.o: ./singles.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+sixteen.o: ./sixteen.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+sixteen-icon.o: icons/sixteen-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+sixteen3.o: ./sixteen.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+slant.o: ./slant.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+slant-icon.o: icons/slant-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+slant5.o: ./slant.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+slant2.o: ./slant.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+solo.o: ./solo.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+solo-icon.o: icons/solo-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+solo5.o: ./solo.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+solo2.o: ./solo.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+tdq.o: ./tdq.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+tents.o: ./tents.c ./puzzles.h ./maxflow.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+tents-icon.o: icons/tents-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+tents5.o: ./tents.c ./puzzles.h ./maxflow.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+tents3.o: ./tents.c ./puzzles.h ./maxflow.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+towers.o: ./towers.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+towers-icon.o: icons/towers-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+towers5.o: ./towers.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+towers2.o: ./towers.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+tracks.o: ./tracks.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+tracks-icon.o: icons/tracks-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+tracks3.o: ./tracks.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+tree234.o: ./tree234.c ./tree234.h ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+twiddle.o: ./twiddle.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+twiddle-icon.o: icons/twiddle-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+twiddle3.o: ./twiddle.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+undead.o: ./undead.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+undead-icon.o: icons/undead-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+undead3.o: ./undead.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+unequal.o: ./unequal.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+unequal-icon.o: icons/unequal-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+unequal5.o: ./unequal.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+unequal2.o: ./unequal.c ./puzzles.h ./latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+unruly.o: ./unruly.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+unruly-icon.o: icons/unruly-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+unruly5.o: ./unruly.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+unruly2.o: ./unruly.c ./puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+untangle.o: ./untangle.c ./puzzles.h ./tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+untangle-icon.o: icons/untangle-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+untangl3.o: ./untangle.c ./puzzles.h ./tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+version.o: ./version.c ./version.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+windows.o: ./windows.c ./puzzles.h ./resource.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+windows1.o: ./windows.c ./puzzles.h ./resource.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+
+.PRECIOUS: %.class
+%.class: %.mips
+       java -cp $(NESTEDVM)/build:$(NESTEDVM)/upstream/build/classgen/build \
+               org.ibex.nestedvm.Compiler -outformat class -d . \
+               PuzzleEngine $<
+               mv PuzzleEngine.class $@
+
+org:
+       mkdir -p org/ibex/nestedvm/util
+       cp $(NESTEDVM)/build/org/ibex/nestedvm/Registers.class org/ibex/nestedvm
+       cp $(NESTEDVM)/build/org/ibex/nestedvm/UsermodeConstants.class org/ibex/nestedvm
+       cp $(NESTEDVM)/build/org/ibex/nestedvm/Runtime*.class org/ibex/nestedvm
+       cp $(NESTEDVM)/build/org/ibex/nestedvm/util/Platform*.class org/ibex/nestedvm/util
+       cp $(NESTEDVM)/build/org/ibex/nestedvm/util/Seekable*.class org/ibex/nestedvm/util
+       echo "Main-Class: PuzzleApplet" >applet.manifest
+
+PuzzleApplet.class: PuzzleApplet.java org
+       javac -source 1.3 -target 1.3 PuzzleApplet.java
+
+%.jar: %.class PuzzleApplet.class org
+       mv $< PuzzleEngine.class
+       jar cfm $@ applet.manifest PuzzleEngine.class PuzzleApplet*.class org
+       echo '<applet archive="'$@'" code="PuzzleApplet" width="700" height="500"></applet>' >$*.html
+       mv PuzzleEngine.class $<
+
+clean:
+       rm -rf *.o *.mips *.class *.html *.jar org applet.manifest
diff --git a/Makefile.osx b/Makefile.osx
new file mode 100644 (file)
index 0000000..5316913
--- /dev/null
@@ -0,0 +1,652 @@
+# Makefile for puzzles under Mac OS X.
+#
+# This file was created by `mkfiles.pl' from the `Recipe' file.
+# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.
+CC = $(TOOLPATH)gcc
+LIPO = $(TOOLPATH)lipo
+
+CFLAGS = -O2 -Wall -Werror -g -I./ -Iicons/
+LDFLAGS = -framework Cocoa
+all: Puzzles fifteensolver fillingsolver galaxiespicture galaxiessolver \
+               keensolver latincheck lightupsolver loopysolver \
+               magnetssolver mapsolver mineobfusc obfusc patternpicture \
+               patternsolver pearlbench signpostsolver singlessolver \
+               slantsolver solosolver tentssolver towerssolver \
+               unequalsolver unrulysolver
+Puzzles_extra = Puzzles.app/Contents/Resources/Help/index.html
+Puzzles.app/Contents/Resources/Help/index.html: \
+       Puzzles.app/Contents/Resources/Help osx-help.but puzzles.but
+       cd Puzzles.app/Contents/Resources/Help; \
+               halibut --html ../../../../osx-help.but ../../../../puzzles.but
+Puzzles.app/Contents/Resources/Help: Puzzles.app/Contents/Resources
+       mkdir -p Puzzles.app/Contents/Resources/Help
+
+release: Puzzles.dmg
+Puzzles.dmg: Puzzles
+       rm -f raw.dmg
+       hdiutil create -megabytes 5 -layout NONE raw.dmg
+       hdid -nomount raw.dmg > devicename
+       newfs_hfs -v "Simon Tatham's Puzzle Collection" `cat devicename`
+       hdiutil eject `cat devicename`
+       hdid raw.dmg | cut -f1 -d' ' > devicename
+       cp -R Puzzles.app /Volumes/"Simon Tatham's Puzzle Collection"
+       hdiutil eject `cat devicename`
+       rm -f Puzzles.dmg
+       hdiutil convert -format UDCO raw.dmg -o Puzzles.dmg
+       rm -f raw.dmg devicename
+
+.SUFFIXES: .o .c .m
+
+
+
+Puzzles.app:
+       mkdir -p $@
+Puzzles.app/Contents: Puzzles.app
+       mkdir -p $@
+Puzzles.app/Contents/MacOS: Puzzles.app/Contents
+       mkdir -p $@
+Puzzles.app/Contents/Resources: Puzzles.app/Contents
+       mkdir -p $@
+Puzzles.app/Contents/Resources/Puzzles.icns: Puzzles.app/Contents/Resources osx.icns
+       cp osx.icns $@
+Puzzles.app/Contents/Info.plist: Puzzles.app/Contents/Resources osx-info.plist
+       cp osx-info.plist $@
+Puzzles: Puzzles.app/Contents/MacOS/Puzzles \
+               Puzzles.app/Contents/Resources/Puzzles.icns \
+               Puzzles.app/Contents/Info.plist $(Puzzles_extra)
+
+Puzzles.i386.bin: blackbo3.i386.o bridges3.i386.o combi.i386.o cube3.i386.o \
+               divvy.i386.o dominos3.i386.o drawing.i386.o dsf.i386.o \
+               fifteen5.i386.o filling5.i386.o findloop.i386.o flip3.i386.o \
+               flood3.i386.o galaxie7.i386.o grid.i386.o guess3.i386.o \
+               inertia3.i386.o keen5.i386.o latin.i386.o laydomino.i386.o \
+               lightup5.i386.o list.i386.o loopgen.i386.o loopy5.i386.o \
+               magnets5.i386.o malloc.i386.o map5.i386.o maxflow.i386.o \
+               midend.i386.o mines5.i386.o misc.i386.o net3.i386.o \
+               netslid3.i386.o osx.i386.o palisad3.i386.o pattern7.i386.o \
+               pearl5.i386.o pegs3.i386.o penrose.i386.o random.i386.o \
+               range3.i386.o rect3.i386.o samegam3.i386.o signpos5.i386.o \
+               singles5.i386.o sixteen3.i386.o slant5.i386.o solo5.i386.o \
+               tdq.i386.o tents5.i386.o towers5.i386.o tracks3.i386.o \
+               tree234.i386.o twiddle3.i386.o undead3.i386.o \
+               unequal5.i386.o unruly5.i386.o untangl3.i386.o \
+               version.i386.o
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(LDFLAGS) -o $@ \
+               blackbo3.i386.o bridges3.i386.o combi.i386.o cube3.i386.o \
+               divvy.i386.o dominos3.i386.o drawing.i386.o dsf.i386.o \
+               fifteen5.i386.o filling5.i386.o findloop.i386.o flip3.i386.o \
+               flood3.i386.o galaxie7.i386.o grid.i386.o guess3.i386.o \
+               inertia3.i386.o keen5.i386.o latin.i386.o laydomino.i386.o \
+               lightup5.i386.o list.i386.o loopgen.i386.o loopy5.i386.o \
+               magnets5.i386.o malloc.i386.o map5.i386.o maxflow.i386.o \
+               midend.i386.o mines5.i386.o misc.i386.o net3.i386.o \
+               netslid3.i386.o osx.i386.o palisad3.i386.o pattern7.i386.o \
+               pearl5.i386.o pegs3.i386.o penrose.i386.o random.i386.o \
+               range3.i386.o rect3.i386.o samegam3.i386.o signpos5.i386.o \
+               singles5.i386.o sixteen3.i386.o slant5.i386.o solo5.i386.o \
+               tdq.i386.o tents5.i386.o towers5.i386.o tracks3.i386.o \
+               tree234.i386.o twiddle3.i386.o undead3.i386.o \
+               unequal5.i386.o unruly5.i386.o untangl3.i386.o \
+               version.i386.o 
+
+Puzzles.app/Contents/MacOS/Puzzles: Puzzles.app/Contents/MacOS \
+               Puzzles.i386.bin
+       $(LIPO) -create  Puzzles.i386.bin -output $@
+
+fifteensolver.i386: fifteen2.i386.o malloc.i386.o misc.i386.o nullfe.i386.o \
+               random.i386.o
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(ULDFLAGS) -o $@ \
+               fifteen2.i386.o malloc.i386.o misc.i386.o nullfe.i386.o \
+               random.i386.o 
+
+fifteensolver: fifteensolver.i386
+       $(LIPO) -create  fifteensolver.i386 -output $@
+
+fillingsolver.i386: dsf.i386.o filling2.i386.o malloc.i386.o misc.i386.o \
+               nullfe.i386.o random.i386.o
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(ULDFLAGS) -o $@ \
+               dsf.i386.o filling2.i386.o malloc.i386.o misc.i386.o \
+               nullfe.i386.o random.i386.o 
+
+fillingsolver: fillingsolver.i386
+       $(LIPO) -create  fillingsolver.i386 -output $@
+
+galaxiespicture.i386: dsf.i386.o galaxie4.i386.o malloc.i386.o misc.i386.o \
+               nullfe.i386.o random.i386.o
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(ULDFLAGS) -o $@ \
+               dsf.i386.o galaxie4.i386.o malloc.i386.o misc.i386.o \
+               nullfe.i386.o random.i386.o -lm
+
+galaxiespicture: galaxiespicture.i386
+       $(LIPO) -create  galaxiespicture.i386 -output $@
+
+galaxiessolver.i386: dsf.i386.o galaxie2.i386.o malloc.i386.o misc.i386.o \
+               nullfe.i386.o random.i386.o
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(ULDFLAGS) -o $@ \
+               dsf.i386.o galaxie2.i386.o malloc.i386.o misc.i386.o \
+               nullfe.i386.o random.i386.o -lm
+
+galaxiessolver: galaxiessolver.i386
+       $(LIPO) -create  galaxiessolver.i386 -output $@
+
+keensolver.i386: dsf.i386.o keen2.i386.o latin6.i386.o malloc.i386.o \
+               maxflow.i386.o misc.i386.o nullfe.i386.o random.i386.o \
+               tree234.i386.o
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(ULDFLAGS) -o $@ \
+               dsf.i386.o keen2.i386.o latin6.i386.o malloc.i386.o \
+               maxflow.i386.o misc.i386.o nullfe.i386.o random.i386.o \
+               tree234.i386.o 
+
+keensolver: keensolver.i386
+       $(LIPO) -create  keensolver.i386 -output $@
+
+latincheck.i386: latin8.i386.o malloc.i386.o maxflow.i386.o misc.i386.o \
+               nullfe.i386.o random.i386.o tree234.i386.o
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(ULDFLAGS) -o $@ \
+               latin8.i386.o malloc.i386.o maxflow.i386.o misc.i386.o \
+               nullfe.i386.o random.i386.o tree234.i386.o 
+
+latincheck: latincheck.i386
+       $(LIPO) -create  latincheck.i386 -output $@
+
+lightupsolver.i386: combi.i386.o lightup2.i386.o malloc.i386.o misc.i386.o \
+               nullfe.i386.o random.i386.o
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(ULDFLAGS) -o $@ \
+               combi.i386.o lightup2.i386.o malloc.i386.o misc.i386.o \
+               nullfe.i386.o random.i386.o 
+
+lightupsolver: lightupsolver.i386
+       $(LIPO) -create  lightupsolver.i386 -output $@
+
+loopysolver.i386: dsf.i386.o grid.i386.o loopgen.i386.o loopy2.i386.o \
+               malloc.i386.o misc.i386.o nullfe.i386.o penrose.i386.o \
+               random.i386.o tree234.i386.o
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(ULDFLAGS) -o $@ \
+               dsf.i386.o grid.i386.o loopgen.i386.o loopy2.i386.o \
+               malloc.i386.o misc.i386.o nullfe.i386.o penrose.i386.o \
+               random.i386.o tree234.i386.o -lm
+
+loopysolver: loopysolver.i386
+       $(LIPO) -create  loopysolver.i386 -output $@
+
+magnetssolver.i386: laydomino.i386.o magnets2.i386.o malloc.i386.o \
+               misc.i386.o nullfe.i386.o random.i386.o
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(ULDFLAGS) -o $@ \
+               laydomino.i386.o magnets2.i386.o malloc.i386.o misc.i386.o \
+               nullfe.i386.o random.i386.o -lm
+
+magnetssolver: magnetssolver.i386
+       $(LIPO) -create  magnetssolver.i386 -output $@
+
+mapsolver.i386: dsf.i386.o malloc.i386.o map2.i386.o misc.i386.o \
+               nullfe.i386.o random.i386.o
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(ULDFLAGS) -o $@ \
+               dsf.i386.o malloc.i386.o map2.i386.o misc.i386.o \
+               nullfe.i386.o random.i386.o -lm
+
+mapsolver: mapsolver.i386
+       $(LIPO) -create  mapsolver.i386 -output $@
+
+mineobfusc.i386: malloc.i386.o mines2.i386.o misc.i386.o nullfe.i386.o \
+               random.i386.o tree234.i386.o
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(ULDFLAGS) -o $@ \
+               malloc.i386.o mines2.i386.o misc.i386.o nullfe.i386.o \
+               random.i386.o tree234.i386.o 
+
+mineobfusc: mineobfusc.i386
+       $(LIPO) -create  mineobfusc.i386 -output $@
+
+obfusc.i386: malloc.i386.o misc.i386.o nullfe.i386.o obfusc.i386.o \
+               random.i386.o
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(ULDFLAGS) -o $@ \
+               malloc.i386.o misc.i386.o nullfe.i386.o obfusc.i386.o \
+               random.i386.o 
+
+obfusc: obfusc.i386
+       $(LIPO) -create  obfusc.i386 -output $@
+
+patternpicture.i386: malloc.i386.o misc.i386.o nullfe.i386.o pattern4.i386.o \
+               random.i386.o
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(ULDFLAGS) -o $@ \
+               malloc.i386.o misc.i386.o nullfe.i386.o pattern4.i386.o \
+               random.i386.o 
+
+patternpicture: patternpicture.i386
+       $(LIPO) -create  patternpicture.i386 -output $@
+
+patternsolver.i386: malloc.i386.o misc.i386.o nullfe.i386.o pattern2.i386.o \
+               random.i386.o
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(ULDFLAGS) -o $@ \
+               malloc.i386.o misc.i386.o nullfe.i386.o pattern2.i386.o \
+               random.i386.o 
+
+patternsolver: patternsolver.i386
+       $(LIPO) -create  patternsolver.i386 -output $@
+
+pearlbench.i386: dsf.i386.o grid.i386.o loopgen.i386.o malloc.i386.o \
+               misc.i386.o nullfe.i386.o pearl2.i386.o penrose.i386.o \
+               random.i386.o tdq.i386.o tree234.i386.o
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(ULDFLAGS) -o $@ \
+               dsf.i386.o grid.i386.o loopgen.i386.o malloc.i386.o \
+               misc.i386.o nullfe.i386.o pearl2.i386.o penrose.i386.o \
+               random.i386.o tdq.i386.o tree234.i386.o -lm
+
+pearlbench: pearlbench.i386
+       $(LIPO) -create  pearlbench.i386 -output $@
+
+signpostsolver.i386: dsf.i386.o malloc.i386.o misc.i386.o nullfe.i386.o \
+               random.i386.o signpos2.i386.o
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(ULDFLAGS) -o $@ \
+               dsf.i386.o malloc.i386.o misc.i386.o nullfe.i386.o \
+               random.i386.o signpos2.i386.o -lm
+
+signpostsolver: signpostsolver.i386
+       $(LIPO) -create  signpostsolver.i386 -output $@
+
+singlessolver.i386: dsf.i386.o latin.i386.o malloc.i386.o maxflow.i386.o \
+               misc.i386.o nullfe.i386.o random.i386.o singles3.i386.o \
+               tree234.i386.o
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(ULDFLAGS) -o $@ \
+               dsf.i386.o latin.i386.o malloc.i386.o maxflow.i386.o \
+               misc.i386.o nullfe.i386.o random.i386.o singles3.i386.o \
+               tree234.i386.o 
+
+singlessolver: singlessolver.i386
+       $(LIPO) -create  singlessolver.i386 -output $@
+
+slantsolver.i386: dsf.i386.o findloop.i386.o malloc.i386.o misc.i386.o \
+               nullfe.i386.o random.i386.o slant2.i386.o
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(ULDFLAGS) -o $@ \
+               dsf.i386.o findloop.i386.o malloc.i386.o misc.i386.o \
+               nullfe.i386.o random.i386.o slant2.i386.o 
+
+slantsolver: slantsolver.i386
+       $(LIPO) -create  slantsolver.i386 -output $@
+
+solosolver.i386: divvy.i386.o dsf.i386.o malloc.i386.o misc.i386.o \
+               nullfe.i386.o random.i386.o solo2.i386.o
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(ULDFLAGS) -o $@ \
+               divvy.i386.o dsf.i386.o malloc.i386.o misc.i386.o \
+               nullfe.i386.o random.i386.o solo2.i386.o 
+
+solosolver: solosolver.i386
+       $(LIPO) -create  solosolver.i386 -output $@
+
+tentssolver.i386: dsf.i386.o malloc.i386.o maxflow.i386.o misc.i386.o \
+               nullfe.i386.o random.i386.o tents3.i386.o
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(ULDFLAGS) -o $@ \
+               dsf.i386.o malloc.i386.o maxflow.i386.o misc.i386.o \
+               nullfe.i386.o random.i386.o tents3.i386.o 
+
+tentssolver: tentssolver.i386
+       $(LIPO) -create  tentssolver.i386 -output $@
+
+towerssolver.i386: latin6.i386.o malloc.i386.o maxflow.i386.o misc.i386.o \
+               nullfe.i386.o random.i386.o towers2.i386.o tree234.i386.o
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(ULDFLAGS) -o $@ \
+               latin6.i386.o malloc.i386.o maxflow.i386.o misc.i386.o \
+               nullfe.i386.o random.i386.o towers2.i386.o tree234.i386.o 
+
+towerssolver: towerssolver.i386
+       $(LIPO) -create  towerssolver.i386 -output $@
+
+unequalsolver.i386: latin6.i386.o malloc.i386.o maxflow.i386.o misc.i386.o \
+               nullfe.i386.o random.i386.o tree234.i386.o unequal2.i386.o
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(ULDFLAGS) -o $@ \
+               latin6.i386.o malloc.i386.o maxflow.i386.o misc.i386.o \
+               nullfe.i386.o random.i386.o tree234.i386.o unequal2.i386.o 
+
+unequalsolver: unequalsolver.i386
+       $(LIPO) -create  unequalsolver.i386 -output $@
+
+unrulysolver.i386: malloc.i386.o misc.i386.o nullfe.i386.o random.i386.o \
+               unruly2.i386.o
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(ULDFLAGS) -o $@ \
+               malloc.i386.o misc.i386.o nullfe.i386.o random.i386.o \
+               unruly2.i386.o 
+
+unrulysolver: unrulysolver.i386
+       $(LIPO) -create  unrulysolver.i386 -output $@
+
+blackbox.i386.o: ./blackbox.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+blackbox-icon.i386.o: icons/blackbox-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+blackbo3.i386.o: ./blackbox.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+bridges.i386.o: ./bridges.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+bridges-icon.i386.o: icons/bridges-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+bridges3.i386.o: ./bridges.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+combi.i386.o: ./combi.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+cube.i386.o: ./cube.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+cube-icon.i386.o: icons/cube-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+cube3.i386.o: ./cube.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+divvy.i386.o: ./divvy.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+dominosa.i386.o: ./dominosa.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+dominosa-icon.i386.o: icons/dominosa-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+dominos3.i386.o: ./dominosa.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+drawing.i386.o: ./drawing.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+dsf.i386.o: ./dsf.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+fifteen.i386.o: ./fifteen.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+fifteen-icon.i386.o: icons/fifteen-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+fifteen5.i386.o: ./fifteen.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+fifteen2.i386.o: ./fifteen.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+filling.i386.o: ./filling.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+filling-icon.i386.o: icons/filling-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+filling5.i386.o: ./filling.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+filling2.i386.o: ./filling.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+findloop.i386.o: ./findloop.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+flip.i386.o: ./flip.c ./puzzles.h ./tree234.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+flip-icon.i386.o: icons/flip-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+flip3.i386.o: ./flip.c ./puzzles.h ./tree234.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+flood.i386.o: ./flood.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+flood-icon.i386.o: icons/flood-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+flood3.i386.o: ./flood.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+galaxies.i386.o: ./galaxies.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+galaxies-icon.i386.o: icons/galaxies-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+galaxie7.i386.o: ./galaxies.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+galaxie4.i386.o: ./galaxies.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_PICTURE_GENERATOR -c $< -o $@
+galaxie2.i386.o: ./galaxies.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+grid.i386.o: ./grid.c ./puzzles.h ./tree234.h ./grid.h ./penrose.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+gtk.i386.o: ./gtk.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+guess.i386.o: ./guess.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+guess-icon.i386.o: icons/guess-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+guess3.i386.o: ./guess.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+inertia.i386.o: ./inertia.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+inertia-icon.i386.o: icons/inertia-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+inertia3.i386.o: ./inertia.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+keen.i386.o: ./keen.c ./puzzles.h ./latin.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+keen-icon.i386.o: icons/keen-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+keen5.i386.o: ./keen.c ./puzzles.h ./latin.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+keen2.i386.o: ./keen.c ./puzzles.h ./latin.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+latin.i386.o: ./latin.c ./puzzles.h ./tree234.h ./maxflow.h ./latin.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+latin8.i386.o: ./latin.c ./puzzles.h ./tree234.h ./maxflow.h ./latin.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_LATIN_TEST -c $< -o $@
+latin6.i386.o: ./latin.c ./puzzles.h ./tree234.h ./maxflow.h ./latin.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+laydomino.i386.o: ./laydomino.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+lightup.i386.o: ./lightup.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+lightup-icon.i386.o: icons/lightup-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+lightup5.i386.o: ./lightup.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+lightup2.i386.o: ./lightup.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+list.i386.o: ./list.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+loopgen.i386.o: ./loopgen.c ./puzzles.h ./tree234.h ./grid.h ./loopgen.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+loopy.i386.o: ./loopy.c ./puzzles.h ./tree234.h ./grid.h ./loopgen.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+loopy-icon.i386.o: icons/loopy-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+loopy5.i386.o: ./loopy.c ./puzzles.h ./tree234.h ./grid.h ./loopgen.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+loopy2.i386.o: ./loopy.c ./puzzles.h ./tree234.h ./grid.h ./loopgen.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+magnets.i386.o: ./magnets.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+magnets-icon.i386.o: icons/magnets-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+magnets5.i386.o: ./magnets.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+magnets2.i386.o: ./magnets.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+malloc.i386.o: ./malloc.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+map.i386.o: ./map.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+map-icon.i386.o: icons/map-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+map5.i386.o: ./map.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+map2.i386.o: ./map.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+maxflow.i386.o: ./maxflow.c ./maxflow.h ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+midend.i386.o: ./midend.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+mines.i386.o: ./mines.c ./tree234.h ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+mines-icon.i386.o: icons/mines-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+mines5.i386.o: ./mines.c ./tree234.h ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+mines2.i386.o: ./mines.c ./tree234.h ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_OBFUSCATOR -c $< -o $@
+misc.i386.o: ./misc.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+net.i386.o: ./net.c ./puzzles.h ./tree234.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+net-icon.i386.o: icons/net-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+net3.i386.o: ./net.c ./puzzles.h ./tree234.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+netslide.i386.o: ./netslide.c ./puzzles.h ./tree234.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+netslide-icon.i386.o: icons/netslide-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+netslid3.i386.o: ./netslide.c ./puzzles.h ./tree234.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+no-icon.i386.o: ./no-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+nullfe.i386.o: ./nullfe.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+nullgame.i386.o: ./nullgame.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+obfusc.i386.o: ./obfusc.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+osx.i386.o: ./osx.m ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 -x objective-c $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+palisade.i386.o: ./palisade.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+palisade-icon.i386.o: icons/palisade-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+palisad3.i386.o: ./palisade.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+pattern.i386.o: ./pattern.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pattern-icon.i386.o: icons/pattern-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pattern7.i386.o: ./pattern.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+pattern4.i386.o: ./pattern.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_PICTURE_GENERATOR -c $< -o $@
+pattern2.i386.o: ./pattern.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+pearl.i386.o: ./pearl.c ./puzzles.h ./grid.h ./loopgen.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pearl-icon.i386.o: icons/pearl-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pearl5.i386.o: ./pearl.c ./puzzles.h ./grid.h ./loopgen.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+pearl2.i386.o: ./pearl.c ./puzzles.h ./grid.h ./loopgen.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+pegs.i386.o: ./pegs.c ./puzzles.h ./tree234.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pegs-icon.i386.o: icons/pegs-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+pegs3.i386.o: ./pegs.c ./puzzles.h ./tree234.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+penrose.i386.o: ./penrose.c ./puzzles.h ./penrose.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+printing.i386.o: ./printing.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+ps.i386.o: ./ps.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+random.i386.o: ./random.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+range.i386.o: ./range.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+range-icon.i386.o: icons/range-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+range3.i386.o: ./range.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+rect.i386.o: ./rect.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+rect-icon.i386.o: icons/rect-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+rect3.i386.o: ./rect.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+samegame.i386.o: ./samegame.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+samegame-icon.i386.o: icons/samegame-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+samegam3.i386.o: ./samegame.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+signpost.i386.o: ./signpost.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+signpost-icon.i386.o: icons/signpost-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+signpos5.i386.o: ./signpost.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+signpos2.i386.o: ./signpost.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+singles.i386.o: ./singles.c ./puzzles.h ./latin.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+singles-icon.i386.o: icons/singles-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+singles5.i386.o: ./singles.c ./puzzles.h ./latin.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+singles3.i386.o: ./singles.c ./puzzles.h ./latin.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+sixteen.i386.o: ./sixteen.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+sixteen-icon.i386.o: icons/sixteen-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+sixteen3.i386.o: ./sixteen.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+slant.i386.o: ./slant.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+slant-icon.i386.o: icons/slant-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+slant5.i386.o: ./slant.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+slant2.i386.o: ./slant.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+solo.i386.o: ./solo.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+solo-icon.i386.o: icons/solo-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+solo5.i386.o: ./solo.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+solo2.i386.o: ./solo.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+tdq.i386.o: ./tdq.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+tents.i386.o: ./tents.c ./puzzles.h ./maxflow.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+tents-icon.i386.o: icons/tents-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+tents5.i386.o: ./tents.c ./puzzles.h ./maxflow.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+tents3.i386.o: ./tents.c ./puzzles.h ./maxflow.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+towers.i386.o: ./towers.c ./puzzles.h ./latin.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+towers-icon.i386.o: icons/towers-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+towers5.i386.o: ./towers.c ./puzzles.h ./latin.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+towers2.i386.o: ./towers.c ./puzzles.h ./latin.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+tracks.i386.o: ./tracks.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+tracks-icon.i386.o: icons/tracks-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+tracks3.i386.o: ./tracks.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+tree234.i386.o: ./tree234.c ./tree234.h ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+twiddle.i386.o: ./twiddle.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+twiddle-icon.i386.o: icons/twiddle-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+twiddle3.i386.o: ./twiddle.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+undead.i386.o: ./undead.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+undead-icon.i386.o: icons/undead-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+undead3.i386.o: ./undead.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+unequal.i386.o: ./unequal.c ./puzzles.h ./latin.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+unequal-icon.i386.o: icons/unequal-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+unequal5.i386.o: ./unequal.c ./puzzles.h ./latin.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+unequal2.i386.o: ./unequal.c ./puzzles.h ./latin.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+unruly.i386.o: ./unruly.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+unruly-icon.i386.o: icons/unruly-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+unruly5.i386.o: ./unruly.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+unruly2.i386.o: ./unruly.c ./puzzles.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DSTANDALONE_SOLVER -c $< -o $@
+untangle.i386.o: ./untangle.c ./puzzles.h ./tree234.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+untangle-icon.i386.o: icons/untangle-icon.c
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+untangl3.i386.o: ./untangle.c ./puzzles.h ./tree234.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+version.i386.o: ./version.c ./version.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+windows.i386.o: ./windows.c ./puzzles.h ./resource.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -c $< -o $@
+windows1.i386.o: ./windows.c ./puzzles.h ./resource.h
+       $(CC) -arch i386 -mmacosx-version-min=10.4 $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) -DCOMBINED -c $< -o $@
+
+clean:
+       rm -f *.o *.dmg fifteensolver fifteensolver.i386 fillingsolver fillingsolver.i386 galaxiespicture galaxiespicture.i386 galaxiessolver galaxiessolver.i386 keensolver keensolver.i386 latincheck latincheck.i386 lightupsolver lightupsolver.i386 loopysolver loopysolver.i386 magnetssolver magnetssolver.i386 mapsolver mapsolver.i386 mineobfusc mineobfusc.i386 obfusc obfusc.i386 patternpicture patternpicture.i386 patternsolver patternsolver.i386 pearlbench pearlbench.i386 signpostsolver signpostsolver.i386 singlessolver singlessolver.i386 slantsolver slantsolver.i386 solosolver solosolver.i386 tentssolver tentssolver.i386 towerssolver towerssolver.i386 unequalsolver unequalsolver.i386 unrulysolver unrulysolver.i386
+       rm -rf *.app
diff --git a/Makefile.vc b/Makefile.vc
new file mode 100644 (file)
index 0000000..7fda755
--- /dev/null
@@ -0,0 +1,1205 @@
+# Makefile for puzzles under Visual C.
+#
+# This file was created by `mkfiles.pl' from the `Recipe' file.
+# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.
+
+# If you rename this file to `Makefile', you should change this line,
+# so that the .rsp files still depend on the correct makefile.
+MAKEFILE = Makefile.vc
+
+# C compilation flags
+CFLAGS = /nologo /W3 /O1 /D_WINDOWS /D_WIN32_WINDOWS=0x401 /DWINVER=0x401 /I.
+LFLAGS = /incremental:no /fixed
+
+all: blackbox.exe bridges.exe cube.exe dominosa.exe fifteen.exe \
+               fifteensolver.exe filling.exe fillingsolver.exe flip.exe \
+               flood.exe galaxies.exe galaxiespicture.exe \
+               galaxiessolver.exe guess.exe inertia.exe keen.exe \
+               keensolver.exe latincheck.exe lightup.exe lightupsolver.exe \
+               loopy.exe loopysolver.exe magnets.exe magnetssolver.exe \
+               map.exe mapsolver.exe mineobfusc.exe mines.exe netgame.exe \
+               netslide.exe nullgame.exe palisade.exe pattern.exe \
+               patternpicture.exe patternsolver.exe pearl.exe \
+               pearlbench.exe pegs.exe puzzles.exe range.exe rect.exe \
+               samegame.exe signpost.exe signpostsolver.exe singles.exe \
+               singlessolver.exe sixteen.exe slant.exe slantsolver.exe \
+               solo.exe solosolver.exe tents.exe tentssolver.exe towers.exe \
+               towerssolver.exe tracks.exe twiddle.exe undead.exe \
+               unequal.exe unequalsolver.exe unruly.exe unrulysolver.exe \
+               untangle.exe
+
+blackbox.exe: blackbox.obj blackbox.res drawing.obj malloc.obj midend.obj \
+               misc.obj printing.obj random.obj version.obj windows.obj \
+               blackbox.rsp
+       link $(LFLAGS) -out:blackbox.exe -map:blackbox.map @blackbox.rsp
+
+bridges.exe: bridges.obj bridges.res drawing.obj dsf.obj findloop.obj \
+               malloc.obj midend.obj misc.obj printing.obj random.obj \
+               version.obj windows.obj bridges.rsp
+       link $(LFLAGS) -out:bridges.exe -map:bridges.map @bridges.rsp
+
+cube.exe: cube.obj cube.res drawing.obj malloc.obj midend.obj misc.obj \
+               printing.obj random.obj version.obj windows.obj cube.rsp
+       link $(LFLAGS) -out:cube.exe -map:cube.map @cube.rsp
+
+dominosa.exe: dominosa.obj dominosa.res drawing.obj laydomino.obj malloc.obj \
+               midend.obj misc.obj printing.obj random.obj version.obj \
+               windows.obj dominosa.rsp
+       link $(LFLAGS) -out:dominosa.exe -map:dominosa.map @dominosa.rsp
+
+fifteen.exe: drawing.obj fifteen.obj fifteen.res malloc.obj midend.obj \
+               misc.obj printing.obj random.obj version.obj windows.obj \
+               fifteen.rsp
+       link $(LFLAGS) -out:fifteen.exe -map:fifteen.map @fifteen.rsp
+
+fifteensolver.exe: fifteen2.obj malloc.obj misc.obj nullfe.obj random.obj \
+               fifteensolver.rsp
+       link $(LFLAGS) -out:fifteensolver.exe -map:fifteensolver.map @fifteensolver.rsp
+
+filling.exe: drawing.obj dsf.obj filling.obj filling.res malloc.obj \
+               midend.obj misc.obj printing.obj random.obj version.obj \
+               windows.obj filling.rsp
+       link $(LFLAGS) -out:filling.exe -map:filling.map @filling.rsp
+
+fillingsolver.exe: dsf.obj filling2.obj malloc.obj misc.obj nullfe.obj \
+               random.obj fillingsolver.rsp
+       link $(LFLAGS) -out:fillingsolver.exe -map:fillingsolver.map @fillingsolver.rsp
+
+flip.exe: drawing.obj flip.obj flip.res malloc.obj midend.obj misc.obj \
+               printing.obj random.obj tree234.obj version.obj windows.obj \
+               flip.rsp
+       link $(LFLAGS) -out:flip.exe -map:flip.map @flip.rsp
+
+flood.exe: drawing.obj flood.obj flood.res malloc.obj midend.obj misc.obj \
+               printing.obj random.obj version.obj windows.obj flood.rsp
+       link $(LFLAGS) -out:flood.exe -map:flood.map @flood.rsp
+
+galaxies.exe: drawing.obj dsf.obj galaxies.obj galaxies.res malloc.obj \
+               midend.obj misc.obj printing.obj random.obj version.obj \
+               windows.obj galaxies.rsp
+       link $(LFLAGS) -out:galaxies.exe -map:galaxies.map @galaxies.rsp
+
+galaxiespicture.exe: dsf.obj galaxie4.obj malloc.obj misc.obj nullfe.obj \
+               random.obj galaxiespicture.rsp
+       link $(LFLAGS) -out:galaxiespicture.exe -map:galaxiespicture.map @galaxiespicture.rsp
+
+galaxiessolver.exe: dsf.obj galaxie2.obj malloc.obj misc.obj nullfe.obj \
+               random.obj galaxiessolver.rsp
+       link $(LFLAGS) -out:galaxiessolver.exe -map:galaxiessolver.map @galaxiessolver.rsp
+
+guess.exe: drawing.obj guess.obj guess.res malloc.obj midend.obj misc.obj \
+               printing.obj random.obj version.obj windows.obj guess.rsp
+       link $(LFLAGS) -out:guess.exe -map:guess.map @guess.rsp
+
+inertia.exe: drawing.obj inertia.obj inertia.res malloc.obj midend.obj \
+               misc.obj printing.obj random.obj version.obj windows.obj \
+               inertia.rsp
+       link $(LFLAGS) -out:inertia.exe -map:inertia.map @inertia.rsp
+
+keen.exe: drawing.obj dsf.obj keen.obj keen.res latin.obj malloc.obj \
+               maxflow.obj midend.obj misc.obj printing.obj random.obj \
+               tree234.obj version.obj windows.obj keen.rsp
+       link $(LFLAGS) -out:keen.exe -map:keen.map @keen.rsp
+
+keensolver.exe: dsf.obj keen2.obj latin6.obj malloc.obj maxflow.obj misc.obj \
+               nullfe.obj random.obj tree234.obj keensolver.rsp
+       link $(LFLAGS) -out:keensolver.exe -map:keensolver.map @keensolver.rsp
+
+latincheck.exe: latin8.obj malloc.obj maxflow.obj misc.obj nullfe.obj \
+               random.obj tree234.obj latincheck.rsp
+       link $(LFLAGS) -out:latincheck.exe -map:latincheck.map @latincheck.rsp
+
+lightup.exe: combi.obj drawing.obj lightup.obj lightup.res malloc.obj \
+               midend.obj misc.obj printing.obj random.obj version.obj \
+               windows.obj lightup.rsp
+       link $(LFLAGS) -out:lightup.exe -map:lightup.map @lightup.rsp
+
+lightupsolver.exe: combi.obj lightup2.obj malloc.obj misc.obj nullfe.obj \
+               random.obj lightupsolver.rsp
+       link $(LFLAGS) -out:lightupsolver.exe -map:lightupsolver.map @lightupsolver.rsp
+
+loopy.exe: drawing.obj dsf.obj grid.obj loopgen.obj loopy.obj loopy.res \
+               malloc.obj midend.obj misc.obj penrose.obj printing.obj \
+               random.obj tree234.obj version.obj windows.obj loopy.rsp
+       link $(LFLAGS) -out:loopy.exe -map:loopy.map @loopy.rsp
+
+loopysolver.exe: dsf.obj grid.obj loopgen.obj loopy2.obj malloc.obj misc.obj \
+               nullfe.obj penrose.obj random.obj tree234.obj \
+               loopysolver.rsp
+       link $(LFLAGS) -out:loopysolver.exe -map:loopysolver.map @loopysolver.rsp
+
+magnets.exe: drawing.obj laydomino.obj magnets.obj magnets.res malloc.obj \
+               midend.obj misc.obj printing.obj random.obj version.obj \
+               windows.obj magnets.rsp
+       link $(LFLAGS) -out:magnets.exe -map:magnets.map @magnets.rsp
+
+magnetssolver.exe: laydomino.obj magnets2.obj malloc.obj misc.obj nullfe.obj \
+               random.obj magnetssolver.rsp
+       link $(LFLAGS) -out:magnetssolver.exe -map:magnetssolver.map @magnetssolver.rsp
+
+map.exe: drawing.obj dsf.obj malloc.obj map.obj map.res midend.obj misc.obj \
+               printing.obj random.obj version.obj windows.obj map.rsp
+       link $(LFLAGS) -out:map.exe -map:map.map @map.rsp
+
+mapsolver.exe: dsf.obj malloc.obj map2.obj misc.obj nullfe.obj random.obj \
+               mapsolver.rsp
+       link $(LFLAGS) -out:mapsolver.exe -map:mapsolver.map @mapsolver.rsp
+
+mineobfusc.exe: malloc.obj mines2.obj misc.obj nullfe.obj random.obj \
+               tree234.obj mineobfusc.rsp
+       link $(LFLAGS) -out:mineobfusc.exe -map:mineobfusc.map @mineobfusc.rsp
+
+mines.exe: drawing.obj malloc.obj midend.obj mines.obj mines.res misc.obj \
+               printing.obj random.obj tree234.obj version.obj windows.obj \
+               mines.rsp
+       link $(LFLAGS) -out:mines.exe -map:mines.map @mines.rsp
+
+netgame.exe: drawing.obj dsf.obj findloop.obj malloc.obj midend.obj misc.obj \
+               net.obj net.res printing.obj random.obj tree234.obj \
+               version.obj windows.obj netgame.rsp
+       link $(LFLAGS) -out:netgame.exe -map:netgame.map @netgame.rsp
+
+netslide.exe: drawing.obj malloc.obj midend.obj misc.obj netslide.obj \
+               netslide.res printing.obj random.obj tree234.obj version.obj \
+               windows.obj netslide.rsp
+       link $(LFLAGS) -out:netslide.exe -map:netslide.map @netslide.rsp
+
+nullgame.exe: drawing.obj malloc.obj midend.obj misc.obj noicon.res \
+               nullgame.obj printing.obj random.obj version.obj windows.obj \
+               nullgame.rsp
+       link $(LFLAGS) -out:nullgame.exe -map:nullgame.map @nullgame.rsp
+
+palisade.exe: divvy.obj drawing.obj dsf.obj malloc.obj midend.obj misc.obj \
+               palisade.obj palisade.res printing.obj random.obj \
+               version.obj windows.obj palisade.rsp
+       link $(LFLAGS) -out:palisade.exe -map:palisade.map @palisade.rsp
+
+pattern.exe: drawing.obj malloc.obj midend.obj misc.obj pattern.obj \
+               pattern.res printing.obj random.obj version.obj windows.obj \
+               pattern.rsp
+       link $(LFLAGS) -out:pattern.exe -map:pattern.map @pattern.rsp
+
+patternpicture.exe: malloc.obj misc.obj nullfe.obj pattern4.obj random.obj \
+               patternpicture.rsp
+       link $(LFLAGS) -out:patternpicture.exe -map:patternpicture.map @patternpicture.rsp
+
+patternsolver.exe: malloc.obj misc.obj nullfe.obj pattern2.obj random.obj \
+               patternsolver.rsp
+       link $(LFLAGS) -out:patternsolver.exe -map:patternsolver.map @patternsolver.rsp
+
+pearl.exe: drawing.obj dsf.obj grid.obj loopgen.obj malloc.obj midend.obj \
+               misc.obj pearl.obj pearl.res penrose.obj printing.obj \
+               random.obj tdq.obj tree234.obj version.obj windows.obj \
+               pearl.rsp
+       link $(LFLAGS) -out:pearl.exe -map:pearl.map @pearl.rsp
+
+pearlbench.exe: dsf.obj grid.obj loopgen.obj malloc.obj misc.obj nullfe.obj \
+               pearl2.obj penrose.obj random.obj tdq.obj tree234.obj \
+               pearlbench.rsp
+       link $(LFLAGS) -out:pearlbench.exe -map:pearlbench.map @pearlbench.rsp
+
+pegs.exe: drawing.obj malloc.obj midend.obj misc.obj pegs.obj pegs.res \
+               printing.obj random.obj tree234.obj version.obj windows.obj \
+               pegs.rsp
+       link $(LFLAGS) -out:pegs.exe -map:pegs.map @pegs.rsp
+
+puzzles.exe: blackbo3.obj bridges3.obj combi.obj cube3.obj divvy.obj \
+               dominos3.obj drawing.obj dsf.obj fifteen5.obj filling5.obj \
+               findloop.obj flip3.obj flood3.obj galaxie7.obj grid.obj \
+               guess3.obj inertia3.obj keen5.obj latin.obj laydomino.obj \
+               lightup5.obj list.obj loopgen.obj loopy5.obj magnets5.obj \
+               malloc.obj map5.obj maxflow.obj midend.obj mines5.obj \
+               misc.obj net3.obj netslid3.obj noicon.res palisad3.obj \
+               pattern7.obj pearl5.obj pegs3.obj penrose.obj printing.obj \
+               random.obj range3.obj rect3.obj samegam3.obj signpos5.obj \
+               singles5.obj sixteen3.obj slant5.obj solo5.obj tdq.obj \
+               tents5.obj towers5.obj tracks3.obj tree234.obj twiddle3.obj \
+               undead3.obj unequal5.obj unruly5.obj untangl3.obj \
+               version.obj windows1.obj puzzles.rsp
+       link $(LFLAGS) -out:puzzles.exe -map:puzzles.map @puzzles.rsp
+
+range.exe: drawing.obj dsf.obj malloc.obj midend.obj misc.obj printing.obj \
+               random.obj range.obj range.res version.obj windows.obj \
+               range.rsp
+       link $(LFLAGS) -out:range.exe -map:range.map @range.rsp
+
+rect.exe: drawing.obj malloc.obj midend.obj misc.obj printing.obj random.obj \
+               rect.obj rect.res version.obj windows.obj rect.rsp
+       link $(LFLAGS) -out:rect.exe -map:rect.map @rect.rsp
+
+samegame.exe: drawing.obj malloc.obj midend.obj misc.obj printing.obj \
+               random.obj samegame.obj samegame.res version.obj windows.obj \
+               samegame.rsp
+       link $(LFLAGS) -out:samegame.exe -map:samegame.map @samegame.rsp
+
+signpost.exe: drawing.obj dsf.obj malloc.obj midend.obj misc.obj \
+               printing.obj random.obj signpost.obj signpost.res \
+               version.obj windows.obj signpost.rsp
+       link $(LFLAGS) -out:signpost.exe -map:signpost.map @signpost.rsp
+
+signpostsolver.exe: dsf.obj malloc.obj misc.obj nullfe.obj random.obj \
+               signpos2.obj signpostsolver.rsp
+       link $(LFLAGS) -out:signpostsolver.exe -map:signpostsolver.map @signpostsolver.rsp
+
+singles.exe: drawing.obj dsf.obj latin.obj malloc.obj maxflow.obj midend.obj \
+               misc.obj printing.obj random.obj singles.obj singles.res \
+               tree234.obj version.obj windows.obj singles.rsp
+       link $(LFLAGS) -out:singles.exe -map:singles.map @singles.rsp
+
+singlessolver.exe: dsf.obj latin.obj malloc.obj maxflow.obj misc.obj \
+               nullfe.obj random.obj singles3.obj tree234.obj \
+               singlessolver.rsp
+       link $(LFLAGS) -out:singlessolver.exe -map:singlessolver.map @singlessolver.rsp
+
+sixteen.exe: drawing.obj malloc.obj midend.obj misc.obj printing.obj \
+               random.obj sixteen.obj sixteen.res version.obj windows.obj \
+               sixteen.rsp
+       link $(LFLAGS) -out:sixteen.exe -map:sixteen.map @sixteen.rsp
+
+slant.exe: drawing.obj dsf.obj findloop.obj malloc.obj midend.obj misc.obj \
+               printing.obj random.obj slant.obj slant.res version.obj \
+               windows.obj slant.rsp
+       link $(LFLAGS) -out:slant.exe -map:slant.map @slant.rsp
+
+slantsolver.exe: dsf.obj findloop.obj malloc.obj misc.obj nullfe.obj \
+               random.obj slant2.obj slantsolver.rsp
+       link $(LFLAGS) -out:slantsolver.exe -map:slantsolver.map @slantsolver.rsp
+
+solo.exe: divvy.obj drawing.obj dsf.obj malloc.obj midend.obj misc.obj \
+               printing.obj random.obj solo.obj solo.res version.obj \
+               windows.obj solo.rsp
+       link $(LFLAGS) -out:solo.exe -map:solo.map @solo.rsp
+
+solosolver.exe: divvy.obj dsf.obj malloc.obj misc.obj nullfe.obj random.obj \
+               solo2.obj solosolver.rsp
+       link $(LFLAGS) -out:solosolver.exe -map:solosolver.map @solosolver.rsp
+
+tents.exe: drawing.obj dsf.obj malloc.obj maxflow.obj midend.obj misc.obj \
+               printing.obj random.obj tents.obj tents.res version.obj \
+               windows.obj tents.rsp
+       link $(LFLAGS) -out:tents.exe -map:tents.map @tents.rsp
+
+tentssolver.exe: dsf.obj malloc.obj maxflow.obj misc.obj nullfe.obj \
+               random.obj tents3.obj tentssolver.rsp
+       link $(LFLAGS) -out:tentssolver.exe -map:tentssolver.map @tentssolver.rsp
+
+towers.exe: drawing.obj latin.obj malloc.obj maxflow.obj midend.obj misc.obj \
+               printing.obj random.obj towers.obj towers.res tree234.obj \
+               version.obj windows.obj towers.rsp
+       link $(LFLAGS) -out:towers.exe -map:towers.map @towers.rsp
+
+towerssolver.exe: latin6.obj malloc.obj maxflow.obj misc.obj nullfe.obj \
+               random.obj towers2.obj tree234.obj towerssolver.rsp
+       link $(LFLAGS) -out:towerssolver.exe -map:towerssolver.map @towerssolver.rsp
+
+tracks.exe: drawing.obj dsf.obj findloop.obj malloc.obj midend.obj misc.obj \
+               printing.obj random.obj tracks.obj tracks.res version.obj \
+               windows.obj tracks.rsp
+       link $(LFLAGS) -out:tracks.exe -map:tracks.map @tracks.rsp
+
+twiddle.exe: drawing.obj malloc.obj midend.obj misc.obj printing.obj \
+               random.obj twiddle.obj twiddle.res version.obj windows.obj \
+               twiddle.rsp
+       link $(LFLAGS) -out:twiddle.exe -map:twiddle.map @twiddle.rsp
+
+undead.exe: drawing.obj malloc.obj midend.obj misc.obj printing.obj \
+               random.obj undead.obj undead.res version.obj windows.obj \
+               undead.rsp
+       link $(LFLAGS) -out:undead.exe -map:undead.map @undead.rsp
+
+unequal.exe: drawing.obj latin.obj malloc.obj maxflow.obj midend.obj \
+               misc.obj printing.obj random.obj tree234.obj unequal.obj \
+               unequal.res version.obj windows.obj unequal.rsp
+       link $(LFLAGS) -out:unequal.exe -map:unequal.map @unequal.rsp
+
+unequalsolver.exe: latin6.obj malloc.obj maxflow.obj misc.obj nullfe.obj \
+               random.obj tree234.obj unequal2.obj unequalsolver.rsp
+       link $(LFLAGS) -out:unequalsolver.exe -map:unequalsolver.map @unequalsolver.rsp
+
+unruly.exe: drawing.obj malloc.obj midend.obj misc.obj printing.obj \
+               random.obj unruly.obj unruly.res version.obj windows.obj \
+               unruly.rsp
+       link $(LFLAGS) -out:unruly.exe -map:unruly.map @unruly.rsp
+
+unrulysolver.exe: malloc.obj misc.obj nullfe.obj random.obj unruly2.obj \
+               unrulysolver.rsp
+       link $(LFLAGS) -out:unrulysolver.exe -map:unrulysolver.map @unrulysolver.rsp
+
+untangle.exe: drawing.obj malloc.obj midend.obj misc.obj printing.obj \
+               random.obj tree234.obj untangle.obj untangle.res version.obj \
+               windows.obj untangle.rsp
+       link $(LFLAGS) -out:untangle.exe -map:untangle.map @untangle.rsp
+
+blackbox.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > blackbox.rsp
+       echo blackbox.obj blackbox.res comctl32.lib >> blackbox.rsp
+       echo comdlg32.lib drawing.obj gdi32.lib malloc.obj >> blackbox.rsp
+       echo midend.obj misc.obj printing.obj random.obj >> blackbox.rsp
+       echo user32.lib version.obj windows.obj winspool.lib >> blackbox.rsp
+
+bridges.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > bridges.rsp
+       echo bridges.obj bridges.res comctl32.lib comdlg32.lib >> bridges.rsp
+       echo drawing.obj dsf.obj findloop.obj gdi32.lib >> bridges.rsp
+       echo malloc.obj midend.obj misc.obj printing.obj >> bridges.rsp
+       echo random.obj user32.lib version.obj windows.obj >> bridges.rsp
+       echo winspool.lib >> bridges.rsp
+
+cube.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > cube.rsp
+       echo comctl32.lib comdlg32.lib cube.obj cube.res >> cube.rsp
+       echo drawing.obj gdi32.lib malloc.obj midend.obj >> cube.rsp
+       echo misc.obj printing.obj random.obj user32.lib >> cube.rsp
+       echo version.obj windows.obj winspool.lib >> cube.rsp
+
+dominosa.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > dominosa.rsp
+       echo comctl32.lib comdlg32.lib dominosa.obj >> dominosa.rsp
+       echo dominosa.res drawing.obj gdi32.lib laydomino.obj >> dominosa.rsp
+       echo malloc.obj midend.obj misc.obj printing.obj >> dominosa.rsp
+       echo random.obj user32.lib version.obj windows.obj >> dominosa.rsp
+       echo winspool.lib >> dominosa.rsp
+
+fifteen.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > fifteen.rsp
+       echo comctl32.lib comdlg32.lib drawing.obj fifteen.obj >> fifteen.rsp
+       echo fifteen.res gdi32.lib malloc.obj midend.obj >> fifteen.rsp
+       echo misc.obj printing.obj random.obj user32.lib >> fifteen.rsp
+       echo version.obj windows.obj winspool.lib >> fifteen.rsp
+
+fifteensolver.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:console > fifteensolver.rsp
+       echo fifteen2.obj malloc.obj misc.obj nullfe.obj >> fifteensolver.rsp
+       echo random.obj >> fifteensolver.rsp
+
+filling.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > filling.rsp
+       echo comctl32.lib comdlg32.lib drawing.obj dsf.obj >> filling.rsp
+       echo filling.obj filling.res gdi32.lib malloc.obj >> filling.rsp
+       echo midend.obj misc.obj printing.obj random.obj >> filling.rsp
+       echo user32.lib version.obj windows.obj winspool.lib >> filling.rsp
+
+fillingsolver.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:console > fillingsolver.rsp
+       echo dsf.obj filling2.obj malloc.obj misc.obj >> fillingsolver.rsp
+       echo nullfe.obj random.obj >> fillingsolver.rsp
+
+flip.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > flip.rsp
+       echo comctl32.lib comdlg32.lib drawing.obj flip.obj >> flip.rsp
+       echo flip.res gdi32.lib malloc.obj midend.obj misc.obj >> flip.rsp
+       echo printing.obj random.obj tree234.obj user32.lib >> flip.rsp
+       echo version.obj windows.obj winspool.lib >> flip.rsp
+
+flood.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > flood.rsp
+       echo comctl32.lib comdlg32.lib drawing.obj flood.obj >> flood.rsp
+       echo flood.res gdi32.lib malloc.obj midend.obj >> flood.rsp
+       echo misc.obj printing.obj random.obj user32.lib >> flood.rsp
+       echo version.obj windows.obj winspool.lib >> flood.rsp
+
+galaxies.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > galaxies.rsp
+       echo comctl32.lib comdlg32.lib drawing.obj dsf.obj >> galaxies.rsp
+       echo galaxies.obj galaxies.res gdi32.lib malloc.obj >> galaxies.rsp
+       echo midend.obj misc.obj printing.obj random.obj >> galaxies.rsp
+       echo user32.lib version.obj windows.obj winspool.lib >> galaxies.rsp
+
+galaxiespicture.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:console > galaxiespicture.rsp
+       echo dsf.obj galaxie4.obj malloc.obj misc.obj >> galaxiespicture.rsp
+       echo nullfe.obj random.obj >> galaxiespicture.rsp
+
+galaxiessolver.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:console > galaxiessolver.rsp
+       echo dsf.obj galaxie2.obj malloc.obj misc.obj >> galaxiessolver.rsp
+       echo nullfe.obj random.obj >> galaxiessolver.rsp
+
+guess.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > guess.rsp
+       echo comctl32.lib comdlg32.lib drawing.obj gdi32.lib >> guess.rsp
+       echo guess.obj guess.res malloc.obj midend.obj >> guess.rsp
+       echo misc.obj printing.obj random.obj user32.lib >> guess.rsp
+       echo version.obj windows.obj winspool.lib >> guess.rsp
+
+inertia.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > inertia.rsp
+       echo comctl32.lib comdlg32.lib drawing.obj gdi32.lib >> inertia.rsp
+       echo inertia.obj inertia.res malloc.obj midend.obj >> inertia.rsp
+       echo misc.obj printing.obj random.obj user32.lib >> inertia.rsp
+       echo version.obj windows.obj winspool.lib >> inertia.rsp
+
+keen.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > keen.rsp
+       echo comctl32.lib comdlg32.lib drawing.obj dsf.obj >> keen.rsp
+       echo gdi32.lib keen.obj keen.res latin.obj malloc.obj >> keen.rsp
+       echo maxflow.obj midend.obj misc.obj printing.obj >> keen.rsp
+       echo random.obj tree234.obj user32.lib version.obj >> keen.rsp
+       echo windows.obj winspool.lib >> keen.rsp
+
+keensolver.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:console > keensolver.rsp
+       echo dsf.obj keen2.obj latin6.obj malloc.obj >> keensolver.rsp
+       echo maxflow.obj misc.obj nullfe.obj random.obj >> keensolver.rsp
+       echo tree234.obj >> keensolver.rsp
+
+latincheck.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:console > latincheck.rsp
+       echo latin8.obj malloc.obj maxflow.obj misc.obj >> latincheck.rsp
+       echo nullfe.obj random.obj tree234.obj >> latincheck.rsp
+
+lightup.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > lightup.rsp
+       echo combi.obj comctl32.lib comdlg32.lib drawing.obj >> lightup.rsp
+       echo gdi32.lib lightup.obj lightup.res malloc.obj >> lightup.rsp
+       echo midend.obj misc.obj printing.obj random.obj >> lightup.rsp
+       echo user32.lib version.obj windows.obj winspool.lib >> lightup.rsp
+
+lightupsolver.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:console > lightupsolver.rsp
+       echo combi.obj lightup2.obj malloc.obj misc.obj >> lightupsolver.rsp
+       echo nullfe.obj random.obj >> lightupsolver.rsp
+
+loopy.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > loopy.rsp
+       echo comctl32.lib comdlg32.lib drawing.obj dsf.obj >> loopy.rsp
+       echo gdi32.lib grid.obj loopgen.obj loopy.obj >> loopy.rsp
+       echo loopy.res malloc.obj midend.obj misc.obj >> loopy.rsp
+       echo penrose.obj printing.obj random.obj tree234.obj >> loopy.rsp
+       echo user32.lib version.obj windows.obj winspool.lib >> loopy.rsp
+
+loopysolver.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:console > loopysolver.rsp
+       echo dsf.obj grid.obj loopgen.obj loopy2.obj >> loopysolver.rsp
+       echo malloc.obj misc.obj nullfe.obj penrose.obj >> loopysolver.rsp
+       echo random.obj tree234.obj >> loopysolver.rsp
+
+magnets.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > magnets.rsp
+       echo comctl32.lib comdlg32.lib drawing.obj gdi32.lib >> magnets.rsp
+       echo laydomino.obj magnets.obj magnets.res malloc.obj >> magnets.rsp
+       echo midend.obj misc.obj printing.obj random.obj >> magnets.rsp
+       echo user32.lib version.obj windows.obj winspool.lib >> magnets.rsp
+
+magnetssolver.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:console > magnetssolver.rsp
+       echo laydomino.obj magnets2.obj malloc.obj misc.obj >> magnetssolver.rsp
+       echo nullfe.obj random.obj >> magnetssolver.rsp
+
+map.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > map.rsp
+       echo comctl32.lib comdlg32.lib drawing.obj dsf.obj >> map.rsp
+       echo gdi32.lib malloc.obj map.obj map.res midend.obj >> map.rsp
+       echo misc.obj printing.obj random.obj user32.lib >> map.rsp
+       echo version.obj windows.obj winspool.lib >> map.rsp
+
+mapsolver.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:console > mapsolver.rsp
+       echo dsf.obj malloc.obj map2.obj misc.obj nullfe.obj >> mapsolver.rsp
+       echo random.obj >> mapsolver.rsp
+
+mineobfusc.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:console > mineobfusc.rsp
+       echo malloc.obj mines2.obj misc.obj nullfe.obj >> mineobfusc.rsp
+       echo random.obj tree234.obj >> mineobfusc.rsp
+
+mines.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > mines.rsp
+       echo comctl32.lib comdlg32.lib drawing.obj gdi32.lib >> mines.rsp
+       echo malloc.obj midend.obj mines.obj mines.res >> mines.rsp
+       echo misc.obj printing.obj random.obj tree234.obj >> mines.rsp
+       echo user32.lib version.obj windows.obj winspool.lib >> mines.rsp
+
+netgame.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > netgame.rsp
+       echo comctl32.lib comdlg32.lib drawing.obj dsf.obj >> netgame.rsp
+       echo findloop.obj gdi32.lib malloc.obj midend.obj >> netgame.rsp
+       echo misc.obj net.obj net.res printing.obj random.obj >> netgame.rsp
+       echo tree234.obj user32.lib version.obj windows.obj >> netgame.rsp
+       echo winspool.lib >> netgame.rsp
+
+netslide.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > netslide.rsp
+       echo comctl32.lib comdlg32.lib drawing.obj gdi32.lib >> netslide.rsp
+       echo malloc.obj midend.obj misc.obj netslide.obj >> netslide.rsp
+       echo netslide.res printing.obj random.obj tree234.obj >> netslide.rsp
+       echo user32.lib version.obj windows.obj winspool.lib >> netslide.rsp
+
+nullgame.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > nullgame.rsp
+       echo comctl32.lib comdlg32.lib drawing.obj gdi32.lib >> nullgame.rsp
+       echo malloc.obj midend.obj misc.obj noicon.res >> nullgame.rsp
+       echo nullgame.obj printing.obj random.obj user32.lib >> nullgame.rsp
+       echo version.obj windows.obj winspool.lib >> nullgame.rsp
+
+palisade.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > palisade.rsp
+       echo comctl32.lib comdlg32.lib divvy.obj drawing.obj >> palisade.rsp
+       echo dsf.obj gdi32.lib malloc.obj midend.obj misc.obj >> palisade.rsp
+       echo palisade.obj palisade.res printing.obj random.obj >> palisade.rsp
+       echo user32.lib version.obj windows.obj winspool.lib >> palisade.rsp
+
+pattern.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > pattern.rsp
+       echo comctl32.lib comdlg32.lib drawing.obj gdi32.lib >> pattern.rsp
+       echo malloc.obj midend.obj misc.obj pattern.obj >> pattern.rsp
+       echo pattern.res printing.obj random.obj user32.lib >> pattern.rsp
+       echo version.obj windows.obj winspool.lib >> pattern.rsp
+
+patternpicture.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:console > patternpicture.rsp
+       echo malloc.obj misc.obj nullfe.obj pattern4.obj >> patternpicture.rsp
+       echo random.obj >> patternpicture.rsp
+
+patternsolver.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:console > patternsolver.rsp
+       echo malloc.obj misc.obj nullfe.obj pattern2.obj >> patternsolver.rsp
+       echo random.obj >> patternsolver.rsp
+
+pearl.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > pearl.rsp
+       echo comctl32.lib comdlg32.lib drawing.obj dsf.obj >> pearl.rsp
+       echo gdi32.lib grid.obj loopgen.obj malloc.obj >> pearl.rsp
+       echo midend.obj misc.obj pearl.obj pearl.res >> pearl.rsp
+       echo penrose.obj printing.obj random.obj tdq.obj >> pearl.rsp
+       echo tree234.obj user32.lib version.obj windows.obj >> pearl.rsp
+       echo winspool.lib >> pearl.rsp
+
+pearlbench.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:console > pearlbench.rsp
+       echo dsf.obj grid.obj loopgen.obj malloc.obj misc.obj >> pearlbench.rsp
+       echo nullfe.obj pearl2.obj penrose.obj random.obj >> pearlbench.rsp
+       echo tdq.obj tree234.obj >> pearlbench.rsp
+
+pegs.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > pegs.rsp
+       echo comctl32.lib comdlg32.lib drawing.obj gdi32.lib >> pegs.rsp
+       echo malloc.obj midend.obj misc.obj pegs.obj pegs.res >> pegs.rsp
+       echo printing.obj random.obj tree234.obj user32.lib >> pegs.rsp
+       echo version.obj windows.obj winspool.lib >> pegs.rsp
+
+puzzles.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > puzzles.rsp
+       echo blackbo3.obj bridges3.obj combi.obj comctl32.lib >> puzzles.rsp
+       echo comdlg32.lib cube3.obj divvy.obj dominos3.obj >> puzzles.rsp
+       echo drawing.obj dsf.obj fifteen5.obj filling5.obj >> puzzles.rsp
+       echo findloop.obj flip3.obj flood3.obj galaxie7.obj >> puzzles.rsp
+       echo gdi32.lib grid.obj guess3.obj inertia3.obj >> puzzles.rsp
+       echo keen5.obj latin.obj laydomino.obj lightup5.obj >> puzzles.rsp
+       echo list.obj loopgen.obj loopy5.obj magnets5.obj >> puzzles.rsp
+       echo malloc.obj map5.obj maxflow.obj midend.obj >> puzzles.rsp
+       echo mines5.obj misc.obj net3.obj netslid3.obj >> puzzles.rsp
+       echo noicon.res palisad3.obj pattern7.obj pearl5.obj >> puzzles.rsp
+       echo pegs3.obj penrose.obj printing.obj random.obj >> puzzles.rsp
+       echo range3.obj rect3.obj samegam3.obj signpos5.obj >> puzzles.rsp
+       echo singles5.obj sixteen3.obj slant5.obj solo5.obj >> puzzles.rsp
+       echo tdq.obj tents5.obj towers5.obj tracks3.obj >> puzzles.rsp
+       echo tree234.obj twiddle3.obj undead3.obj unequal5.obj >> puzzles.rsp
+       echo unruly5.obj untangl3.obj user32.lib version.obj >> puzzles.rsp
+       echo windows1.obj winspool.lib >> puzzles.rsp
+
+range.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > range.rsp
+       echo comctl32.lib comdlg32.lib drawing.obj dsf.obj >> range.rsp
+       echo gdi32.lib malloc.obj midend.obj misc.obj >> range.rsp
+       echo printing.obj random.obj range.obj range.res >> range.rsp
+       echo user32.lib version.obj windows.obj winspool.lib >> range.rsp
+
+rect.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > rect.rsp
+       echo comctl32.lib comdlg32.lib drawing.obj gdi32.lib >> rect.rsp
+       echo malloc.obj midend.obj misc.obj printing.obj >> rect.rsp
+       echo random.obj rect.obj rect.res user32.lib >> rect.rsp
+       echo version.obj windows.obj winspool.lib >> rect.rsp
+
+samegame.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > samegame.rsp
+       echo comctl32.lib comdlg32.lib drawing.obj gdi32.lib >> samegame.rsp
+       echo malloc.obj midend.obj misc.obj printing.obj >> samegame.rsp
+       echo random.obj samegame.obj samegame.res user32.lib >> samegame.rsp
+       echo version.obj windows.obj winspool.lib >> samegame.rsp
+
+signpost.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > signpost.rsp
+       echo comctl32.lib comdlg32.lib drawing.obj dsf.obj >> signpost.rsp
+       echo gdi32.lib malloc.obj midend.obj misc.obj >> signpost.rsp
+       echo printing.obj random.obj signpost.obj signpost.res >> signpost.rsp
+       echo user32.lib version.obj windows.obj winspool.lib >> signpost.rsp
+
+signpostsolver.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:console > signpostsolver.rsp
+       echo dsf.obj malloc.obj misc.obj nullfe.obj random.obj >> signpostsolver.rsp
+       echo signpos2.obj >> signpostsolver.rsp
+
+singles.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > singles.rsp
+       echo comctl32.lib comdlg32.lib drawing.obj dsf.obj >> singles.rsp
+       echo gdi32.lib latin.obj malloc.obj maxflow.obj >> singles.rsp
+       echo midend.obj misc.obj printing.obj random.obj >> singles.rsp
+       echo singles.obj singles.res tree234.obj user32.lib >> singles.rsp
+       echo version.obj windows.obj winspool.lib >> singles.rsp
+
+singlessolver.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:console > singlessolver.rsp
+       echo dsf.obj latin.obj malloc.obj maxflow.obj misc.obj >> singlessolver.rsp
+       echo nullfe.obj random.obj singles3.obj tree234.obj >> singlessolver.rsp
+
+sixteen.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > sixteen.rsp
+       echo comctl32.lib comdlg32.lib drawing.obj gdi32.lib >> sixteen.rsp
+       echo malloc.obj midend.obj misc.obj printing.obj >> sixteen.rsp
+       echo random.obj sixteen.obj sixteen.res user32.lib >> sixteen.rsp
+       echo version.obj windows.obj winspool.lib >> sixteen.rsp
+
+slant.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > slant.rsp
+       echo comctl32.lib comdlg32.lib drawing.obj dsf.obj >> slant.rsp
+       echo findloop.obj gdi32.lib malloc.obj midend.obj >> slant.rsp
+       echo misc.obj printing.obj random.obj slant.obj >> slant.rsp
+       echo slant.res user32.lib version.obj windows.obj >> slant.rsp
+       echo winspool.lib >> slant.rsp
+
+slantsolver.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:console > slantsolver.rsp
+       echo dsf.obj findloop.obj malloc.obj misc.obj >> slantsolver.rsp
+       echo nullfe.obj random.obj slant2.obj >> slantsolver.rsp
+
+solo.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > solo.rsp
+       echo comctl32.lib comdlg32.lib divvy.obj drawing.obj >> solo.rsp
+       echo dsf.obj gdi32.lib malloc.obj midend.obj misc.obj >> solo.rsp
+       echo printing.obj random.obj solo.obj solo.res >> solo.rsp
+       echo user32.lib version.obj windows.obj winspool.lib >> solo.rsp
+
+solosolver.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:console > solosolver.rsp
+       echo divvy.obj dsf.obj malloc.obj misc.obj nullfe.obj >> solosolver.rsp
+       echo random.obj solo2.obj >> solosolver.rsp
+
+tents.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > tents.rsp
+       echo comctl32.lib comdlg32.lib drawing.obj dsf.obj >> tents.rsp
+       echo gdi32.lib malloc.obj maxflow.obj midend.obj >> tents.rsp
+       echo misc.obj printing.obj random.obj tents.obj >> tents.rsp
+       echo tents.res user32.lib version.obj windows.obj >> tents.rsp
+       echo winspool.lib >> tents.rsp
+
+tentssolver.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:console > tentssolver.rsp
+       echo dsf.obj malloc.obj maxflow.obj misc.obj >> tentssolver.rsp
+       echo nullfe.obj random.obj tents3.obj >> tentssolver.rsp
+
+towers.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > towers.rsp
+       echo comctl32.lib comdlg32.lib drawing.obj gdi32.lib >> towers.rsp
+       echo latin.obj malloc.obj maxflow.obj midend.obj >> towers.rsp
+       echo misc.obj printing.obj random.obj towers.obj >> towers.rsp
+       echo towers.res tree234.obj user32.lib version.obj >> towers.rsp
+       echo windows.obj winspool.lib >> towers.rsp
+
+towerssolver.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:console > towerssolver.rsp
+       echo latin6.obj malloc.obj maxflow.obj misc.obj >> towerssolver.rsp
+       echo nullfe.obj random.obj towers2.obj tree234.obj >> towerssolver.rsp
+
+tracks.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > tracks.rsp
+       echo comctl32.lib comdlg32.lib drawing.obj dsf.obj >> tracks.rsp
+       echo findloop.obj gdi32.lib malloc.obj midend.obj >> tracks.rsp
+       echo misc.obj printing.obj random.obj tracks.obj >> tracks.rsp
+       echo tracks.res user32.lib version.obj windows.obj >> tracks.rsp
+       echo winspool.lib >> tracks.rsp
+
+twiddle.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > twiddle.rsp
+       echo comctl32.lib comdlg32.lib drawing.obj gdi32.lib >> twiddle.rsp
+       echo malloc.obj midend.obj misc.obj printing.obj >> twiddle.rsp
+       echo random.obj twiddle.obj twiddle.res user32.lib >> twiddle.rsp
+       echo version.obj windows.obj winspool.lib >> twiddle.rsp
+
+undead.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > undead.rsp
+       echo comctl32.lib comdlg32.lib drawing.obj gdi32.lib >> undead.rsp
+       echo malloc.obj midend.obj misc.obj printing.obj >> undead.rsp
+       echo random.obj undead.obj undead.res user32.lib >> undead.rsp
+       echo version.obj windows.obj winspool.lib >> undead.rsp
+
+unequal.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > unequal.rsp
+       echo comctl32.lib comdlg32.lib drawing.obj gdi32.lib >> unequal.rsp
+       echo latin.obj malloc.obj maxflow.obj midend.obj >> unequal.rsp
+       echo misc.obj printing.obj random.obj tree234.obj >> unequal.rsp
+       echo unequal.obj unequal.res user32.lib version.obj >> unequal.rsp
+       echo windows.obj winspool.lib >> unequal.rsp
+
+unequalsolver.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:console > unequalsolver.rsp
+       echo latin6.obj malloc.obj maxflow.obj misc.obj >> unequalsolver.rsp
+       echo nullfe.obj random.obj tree234.obj unequal2.obj >> unequalsolver.rsp
+
+unruly.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > unruly.rsp
+       echo comctl32.lib comdlg32.lib drawing.obj gdi32.lib >> unruly.rsp
+       echo malloc.obj midend.obj misc.obj printing.obj >> unruly.rsp
+       echo random.obj unruly.obj unruly.res user32.lib >> unruly.rsp
+       echo version.obj windows.obj winspool.lib >> unruly.rsp
+
+unrulysolver.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:console > unrulysolver.rsp
+       echo malloc.obj misc.obj nullfe.obj random.obj >> unrulysolver.rsp
+       echo unruly2.obj >> unrulysolver.rsp
+
+untangle.rsp: $(MAKEFILE)
+       echo /nologo /subsystem:windows > untangle.rsp
+       echo comctl32.lib comdlg32.lib drawing.obj gdi32.lib >> untangle.rsp
+       echo malloc.obj midend.obj misc.obj printing.obj >> untangle.rsp
+       echo random.obj tree234.obj untangle.obj untangle.res >> untangle.rsp
+       echo user32.lib version.obj windows.obj winspool.lib >> untangle.rsp
+
+blackbox.obj: .\blackbox.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\blackbox.c /Foblackbox.obj
+blackbox-icon.obj: icons\blackbox-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\blackbox-icon.c /Foblackbox-icon.obj
+blackbox.res: icons\blackbox.rc .\puzzles.rc2 icons\blackbox.ico \
+               .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -foblackbox.res icons\blackbox.rc
+blackbo3.obj: .\blackbox.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\blackbox.c /Foblackbo3.obj
+bridges.obj: .\bridges.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\bridges.c /Fobridges.obj
+bridges-icon.obj: icons\bridges-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\bridges-icon.c /Fobridges-icon.obj
+bridges.res: icons\bridges.rc .\puzzles.rc2 icons\bridges.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -fobridges.res icons\bridges.rc
+bridges3.obj: .\bridges.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\bridges.c /Fobridges3.obj
+combi.obj: .\combi.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\combi.c /Focombi.obj
+cube.obj: .\cube.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\cube.c /Focube.obj
+cube-icon.obj: icons\cube-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\cube-icon.c /Focube-icon.obj
+cube.res: icons\cube.rc .\puzzles.rc2 icons\cube.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -focube.res icons\cube.rc
+cube3.obj: .\cube.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\cube.c /Focube3.obj
+divvy.obj: .\divvy.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\divvy.c /Fodivvy.obj
+dominosa.obj: .\dominosa.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\dominosa.c /Fodominosa.obj
+dominosa-icon.obj: icons\dominosa-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\dominosa-icon.c /Fodominosa-icon.obj
+dominosa.res: icons\dominosa.rc .\puzzles.rc2 icons\dominosa.ico \
+               .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -fodominosa.res icons\dominosa.rc
+dominos3.obj: .\dominosa.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\dominosa.c /Fodominos3.obj
+drawing.obj: .\drawing.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\drawing.c /Fodrawing.obj
+dsf.obj: .\dsf.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\dsf.c /Fodsf.obj
+fifteen.obj: .\fifteen.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\fifteen.c /Fofifteen.obj
+fifteen-icon.obj: icons\fifteen-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\fifteen-icon.c /Fofifteen-icon.obj
+fifteen.res: icons\fifteen.rc .\puzzles.rc2 icons\fifteen.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -fofifteen.res icons\fifteen.rc
+fifteen5.obj: .\fifteen.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\fifteen.c /Fofifteen5.obj
+fifteen2.obj: .\fifteen.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\fifteen.c /Fofifteen2.obj
+filling.obj: .\filling.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\filling.c /Fofilling.obj
+filling-icon.obj: icons\filling-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\filling-icon.c /Fofilling-icon.obj
+filling.res: icons\filling.rc .\puzzles.rc2 icons\filling.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -fofilling.res icons\filling.rc
+filling5.obj: .\filling.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\filling.c /Fofilling5.obj
+filling2.obj: .\filling.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\filling.c /Fofilling2.obj
+findloop.obj: .\findloop.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\findloop.c /Fofindloop.obj
+flip.obj: .\flip.c .\puzzles.h .\tree234.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\flip.c /Foflip.obj
+flip-icon.obj: icons\flip-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\flip-icon.c /Foflip-icon.obj
+flip.res: icons\flip.rc .\puzzles.rc2 icons\flip.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -foflip.res icons\flip.rc
+flip3.obj: .\flip.c .\puzzles.h .\tree234.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\flip.c /Foflip3.obj
+flood.obj: .\flood.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\flood.c /Foflood.obj
+flood-icon.obj: icons\flood-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\flood-icon.c /Foflood-icon.obj
+flood.res: icons\flood.rc .\puzzles.rc2 icons\flood.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -foflood.res icons\flood.rc
+flood3.obj: .\flood.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\flood.c /Foflood3.obj
+galaxies.obj: .\galaxies.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\galaxies.c /Fogalaxies.obj
+galaxies-icon.obj: icons\galaxies-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\galaxies-icon.c /Fogalaxies-icon.obj
+galaxies.res: icons\galaxies.rc .\puzzles.rc2 icons\galaxies.ico \
+               .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -fogalaxies.res icons\galaxies.rc
+galaxie7.obj: .\galaxies.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\galaxies.c /Fogalaxie7.obj
+galaxie4.obj: .\galaxies.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_PICTURE_GENERATOR /c .\galaxies.c /Fogalaxie4.obj
+galaxie2.obj: .\galaxies.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\galaxies.c /Fogalaxie2.obj
+grid.obj: .\grid.c .\puzzles.h .\tree234.h .\grid.h .\penrose.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\grid.c /Fogrid.obj
+gtk.obj: .\gtk.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\gtk.c /Fogtk.obj
+guess.obj: .\guess.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\guess.c /Foguess.obj
+guess-icon.obj: icons\guess-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\guess-icon.c /Foguess-icon.obj
+guess.res: icons\guess.rc .\puzzles.rc2 icons\guess.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -foguess.res icons\guess.rc
+guess3.obj: .\guess.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\guess.c /Foguess3.obj
+inertia.obj: .\inertia.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\inertia.c /Foinertia.obj
+inertia-icon.obj: icons\inertia-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\inertia-icon.c /Foinertia-icon.obj
+inertia.res: icons\inertia.rc .\puzzles.rc2 icons\inertia.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -foinertia.res icons\inertia.rc
+inertia3.obj: .\inertia.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\inertia.c /Foinertia3.obj
+keen.obj: .\keen.c .\puzzles.h .\latin.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\keen.c /Fokeen.obj
+keen-icon.obj: icons\keen-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\keen-icon.c /Fokeen-icon.obj
+keen.res: icons\keen.rc .\puzzles.rc2 icons\keen.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -fokeen.res icons\keen.rc
+keen5.obj: .\keen.c .\puzzles.h .\latin.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\keen.c /Fokeen5.obj
+keen2.obj: .\keen.c .\puzzles.h .\latin.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\keen.c /Fokeen2.obj
+latin.obj: .\latin.c .\puzzles.h .\tree234.h .\maxflow.h .\latin.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\latin.c /Folatin.obj
+latin8.obj: .\latin.c .\puzzles.h .\tree234.h .\maxflow.h .\latin.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_LATIN_TEST /c .\latin.c /Folatin8.obj
+latin6.obj: .\latin.c .\puzzles.h .\tree234.h .\maxflow.h .\latin.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\latin.c /Folatin6.obj
+laydomino.obj: .\laydomino.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\laydomino.c /Folaydomino.obj
+lightup.obj: .\lightup.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\lightup.c /Folightup.obj
+lightup-icon.obj: icons\lightup-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\lightup-icon.c /Folightup-icon.obj
+lightup.res: icons\lightup.rc .\puzzles.rc2 icons\lightup.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -folightup.res icons\lightup.rc
+lightup5.obj: .\lightup.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\lightup.c /Folightup5.obj
+lightup2.obj: .\lightup.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\lightup.c /Folightup2.obj
+list.obj: .\list.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\list.c /Folist.obj
+loopgen.obj: .\loopgen.c .\puzzles.h .\tree234.h .\grid.h .\loopgen.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\loopgen.c /Foloopgen.obj
+loopy.obj: .\loopy.c .\puzzles.h .\tree234.h .\grid.h .\loopgen.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\loopy.c /Foloopy.obj
+loopy-icon.obj: icons\loopy-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\loopy-icon.c /Foloopy-icon.obj
+loopy.res: icons\loopy.rc .\puzzles.rc2 icons\loopy.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -foloopy.res icons\loopy.rc
+loopy5.obj: .\loopy.c .\puzzles.h .\tree234.h .\grid.h .\loopgen.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\loopy.c /Foloopy5.obj
+loopy2.obj: .\loopy.c .\puzzles.h .\tree234.h .\grid.h .\loopgen.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\loopy.c /Foloopy2.obj
+magnets.obj: .\magnets.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\magnets.c /Fomagnets.obj
+magnets-icon.obj: icons\magnets-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\magnets-icon.c /Fomagnets-icon.obj
+magnets.res: icons\magnets.rc .\puzzles.rc2 icons\magnets.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -fomagnets.res icons\magnets.rc
+magnets5.obj: .\magnets.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\magnets.c /Fomagnets5.obj
+magnets2.obj: .\magnets.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\magnets.c /Fomagnets2.obj
+malloc.obj: .\malloc.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\malloc.c /Fomalloc.obj
+map.obj: .\map.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\map.c /Fomap.obj
+map-icon.obj: icons\map-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\map-icon.c /Fomap-icon.obj
+map.res: icons\map.rc .\puzzles.rc2 icons\map.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -fomap.res icons\map.rc
+map5.obj: .\map.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\map.c /Fomap5.obj
+map2.obj: .\map.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\map.c /Fomap2.obj
+maxflow.obj: .\maxflow.c .\maxflow.h .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\maxflow.c /Fomaxflow.obj
+midend.obj: .\midend.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\midend.c /Fomidend.obj
+mines.obj: .\mines.c .\tree234.h .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\mines.c /Fomines.obj
+mines-icon.obj: icons\mines-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\mines-icon.c /Fomines-icon.obj
+mines.res: icons\mines.rc .\puzzles.rc2 icons\mines.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -fomines.res icons\mines.rc
+mines5.obj: .\mines.c .\tree234.h .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\mines.c /Fomines5.obj
+mines2.obj: .\mines.c .\tree234.h .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_OBFUSCATOR /c .\mines.c /Fomines2.obj
+misc.obj: .\misc.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\misc.c /Fomisc.obj
+net.obj: .\net.c .\puzzles.h .\tree234.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\net.c /Fonet.obj
+net-icon.obj: icons\net-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\net-icon.c /Fonet-icon.obj
+net.res: icons\net.rc .\puzzles.rc2 icons\net.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -fonet.res icons\net.rc
+net3.obj: .\net.c .\puzzles.h .\tree234.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\net.c /Fonet3.obj
+netslide.obj: .\netslide.c .\puzzles.h .\tree234.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\netslide.c /Fonetslide.obj
+netslide-icon.obj: icons\netslide-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\netslide-icon.c /Fonetslide-icon.obj
+netslide.res: icons\netslide.rc .\puzzles.rc2 icons\netslide.ico \
+               .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -fonetslide.res icons\netslide.rc
+netslid3.obj: .\netslide.c .\puzzles.h .\tree234.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\netslide.c /Fonetslid3.obj
+no-icon.obj: .\no-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\no-icon.c /Fono-icon.obj
+noicon.res: .\noicon.rc .\puzzles.rc2 .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -fonoicon.res .\noicon.rc
+nullfe.obj: .\nullfe.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\nullfe.c /Fonullfe.obj
+nullgame.obj: .\nullgame.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\nullgame.c /Fonullgame.obj
+obfusc.obj: .\obfusc.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\obfusc.c /Foobfusc.obj
+osx.obj: .\osx.m .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\osx.m /Foosx.obj
+palisade.obj: .\palisade.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\palisade.c /Fopalisade.obj
+palisade-icon.obj: icons\palisade-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\palisade-icon.c /Fopalisade-icon.obj
+palisade.res: icons\palisade.rc .\puzzles.rc2 icons\palisade.ico \
+               .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -fopalisade.res icons\palisade.rc
+palisad3.obj: .\palisade.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\palisade.c /Fopalisad3.obj
+pattern.obj: .\pattern.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\pattern.c /Fopattern.obj
+pattern-icon.obj: icons\pattern-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\pattern-icon.c /Fopattern-icon.obj
+pattern.res: icons\pattern.rc .\puzzles.rc2 icons\pattern.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -fopattern.res icons\pattern.rc
+pattern7.obj: .\pattern.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\pattern.c /Fopattern7.obj
+pattern4.obj: .\pattern.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_PICTURE_GENERATOR /c .\pattern.c /Fopattern4.obj
+pattern2.obj: .\pattern.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\pattern.c /Fopattern2.obj
+pearl.obj: .\pearl.c .\puzzles.h .\grid.h .\loopgen.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\pearl.c /Fopearl.obj
+pearl-icon.obj: icons\pearl-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\pearl-icon.c /Fopearl-icon.obj
+pearl.res: icons\pearl.rc .\puzzles.rc2 icons\pearl.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -fopearl.res icons\pearl.rc
+pearl5.obj: .\pearl.c .\puzzles.h .\grid.h .\loopgen.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\pearl.c /Fopearl5.obj
+pearl2.obj: .\pearl.c .\puzzles.h .\grid.h .\loopgen.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\pearl.c /Fopearl2.obj
+pegs.obj: .\pegs.c .\puzzles.h .\tree234.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\pegs.c /Fopegs.obj
+pegs-icon.obj: icons\pegs-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\pegs-icon.c /Fopegs-icon.obj
+pegs.res: icons\pegs.rc .\puzzles.rc2 icons\pegs.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -fopegs.res icons\pegs.rc
+pegs3.obj: .\pegs.c .\puzzles.h .\tree234.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\pegs.c /Fopegs3.obj
+penrose.obj: .\penrose.c .\puzzles.h .\penrose.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\penrose.c /Fopenrose.obj
+printing.obj: .\printing.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\printing.c /Foprinting.obj
+ps.obj: .\ps.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\ps.c /Fops.obj
+random.obj: .\random.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\random.c /Forandom.obj
+range.obj: .\range.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\range.c /Forange.obj
+range-icon.obj: icons\range-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\range-icon.c /Forange-icon.obj
+range.res: icons\range.rc .\puzzles.rc2 icons\range.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -forange.res icons\range.rc
+range3.obj: .\range.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\range.c /Forange3.obj
+rect.obj: .\rect.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\rect.c /Forect.obj
+rect-icon.obj: icons\rect-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\rect-icon.c /Forect-icon.obj
+rect.res: icons\rect.rc .\puzzles.rc2 icons\rect.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -forect.res icons\rect.rc
+rect3.obj: .\rect.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\rect.c /Forect3.obj
+samegame.obj: .\samegame.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\samegame.c /Fosamegame.obj
+samegame-icon.obj: icons\samegame-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\samegame-icon.c /Fosamegame-icon.obj
+samegame.res: icons\samegame.rc .\puzzles.rc2 icons\samegame.ico \
+               .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -fosamegame.res icons\samegame.rc
+samegam3.obj: .\samegame.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\samegame.c /Fosamegam3.obj
+signpost.obj: .\signpost.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\signpost.c /Fosignpost.obj
+signpost-icon.obj: icons\signpost-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\signpost-icon.c /Fosignpost-icon.obj
+signpost.res: icons\signpost.rc .\puzzles.rc2 icons\signpost.ico \
+               .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -fosignpost.res icons\signpost.rc
+signpos5.obj: .\signpost.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\signpost.c /Fosignpos5.obj
+signpos2.obj: .\signpost.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\signpost.c /Fosignpos2.obj
+singles.obj: .\singles.c .\puzzles.h .\latin.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\singles.c /Fosingles.obj
+singles-icon.obj: icons\singles-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\singles-icon.c /Fosingles-icon.obj
+singles.res: icons\singles.rc .\puzzles.rc2 icons\singles.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -fosingles.res icons\singles.rc
+singles5.obj: .\singles.c .\puzzles.h .\latin.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\singles.c /Fosingles5.obj
+singles3.obj: .\singles.c .\puzzles.h .\latin.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\singles.c /Fosingles3.obj
+sixteen.obj: .\sixteen.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\sixteen.c /Fosixteen.obj
+sixteen-icon.obj: icons\sixteen-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\sixteen-icon.c /Fosixteen-icon.obj
+sixteen.res: icons\sixteen.rc .\puzzles.rc2 icons\sixteen.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -fosixteen.res icons\sixteen.rc
+sixteen3.obj: .\sixteen.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\sixteen.c /Fosixteen3.obj
+slant.obj: .\slant.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\slant.c /Foslant.obj
+slant-icon.obj: icons\slant-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\slant-icon.c /Foslant-icon.obj
+slant.res: icons\slant.rc .\puzzles.rc2 icons\slant.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -foslant.res icons\slant.rc
+slant5.obj: .\slant.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\slant.c /Foslant5.obj
+slant2.obj: .\slant.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\slant.c /Foslant2.obj
+solo.obj: .\solo.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\solo.c /Fosolo.obj
+solo-icon.obj: icons\solo-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\solo-icon.c /Fosolo-icon.obj
+solo.res: icons\solo.rc .\puzzles.rc2 icons\solo.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -fosolo.res icons\solo.rc
+solo5.obj: .\solo.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\solo.c /Fosolo5.obj
+solo2.obj: .\solo.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\solo.c /Fosolo2.obj
+tdq.obj: .\tdq.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\tdq.c /Fotdq.obj
+tents.obj: .\tents.c .\puzzles.h .\maxflow.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\tents.c /Fotents.obj
+tents-icon.obj: icons\tents-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\tents-icon.c /Fotents-icon.obj
+tents.res: icons\tents.rc .\puzzles.rc2 icons\tents.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -fotents.res icons\tents.rc
+tents5.obj: .\tents.c .\puzzles.h .\maxflow.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\tents.c /Fotents5.obj
+tents3.obj: .\tents.c .\puzzles.h .\maxflow.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\tents.c /Fotents3.obj
+towers.obj: .\towers.c .\puzzles.h .\latin.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\towers.c /Fotowers.obj
+towers-icon.obj: icons\towers-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\towers-icon.c /Fotowers-icon.obj
+towers.res: icons\towers.rc .\puzzles.rc2 icons\towers.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -fotowers.res icons\towers.rc
+towers5.obj: .\towers.c .\puzzles.h .\latin.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\towers.c /Fotowers5.obj
+towers2.obj: .\towers.c .\puzzles.h .\latin.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\towers.c /Fotowers2.obj
+tracks.obj: .\tracks.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\tracks.c /Fotracks.obj
+tracks-icon.obj: icons\tracks-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\tracks-icon.c /Fotracks-icon.obj
+tracks.res: icons\tracks.rc .\puzzles.rc2 icons\tracks.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -fotracks.res icons\tracks.rc
+tracks3.obj: .\tracks.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\tracks.c /Fotracks3.obj
+tree234.obj: .\tree234.c .\tree234.h .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\tree234.c /Fotree234.obj
+twiddle.obj: .\twiddle.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\twiddle.c /Fotwiddle.obj
+twiddle-icon.obj: icons\twiddle-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\twiddle-icon.c /Fotwiddle-icon.obj
+twiddle.res: icons\twiddle.rc .\puzzles.rc2 icons\twiddle.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -fotwiddle.res icons\twiddle.rc
+twiddle3.obj: .\twiddle.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\twiddle.c /Fotwiddle3.obj
+undead.obj: .\undead.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\undead.c /Foundead.obj
+undead-icon.obj: icons\undead-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\undead-icon.c /Foundead-icon.obj
+undead.res: icons\undead.rc .\puzzles.rc2 icons\undead.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -foundead.res icons\undead.rc
+undead3.obj: .\undead.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\undead.c /Foundead3.obj
+unequal.obj: .\unequal.c .\puzzles.h .\latin.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\unequal.c /Founequal.obj
+unequal-icon.obj: icons\unequal-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\unequal-icon.c /Founequal-icon.obj
+unequal.res: icons\unequal.rc .\puzzles.rc2 icons\unequal.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -founequal.res icons\unequal.rc
+unequal5.obj: .\unequal.c .\puzzles.h .\latin.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\unequal.c /Founequal5.obj
+unequal2.obj: .\unequal.c .\puzzles.h .\latin.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\unequal.c /Founequal2.obj
+unruly.obj: .\unruly.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\unruly.c /Founruly.obj
+unruly-icon.obj: icons\unruly-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\unruly-icon.c /Founruly-icon.obj
+unruly.res: icons\unruly.rc .\puzzles.rc2 icons\unruly.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -founruly.res icons\unruly.rc
+unruly5.obj: .\unruly.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\unruly.c /Founruly5.obj
+unruly2.obj: .\unruly.c .\puzzles.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\unruly.c /Founruly2.obj
+untangle.obj: .\untangle.c .\puzzles.h .\tree234.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\untangle.c /Fountangle.obj
+untangle-icon.obj: icons\untangle-icon.c
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\untangle-icon.c /Fountangle-icon.obj
+untangle.res: icons\untangle.rc .\puzzles.rc2 icons\untangle.ico \
+               .\resource.h
+       rc $(FWHACK) $(RCFL) -r -DWIN32 -D_WIN32 -DWINVER=0x0400 -fountangle.res icons\untangle.rc
+untangl3.obj: .\untangle.c .\puzzles.h .\tree234.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\untangle.c /Fountangl3.obj
+version.obj: .\version.c .\version.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\version.c /Foversion.obj
+windows.obj: .\windows.c .\puzzles.h .\resource.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\windows.c /Fowindows.obj
+windows1.obj: .\windows.c .\puzzles.h .\resource.h
+       cl $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\windows.c /Fowindows1.obj
+
+
+clean: tidy
+       -del *.exe
+
+tidy:
+       -del *.obj
+       -del *.res
+       -del *.pch
+       -del *.aps
+       -del *.ilk
+       -del *.pdb
+       -del *.rsp
+       -del *.dsp
+       -del *.dsw
+       -del *.ncb
+       -del *.opt
+       -del *.plg
+       -del *.map
+       -del *.idb
+       -del debug.log
diff --git a/Makefile.wce b/Makefile.wce
new file mode 100644 (file)
index 0000000..0bd9f7d
--- /dev/null
@@ -0,0 +1,971 @@
+# Makefile for puzzles on PocketPC using eMbedded Visual C.
+#
+# This file was created by `mkfiles.pl' from the `Recipe' file.
+# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.
+
+# If you rename this file to `Makefile', you should change this line,
+# so that the .rsp files still depend on the correct makefile.
+MAKEFILE = Makefile.wce
+
+# This makefile expects the environment to have been set up by one
+# of the PocketPC batch files wcearmv4.bat and wceemulator.bat. No
+# other build targets are currently supported, because they would
+# need a section in this if statement.
+!if "$(TARGETCPU)" == "emulator"
+PLATFORM_DEFS=/D "_i386_" /D "i_386_" /D "_X86_" /D "x86"
+CC=cl
+BASELIBS=commctrl.lib coredll.lib corelibc.lib aygshell.lib
+MACHINE=IX86
+!else
+PLATFORM_DEFS=/D "ARM" /D "_ARM_" /D "ARMV4"
+CC=clarm
+BASELIBS=commctrl.lib coredll.lib aygshell.lib
+MACHINE=ARM
+!endif
+
+# C compilation flags
+CFLAGS = /nologo /W3 /O1 /MC /D _WIN32_WCE=420 /D "WIN32_PLATFORM_PSPC=400" /D UNDER_CE=420 \
+         $(PLATFORM_DEFS) \
+         /D "UNICODE" /D "_UNICODE" /D "NDEBUG" /D "NO_HTMLHELP"
+
+LFLAGS = /nologo /incremental:no \
+         /base:0x00010000 /stack:0x10000,0x1000 /entry:WinMainCRTStartup \
+         /nodefaultlib:libc.lib /nodefaultlib:libcmt.lib /nodefaultlib:msvcrt.lib /nodefaultlib:OLDNAMES.lib \
+         /subsystem:windowsce,4.20 /align:4096 /MACHINE:$(MACHINE)
+
+RCFL = /d UNDER_CE=420 /d _WIN32_WCE=420 /d "WIN32_PLATFORM_PSPC=400" \
+       $(PLATFORM_DEFS) \
+       /d "NDEBUG" /d "UNICODE" /d "_UNICODE"
+
+all: blackbox.exe bridges.exe cube.exe dominosa.exe fifteen.exe filling.exe \
+               flip.exe flood.exe galaxies.exe guess.exe inertia.exe \
+               keen.exe lightup.exe loopy.exe magnets.exe map.exe mines.exe \
+               netgame.exe netslide.exe nullgame.exe palisade.exe \
+               pattern.exe pearl.exe pegs.exe puzzles.exe range.exe \
+               rect.exe samegame.exe signpost.exe singles.exe sixteen.exe \
+               slant.exe solo.exe tents.exe towers.exe tracks.exe \
+               twiddle.exe undead.exe unequal.exe unruly.exe untangle.exe
+
+blackbox.exe: blackbox.obj blackbox.res drawing.obj malloc.obj midend.obj \
+               misc.obj printing.obj random.obj version.obj windows.obj \
+               blackbox.rsp
+       link $(LFLAGS) -out:blackbox.exe -map:blackbox.map @blackbox.rsp
+
+bridges.exe: bridges.obj bridges.res drawing.obj dsf.obj findloop.obj \
+               malloc.obj midend.obj misc.obj printing.obj random.obj \
+               version.obj windows.obj bridges.rsp
+       link $(LFLAGS) -out:bridges.exe -map:bridges.map @bridges.rsp
+
+cube.exe: cube.obj cube.res drawing.obj malloc.obj midend.obj misc.obj \
+               printing.obj random.obj version.obj windows.obj cube.rsp
+       link $(LFLAGS) -out:cube.exe -map:cube.map @cube.rsp
+
+dominosa.exe: dominosa.obj dominosa.res drawing.obj laydomino.obj malloc.obj \
+               midend.obj misc.obj printing.obj random.obj version.obj \
+               windows.obj dominosa.rsp
+       link $(LFLAGS) -out:dominosa.exe -map:dominosa.map @dominosa.rsp
+
+fifteen.exe: drawing.obj fifteen.obj fifteen.res malloc.obj midend.obj \
+               misc.obj printing.obj random.obj version.obj windows.obj \
+               fifteen.rsp
+       link $(LFLAGS) -out:fifteen.exe -map:fifteen.map @fifteen.rsp
+
+filling.exe: drawing.obj dsf.obj filling.obj filling.res malloc.obj \
+               midend.obj misc.obj printing.obj random.obj version.obj \
+               windows.obj filling.rsp
+       link $(LFLAGS) -out:filling.exe -map:filling.map @filling.rsp
+
+flip.exe: drawing.obj flip.obj flip.res malloc.obj midend.obj misc.obj \
+               printing.obj random.obj tree234.obj version.obj windows.obj \
+               flip.rsp
+       link $(LFLAGS) -out:flip.exe -map:flip.map @flip.rsp
+
+flood.exe: drawing.obj flood.obj flood.res malloc.obj midend.obj misc.obj \
+               printing.obj random.obj version.obj windows.obj flood.rsp
+       link $(LFLAGS) -out:flood.exe -map:flood.map @flood.rsp
+
+galaxies.exe: drawing.obj dsf.obj galaxies.obj galaxies.res malloc.obj \
+               midend.obj misc.obj printing.obj random.obj version.obj \
+               windows.obj galaxies.rsp
+       link $(LFLAGS) -out:galaxies.exe -map:galaxies.map @galaxies.rsp
+
+guess.exe: drawing.obj guess.obj guess.res malloc.obj midend.obj misc.obj \
+               printing.obj random.obj version.obj windows.obj guess.rsp
+       link $(LFLAGS) -out:guess.exe -map:guess.map @guess.rsp
+
+inertia.exe: drawing.obj inertia.obj inertia.res malloc.obj midend.obj \
+               misc.obj printing.obj random.obj version.obj windows.obj \
+               inertia.rsp
+       link $(LFLAGS) -out:inertia.exe -map:inertia.map @inertia.rsp
+
+keen.exe: drawing.obj dsf.obj keen.obj keen.res latin.obj malloc.obj \
+               maxflow.obj midend.obj misc.obj printing.obj random.obj \
+               tree234.obj version.obj windows.obj keen.rsp
+       link $(LFLAGS) -out:keen.exe -map:keen.map @keen.rsp
+
+lightup.exe: combi.obj drawing.obj lightup.obj lightup.res malloc.obj \
+               midend.obj misc.obj printing.obj random.obj version.obj \
+               windows.obj lightup.rsp
+       link $(LFLAGS) -out:lightup.exe -map:lightup.map @lightup.rsp
+
+loopy.exe: drawing.obj dsf.obj grid.obj loopgen.obj loopy.obj loopy.res \
+               malloc.obj midend.obj misc.obj penrose.obj printing.obj \
+               random.obj tree234.obj version.obj windows.obj loopy.rsp
+       link $(LFLAGS) -out:loopy.exe -map:loopy.map @loopy.rsp
+
+magnets.exe: drawing.obj laydomino.obj magnets.obj magnets.res malloc.obj \
+               midend.obj misc.obj printing.obj random.obj version.obj \
+               windows.obj magnets.rsp
+       link $(LFLAGS) -out:magnets.exe -map:magnets.map @magnets.rsp
+
+map.exe: drawing.obj dsf.obj malloc.obj map.obj map.res midend.obj misc.obj \
+               printing.obj random.obj version.obj windows.obj map.rsp
+       link $(LFLAGS) -out:map.exe -map:map.map @map.rsp
+
+mines.exe: drawing.obj malloc.obj midend.obj mines.obj mines.res misc.obj \
+               printing.obj random.obj tree234.obj version.obj windows.obj \
+               mines.rsp
+       link $(LFLAGS) -out:mines.exe -map:mines.map @mines.rsp
+
+netgame.exe: drawing.obj dsf.obj findloop.obj malloc.obj midend.obj misc.obj \
+               net.obj net.res printing.obj random.obj tree234.obj \
+               version.obj windows.obj netgame.rsp
+       link $(LFLAGS) -out:netgame.exe -map:netgame.map @netgame.rsp
+
+netslide.exe: drawing.obj malloc.obj midend.obj misc.obj netslide.obj \
+               netslide.res printing.obj random.obj tree234.obj version.obj \
+               windows.obj netslide.rsp
+       link $(LFLAGS) -out:netslide.exe -map:netslide.map @netslide.rsp
+
+nullgame.exe: drawing.obj malloc.obj midend.obj misc.obj noicon.res \
+               nullgame.obj printing.obj random.obj version.obj windows.obj \
+               nullgame.rsp
+       link $(LFLAGS) -out:nullgame.exe -map:nullgame.map @nullgame.rsp
+
+palisade.exe: divvy.obj drawing.obj dsf.obj malloc.obj midend.obj misc.obj \
+               palisade.obj palisade.res printing.obj random.obj \
+               version.obj windows.obj palisade.rsp
+       link $(LFLAGS) -out:palisade.exe -map:palisade.map @palisade.rsp
+
+pattern.exe: drawing.obj malloc.obj midend.obj misc.obj pattern.obj \
+               pattern.res printing.obj random.obj version.obj windows.obj \
+               pattern.rsp
+       link $(LFLAGS) -out:pattern.exe -map:pattern.map @pattern.rsp
+
+pearl.exe: drawing.obj dsf.obj grid.obj loopgen.obj malloc.obj midend.obj \
+               misc.obj pearl.obj pearl.res penrose.obj printing.obj \
+               random.obj tdq.obj tree234.obj version.obj windows.obj \
+               pearl.rsp
+       link $(LFLAGS) -out:pearl.exe -map:pearl.map @pearl.rsp
+
+pegs.exe: drawing.obj malloc.obj midend.obj misc.obj pegs.obj pegs.res \
+               printing.obj random.obj tree234.obj version.obj windows.obj \
+               pegs.rsp
+       link $(LFLAGS) -out:pegs.exe -map:pegs.map @pegs.rsp
+
+puzzles.exe: blackbo3.obj bridges3.obj combi.obj cube3.obj divvy.obj \
+               dominos3.obj drawing.obj dsf.obj fifteen5.obj filling5.obj \
+               findloop.obj flip3.obj flood3.obj galaxie7.obj grid.obj \
+               guess3.obj inertia3.obj keen5.obj latin.obj laydomino.obj \
+               lightup5.obj list.obj loopgen.obj loopy5.obj magnets5.obj \
+               malloc.obj map5.obj maxflow.obj midend.obj mines5.obj \
+               misc.obj net3.obj netslid3.obj noicon.res palisad3.obj \
+               pattern7.obj pearl5.obj pegs3.obj penrose.obj printing.obj \
+               random.obj range3.obj rect3.obj samegam3.obj signpos5.obj \
+               singles5.obj sixteen3.obj slant5.obj solo5.obj tdq.obj \
+               tents5.obj towers5.obj tracks3.obj tree234.obj twiddle3.obj \
+               undead3.obj unequal5.obj unruly5.obj untangl3.obj \
+               version.obj windows1.obj puzzles.rsp
+       link $(LFLAGS) -out:puzzles.exe -map:puzzles.map @puzzles.rsp
+
+range.exe: drawing.obj dsf.obj malloc.obj midend.obj misc.obj printing.obj \
+               random.obj range.obj range.res version.obj windows.obj \
+               range.rsp
+       link $(LFLAGS) -out:range.exe -map:range.map @range.rsp
+
+rect.exe: drawing.obj malloc.obj midend.obj misc.obj printing.obj random.obj \
+               rect.obj rect.res version.obj windows.obj rect.rsp
+       link $(LFLAGS) -out:rect.exe -map:rect.map @rect.rsp
+
+samegame.exe: drawing.obj malloc.obj midend.obj misc.obj printing.obj \
+               random.obj samegame.obj samegame.res version.obj windows.obj \
+               samegame.rsp
+       link $(LFLAGS) -out:samegame.exe -map:samegame.map @samegame.rsp
+
+signpost.exe: drawing.obj dsf.obj malloc.obj midend.obj misc.obj \
+               printing.obj random.obj signpost.obj signpost.res \
+               version.obj windows.obj signpost.rsp
+       link $(LFLAGS) -out:signpost.exe -map:signpost.map @signpost.rsp
+
+singles.exe: drawing.obj dsf.obj latin.obj malloc.obj maxflow.obj midend.obj \
+               misc.obj printing.obj random.obj singles.obj singles.res \
+               tree234.obj version.obj windows.obj singles.rsp
+       link $(LFLAGS) -out:singles.exe -map:singles.map @singles.rsp
+
+sixteen.exe: drawing.obj malloc.obj midend.obj misc.obj printing.obj \
+               random.obj sixteen.obj sixteen.res version.obj windows.obj \
+               sixteen.rsp
+       link $(LFLAGS) -out:sixteen.exe -map:sixteen.map @sixteen.rsp
+
+slant.exe: drawing.obj dsf.obj findloop.obj malloc.obj midend.obj misc.obj \
+               printing.obj random.obj slant.obj slant.res version.obj \
+               windows.obj slant.rsp
+       link $(LFLAGS) -out:slant.exe -map:slant.map @slant.rsp
+
+solo.exe: divvy.obj drawing.obj dsf.obj malloc.obj midend.obj misc.obj \
+               printing.obj random.obj solo.obj solo.res version.obj \
+               windows.obj solo.rsp
+       link $(LFLAGS) -out:solo.exe -map:solo.map @solo.rsp
+
+tents.exe: drawing.obj dsf.obj malloc.obj maxflow.obj midend.obj misc.obj \
+               printing.obj random.obj tents.obj tents.res version.obj \
+               windows.obj tents.rsp
+       link $(LFLAGS) -out:tents.exe -map:tents.map @tents.rsp
+
+towers.exe: drawing.obj latin.obj malloc.obj maxflow.obj midend.obj misc.obj \
+               printing.obj random.obj towers.obj towers.res tree234.obj \
+               version.obj windows.obj towers.rsp
+       link $(LFLAGS) -out:towers.exe -map:towers.map @towers.rsp
+
+tracks.exe: drawing.obj dsf.obj findloop.obj malloc.obj midend.obj misc.obj \
+               printing.obj random.obj tracks.obj tracks.res version.obj \
+               windows.obj tracks.rsp
+       link $(LFLAGS) -out:tracks.exe -map:tracks.map @tracks.rsp
+
+twiddle.exe: drawing.obj malloc.obj midend.obj misc.obj printing.obj \
+               random.obj twiddle.obj twiddle.res version.obj windows.obj \
+               twiddle.rsp
+       link $(LFLAGS) -out:twiddle.exe -map:twiddle.map @twiddle.rsp
+
+undead.exe: drawing.obj malloc.obj midend.obj misc.obj printing.obj \
+               random.obj undead.obj undead.res version.obj windows.obj \
+               undead.rsp
+       link $(LFLAGS) -out:undead.exe -map:undead.map @undead.rsp
+
+unequal.exe: drawing.obj latin.obj malloc.obj maxflow.obj midend.obj \
+               misc.obj printing.obj random.obj tree234.obj unequal.obj \
+               unequal.res version.obj windows.obj unequal.rsp
+       link $(LFLAGS) -out:unequal.exe -map:unequal.map @unequal.rsp
+
+unruly.exe: drawing.obj malloc.obj midend.obj misc.obj printing.obj \
+               random.obj unruly.obj unruly.res version.obj windows.obj \
+               unruly.rsp
+       link $(LFLAGS) -out:unruly.exe -map:unruly.map @unruly.rsp
+
+untangle.exe: drawing.obj malloc.obj midend.obj misc.obj printing.obj \
+               random.obj tree234.obj untangle.obj untangle.res version.obj \
+               windows.obj untangle.rsp
+       link $(LFLAGS) -out:untangle.exe -map:untangle.map @untangle.rsp
+
+blackbox.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > blackbox.rsp
+       echo blackbox.obj blackbox.res drawing.obj malloc.obj >> blackbox.rsp
+       echo midend.obj misc.obj printing.obj random.obj >> blackbox.rsp
+       echo version.obj windows.obj >> blackbox.rsp
+
+bridges.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > bridges.rsp
+       echo bridges.obj bridges.res drawing.obj dsf.obj >> bridges.rsp
+       echo findloop.obj malloc.obj midend.obj misc.obj >> bridges.rsp
+       echo printing.obj random.obj version.obj windows.obj >> bridges.rsp
+
+cube.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > cube.rsp
+       echo cube.obj cube.res drawing.obj malloc.obj >> cube.rsp
+       echo midend.obj misc.obj printing.obj random.obj >> cube.rsp
+       echo version.obj windows.obj >> cube.rsp
+
+dominosa.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > dominosa.rsp
+       echo dominosa.obj dominosa.res drawing.obj >> dominosa.rsp
+       echo laydomino.obj malloc.obj midend.obj misc.obj >> dominosa.rsp
+       echo printing.obj random.obj version.obj windows.obj >> dominosa.rsp
+
+fifteen.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > fifteen.rsp
+       echo drawing.obj fifteen.obj fifteen.res malloc.obj >> fifteen.rsp
+       echo midend.obj misc.obj printing.obj random.obj >> fifteen.rsp
+       echo version.obj windows.obj >> fifteen.rsp
+
+filling.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > filling.rsp
+       echo drawing.obj dsf.obj filling.obj filling.res >> filling.rsp
+       echo malloc.obj midend.obj misc.obj printing.obj >> filling.rsp
+       echo random.obj version.obj windows.obj >> filling.rsp
+
+flip.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > flip.rsp
+       echo drawing.obj flip.obj flip.res malloc.obj >> flip.rsp
+       echo midend.obj misc.obj printing.obj random.obj >> flip.rsp
+       echo tree234.obj version.obj windows.obj >> flip.rsp
+
+flood.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > flood.rsp
+       echo drawing.obj flood.obj flood.res malloc.obj >> flood.rsp
+       echo midend.obj misc.obj printing.obj random.obj >> flood.rsp
+       echo version.obj windows.obj >> flood.rsp
+
+galaxies.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > galaxies.rsp
+       echo drawing.obj dsf.obj galaxies.obj galaxies.res >> galaxies.rsp
+       echo malloc.obj midend.obj misc.obj printing.obj >> galaxies.rsp
+       echo random.obj version.obj windows.obj >> galaxies.rsp
+
+guess.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > guess.rsp
+       echo drawing.obj guess.obj guess.res malloc.obj >> guess.rsp
+       echo midend.obj misc.obj printing.obj random.obj >> guess.rsp
+       echo version.obj windows.obj >> guess.rsp
+
+inertia.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > inertia.rsp
+       echo drawing.obj inertia.obj inertia.res malloc.obj >> inertia.rsp
+       echo midend.obj misc.obj printing.obj random.obj >> inertia.rsp
+       echo version.obj windows.obj >> inertia.rsp
+
+keen.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > keen.rsp
+       echo drawing.obj dsf.obj keen.obj keen.res latin.obj >> keen.rsp
+       echo malloc.obj maxflow.obj midend.obj misc.obj >> keen.rsp
+       echo printing.obj random.obj tree234.obj version.obj >> keen.rsp
+       echo windows.obj >> keen.rsp
+
+lightup.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > lightup.rsp
+       echo combi.obj drawing.obj lightup.obj lightup.res >> lightup.rsp
+       echo malloc.obj midend.obj misc.obj printing.obj >> lightup.rsp
+       echo random.obj version.obj windows.obj >> lightup.rsp
+
+loopy.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > loopy.rsp
+       echo drawing.obj dsf.obj grid.obj loopgen.obj >> loopy.rsp
+       echo loopy.obj loopy.res malloc.obj midend.obj >> loopy.rsp
+       echo misc.obj penrose.obj printing.obj random.obj >> loopy.rsp
+       echo tree234.obj version.obj windows.obj >> loopy.rsp
+
+magnets.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > magnets.rsp
+       echo drawing.obj laydomino.obj magnets.obj magnets.res >> magnets.rsp
+       echo malloc.obj midend.obj misc.obj printing.obj >> magnets.rsp
+       echo random.obj version.obj windows.obj >> magnets.rsp
+
+map.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > map.rsp
+       echo drawing.obj dsf.obj malloc.obj map.obj map.res >> map.rsp
+       echo midend.obj misc.obj printing.obj random.obj >> map.rsp
+       echo version.obj windows.obj >> map.rsp
+
+mines.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > mines.rsp
+       echo drawing.obj malloc.obj midend.obj mines.obj >> mines.rsp
+       echo mines.res misc.obj printing.obj random.obj >> mines.rsp
+       echo tree234.obj version.obj windows.obj >> mines.rsp
+
+netgame.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > netgame.rsp
+       echo drawing.obj dsf.obj findloop.obj malloc.obj >> netgame.rsp
+       echo midend.obj misc.obj net.obj net.res printing.obj >> netgame.rsp
+       echo random.obj tree234.obj version.obj windows.obj >> netgame.rsp
+
+netslide.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > netslide.rsp
+       echo drawing.obj malloc.obj midend.obj misc.obj >> netslide.rsp
+       echo netslide.obj netslide.res printing.obj random.obj >> netslide.rsp
+       echo tree234.obj version.obj windows.obj >> netslide.rsp
+
+nullgame.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > nullgame.rsp
+       echo drawing.obj malloc.obj midend.obj misc.obj >> nullgame.rsp
+       echo noicon.res nullgame.obj printing.obj random.obj >> nullgame.rsp
+       echo version.obj windows.obj >> nullgame.rsp
+
+palisade.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > palisade.rsp
+       echo divvy.obj drawing.obj dsf.obj malloc.obj >> palisade.rsp
+       echo midend.obj misc.obj palisade.obj palisade.res >> palisade.rsp
+       echo printing.obj random.obj version.obj windows.obj >> palisade.rsp
+
+pattern.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > pattern.rsp
+       echo drawing.obj malloc.obj midend.obj misc.obj >> pattern.rsp
+       echo pattern.obj pattern.res printing.obj random.obj >> pattern.rsp
+       echo version.obj windows.obj >> pattern.rsp
+
+pearl.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > pearl.rsp
+       echo drawing.obj dsf.obj grid.obj loopgen.obj >> pearl.rsp
+       echo malloc.obj midend.obj misc.obj pearl.obj >> pearl.rsp
+       echo pearl.res penrose.obj printing.obj random.obj >> pearl.rsp
+       echo tdq.obj tree234.obj version.obj windows.obj >> pearl.rsp
+
+pegs.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > pegs.rsp
+       echo drawing.obj malloc.obj midend.obj misc.obj >> pegs.rsp
+       echo pegs.obj pegs.res printing.obj random.obj >> pegs.rsp
+       echo tree234.obj version.obj windows.obj >> pegs.rsp
+
+puzzles.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > puzzles.rsp
+       echo blackbo3.obj bridges3.obj combi.obj cube3.obj >> puzzles.rsp
+       echo divvy.obj dominos3.obj drawing.obj dsf.obj >> puzzles.rsp
+       echo fifteen5.obj filling5.obj findloop.obj flip3.obj >> puzzles.rsp
+       echo flood3.obj galaxie7.obj grid.obj guess3.obj >> puzzles.rsp
+       echo inertia3.obj keen5.obj latin.obj laydomino.obj >> puzzles.rsp
+       echo lightup5.obj list.obj loopgen.obj loopy5.obj >> puzzles.rsp
+       echo magnets5.obj malloc.obj map5.obj maxflow.obj >> puzzles.rsp
+       echo midend.obj mines5.obj misc.obj net3.obj >> puzzles.rsp
+       echo netslid3.obj noicon.res palisad3.obj pattern7.obj >> puzzles.rsp
+       echo pearl5.obj pegs3.obj penrose.obj printing.obj >> puzzles.rsp
+       echo random.obj range3.obj rect3.obj samegam3.obj >> puzzles.rsp
+       echo signpos5.obj singles5.obj sixteen3.obj slant5.obj >> puzzles.rsp
+       echo solo5.obj tdq.obj tents5.obj towers5.obj >> puzzles.rsp
+       echo tracks3.obj tree234.obj twiddle3.obj undead3.obj >> puzzles.rsp
+       echo unequal5.obj unruly5.obj untangl3.obj version.obj >> puzzles.rsp
+       echo windows1.obj >> puzzles.rsp
+
+range.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > range.rsp
+       echo drawing.obj dsf.obj malloc.obj midend.obj >> range.rsp
+       echo misc.obj printing.obj random.obj range.obj >> range.rsp
+       echo range.res version.obj windows.obj >> range.rsp
+
+rect.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > rect.rsp
+       echo drawing.obj malloc.obj midend.obj misc.obj >> rect.rsp
+       echo printing.obj random.obj rect.obj rect.res >> rect.rsp
+       echo version.obj windows.obj >> rect.rsp
+
+samegame.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > samegame.rsp
+       echo drawing.obj malloc.obj midend.obj misc.obj >> samegame.rsp
+       echo printing.obj random.obj samegame.obj samegame.res >> samegame.rsp
+       echo version.obj windows.obj >> samegame.rsp
+
+signpost.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > signpost.rsp
+       echo drawing.obj dsf.obj malloc.obj midend.obj >> signpost.rsp
+       echo misc.obj printing.obj random.obj signpost.obj >> signpost.rsp
+       echo signpost.res version.obj windows.obj >> signpost.rsp
+
+singles.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > singles.rsp
+       echo drawing.obj dsf.obj latin.obj malloc.obj >> singles.rsp
+       echo maxflow.obj midend.obj misc.obj printing.obj >> singles.rsp
+       echo random.obj singles.obj singles.res tree234.obj >> singles.rsp
+       echo version.obj windows.obj >> singles.rsp
+
+sixteen.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > sixteen.rsp
+       echo drawing.obj malloc.obj midend.obj misc.obj >> sixteen.rsp
+       echo printing.obj random.obj sixteen.obj sixteen.res >> sixteen.rsp
+       echo version.obj windows.obj >> sixteen.rsp
+
+slant.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > slant.rsp
+       echo drawing.obj dsf.obj findloop.obj malloc.obj >> slant.rsp
+       echo midend.obj misc.obj printing.obj random.obj >> slant.rsp
+       echo slant.obj slant.res version.obj windows.obj >> slant.rsp
+
+solo.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > solo.rsp
+       echo divvy.obj drawing.obj dsf.obj malloc.obj >> solo.rsp
+       echo midend.obj misc.obj printing.obj random.obj >> solo.rsp
+       echo solo.obj solo.res version.obj windows.obj >> solo.rsp
+
+tents.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > tents.rsp
+       echo drawing.obj dsf.obj malloc.obj maxflow.obj >> tents.rsp
+       echo midend.obj misc.obj printing.obj random.obj >> tents.rsp
+       echo tents.obj tents.res version.obj windows.obj >> tents.rsp
+
+towers.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > towers.rsp
+       echo drawing.obj latin.obj malloc.obj maxflow.obj >> towers.rsp
+       echo midend.obj misc.obj printing.obj random.obj >> towers.rsp
+       echo towers.obj towers.res tree234.obj version.obj >> towers.rsp
+       echo windows.obj >> towers.rsp
+
+tracks.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > tracks.rsp
+       echo drawing.obj dsf.obj findloop.obj malloc.obj >> tracks.rsp
+       echo midend.obj misc.obj printing.obj random.obj >> tracks.rsp
+       echo tracks.obj tracks.res version.obj windows.obj >> tracks.rsp
+
+twiddle.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > twiddle.rsp
+       echo drawing.obj malloc.obj midend.obj misc.obj >> twiddle.rsp
+       echo printing.obj random.obj twiddle.obj twiddle.res >> twiddle.rsp
+       echo version.obj windows.obj >> twiddle.rsp
+
+undead.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > undead.rsp
+       echo drawing.obj malloc.obj midend.obj misc.obj >> undead.rsp
+       echo printing.obj random.obj undead.obj undead.res >> undead.rsp
+       echo version.obj windows.obj >> undead.rsp
+
+unequal.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > unequal.rsp
+       echo drawing.obj latin.obj malloc.obj maxflow.obj >> unequal.rsp
+       echo midend.obj misc.obj printing.obj random.obj >> unequal.rsp
+       echo tree234.obj unequal.obj unequal.res version.obj >> unequal.rsp
+       echo windows.obj >> unequal.rsp
+
+unruly.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > unruly.rsp
+       echo drawing.obj malloc.obj midend.obj misc.obj >> unruly.rsp
+       echo printing.obj random.obj unruly.obj unruly.res >> unruly.rsp
+       echo version.obj windows.obj >> unruly.rsp
+
+untangle.rsp: $(MAKEFILE)
+       echo $(BASELIBS) > untangle.rsp
+       echo drawing.obj malloc.obj midend.obj misc.obj >> untangle.rsp
+       echo printing.obj random.obj tree234.obj untangle.obj >> untangle.rsp
+       echo untangle.res version.obj windows.obj >> untangle.rsp
+
+blackbox.obj: .\blackbox.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\blackbox.c /Foblackbox.obj
+blackbox-icon.obj: icons\blackbox-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\blackbox-icon.c /Foblackbox-icon.obj
+blackbox.res: icons\blackbox.rc .\puzzles.rc2 icons\blackbox.ico \
+               .\resource.h
+       rc $(FWHACK) $(RCFL) -r -foblackbox.res icons\blackbox.rc
+blackbo3.obj: .\blackbox.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\blackbox.c /Foblackbo3.obj
+bridges.obj: .\bridges.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\bridges.c /Fobridges.obj
+bridges-icon.obj: icons\bridges-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\bridges-icon.c /Fobridges-icon.obj
+bridges.res: icons\bridges.rc .\puzzles.rc2 icons\bridges.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -fobridges.res icons\bridges.rc
+bridges3.obj: .\bridges.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\bridges.c /Fobridges3.obj
+combi.obj: .\combi.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\combi.c /Focombi.obj
+cube.obj: .\cube.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\cube.c /Focube.obj
+cube-icon.obj: icons\cube-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\cube-icon.c /Focube-icon.obj
+cube.res: icons\cube.rc .\puzzles.rc2 icons\cube.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -focube.res icons\cube.rc
+cube3.obj: .\cube.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\cube.c /Focube3.obj
+divvy.obj: .\divvy.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\divvy.c /Fodivvy.obj
+dominosa.obj: .\dominosa.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\dominosa.c /Fodominosa.obj
+dominosa-icon.obj: icons\dominosa-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\dominosa-icon.c /Fodominosa-icon.obj
+dominosa.res: icons\dominosa.rc .\puzzles.rc2 icons\dominosa.ico \
+               .\resource.h
+       rc $(FWHACK) $(RCFL) -r -fodominosa.res icons\dominosa.rc
+dominos3.obj: .\dominosa.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\dominosa.c /Fodominos3.obj
+drawing.obj: .\drawing.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\drawing.c /Fodrawing.obj
+dsf.obj: .\dsf.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\dsf.c /Fodsf.obj
+fifteen.obj: .\fifteen.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\fifteen.c /Fofifteen.obj
+fifteen-icon.obj: icons\fifteen-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\fifteen-icon.c /Fofifteen-icon.obj
+fifteen.res: icons\fifteen.rc .\puzzles.rc2 icons\fifteen.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -fofifteen.res icons\fifteen.rc
+fifteen5.obj: .\fifteen.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\fifteen.c /Fofifteen5.obj
+fifteen2.obj: .\fifteen.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\fifteen.c /Fofifteen2.obj
+filling.obj: .\filling.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\filling.c /Fofilling.obj
+filling-icon.obj: icons\filling-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\filling-icon.c /Fofilling-icon.obj
+filling.res: icons\filling.rc .\puzzles.rc2 icons\filling.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -fofilling.res icons\filling.rc
+filling5.obj: .\filling.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\filling.c /Fofilling5.obj
+filling2.obj: .\filling.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\filling.c /Fofilling2.obj
+findloop.obj: .\findloop.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\findloop.c /Fofindloop.obj
+flip.obj: .\flip.c .\puzzles.h .\tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\flip.c /Foflip.obj
+flip-icon.obj: icons\flip-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\flip-icon.c /Foflip-icon.obj
+flip.res: icons\flip.rc .\puzzles.rc2 icons\flip.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -foflip.res icons\flip.rc
+flip3.obj: .\flip.c .\puzzles.h .\tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\flip.c /Foflip3.obj
+flood.obj: .\flood.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\flood.c /Foflood.obj
+flood-icon.obj: icons\flood-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\flood-icon.c /Foflood-icon.obj
+flood.res: icons\flood.rc .\puzzles.rc2 icons\flood.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -foflood.res icons\flood.rc
+flood3.obj: .\flood.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\flood.c /Foflood3.obj
+galaxies.obj: .\galaxies.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\galaxies.c /Fogalaxies.obj
+galaxies-icon.obj: icons\galaxies-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\galaxies-icon.c /Fogalaxies-icon.obj
+galaxies.res: icons\galaxies.rc .\puzzles.rc2 icons\galaxies.ico \
+               .\resource.h
+       rc $(FWHACK) $(RCFL) -r -fogalaxies.res icons\galaxies.rc
+galaxie7.obj: .\galaxies.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\galaxies.c /Fogalaxie7.obj
+galaxie4.obj: .\galaxies.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_PICTURE_GENERATOR /c .\galaxies.c /Fogalaxie4.obj
+galaxie2.obj: .\galaxies.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\galaxies.c /Fogalaxie2.obj
+grid.obj: .\grid.c .\puzzles.h .\tree234.h .\grid.h .\penrose.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\grid.c /Fogrid.obj
+gtk.obj: .\gtk.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\gtk.c /Fogtk.obj
+guess.obj: .\guess.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\guess.c /Foguess.obj
+guess-icon.obj: icons\guess-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\guess-icon.c /Foguess-icon.obj
+guess.res: icons\guess.rc .\puzzles.rc2 icons\guess.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -foguess.res icons\guess.rc
+guess3.obj: .\guess.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\guess.c /Foguess3.obj
+inertia.obj: .\inertia.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\inertia.c /Foinertia.obj
+inertia-icon.obj: icons\inertia-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\inertia-icon.c /Foinertia-icon.obj
+inertia.res: icons\inertia.rc .\puzzles.rc2 icons\inertia.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -foinertia.res icons\inertia.rc
+inertia3.obj: .\inertia.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\inertia.c /Foinertia3.obj
+keen.obj: .\keen.c .\puzzles.h .\latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\keen.c /Fokeen.obj
+keen-icon.obj: icons\keen-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\keen-icon.c /Fokeen-icon.obj
+keen.res: icons\keen.rc .\puzzles.rc2 icons\keen.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -fokeen.res icons\keen.rc
+keen5.obj: .\keen.c .\puzzles.h .\latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\keen.c /Fokeen5.obj
+keen2.obj: .\keen.c .\puzzles.h .\latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\keen.c /Fokeen2.obj
+latin.obj: .\latin.c .\puzzles.h .\tree234.h .\maxflow.h .\latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\latin.c /Folatin.obj
+latin8.obj: .\latin.c .\puzzles.h .\tree234.h .\maxflow.h .\latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_LATIN_TEST /c .\latin.c /Folatin8.obj
+latin6.obj: .\latin.c .\puzzles.h .\tree234.h .\maxflow.h .\latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\latin.c /Folatin6.obj
+laydomino.obj: .\laydomino.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\laydomino.c /Folaydomino.obj
+lightup.obj: .\lightup.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\lightup.c /Folightup.obj
+lightup-icon.obj: icons\lightup-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\lightup-icon.c /Folightup-icon.obj
+lightup.res: icons\lightup.rc .\puzzles.rc2 icons\lightup.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -folightup.res icons\lightup.rc
+lightup5.obj: .\lightup.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\lightup.c /Folightup5.obj
+lightup2.obj: .\lightup.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\lightup.c /Folightup2.obj
+list.obj: .\list.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\list.c /Folist.obj
+loopgen.obj: .\loopgen.c .\puzzles.h .\tree234.h .\grid.h .\loopgen.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\loopgen.c /Foloopgen.obj
+loopy.obj: .\loopy.c .\puzzles.h .\tree234.h .\grid.h .\loopgen.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\loopy.c /Foloopy.obj
+loopy-icon.obj: icons\loopy-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\loopy-icon.c /Foloopy-icon.obj
+loopy.res: icons\loopy.rc .\puzzles.rc2 icons\loopy.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -foloopy.res icons\loopy.rc
+loopy5.obj: .\loopy.c .\puzzles.h .\tree234.h .\grid.h .\loopgen.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\loopy.c /Foloopy5.obj
+loopy2.obj: .\loopy.c .\puzzles.h .\tree234.h .\grid.h .\loopgen.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\loopy.c /Foloopy2.obj
+magnets.obj: .\magnets.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\magnets.c /Fomagnets.obj
+magnets-icon.obj: icons\magnets-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\magnets-icon.c /Fomagnets-icon.obj
+magnets.res: icons\magnets.rc .\puzzles.rc2 icons\magnets.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -fomagnets.res icons\magnets.rc
+magnets5.obj: .\magnets.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\magnets.c /Fomagnets5.obj
+magnets2.obj: .\magnets.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\magnets.c /Fomagnets2.obj
+malloc.obj: .\malloc.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\malloc.c /Fomalloc.obj
+map.obj: .\map.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\map.c /Fomap.obj
+map-icon.obj: icons\map-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\map-icon.c /Fomap-icon.obj
+map.res: icons\map.rc .\puzzles.rc2 icons\map.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -fomap.res icons\map.rc
+map5.obj: .\map.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\map.c /Fomap5.obj
+map2.obj: .\map.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\map.c /Fomap2.obj
+maxflow.obj: .\maxflow.c .\maxflow.h .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\maxflow.c /Fomaxflow.obj
+midend.obj: .\midend.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\midend.c /Fomidend.obj
+mines.obj: .\mines.c .\tree234.h .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\mines.c /Fomines.obj
+mines-icon.obj: icons\mines-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\mines-icon.c /Fomines-icon.obj
+mines.res: icons\mines.rc .\puzzles.rc2 icons\mines.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -fomines.res icons\mines.rc
+mines5.obj: .\mines.c .\tree234.h .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\mines.c /Fomines5.obj
+mines2.obj: .\mines.c .\tree234.h .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_OBFUSCATOR /c .\mines.c /Fomines2.obj
+misc.obj: .\misc.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\misc.c /Fomisc.obj
+net.obj: .\net.c .\puzzles.h .\tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\net.c /Fonet.obj
+net-icon.obj: icons\net-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\net-icon.c /Fonet-icon.obj
+net.res: icons\net.rc .\puzzles.rc2 icons\net.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -fonet.res icons\net.rc
+net3.obj: .\net.c .\puzzles.h .\tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\net.c /Fonet3.obj
+netslide.obj: .\netslide.c .\puzzles.h .\tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\netslide.c /Fonetslide.obj
+netslide-icon.obj: icons\netslide-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\netslide-icon.c /Fonetslide-icon.obj
+netslide.res: icons\netslide.rc .\puzzles.rc2 icons\netslide.ico \
+               .\resource.h
+       rc $(FWHACK) $(RCFL) -r -fonetslide.res icons\netslide.rc
+netslid3.obj: .\netslide.c .\puzzles.h .\tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\netslide.c /Fonetslid3.obj
+no-icon.obj: .\no-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\no-icon.c /Fono-icon.obj
+noicon.res: .\noicon.rc .\puzzles.rc2 .\resource.h
+       rc $(FWHACK) $(RCFL) -r -fonoicon.res .\noicon.rc
+nullfe.obj: .\nullfe.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\nullfe.c /Fonullfe.obj
+nullgame.obj: .\nullgame.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\nullgame.c /Fonullgame.obj
+obfusc.obj: .\obfusc.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\obfusc.c /Foobfusc.obj
+osx.obj: .\osx.m .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\osx.m /Foosx.obj
+palisade.obj: .\palisade.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\palisade.c /Fopalisade.obj
+palisade-icon.obj: icons\palisade-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\palisade-icon.c /Fopalisade-icon.obj
+palisade.res: icons\palisade.rc .\puzzles.rc2 icons\palisade.ico \
+               .\resource.h
+       rc $(FWHACK) $(RCFL) -r -fopalisade.res icons\palisade.rc
+palisad3.obj: .\palisade.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\palisade.c /Fopalisad3.obj
+pattern.obj: .\pattern.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\pattern.c /Fopattern.obj
+pattern-icon.obj: icons\pattern-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\pattern-icon.c /Fopattern-icon.obj
+pattern.res: icons\pattern.rc .\puzzles.rc2 icons\pattern.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -fopattern.res icons\pattern.rc
+pattern7.obj: .\pattern.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\pattern.c /Fopattern7.obj
+pattern4.obj: .\pattern.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_PICTURE_GENERATOR /c .\pattern.c /Fopattern4.obj
+pattern2.obj: .\pattern.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\pattern.c /Fopattern2.obj
+pearl.obj: .\pearl.c .\puzzles.h .\grid.h .\loopgen.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\pearl.c /Fopearl.obj
+pearl-icon.obj: icons\pearl-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\pearl-icon.c /Fopearl-icon.obj
+pearl.res: icons\pearl.rc .\puzzles.rc2 icons\pearl.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -fopearl.res icons\pearl.rc
+pearl5.obj: .\pearl.c .\puzzles.h .\grid.h .\loopgen.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\pearl.c /Fopearl5.obj
+pearl2.obj: .\pearl.c .\puzzles.h .\grid.h .\loopgen.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\pearl.c /Fopearl2.obj
+pegs.obj: .\pegs.c .\puzzles.h .\tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\pegs.c /Fopegs.obj
+pegs-icon.obj: icons\pegs-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\pegs-icon.c /Fopegs-icon.obj
+pegs.res: icons\pegs.rc .\puzzles.rc2 icons\pegs.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -fopegs.res icons\pegs.rc
+pegs3.obj: .\pegs.c .\puzzles.h .\tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\pegs.c /Fopegs3.obj
+penrose.obj: .\penrose.c .\puzzles.h .\penrose.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\penrose.c /Fopenrose.obj
+printing.obj: .\printing.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\printing.c /Foprinting.obj
+ps.obj: .\ps.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\ps.c /Fops.obj
+random.obj: .\random.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\random.c /Forandom.obj
+range.obj: .\range.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\range.c /Forange.obj
+range-icon.obj: icons\range-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\range-icon.c /Forange-icon.obj
+range.res: icons\range.rc .\puzzles.rc2 icons\range.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -forange.res icons\range.rc
+range3.obj: .\range.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\range.c /Forange3.obj
+rect.obj: .\rect.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\rect.c /Forect.obj
+rect-icon.obj: icons\rect-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\rect-icon.c /Forect-icon.obj
+rect.res: icons\rect.rc .\puzzles.rc2 icons\rect.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -forect.res icons\rect.rc
+rect3.obj: .\rect.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\rect.c /Forect3.obj
+samegame.obj: .\samegame.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\samegame.c /Fosamegame.obj
+samegame-icon.obj: icons\samegame-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\samegame-icon.c /Fosamegame-icon.obj
+samegame.res: icons\samegame.rc .\puzzles.rc2 icons\samegame.ico \
+               .\resource.h
+       rc $(FWHACK) $(RCFL) -r -fosamegame.res icons\samegame.rc
+samegam3.obj: .\samegame.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\samegame.c /Fosamegam3.obj
+signpost.obj: .\signpost.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\signpost.c /Fosignpost.obj
+signpost-icon.obj: icons\signpost-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\signpost-icon.c /Fosignpost-icon.obj
+signpost.res: icons\signpost.rc .\puzzles.rc2 icons\signpost.ico \
+               .\resource.h
+       rc $(FWHACK) $(RCFL) -r -fosignpost.res icons\signpost.rc
+signpos5.obj: .\signpost.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\signpost.c /Fosignpos5.obj
+signpos2.obj: .\signpost.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\signpost.c /Fosignpos2.obj
+singles.obj: .\singles.c .\puzzles.h .\latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\singles.c /Fosingles.obj
+singles-icon.obj: icons\singles-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\singles-icon.c /Fosingles-icon.obj
+singles.res: icons\singles.rc .\puzzles.rc2 icons\singles.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -fosingles.res icons\singles.rc
+singles5.obj: .\singles.c .\puzzles.h .\latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\singles.c /Fosingles5.obj
+singles3.obj: .\singles.c .\puzzles.h .\latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\singles.c /Fosingles3.obj
+sixteen.obj: .\sixteen.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\sixteen.c /Fosixteen.obj
+sixteen-icon.obj: icons\sixteen-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\sixteen-icon.c /Fosixteen-icon.obj
+sixteen.res: icons\sixteen.rc .\puzzles.rc2 icons\sixteen.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -fosixteen.res icons\sixteen.rc
+sixteen3.obj: .\sixteen.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\sixteen.c /Fosixteen3.obj
+slant.obj: .\slant.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\slant.c /Foslant.obj
+slant-icon.obj: icons\slant-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\slant-icon.c /Foslant-icon.obj
+slant.res: icons\slant.rc .\puzzles.rc2 icons\slant.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -foslant.res icons\slant.rc
+slant5.obj: .\slant.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\slant.c /Foslant5.obj
+slant2.obj: .\slant.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\slant.c /Foslant2.obj
+solo.obj: .\solo.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\solo.c /Fosolo.obj
+solo-icon.obj: icons\solo-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\solo-icon.c /Fosolo-icon.obj
+solo.res: icons\solo.rc .\puzzles.rc2 icons\solo.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -fosolo.res icons\solo.rc
+solo5.obj: .\solo.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\solo.c /Fosolo5.obj
+solo2.obj: .\solo.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\solo.c /Fosolo2.obj
+tdq.obj: .\tdq.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\tdq.c /Fotdq.obj
+tents.obj: .\tents.c .\puzzles.h .\maxflow.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\tents.c /Fotents.obj
+tents-icon.obj: icons\tents-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\tents-icon.c /Fotents-icon.obj
+tents.res: icons\tents.rc .\puzzles.rc2 icons\tents.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -fotents.res icons\tents.rc
+tents5.obj: .\tents.c .\puzzles.h .\maxflow.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\tents.c /Fotents5.obj
+tents3.obj: .\tents.c .\puzzles.h .\maxflow.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\tents.c /Fotents3.obj
+towers.obj: .\towers.c .\puzzles.h .\latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\towers.c /Fotowers.obj
+towers-icon.obj: icons\towers-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\towers-icon.c /Fotowers-icon.obj
+towers.res: icons\towers.rc .\puzzles.rc2 icons\towers.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -fotowers.res icons\towers.rc
+towers5.obj: .\towers.c .\puzzles.h .\latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\towers.c /Fotowers5.obj
+towers2.obj: .\towers.c .\puzzles.h .\latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\towers.c /Fotowers2.obj
+tracks.obj: .\tracks.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\tracks.c /Fotracks.obj
+tracks-icon.obj: icons\tracks-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\tracks-icon.c /Fotracks-icon.obj
+tracks.res: icons\tracks.rc .\puzzles.rc2 icons\tracks.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -fotracks.res icons\tracks.rc
+tracks3.obj: .\tracks.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\tracks.c /Fotracks3.obj
+tree234.obj: .\tree234.c .\tree234.h .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\tree234.c /Fotree234.obj
+twiddle.obj: .\twiddle.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\twiddle.c /Fotwiddle.obj
+twiddle-icon.obj: icons\twiddle-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\twiddle-icon.c /Fotwiddle-icon.obj
+twiddle.res: icons\twiddle.rc .\puzzles.rc2 icons\twiddle.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -fotwiddle.res icons\twiddle.rc
+twiddle3.obj: .\twiddle.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\twiddle.c /Fotwiddle3.obj
+undead.obj: .\undead.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\undead.c /Foundead.obj
+undead-icon.obj: icons\undead-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\undead-icon.c /Foundead-icon.obj
+undead.res: icons\undead.rc .\puzzles.rc2 icons\undead.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -foundead.res icons\undead.rc
+undead3.obj: .\undead.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\undead.c /Foundead3.obj
+unequal.obj: .\unequal.c .\puzzles.h .\latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\unequal.c /Founequal.obj
+unequal-icon.obj: icons\unequal-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\unequal-icon.c /Founequal-icon.obj
+unequal.res: icons\unequal.rc .\puzzles.rc2 icons\unequal.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -founequal.res icons\unequal.rc
+unequal5.obj: .\unequal.c .\puzzles.h .\latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\unequal.c /Founequal5.obj
+unequal2.obj: .\unequal.c .\puzzles.h .\latin.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\unequal.c /Founequal2.obj
+unruly.obj: .\unruly.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\unruly.c /Founruly.obj
+unruly-icon.obj: icons\unruly-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\unruly-icon.c /Founruly-icon.obj
+unruly.res: icons\unruly.rc .\puzzles.rc2 icons\unruly.ico .\resource.h
+       rc $(FWHACK) $(RCFL) -r -founruly.res icons\unruly.rc
+unruly5.obj: .\unruly.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\unruly.c /Founruly5.obj
+unruly2.obj: .\unruly.c .\puzzles.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DSTANDALONE_SOLVER /c .\unruly.c /Founruly2.obj
+untangle.obj: .\untangle.c .\puzzles.h .\tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\untangle.c /Fountangle.obj
+untangle-icon.obj: icons\untangle-icon.c
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c icons\untangle-icon.c /Fountangle-icon.obj
+untangle.res: icons\untangle.rc .\puzzles.rc2 icons\untangle.ico \
+               .\resource.h
+       rc $(FWHACK) $(RCFL) -r -fountangle.res icons\untangle.rc
+untangl3.obj: .\untangle.c .\puzzles.h .\tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\untangle.c /Fountangl3.obj
+version.obj: .\version.c .\version.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\version.c /Foversion.obj
+windows.obj: .\windows.c .\puzzles.h .\resource.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /c .\windows.c /Fowindows.obj
+windows1.obj: .\windows.c .\puzzles.h .\resource.h
+       $(CC) $(COMPAT) $(FWHACK) $(CFLAGS) $(XFLAGS) /DCOMBINED /c .\windows.c /Fowindows1.obj
+
+
+clean: tidy
+       -del *.exe
+
+tidy:
+       -del *.obj
+       -del *.res
+       -del *.pch
+       -del *.aps
+       -del *.ilk
+       -del *.pdb
+       -del *.rsp
+       -del *.dsp
+       -del *.dsw
+       -del *.ncb
+       -del *.opt
+       -del *.plg
+       -del *.map
+       -del *.idb
+       -del debug.log
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..890db56
--- /dev/null
+++ b/README
@@ -0,0 +1,54 @@
+This is the README accompanying the source code to Simon Tatham's
+puzzle collection. The collection's web site is at
+<http://www.chiark.greenend.org.uk/~sgtatham/puzzles/>.
+
+If you've obtained the source code by downloading a .tar.gz archive
+from the Puzzles web site, you should find several Makefiles in the
+source code. However, if you've checked the source code out from the
+Puzzles git repository, you won't find the Makefiles: they're
+automatically generated by `mkfiles.pl', so run that to create them.
+
+The Makefiles include:
+
+ - `Makefile.am', together with the static `configure.ac', is intended
+   as input to automake. Run `mkauto.sh' to turn these into a
+   configure script and Makefile.in, after which you can then run
+   `./configure' to create an actual Unix Makefile.
+
+ - `Makefile.vc' should work under MS Visual C++ on Windows. Run
+   'nmake /f Makefile.vc' in a Visual Studio command prompt.
+
+ - `Makefile.cyg' should work under Cygwin / MinGW. With appropriate
+   tweaks and setting of TOOLPATH, it should work for both compiling
+   on Windows and cross-compiling on Unix.
+
+ - `Makefile.osx' should work under Mac OS X, provided the Xcode
+   tools are installed. It builds a single monolithic OS X
+   application capable of running any of the puzzles, or even more
+   than one of them at a time.
+
+ - `Makefile.wce' should work under MS eMbedded Visual C++ on
+   Windows and the Pocket PC SDK; it builds Pocket PC binaries.
+
+Many of these Makefiles build a program called `nullgame' in
+addition to the actual game binaries. This program doesn't do
+anything; it's just a template for people to start from when adding
+a new game to the collection, and it's compiled every time to ensure
+that it _does_ compile and link successfully (because otherwise it
+wouldn't be much use as a template). Once it's built, you can run it
+if you really want to (but it's very boring), and then you should
+ignore it.
+
+DO NOT EDIT THE MAKEFILES DIRECTLY, if you plan to send any changes
+back to the maintainer. The makefiles are generated automatically by
+the Perl script `mkfiles.pl' from the file `Recipe' and the various
+.R files. If you need to change the makefiles as part of a patch,
+you should change Recipe, *.R, and/or mkfiles.pl.
+
+The manual is provided in Windows Help format for the Windows build;
+in text format for anyone who needs it; and in HTML for the Mac OS X
+application and for the web site. It is generated from a Halibut
+source file (puzzles.but), which is the preferred form for
+modification. To generate the manual in other formats, rebuild it,
+or learn about Halibut, visit the Halibut website at
+<http://www.chiark.greenend.org.uk/~sgtatham/halibut/>.
diff --git a/Recipe b/Recipe
new file mode 100644 (file)
index 0000000..ba8317f
--- /dev/null
+++ b/Recipe
@@ -0,0 +1,157 @@
+# -*- makefile -*-
+# 
+# This file describes which puzzle binaries are made up from which
+# object and resource files. It is processed into the various
+# Makefiles by means of a Perl script. Makefile changes should
+# really be made by editing this file and/or the Perl script, not
+# by editing the actual Makefiles.
+
+!name puzzles
+
+!makefile gtk Makefile.gtk
+!makefile am Makefile.am
+!makefile vc Makefile.vc
+!makefile wce Makefile.wce
+!makefile cygwin Makefile.cyg
+!makefile osx Makefile.osx
+!makefile gnustep Makefile.gnustep
+!makefile nestedvm Makefile.nestedvm
+!makefile emcc Makefile.emcc
+
+!srcdir icons/
+
+WINDOWS_COMMON = printing
+         + user32.lib gdi32.lib comctl32.lib comdlg32.lib winspool.lib
+WINDOWS  = windows WINDOWS_COMMON
+COMMON   = midend drawing misc malloc random version
+GTK      = gtk printing ps
+# Objects needed for auxiliary command-line programs.
+STANDALONE = nullfe random misc malloc
+
+ALL      = list
+
+# First half of list.c.
+!begin >list.c
+/*
+ * list.c: List of pointers to puzzle structures, for monolithic
+ * platforms.
+ *
+ * This file is automatically generated by mkfiles.pl. Do not edit
+ * it directly, or the changes will be lost next time mkfiles.pl runs.
+ * Instead, edit Recipe and/or its *.R subfiles.
+ */
+#include "puzzles.h"
+#define GAMELIST(A) \
+!end
+
+# Now each .R file adds part of the macro definition of GAMELIST to list.c.
+!include *.R
+
+# Then we finish up list.c as follows:
+!begin >list.c
+
+#define DECL(x) extern const game x;
+#define REF(x) &x,
+GAMELIST(DECL)
+const game *gamelist[] = { GAMELIST(REF) };
+const int gamecount = lenof(gamelist);
+!end
+
+# Unix standalone application for special-purpose obfuscation.
+obfusc : [U] obfusc STANDALONE
+
+puzzles  : [G] windows[COMBINED] WINDOWS_COMMON COMMON ALL noicon.res
+
+# Mac OS X unified application containing all the puzzles.
+Puzzles  : [MX] osx osx.icns osx-info.plist COMMON ALL
+# For OS X, we must create the online help and include it in the
+# application bundle.) Also we add -DCOMBINED to the compiler flags
+# so as to inform the code that we're building a single binary for
+# all the puzzles. Then I've also got some code in here to build a
+# distributable .dmg disk image.
+!begin osx
+Puzzles_extra = Puzzles.app/Contents/Resources/Help/index.html
+Puzzles.app/Contents/Resources/Help/index.html: \
+       Puzzles.app/Contents/Resources/Help osx-help.but puzzles.but
+       cd Puzzles.app/Contents/Resources/Help; \
+               halibut --html ../../../../osx-help.but ../../../../puzzles.but
+Puzzles.app/Contents/Resources/Help: Puzzles.app/Contents/Resources
+       mkdir -p Puzzles.app/Contents/Resources/Help
+
+release: Puzzles.dmg
+Puzzles.dmg: Puzzles
+       rm -f raw.dmg
+       hdiutil create -megabytes 5 -layout NONE raw.dmg
+       hdid -nomount raw.dmg > devicename
+       newfs_hfs -v "Simon Tatham's Puzzle Collection" `cat devicename`
+       hdiutil eject `cat devicename`
+       hdid raw.dmg | cut -f1 -d' ' > devicename
+       cp -R Puzzles.app /Volumes/"Simon Tatham's Puzzle Collection"
+       hdiutil eject `cat devicename`
+       rm -f Puzzles.dmg
+       hdiutil convert -format UDCO raw.dmg -o Puzzles.dmg
+       rm -f raw.dmg devicename
+!end
+
+!begin am
+bin_PROGRAMS = $(GAMES)
+!end
+!begin am_begin
+GAMES =
+!end
+
+# make install for Unix.
+!begin gtk
+install:
+       for i in $(GAMES); do \
+               $(INSTALL_PROGRAM) -m 755 $(BINPREFIX)$$i $(DESTDIR)$(gamesdir)/$(BINPREFIX)$$i \
+               || exit 1; \
+       done
+!end
+!begin nestedvm
+.PRECIOUS: %.class
+%.class: %.mips
+       java -cp $(NESTEDVM)/build:$(NESTEDVM)/upstream/build/classgen/build \
+               org.ibex.nestedvm.Compiler -outformat class -d . \
+               PuzzleEngine $<
+               mv PuzzleEngine.class $@
+
+org:
+       mkdir -p org/ibex/nestedvm/util
+       cp $(NESTEDVM)/build/org/ibex/nestedvm/Registers.class org/ibex/nestedvm
+       cp $(NESTEDVM)/build/org/ibex/nestedvm/UsermodeConstants.class org/ibex/nestedvm
+       cp $(NESTEDVM)/build/org/ibex/nestedvm/Runtime*.class org/ibex/nestedvm
+       cp $(NESTEDVM)/build/org/ibex/nestedvm/util/Platform*.class org/ibex/nestedvm/util
+       cp $(NESTEDVM)/build/org/ibex/nestedvm/util/Seekable*.class org/ibex/nestedvm/util
+       echo "Main-Class: PuzzleApplet" >applet.manifest
+
+PuzzleApplet.class: PuzzleApplet.java org
+       javac -source 1.3 -target 1.3 PuzzleApplet.java
+
+%.jar: %.class PuzzleApplet.class org
+       mv $< PuzzleEngine.class
+       jar cfm $@ applet.manifest PuzzleEngine.class PuzzleApplet*.class org
+       echo '<applet archive="'$@'" code="PuzzleApplet" width="700" height="500"></applet>' >$*.html
+       mv PuzzleEngine.class $<
+!end
+
+# A benchmarking and testing target for the GTK puzzles.
+!begin gtk
+test: benchmark.html benchmark.txt
+
+benchmark.html: benchmark.txt benchmark.pl
+       ./benchmark.pl benchmark.txt > $@
+
+benchmark.txt: benchmark.sh $(GAMES)
+       ./benchmark.sh > $@
+
+!end
+!begin am
+test: benchmark.html benchmark.txt
+
+benchmark.html: benchmark.txt benchmark.pl
+       ./benchmark.pl benchmark.txt > $@
+
+benchmark.txt: benchmark.sh $(GAMES)
+       ./benchmark.sh > $@
+!end
diff --git a/aclocal.m4 b/aclocal.m4
new file mode 100644 (file)
index 0000000..9d34d2f
--- /dev/null
@@ -0,0 +1,1832 @@
+# generated automatically by aclocal 1.15 -*- Autoconf -*-
+
+# Copyright (C) 1996-2014 Free Software Foundation, Inc.
+
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], [])m4_defun([AC_CONFIG_MACRO_DIRS], [_AM_CONFIG_MACRO_DIRS($@)])])
+m4_ifndef([AC_AUTOCONF_VERSION],
+  [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl
+m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.69],,
+[m4_warning([this file was generated for autoconf 2.69.
+You have another version of autoconf.  It may work, but is not guaranteed to.
+If you have problems, you may need to regenerate the build system entirely.
+To do so, use the procedure documented by the package, typically 'autoreconf'.])])
+
+# Configure paths for GTK+
+# Owen Taylor     1997-2001
+
+dnl AM_PATH_GTK_2_0([MINIMUM-VERSION, [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND [, MODULES]]]])
+dnl Test for GTK+, and define GTK_CFLAGS and GTK_LIBS, if gthread is specified in MODULES, 
+dnl pass to pkg-config
+dnl
+AC_DEFUN([AM_PATH_GTK_2_0],
+[dnl 
+dnl Get the cflags and libraries from pkg-config
+dnl
+AC_ARG_ENABLE(gtktest, [  --disable-gtktest       do not try to compile and run a test GTK+ program],
+                   , enable_gtktest=yes)
+
+  pkg_config_args=gtk+-2.0
+  for module in . $4
+  do
+      case "$module" in
+         gthread) 
+             pkg_config_args="$pkg_config_args gthread-2.0"
+         ;;
+      esac
+  done
+
+  no_gtk=""
+
+  AC_REQUIRE([PKG_PROG_PKG_CONFIG])
+  PKG_PROG_PKG_CONFIG([0.7])
+
+  min_gtk_version=ifelse([$1], ,2.0.0,$1)
+  AC_MSG_CHECKING(for GTK+ - version >= $min_gtk_version)
+
+  if test x$PKG_CONFIG != xno ; then
+    ## don't try to run the test against uninstalled libtool libs
+    if $PKG_CONFIG --uninstalled $pkg_config_args; then
+         echo "Will use uninstalled version of GTK+ found in PKG_CONFIG_PATH"
+         enable_gtktest=no
+    fi
+
+    if $PKG_CONFIG --atleast-version $min_gtk_version $pkg_config_args; then
+         :
+    else
+         no_gtk=yes
+    fi
+  fi
+
+  if test x"$no_gtk" = x ; then
+    GTK_CFLAGS=`$PKG_CONFIG $pkg_config_args --cflags`
+    GTK_LIBS=`$PKG_CONFIG $pkg_config_args --libs`
+    gtk_config_major_version=`$PKG_CONFIG --modversion gtk+-2.0 | \
+           sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\1/'`
+    gtk_config_minor_version=`$PKG_CONFIG --modversion gtk+-2.0 | \
+           sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\2/'`
+    gtk_config_micro_version=`$PKG_CONFIG --modversion gtk+-2.0 | \
+           sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\3/'`
+    if test "x$enable_gtktest" = "xyes" ; then
+      ac_save_CFLAGS="$CFLAGS"
+      ac_save_LIBS="$LIBS"
+      CFLAGS="$CFLAGS $GTK_CFLAGS"
+      LIBS="$GTK_LIBS $LIBS"
+dnl
+dnl Now check if the installed GTK+ is sufficiently new. (Also sanity
+dnl checks the results of pkg-config to some extent)
+dnl
+      rm -f conf.gtktest
+      AC_TRY_RUN([
+#include <gtk/gtk.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+int 
+main ()
+{
+  int major, minor, micro;
+  char *tmp_version;
+
+  fclose (fopen ("conf.gtktest", "w"));
+
+  /* HP/UX 9 (%@#!) writes to sscanf strings */
+  tmp_version = g_strdup("$min_gtk_version");
+  if (sscanf(tmp_version, "%d.%d.%d", &major, &minor, &micro) != 3) {
+     printf("%s, bad version string\n", "$min_gtk_version");
+     exit(1);
+   }
+
+  if ((gtk_major_version != $gtk_config_major_version) ||
+      (gtk_minor_version != $gtk_config_minor_version) ||
+      (gtk_micro_version != $gtk_config_micro_version))
+    {
+      printf("\n*** 'pkg-config --modversion gtk+-2.0' returned %d.%d.%d, but GTK+ (%d.%d.%d)\n", 
+             $gtk_config_major_version, $gtk_config_minor_version, $gtk_config_micro_version,
+             gtk_major_version, gtk_minor_version, gtk_micro_version);
+      printf ("*** was found! If pkg-config was correct, then it is best\n");
+      printf ("*** to remove the old version of GTK+. You may also be able to fix the error\n");
+      printf("*** by modifying your LD_LIBRARY_PATH enviroment variable, or by editing\n");
+      printf("*** /etc/ld.so.conf. Make sure you have run ldconfig if that is\n");
+      printf("*** required on your system.\n");
+      printf("*** If pkg-config was wrong, set the environment variable PKG_CONFIG_PATH\n");
+      printf("*** to point to the correct configuration files\n");
+    } 
+  else if ((gtk_major_version != GTK_MAJOR_VERSION) ||
+          (gtk_minor_version != GTK_MINOR_VERSION) ||
+           (gtk_micro_version != GTK_MICRO_VERSION))
+    {
+      printf("*** GTK+ header files (version %d.%d.%d) do not match\n",
+            GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION);
+      printf("*** library (version %d.%d.%d)\n",
+            gtk_major_version, gtk_minor_version, gtk_micro_version);
+    }
+  else
+    {
+      if ((gtk_major_version > major) ||
+        ((gtk_major_version == major) && (gtk_minor_version > minor)) ||
+        ((gtk_major_version == major) && (gtk_minor_version == minor) && (gtk_micro_version >= micro)))
+      {
+        return 0;
+       }
+     else
+      {
+        printf("\n*** An old version of GTK+ (%d.%d.%d) was found.\n",
+               gtk_major_version, gtk_minor_version, gtk_micro_version);
+        printf("*** You need a version of GTK+ newer than %d.%d.%d. The latest version of\n",
+              major, minor, micro);
+        printf("*** GTK+ is always available from ftp://ftp.gtk.org.\n");
+        printf("***\n");
+        printf("*** If you have already installed a sufficiently new version, this error\n");
+        printf("*** probably means that the wrong copy of the pkg-config shell script is\n");
+        printf("*** being found. The easiest way to fix this is to remove the old version\n");
+        printf("*** of GTK+, but you can also set the PKG_CONFIG environment to point to the\n");
+        printf("*** correct copy of pkg-config. (In this case, you will have to\n");
+        printf("*** modify your LD_LIBRARY_PATH enviroment variable, or edit /etc/ld.so.conf\n");
+        printf("*** so that the correct libraries are found at run-time))\n");
+      }
+    }
+  return 1;
+}
+],, no_gtk=yes,[echo $ac_n "cross compiling; assumed OK... $ac_c"])
+       CFLAGS="$ac_save_CFLAGS"
+       LIBS="$ac_save_LIBS"
+     fi
+  fi
+  if test "x$no_gtk" = x ; then
+     AC_MSG_RESULT(yes (version $gtk_config_major_version.$gtk_config_minor_version.$gtk_config_micro_version))
+     ifelse([$2], , :, [$2])     
+  else
+     AC_MSG_RESULT(no)
+     if test "$PKG_CONFIG" = "no" ; then
+       echo "*** A new enough version of pkg-config was not found."
+       echo "*** See http://pkgconfig.sourceforge.net"
+     else
+       if test -f conf.gtktest ; then
+        :
+       else
+          echo "*** Could not run GTK+ test program, checking why..."
+         ac_save_CFLAGS="$CFLAGS"
+         ac_save_LIBS="$LIBS"
+          CFLAGS="$CFLAGS $GTK_CFLAGS"
+          LIBS="$LIBS $GTK_LIBS"
+          AC_TRY_LINK([
+#include <gtk/gtk.h>
+#include <stdio.h>
+],      [ return ((gtk_major_version) || (gtk_minor_version) || (gtk_micro_version)); ],
+        [ echo "*** The test program compiled, but did not run. This usually means"
+          echo "*** that the run-time linker is not finding GTK+ or finding the wrong"
+          echo "*** version of GTK+. If it is not finding GTK+, you'll need to set your"
+          echo "*** LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point"
+          echo "*** to the installed location  Also, make sure you have run ldconfig if that"
+          echo "*** is required on your system"
+         echo "***"
+          echo "*** If you have an old version installed, it is best to remove it, although"
+          echo "*** you may also be able to get things to work by modifying LD_LIBRARY_PATH" ],
+        [ echo "*** The test program failed to compile or link. See the file config.log for the"
+          echo "*** exact error that occured. This usually means GTK+ is incorrectly installed."])
+          CFLAGS="$ac_save_CFLAGS"
+          LIBS="$ac_save_LIBS"
+       fi
+     fi
+     GTK_CFLAGS=""
+     GTK_LIBS=""
+     ifelse([$3], , :, [$3])
+  fi
+  AC_SUBST(GTK_CFLAGS)
+  AC_SUBST(GTK_LIBS)
+  rm -f conf.gtktest
+])
+
+# Configure paths for GTK+
+# Owen Taylor     1997-2001
+
+dnl AM_PATH_GTK_3_0([MINIMUM-VERSION, [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND [, MODULES]]]])
+dnl Test for GTK+, and define GTK_CFLAGS and GTK_LIBS, if gthread is specified in MODULES, 
+dnl pass to pkg-config
+dnl
+AC_DEFUN([AM_PATH_GTK_3_0],
+[m4_warn([obsolete], [AM_PATH_GTK_3_0 is deprecated, use PKG_CHECK_MODULES([GTK], [gtk+-3.0]) instead])
+dnl Get the cflags and libraries from pkg-config
+dnl
+AC_ARG_ENABLE(gtktest, [  --disable-gtktest       do not try to compile and run a test GTK+ program],
+                   , enable_gtktest=yes)
+  min_gtk_version=ifelse([$1], [], [3.0.0], [$1])
+
+  pkg_config_args="gtk+-3.0 >= $min_gtk_version"
+  for module in . $4
+  do
+      case "$module" in
+         gthread)
+             pkg_config_args="$pkg_config_args gthread-2.0"
+         ;;
+      esac
+  done
+
+  no_gtk=""
+
+  AC_PATH_PROG(PKG_CONFIG, pkg-config, no)
+
+  if test x$PKG_CONFIG != xno ; then
+    if $PKG_CONFIG --atleast-pkgconfig-version 0.7 ; then
+      :
+    else
+      echo "*** pkg-config too old; version 0.7 or better required."
+      no_gtk=yes
+      PKG_CONFIG=no
+    fi
+  else
+    no_gtk=yes
+  fi
+
+  AC_MSG_CHECKING(for GTK+ - version >= $min_gtk_version)
+
+  if test x$PKG_CONFIG != xno ; then
+    ## don't try to run the test against uninstalled libtool libs
+    if $PKG_CONFIG --uninstalled $pkg_config_args; then
+         echo "Will use uninstalled version of GTK+ found in PKG_CONFIG_PATH"
+         enable_gtktest=no
+    fi
+
+    if $PKG_CONFIG $pkg_config_args; then
+         :
+    else
+         no_gtk=yes
+    fi
+  fi
+
+  if test x"$no_gtk" = x ; then
+    GTK_CFLAGS=`$PKG_CONFIG $pkg_config_args --cflags`
+    GTK_LIBS=`$PKG_CONFIG $pkg_config_args --libs`
+    gtk_config_major_version=`$PKG_CONFIG --modversion gtk+-3.0 | \
+           sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\1/'`
+    gtk_config_minor_version=`$PKG_CONFIG --modversion gtk+-3.0 | \
+           sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\2/'`
+    gtk_config_micro_version=`$PKG_CONFIG --modversion gtk+-3.0 | \
+           sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\3/'`
+    if test "x$enable_gtktest" = "xyes" ; then
+      ac_save_CFLAGS="$CFLAGS"
+      ac_save_LIBS="$LIBS"
+      CFLAGS="$CFLAGS $GTK_CFLAGS"
+      LIBS="$GTK_LIBS $LIBS"
+dnl
+dnl Now check if the installed GTK+ is sufficiently new. (Also sanity
+dnl checks the results of pkg-config to some extent)
+dnl
+      rm -f conf.gtktest
+      AC_TRY_RUN([
+#include <gtk/gtk.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+int 
+main ()
+{
+  unsigned int major, minor, micro;
+
+  fclose (fopen ("conf.gtktest", "w"));
+
+  if (sscanf("$min_gtk_version", "%u.%u.%u", &major, &minor, &micro) != 3) {
+     printf("%s, bad version string\n", "$min_gtk_version");
+     exit(1);
+   }
+
+  if ((gtk_major_version != $gtk_config_major_version) ||
+      (gtk_minor_version != $gtk_config_minor_version) ||
+      (gtk_micro_version != $gtk_config_micro_version))
+    {
+      printf("\n*** 'pkg-config --modversion gtk+-3.0' returned %d.%d.%d, but GTK+ (%d.%d.%d)\n", 
+             $gtk_config_major_version, $gtk_config_minor_version, $gtk_config_micro_version,
+             gtk_major_version, gtk_minor_version, gtk_micro_version);
+      printf ("*** was found! If pkg-config was correct, then it is best\n");
+      printf ("*** to remove the old version of GTK+. You may also be able to fix the error\n");
+      printf("*** by modifying your LD_LIBRARY_PATH enviroment variable, or by editing\n");
+      printf("*** /etc/ld.so.conf. Make sure you have run ldconfig if that is\n");
+      printf("*** required on your system.\n");
+      printf("*** If pkg-config was wrong, set the environment variable PKG_CONFIG_PATH\n");
+      printf("*** to point to the correct configuration files\n");
+    } 
+  else if ((gtk_major_version != GTK_MAJOR_VERSION) ||
+          (gtk_minor_version != GTK_MINOR_VERSION) ||
+           (gtk_micro_version != GTK_MICRO_VERSION))
+    {
+      printf("*** GTK+ header files (version %d.%d.%d) do not match\n",
+            GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION);
+      printf("*** library (version %d.%d.%d)\n",
+            gtk_major_version, gtk_minor_version, gtk_micro_version);
+    }
+  else
+    {
+      if ((gtk_major_version > major) ||
+        ((gtk_major_version == major) && (gtk_minor_version > minor)) ||
+        ((gtk_major_version == major) && (gtk_minor_version == minor) && (gtk_micro_version >= micro)))
+      {
+        return 0;
+       }
+     else
+      {
+        printf("\n*** An old version of GTK+ (%u.%u.%u) was found.\n",
+               gtk_major_version, gtk_minor_version, gtk_micro_version);
+        printf("*** You need a version of GTK+ newer than %u.%u.%u. The latest version of\n",
+              major, minor, micro);
+        printf("*** GTK+ is always available from ftp://ftp.gtk.org.\n");
+        printf("***\n");
+        printf("*** If you have already installed a sufficiently new version, this error\n");
+        printf("*** probably means that the wrong copy of the pkg-config shell script is\n");
+        printf("*** being found. The easiest way to fix this is to remove the old version\n");
+        printf("*** of GTK+, but you can also set the PKG_CONFIG environment to point to the\n");
+        printf("*** correct copy of pkg-config. (In this case, you will have to\n");
+        printf("*** modify your LD_LIBRARY_PATH enviroment variable, or edit /etc/ld.so.conf\n");
+        printf("*** so that the correct libraries are found at run-time))\n");
+      }
+    }
+  return 1;
+}
+],, no_gtk=yes,[echo $ac_n "cross compiling; assumed OK... $ac_c"])
+       CFLAGS="$ac_save_CFLAGS"
+       LIBS="$ac_save_LIBS"
+     fi
+  fi
+  if test "x$no_gtk" = x ; then
+     AC_MSG_RESULT(yes (version $gtk_config_major_version.$gtk_config_minor_version.$gtk_config_micro_version))
+     ifelse([$2], , :, [$2])
+  else
+     AC_MSG_RESULT(no)
+     if test "$PKG_CONFIG" = "no" ; then
+       echo "*** A new enough version of pkg-config was not found."
+       echo "*** See http://pkgconfig.sourceforge.net"
+     else
+       if test -f conf.gtktest ; then
+        :
+       else
+          echo "*** Could not run GTK+ test program, checking why..."
+         ac_save_CFLAGS="$CFLAGS"
+         ac_save_LIBS="$LIBS"
+          CFLAGS="$CFLAGS $GTK_CFLAGS"
+          LIBS="$LIBS $GTK_LIBS"
+          AC_TRY_LINK([
+#include <gtk/gtk.h>
+#include <stdio.h>
+],      [ return ((gtk_major_version) || (gtk_minor_version) || (gtk_micro_version)); ],
+        [ echo "*** The test program compiled, but did not run. This usually means"
+          echo "*** that the run-time linker is not finding GTK+ or finding the wrong"
+          echo "*** version of GTK+. If it is not finding GTK+, you'll need to set your"
+          echo "*** LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point"
+          echo "*** to the installed location  Also, make sure you have run ldconfig if that"
+          echo "*** is required on your system"
+         echo "***"
+          echo "*** If you have an old version installed, it is best to remove it, although"
+          echo "*** you may also be able to get things to work by modifying LD_LIBRARY_PATH" ],
+        [ echo "*** The test program failed to compile or link. See the file config.log for the"
+          echo "*** exact error that occured. This usually means GTK+ is incorrectly installed."])
+          CFLAGS="$ac_save_CFLAGS"
+          LIBS="$ac_save_LIBS"
+       fi
+     fi
+     GTK_CFLAGS=""
+     GTK_LIBS=""
+     ifelse([$3], , :, [$3])
+  fi
+  AC_SUBST(GTK_CFLAGS)
+  AC_SUBST(GTK_LIBS)
+  rm -f conf.gtktest
+])
+
+dnl GTK_CHECK_BACKEND(BACKEND-NAME [, MINIMUM-VERSION [, ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]])
+dnl   Tests for BACKEND-NAME in the GTK targets list
+dnl
+AC_DEFUN([GTK_CHECK_BACKEND],
+[m4_warn([obsolete], [GTK_CHECK_BACKEND is deprecated, use PKG_CHECK_MODULES([GTK_X11], [gtk+-x11-3.0]) or similar instead])
+  pkg_config_args=ifelse([$1],,gtk+-3.0, gtk+-$1-3.0)
+  min_gtk_version=ifelse([$2],,3.0.0,$2)
+  pkg_config_args="$pkg_config_args >= $min_gtk_version"
+
+  AC_PATH_PROG(PKG_CONFIG, [pkg-config], [AC_MSG_ERROR([No pkg-config found])])
+
+  if $PKG_CONFIG $pkg_config_args ; then
+    target_found=yes
+  else
+    target_found=no
+  fi
+
+  if test "x$target_found" = "xno"; then
+    ifelse([$4],,[AC_MSG_ERROR([Backend $backend not found.])],[$4])
+  else
+    ifelse([$3],,[:],[$3])
+  fi
+])
+
+dnl pkg.m4 - Macros to locate and utilise pkg-config.   -*- Autoconf -*-
+dnl serial 11 (pkg-config-0.29.1)
+dnl
+dnl Copyright Â© 2004 Scott James Remnant <scott@netsplit.com>.
+dnl Copyright Â© 2012-2015 Dan Nicholson <dbn.lists@gmail.com>
+dnl
+dnl This program is free software; you can redistribute it and/or modify
+dnl it under the terms of the GNU General Public License as published by
+dnl the Free Software Foundation; either version 2 of the License, or
+dnl (at your option) any later version.
+dnl
+dnl This program is distributed in the hope that it will be useful, but
+dnl WITHOUT ANY WARRANTY; without even the implied warranty of
+dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+dnl General Public License for more details.
+dnl
+dnl You should have received a copy of the GNU General Public License
+dnl along with this program; if not, write to the Free Software
+dnl Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+dnl 02111-1307, USA.
+dnl
+dnl As a special exception to the GNU General Public License, if you
+dnl distribute this file as part of a program that contains a
+dnl configuration script generated by Autoconf, you may include it under
+dnl the same distribution terms that you use for the rest of that
+dnl program.
+
+dnl PKG_PREREQ(MIN-VERSION)
+dnl -----------------------
+dnl Since: 0.29
+dnl
+dnl Verify that the version of the pkg-config macros are at least
+dnl MIN-VERSION. Unlike PKG_PROG_PKG_CONFIG, which checks the user's
+dnl installed version of pkg-config, this checks the developer's version
+dnl of pkg.m4 when generating configure.
+dnl
+dnl To ensure that this macro is defined, also add:
+dnl m4_ifndef([PKG_PREREQ],
+dnl     [m4_fatal([must install pkg-config 0.29 or later before running autoconf/autogen])])
+dnl
+dnl See the "Since" comment for each macro you use to see what version
+dnl of the macros you require.
+m4_defun([PKG_PREREQ],
+[m4_define([PKG_MACROS_VERSION], [0.29.1])
+m4_if(m4_version_compare(PKG_MACROS_VERSION, [$1]), -1,
+    [m4_fatal([pkg.m4 version $1 or higher is required but ]PKG_MACROS_VERSION[ found])])
+])dnl PKG_PREREQ
+
+dnl PKG_PROG_PKG_CONFIG([MIN-VERSION])
+dnl ----------------------------------
+dnl Since: 0.16
+dnl
+dnl Search for the pkg-config tool and set the PKG_CONFIG variable to
+dnl first found in the path. Checks that the version of pkg-config found
+dnl is at least MIN-VERSION. If MIN-VERSION is not specified, 0.9.0 is
+dnl used since that's the first version where most current features of
+dnl pkg-config existed.
+AC_DEFUN([PKG_PROG_PKG_CONFIG],
+[m4_pattern_forbid([^_?PKG_[A-Z_]+$])
+m4_pattern_allow([^PKG_CONFIG(_(PATH|LIBDIR|SYSROOT_DIR|ALLOW_SYSTEM_(CFLAGS|LIBS)))?$])
+m4_pattern_allow([^PKG_CONFIG_(DISABLE_UNINSTALLED|TOP_BUILD_DIR|DEBUG_SPEW)$])
+AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility])
+AC_ARG_VAR([PKG_CONFIG_PATH], [directories to add to pkg-config's search path])
+AC_ARG_VAR([PKG_CONFIG_LIBDIR], [path overriding pkg-config's built-in search path])
+
+if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then
+       AC_PATH_TOOL([PKG_CONFIG], [pkg-config])
+fi
+if test -n "$PKG_CONFIG"; then
+       _pkg_min_version=m4_default([$1], [0.9.0])
+       AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version])
+       if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then
+               AC_MSG_RESULT([yes])
+       else
+               AC_MSG_RESULT([no])
+               PKG_CONFIG=""
+       fi
+fi[]dnl
+])dnl PKG_PROG_PKG_CONFIG
+
+dnl PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
+dnl -------------------------------------------------------------------
+dnl Since: 0.18
+dnl
+dnl Check to see whether a particular set of modules exists. Similar to
+dnl PKG_CHECK_MODULES(), but does not set variables or print errors.
+dnl
+dnl Please remember that m4 expands AC_REQUIRE([PKG_PROG_PKG_CONFIG])
+dnl only at the first occurence in configure.ac, so if the first place
+dnl it's called might be skipped (such as if it is within an "if", you
+dnl have to call PKG_CHECK_EXISTS manually
+AC_DEFUN([PKG_CHECK_EXISTS],
+[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
+if test -n "$PKG_CONFIG" && \
+    AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then
+  m4_default([$2], [:])
+m4_ifvaln([$3], [else
+  $3])dnl
+fi])
+
+dnl _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES])
+dnl ---------------------------------------------
+dnl Internal wrapper calling pkg-config via PKG_CONFIG and setting
+dnl pkg_failed based on the result.
+m4_define([_PKG_CONFIG],
+[if test -n "$$1"; then
+    pkg_cv_[]$1="$$1"
+ elif test -n "$PKG_CONFIG"; then
+    PKG_CHECK_EXISTS([$3],
+                     [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null`
+                     test "x$?" != "x0" && pkg_failed=yes ],
+                    [pkg_failed=yes])
+ else
+    pkg_failed=untried
+fi[]dnl
+])dnl _PKG_CONFIG
+
+dnl _PKG_SHORT_ERRORS_SUPPORTED
+dnl ---------------------------
+dnl Internal check to see if pkg-config supports short errors.
+AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED],
+[AC_REQUIRE([PKG_PROG_PKG_CONFIG])
+if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
+        _pkg_short_errors_supported=yes
+else
+        _pkg_short_errors_supported=no
+fi[]dnl
+])dnl _PKG_SHORT_ERRORS_SUPPORTED
+
+
+dnl PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND],
+dnl   [ACTION-IF-NOT-FOUND])
+dnl --------------------------------------------------------------
+dnl Since: 0.4.0
+dnl
+dnl Note that if there is a possibility the first call to
+dnl PKG_CHECK_MODULES might not happen, you should be sure to include an
+dnl explicit call to PKG_PROG_PKG_CONFIG in your configure.ac
+AC_DEFUN([PKG_CHECK_MODULES],
+[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
+AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl
+AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl
+
+pkg_failed=no
+AC_MSG_CHECKING([for $1])
+
+_PKG_CONFIG([$1][_CFLAGS], [cflags], [$2])
+_PKG_CONFIG([$1][_LIBS], [libs], [$2])
+
+m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS
+and $1[]_LIBS to avoid the need to call pkg-config.
+See the pkg-config man page for more details.])
+
+if test $pkg_failed = yes; then
+       AC_MSG_RESULT([no])
+        _PKG_SHORT_ERRORS_SUPPORTED
+        if test $_pkg_short_errors_supported = yes; then
+               $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$2" 2>&1`
+        else 
+               $1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$2" 2>&1`
+        fi
+       # Put the nasty error message in config.log where it belongs
+       echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD
+
+       m4_default([$4], [AC_MSG_ERROR(
+[Package requirements ($2) were not met:
+
+$$1_PKG_ERRORS
+
+Consider adjusting the PKG_CONFIG_PATH environment variable if you
+installed software in a non-standard prefix.
+
+_PKG_TEXT])[]dnl
+        ])
+elif test $pkg_failed = untried; then
+       AC_MSG_RESULT([no])
+       m4_default([$4], [AC_MSG_FAILURE(
+[The pkg-config script could not be found or is too old.  Make sure it
+is in your PATH or set the PKG_CONFIG environment variable to the full
+path to pkg-config.
+
+_PKG_TEXT
+
+To get pkg-config, see <http://pkg-config.freedesktop.org/>.])[]dnl
+        ])
+else
+       $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS
+       $1[]_LIBS=$pkg_cv_[]$1[]_LIBS
+        AC_MSG_RESULT([yes])
+       $3
+fi[]dnl
+])dnl PKG_CHECK_MODULES
+
+
+dnl PKG_CHECK_MODULES_STATIC(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND],
+dnl   [ACTION-IF-NOT-FOUND])
+dnl ---------------------------------------------------------------------
+dnl Since: 0.29
+dnl
+dnl Checks for existence of MODULES and gathers its build flags with
+dnl static libraries enabled. Sets VARIABLE-PREFIX_CFLAGS from --cflags
+dnl and VARIABLE-PREFIX_LIBS from --libs.
+dnl
+dnl Note that if there is a possibility the first call to
+dnl PKG_CHECK_MODULES_STATIC might not happen, you should be sure to
+dnl include an explicit call to PKG_PROG_PKG_CONFIG in your
+dnl configure.ac.
+AC_DEFUN([PKG_CHECK_MODULES_STATIC],
+[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
+_save_PKG_CONFIG=$PKG_CONFIG
+PKG_CONFIG="$PKG_CONFIG --static"
+PKG_CHECK_MODULES($@)
+PKG_CONFIG=$_save_PKG_CONFIG[]dnl
+])dnl PKG_CHECK_MODULES_STATIC
+
+
+dnl PKG_INSTALLDIR([DIRECTORY])
+dnl -------------------------
+dnl Since: 0.27
+dnl
+dnl Substitutes the variable pkgconfigdir as the location where a module
+dnl should install pkg-config .pc files. By default the directory is
+dnl $libdir/pkgconfig, but the default can be changed by passing
+dnl DIRECTORY. The user can override through the --with-pkgconfigdir
+dnl parameter.
+AC_DEFUN([PKG_INSTALLDIR],
+[m4_pushdef([pkg_default], [m4_default([$1], ['${libdir}/pkgconfig'])])
+m4_pushdef([pkg_description],
+    [pkg-config installation directory @<:@]pkg_default[@:>@])
+AC_ARG_WITH([pkgconfigdir],
+    [AS_HELP_STRING([--with-pkgconfigdir], pkg_description)],,
+    [with_pkgconfigdir=]pkg_default)
+AC_SUBST([pkgconfigdir], [$with_pkgconfigdir])
+m4_popdef([pkg_default])
+m4_popdef([pkg_description])
+])dnl PKG_INSTALLDIR
+
+
+dnl PKG_NOARCH_INSTALLDIR([DIRECTORY])
+dnl --------------------------------
+dnl Since: 0.27
+dnl
+dnl Substitutes the variable noarch_pkgconfigdir as the location where a
+dnl module should install arch-independent pkg-config .pc files. By
+dnl default the directory is $datadir/pkgconfig, but the default can be
+dnl changed by passing DIRECTORY. The user can override through the
+dnl --with-noarch-pkgconfigdir parameter.
+AC_DEFUN([PKG_NOARCH_INSTALLDIR],
+[m4_pushdef([pkg_default], [m4_default([$1], ['${datadir}/pkgconfig'])])
+m4_pushdef([pkg_description],
+    [pkg-config arch-independent installation directory @<:@]pkg_default[@:>@])
+AC_ARG_WITH([noarch-pkgconfigdir],
+    [AS_HELP_STRING([--with-noarch-pkgconfigdir], pkg_description)],,
+    [with_noarch_pkgconfigdir=]pkg_default)
+AC_SUBST([noarch_pkgconfigdir], [$with_noarch_pkgconfigdir])
+m4_popdef([pkg_default])
+m4_popdef([pkg_description])
+])dnl PKG_NOARCH_INSTALLDIR
+
+
+dnl PKG_CHECK_VAR(VARIABLE, MODULE, CONFIG-VARIABLE,
+dnl [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
+dnl -------------------------------------------
+dnl Since: 0.28
+dnl
+dnl Retrieves the value of the pkg-config variable for the given module.
+AC_DEFUN([PKG_CHECK_VAR],
+[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
+AC_ARG_VAR([$1], [value of $3 for $2, overriding pkg-config])dnl
+
+_PKG_CONFIG([$1], [variable="][$3]["], [$2])
+AS_VAR_COPY([$1], [pkg_cv_][$1])
+
+AS_VAR_IF([$1], [""], [$5], [$4])dnl
+])dnl PKG_CHECK_VAR
+
+# Copyright (C) 2002-2014 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_AUTOMAKE_VERSION(VERSION)
+# ----------------------------
+# Automake X.Y traces this macro to ensure aclocal.m4 has been
+# generated from the m4 files accompanying Automake X.Y.
+# (This private macro should not be called outside this file.)
+AC_DEFUN([AM_AUTOMAKE_VERSION],
+[am__api_version='1.15'
+dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to
+dnl require some minimum version.  Point them to the right macro.
+m4_if([$1], [1.15], [],
+      [AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl
+])
+
+# _AM_AUTOCONF_VERSION(VERSION)
+# -----------------------------
+# aclocal traces this macro to find the Autoconf version.
+# This is a private macro too.  Using m4_define simplifies
+# the logic in aclocal, which can simply ignore this definition.
+m4_define([_AM_AUTOCONF_VERSION], [])
+
+# AM_SET_CURRENT_AUTOMAKE_VERSION
+# -------------------------------
+# Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced.
+# This function is AC_REQUIREd by AM_INIT_AUTOMAKE.
+AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION],
+[AM_AUTOMAKE_VERSION([1.15])dnl
+m4_ifndef([AC_AUTOCONF_VERSION],
+  [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl
+_AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))])
+
+# AM_AUX_DIR_EXPAND                                         -*- Autoconf -*-
+
+# Copyright (C) 2001-2014 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# For projects using AC_CONFIG_AUX_DIR([foo]), Autoconf sets
+# $ac_aux_dir to '$srcdir/foo'.  In other projects, it is set to
+# '$srcdir', '$srcdir/..', or '$srcdir/../..'.
+#
+# Of course, Automake must honor this variable whenever it calls a
+# tool from the auxiliary directory.  The problem is that $srcdir (and
+# therefore $ac_aux_dir as well) can be either absolute or relative,
+# depending on how configure is run.  This is pretty annoying, since
+# it makes $ac_aux_dir quite unusable in subdirectories: in the top
+# source directory, any form will work fine, but in subdirectories a
+# relative path needs to be adjusted first.
+#
+# $ac_aux_dir/missing
+#    fails when called from a subdirectory if $ac_aux_dir is relative
+# $top_srcdir/$ac_aux_dir/missing
+#    fails if $ac_aux_dir is absolute,
+#    fails when called from a subdirectory in a VPATH build with
+#          a relative $ac_aux_dir
+#
+# The reason of the latter failure is that $top_srcdir and $ac_aux_dir
+# are both prefixed by $srcdir.  In an in-source build this is usually
+# harmless because $srcdir is '.', but things will broke when you
+# start a VPATH build or use an absolute $srcdir.
+#
+# So we could use something similar to $top_srcdir/$ac_aux_dir/missing,
+# iff we strip the leading $srcdir from $ac_aux_dir.  That would be:
+#   am_aux_dir='\$(top_srcdir)/'`expr "$ac_aux_dir" : "$srcdir//*\(.*\)"`
+# and then we would define $MISSING as
+#   MISSING="\${SHELL} $am_aux_dir/missing"
+# This will work as long as MISSING is not called from configure, because
+# unfortunately $(top_srcdir) has no meaning in configure.
+# However there are other variables, like CC, which are often used in
+# configure, and could therefore not use this "fixed" $ac_aux_dir.
+#
+# Another solution, used here, is to always expand $ac_aux_dir to an
+# absolute PATH.  The drawback is that using absolute paths prevent a
+# configured tree to be moved without reconfiguration.
+
+AC_DEFUN([AM_AUX_DIR_EXPAND],
+[AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT])dnl
+# Expand $ac_aux_dir to an absolute path.
+am_aux_dir=`cd "$ac_aux_dir" && pwd`
+])
+
+# AM_CONDITIONAL                                            -*- Autoconf -*-
+
+# Copyright (C) 1997-2014 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_CONDITIONAL(NAME, SHELL-CONDITION)
+# -------------------------------------
+# Define a conditional.
+AC_DEFUN([AM_CONDITIONAL],
+[AC_PREREQ([2.52])dnl
+ m4_if([$1], [TRUE],  [AC_FATAL([$0: invalid condition: $1])],
+       [$1], [FALSE], [AC_FATAL([$0: invalid condition: $1])])dnl
+AC_SUBST([$1_TRUE])dnl
+AC_SUBST([$1_FALSE])dnl
+_AM_SUBST_NOTMAKE([$1_TRUE])dnl
+_AM_SUBST_NOTMAKE([$1_FALSE])dnl
+m4_define([_AM_COND_VALUE_$1], [$2])dnl
+if $2; then
+  $1_TRUE=
+  $1_FALSE='#'
+else
+  $1_TRUE='#'
+  $1_FALSE=
+fi
+AC_CONFIG_COMMANDS_PRE(
+[if test -z "${$1_TRUE}" && test -z "${$1_FALSE}"; then
+  AC_MSG_ERROR([[conditional "$1" was never defined.
+Usually this means the macro was only invoked conditionally.]])
+fi])])
+
+# Copyright (C) 1999-2014 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+
+# There are a few dirty hacks below to avoid letting 'AC_PROG_CC' be
+# written in clear, in which case automake, when reading aclocal.m4,
+# will think it sees a *use*, and therefore will trigger all it's
+# C support machinery.  Also note that it means that autoscan, seeing
+# CC etc. in the Makefile, will ask for an AC_PROG_CC use...
+
+
+# _AM_DEPENDENCIES(NAME)
+# ----------------------
+# See how the compiler implements dependency checking.
+# NAME is "CC", "CXX", "OBJC", "OBJCXX", "UPC", or "GJC".
+# We try a few techniques and use that to set a single cache variable.
+#
+# We don't AC_REQUIRE the corresponding AC_PROG_CC since the latter was
+# modified to invoke _AM_DEPENDENCIES(CC); we would have a circular
+# dependency, and given that the user is not expected to run this macro,
+# just rely on AC_PROG_CC.
+AC_DEFUN([_AM_DEPENDENCIES],
+[AC_REQUIRE([AM_SET_DEPDIR])dnl
+AC_REQUIRE([AM_OUTPUT_DEPENDENCY_COMMANDS])dnl
+AC_REQUIRE([AM_MAKE_INCLUDE])dnl
+AC_REQUIRE([AM_DEP_TRACK])dnl
+
+m4_if([$1], [CC],   [depcc="$CC"   am_compiler_list=],
+      [$1], [CXX],  [depcc="$CXX"  am_compiler_list=],
+      [$1], [OBJC], [depcc="$OBJC" am_compiler_list='gcc3 gcc'],
+      [$1], [OBJCXX], [depcc="$OBJCXX" am_compiler_list='gcc3 gcc'],
+      [$1], [UPC],  [depcc="$UPC"  am_compiler_list=],
+      [$1], [GCJ],  [depcc="$GCJ"  am_compiler_list='gcc3 gcc'],
+                    [depcc="$$1"   am_compiler_list=])
+
+AC_CACHE_CHECK([dependency style of $depcc],
+               [am_cv_$1_dependencies_compiler_type],
+[if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then
+  # We make a subdir and do the tests there.  Otherwise we can end up
+  # making bogus files that we don't know about and never remove.  For
+  # instance it was reported that on HP-UX the gcc test will end up
+  # making a dummy file named 'D' -- because '-MD' means "put the output
+  # in D".
+  rm -rf conftest.dir
+  mkdir conftest.dir
+  # Copy depcomp to subdir because otherwise we won't find it if we're
+  # using a relative directory.
+  cp "$am_depcomp" conftest.dir
+  cd conftest.dir
+  # We will build objects and dependencies in a subdirectory because
+  # it helps to detect inapplicable dependency modes.  For instance
+  # both Tru64's cc and ICC support -MD to output dependencies as a
+  # side effect of compilation, but ICC will put the dependencies in
+  # the current directory while Tru64 will put them in the object
+  # directory.
+  mkdir sub
+
+  am_cv_$1_dependencies_compiler_type=none
+  if test "$am_compiler_list" = ""; then
+     am_compiler_list=`sed -n ['s/^#*\([a-zA-Z0-9]*\))$/\1/p'] < ./depcomp`
+  fi
+  am__universal=false
+  m4_case([$1], [CC],
+    [case " $depcc " in #(
+     *\ -arch\ *\ -arch\ *) am__universal=true ;;
+     esac],
+    [CXX],
+    [case " $depcc " in #(
+     *\ -arch\ *\ -arch\ *) am__universal=true ;;
+     esac])
+
+  for depmode in $am_compiler_list; do
+    # Setup a source with many dependencies, because some compilers
+    # like to wrap large dependency lists on column 80 (with \), and
+    # we should not choose a depcomp mode which is confused by this.
+    #
+    # We need to recreate these files for each test, as the compiler may
+    # overwrite some of them when testing with obscure command lines.
+    # This happens at least with the AIX C compiler.
+    : > sub/conftest.c
+    for i in 1 2 3 4 5 6; do
+      echo '#include "conftst'$i'.h"' >> sub/conftest.c
+      # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with
+      # Solaris 10 /bin/sh.
+      echo '/* dummy */' > sub/conftst$i.h
+    done
+    echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf
+
+    # We check with '-c' and '-o' for the sake of the "dashmstdout"
+    # mode.  It turns out that the SunPro C++ compiler does not properly
+    # handle '-M -o', and we need to detect this.  Also, some Intel
+    # versions had trouble with output in subdirs.
+    am__obj=sub/conftest.${OBJEXT-o}
+    am__minus_obj="-o $am__obj"
+    case $depmode in
+    gcc)
+      # This depmode causes a compiler race in universal mode.
+      test "$am__universal" = false || continue
+      ;;
+    nosideeffect)
+      # After this tag, mechanisms are not by side-effect, so they'll
+      # only be used when explicitly requested.
+      if test "x$enable_dependency_tracking" = xyes; then
+       continue
+      else
+       break
+      fi
+      ;;
+    msvc7 | msvc7msys | msvisualcpp | msvcmsys)
+      # This compiler won't grok '-c -o', but also, the minuso test has
+      # not run yet.  These depmodes are late enough in the game, and
+      # so weak that their functioning should not be impacted.
+      am__obj=conftest.${OBJEXT-o}
+      am__minus_obj=
+      ;;
+    none) break ;;
+    esac
+    if depmode=$depmode \
+       source=sub/conftest.c object=$am__obj \
+       depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \
+       $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \
+         >/dev/null 2>conftest.err &&
+       grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 &&
+       grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 &&
+       grep $am__obj sub/conftest.Po > /dev/null 2>&1 &&
+       ${MAKE-make} -s -f confmf > /dev/null 2>&1; then
+      # icc doesn't choke on unknown options, it will just issue warnings
+      # or remarks (even with -Werror).  So we grep stderr for any message
+      # that says an option was ignored or not supported.
+      # When given -MP, icc 7.0 and 7.1 complain thusly:
+      #   icc: Command line warning: ignoring option '-M'; no argument required
+      # The diagnosis changed in icc 8.0:
+      #   icc: Command line remark: option '-MP' not supported
+      if (grep 'ignoring option' conftest.err ||
+          grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else
+        am_cv_$1_dependencies_compiler_type=$depmode
+        break
+      fi
+    fi
+  done
+
+  cd ..
+  rm -rf conftest.dir
+else
+  am_cv_$1_dependencies_compiler_type=none
+fi
+])
+AC_SUBST([$1DEPMODE], [depmode=$am_cv_$1_dependencies_compiler_type])
+AM_CONDITIONAL([am__fastdep$1], [
+  test "x$enable_dependency_tracking" != xno \
+  && test "$am_cv_$1_dependencies_compiler_type" = gcc3])
+])
+
+
+# AM_SET_DEPDIR
+# -------------
+# Choose a directory name for dependency files.
+# This macro is AC_REQUIREd in _AM_DEPENDENCIES.
+AC_DEFUN([AM_SET_DEPDIR],
+[AC_REQUIRE([AM_SET_LEADING_DOT])dnl
+AC_SUBST([DEPDIR], ["${am__leading_dot}deps"])dnl
+])
+
+
+# AM_DEP_TRACK
+# ------------
+AC_DEFUN([AM_DEP_TRACK],
+[AC_ARG_ENABLE([dependency-tracking], [dnl
+AS_HELP_STRING(
+  [--enable-dependency-tracking],
+  [do not reject slow dependency extractors])
+AS_HELP_STRING(
+  [--disable-dependency-tracking],
+  [speeds up one-time build])])
+if test "x$enable_dependency_tracking" != xno; then
+  am_depcomp="$ac_aux_dir/depcomp"
+  AMDEPBACKSLASH='\'
+  am__nodep='_no'
+fi
+AM_CONDITIONAL([AMDEP], [test "x$enable_dependency_tracking" != xno])
+AC_SUBST([AMDEPBACKSLASH])dnl
+_AM_SUBST_NOTMAKE([AMDEPBACKSLASH])dnl
+AC_SUBST([am__nodep])dnl
+_AM_SUBST_NOTMAKE([am__nodep])dnl
+])
+
+# Generate code to set up dependency tracking.              -*- Autoconf -*-
+
+# Copyright (C) 1999-2014 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+
+# _AM_OUTPUT_DEPENDENCY_COMMANDS
+# ------------------------------
+AC_DEFUN([_AM_OUTPUT_DEPENDENCY_COMMANDS],
+[{
+  # Older Autoconf quotes --file arguments for eval, but not when files
+  # are listed without --file.  Let's play safe and only enable the eval
+  # if we detect the quoting.
+  case $CONFIG_FILES in
+  *\'*) eval set x "$CONFIG_FILES" ;;
+  *)   set x $CONFIG_FILES ;;
+  esac
+  shift
+  for mf
+  do
+    # Strip MF so we end up with the name of the file.
+    mf=`echo "$mf" | sed -e 's/:.*$//'`
+    # Check whether this is an Automake generated Makefile or not.
+    # We used to match only the files named 'Makefile.in', but
+    # some people rename them; so instead we look at the file content.
+    # Grep'ing the first line is not enough: some people post-process
+    # each Makefile.in and add a new line on top of each file to say so.
+    # Grep'ing the whole file is not good either: AIX grep has a line
+    # limit of 2048, but all sed's we know have understand at least 4000.
+    if sed -n 's,^#.*generated by automake.*,X,p' "$mf" | grep X >/dev/null 2>&1; then
+      dirpart=`AS_DIRNAME("$mf")`
+    else
+      continue
+    fi
+    # Extract the definition of DEPDIR, am__include, and am__quote
+    # from the Makefile without running 'make'.
+    DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"`
+    test -z "$DEPDIR" && continue
+    am__include=`sed -n 's/^am__include = //p' < "$mf"`
+    test -z "$am__include" && continue
+    am__quote=`sed -n 's/^am__quote = //p' < "$mf"`
+    # Find all dependency output files, they are included files with
+    # $(DEPDIR) in their names.  We invoke sed twice because it is the
+    # simplest approach to changing $(DEPDIR) to its actual value in the
+    # expansion.
+    for file in `sed -n "
+      s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \
+        sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g'`; do
+      # Make sure the directory exists.
+      test -f "$dirpart/$file" && continue
+      fdir=`AS_DIRNAME(["$file"])`
+      AS_MKDIR_P([$dirpart/$fdir])
+      # echo "creating $dirpart/$file"
+      echo '# dummy' > "$dirpart/$file"
+    done
+  done
+}
+])# _AM_OUTPUT_DEPENDENCY_COMMANDS
+
+
+# AM_OUTPUT_DEPENDENCY_COMMANDS
+# -----------------------------
+# This macro should only be invoked once -- use via AC_REQUIRE.
+#
+# This code is only required when automatic dependency tracking
+# is enabled.  FIXME.  This creates each '.P' file that we will
+# need in order to bootstrap the dependency handling code.
+AC_DEFUN([AM_OUTPUT_DEPENDENCY_COMMANDS],
+[AC_CONFIG_COMMANDS([depfiles],
+     [test x"$AMDEP_TRUE" != x"" || _AM_OUTPUT_DEPENDENCY_COMMANDS],
+     [AMDEP_TRUE="$AMDEP_TRUE" ac_aux_dir="$ac_aux_dir"])
+])
+
+# Do all the work for Automake.                             -*- Autoconf -*-
+
+# Copyright (C) 1996-2014 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This macro actually does too much.  Some checks are only needed if
+# your package does certain things.  But this isn't really a big deal.
+
+dnl Redefine AC_PROG_CC to automatically invoke _AM_PROG_CC_C_O.
+m4_define([AC_PROG_CC],
+m4_defn([AC_PROG_CC])
+[_AM_PROG_CC_C_O
+])
+
+# AM_INIT_AUTOMAKE(PACKAGE, VERSION, [NO-DEFINE])
+# AM_INIT_AUTOMAKE([OPTIONS])
+# -----------------------------------------------
+# The call with PACKAGE and VERSION arguments is the old style
+# call (pre autoconf-2.50), which is being phased out.  PACKAGE
+# and VERSION should now be passed to AC_INIT and removed from
+# the call to AM_INIT_AUTOMAKE.
+# We support both call styles for the transition.  After
+# the next Automake release, Autoconf can make the AC_INIT
+# arguments mandatory, and then we can depend on a new Autoconf
+# release and drop the old call support.
+AC_DEFUN([AM_INIT_AUTOMAKE],
+[AC_PREREQ([2.65])dnl
+dnl Autoconf wants to disallow AM_ names.  We explicitly allow
+dnl the ones we care about.
+m4_pattern_allow([^AM_[A-Z]+FLAGS$])dnl
+AC_REQUIRE([AM_SET_CURRENT_AUTOMAKE_VERSION])dnl
+AC_REQUIRE([AC_PROG_INSTALL])dnl
+if test "`cd $srcdir && pwd`" != "`pwd`"; then
+  # Use -I$(srcdir) only when $(srcdir) != ., so that make's output
+  # is not polluted with repeated "-I."
+  AC_SUBST([am__isrc], [' -I$(srcdir)'])_AM_SUBST_NOTMAKE([am__isrc])dnl
+  # test to see if srcdir already configured
+  if test -f $srcdir/config.status; then
+    AC_MSG_ERROR([source directory already configured; run "make distclean" there first])
+  fi
+fi
+
+# test whether we have cygpath
+if test -z "$CYGPATH_W"; then
+  if (cygpath --version) >/dev/null 2>/dev/null; then
+    CYGPATH_W='cygpath -w'
+  else
+    CYGPATH_W=echo
+  fi
+fi
+AC_SUBST([CYGPATH_W])
+
+# Define the identity of the package.
+dnl Distinguish between old-style and new-style calls.
+m4_ifval([$2],
+[AC_DIAGNOSE([obsolete],
+             [$0: two- and three-arguments forms are deprecated.])
+m4_ifval([$3], [_AM_SET_OPTION([no-define])])dnl
+ AC_SUBST([PACKAGE], [$1])dnl
+ AC_SUBST([VERSION], [$2])],
+[_AM_SET_OPTIONS([$1])dnl
+dnl Diagnose old-style AC_INIT with new-style AM_AUTOMAKE_INIT.
+m4_if(
+  m4_ifdef([AC_PACKAGE_NAME], [ok]):m4_ifdef([AC_PACKAGE_VERSION], [ok]),
+  [ok:ok],,
+  [m4_fatal([AC_INIT should be called with package and version arguments])])dnl
+ AC_SUBST([PACKAGE], ['AC_PACKAGE_TARNAME'])dnl
+ AC_SUBST([VERSION], ['AC_PACKAGE_VERSION'])])dnl
+
+_AM_IF_OPTION([no-define],,
+[AC_DEFINE_UNQUOTED([PACKAGE], ["$PACKAGE"], [Name of package])
+ AC_DEFINE_UNQUOTED([VERSION], ["$VERSION"], [Version number of package])])dnl
+
+# Some tools Automake needs.
+AC_REQUIRE([AM_SANITY_CHECK])dnl
+AC_REQUIRE([AC_ARG_PROGRAM])dnl
+AM_MISSING_PROG([ACLOCAL], [aclocal-${am__api_version}])
+AM_MISSING_PROG([AUTOCONF], [autoconf])
+AM_MISSING_PROG([AUTOMAKE], [automake-${am__api_version}])
+AM_MISSING_PROG([AUTOHEADER], [autoheader])
+AM_MISSING_PROG([MAKEINFO], [makeinfo])
+AC_REQUIRE([AM_PROG_INSTALL_SH])dnl
+AC_REQUIRE([AM_PROG_INSTALL_STRIP])dnl
+AC_REQUIRE([AC_PROG_MKDIR_P])dnl
+# For better backward compatibility.  To be removed once Automake 1.9.x
+# dies out for good.  For more background, see:
+# <http://lists.gnu.org/archive/html/automake/2012-07/msg00001.html>
+# <http://lists.gnu.org/archive/html/automake/2012-07/msg00014.html>
+AC_SUBST([mkdir_p], ['$(MKDIR_P)'])
+# We need awk for the "check" target (and possibly the TAP driver).  The
+# system "awk" is bad on some platforms.
+AC_REQUIRE([AC_PROG_AWK])dnl
+AC_REQUIRE([AC_PROG_MAKE_SET])dnl
+AC_REQUIRE([AM_SET_LEADING_DOT])dnl
+_AM_IF_OPTION([tar-ustar], [_AM_PROG_TAR([ustar])],
+             [_AM_IF_OPTION([tar-pax], [_AM_PROG_TAR([pax])],
+                            [_AM_PROG_TAR([v7])])])
+_AM_IF_OPTION([no-dependencies],,
+[AC_PROVIDE_IFELSE([AC_PROG_CC],
+                 [_AM_DEPENDENCIES([CC])],
+                 [m4_define([AC_PROG_CC],
+                            m4_defn([AC_PROG_CC])[_AM_DEPENDENCIES([CC])])])dnl
+AC_PROVIDE_IFELSE([AC_PROG_CXX],
+                 [_AM_DEPENDENCIES([CXX])],
+                 [m4_define([AC_PROG_CXX],
+                            m4_defn([AC_PROG_CXX])[_AM_DEPENDENCIES([CXX])])])dnl
+AC_PROVIDE_IFELSE([AC_PROG_OBJC],
+                 [_AM_DEPENDENCIES([OBJC])],
+                 [m4_define([AC_PROG_OBJC],
+                            m4_defn([AC_PROG_OBJC])[_AM_DEPENDENCIES([OBJC])])])dnl
+AC_PROVIDE_IFELSE([AC_PROG_OBJCXX],
+                 [_AM_DEPENDENCIES([OBJCXX])],
+                 [m4_define([AC_PROG_OBJCXX],
+                            m4_defn([AC_PROG_OBJCXX])[_AM_DEPENDENCIES([OBJCXX])])])dnl
+])
+AC_REQUIRE([AM_SILENT_RULES])dnl
+dnl The testsuite driver may need to know about EXEEXT, so add the
+dnl 'am__EXEEXT' conditional if _AM_COMPILER_EXEEXT was seen.  This
+dnl macro is hooked onto _AC_COMPILER_EXEEXT early, see below.
+AC_CONFIG_COMMANDS_PRE(dnl
+[m4_provide_if([_AM_COMPILER_EXEEXT],
+  [AM_CONDITIONAL([am__EXEEXT], [test -n "$EXEEXT"])])])dnl
+
+# POSIX will say in a future version that running "rm -f" with no argument
+# is OK; and we want to be able to make that assumption in our Makefile
+# recipes.  So use an aggressive probe to check that the usage we want is
+# actually supported "in the wild" to an acceptable degree.
+# See automake bug#10828.
+# To make any issue more visible, cause the running configure to be aborted
+# by default if the 'rm' program in use doesn't match our expectations; the
+# user can still override this though.
+if rm -f && rm -fr && rm -rf; then : OK; else
+  cat >&2 <<'END'
+Oops!
+
+Your 'rm' program seems unable to run without file operands specified
+on the command line, even when the '-f' option is present.  This is contrary
+to the behaviour of most rm programs out there, and not conforming with
+the upcoming POSIX standard: <http://austingroupbugs.net/view.php?id=542>
+
+Please tell bug-automake@gnu.org about your system, including the value
+of your $PATH and any error possibly output before this message.  This
+can help us improve future automake versions.
+
+END
+  if test x"$ACCEPT_INFERIOR_RM_PROGRAM" = x"yes"; then
+    echo 'Configuration will proceed anyway, since you have set the' >&2
+    echo 'ACCEPT_INFERIOR_RM_PROGRAM variable to "yes"' >&2
+    echo >&2
+  else
+    cat >&2 <<'END'
+Aborting the configuration process, to ensure you take notice of the issue.
+
+You can download and install GNU coreutils to get an 'rm' implementation
+that behaves properly: <http://www.gnu.org/software/coreutils/>.
+
+If you want to complete the configuration process using your problematic
+'rm' anyway, export the environment variable ACCEPT_INFERIOR_RM_PROGRAM
+to "yes", and re-run configure.
+
+END
+    AC_MSG_ERROR([Your 'rm' program is bad, sorry.])
+  fi
+fi
+dnl The trailing newline in this macro's definition is deliberate, for
+dnl backward compatibility and to allow trailing 'dnl'-style comments
+dnl after the AM_INIT_AUTOMAKE invocation. See automake bug#16841.
+])
+
+dnl Hook into '_AC_COMPILER_EXEEXT' early to learn its expansion.  Do not
+dnl add the conditional right here, as _AC_COMPILER_EXEEXT may be further
+dnl mangled by Autoconf and run in a shell conditional statement.
+m4_define([_AC_COMPILER_EXEEXT],
+m4_defn([_AC_COMPILER_EXEEXT])[m4_provide([_AM_COMPILER_EXEEXT])])
+
+# When config.status generates a header, we must update the stamp-h file.
+# This file resides in the same directory as the config header
+# that is generated.  The stamp files are numbered to have different names.
+
+# Autoconf calls _AC_AM_CONFIG_HEADER_HOOK (when defined) in the
+# loop where config.status creates the headers, so we can generate
+# our stamp files there.
+AC_DEFUN([_AC_AM_CONFIG_HEADER_HOOK],
+[# Compute $1's index in $config_headers.
+_am_arg=$1
+_am_stamp_count=1
+for _am_header in $config_headers :; do
+  case $_am_header in
+    $_am_arg | $_am_arg:* )
+      break ;;
+    * )
+      _am_stamp_count=`expr $_am_stamp_count + 1` ;;
+  esac
+done
+echo "timestamp for $_am_arg" >`AS_DIRNAME(["$_am_arg"])`/stamp-h[]$_am_stamp_count])
+
+# Copyright (C) 2001-2014 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_PROG_INSTALL_SH
+# ------------------
+# Define $install_sh.
+AC_DEFUN([AM_PROG_INSTALL_SH],
+[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl
+if test x"${install_sh+set}" != xset; then
+  case $am_aux_dir in
+  *\ * | *\    *)
+    install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;;
+  *)
+    install_sh="\${SHELL} $am_aux_dir/install-sh"
+  esac
+fi
+AC_SUBST([install_sh])])
+
+# Copyright (C) 2003-2014 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# Check whether the underlying file-system supports filenames
+# with a leading dot.  For instance MS-DOS doesn't.
+AC_DEFUN([AM_SET_LEADING_DOT],
+[rm -rf .tst 2>/dev/null
+mkdir .tst 2>/dev/null
+if test -d .tst; then
+  am__leading_dot=.
+else
+  am__leading_dot=_
+fi
+rmdir .tst 2>/dev/null
+AC_SUBST([am__leading_dot])])
+
+# Check to see how 'make' treats includes.                 -*- Autoconf -*-
+
+# Copyright (C) 2001-2014 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_MAKE_INCLUDE()
+# -----------------
+# Check to see how make treats includes.
+AC_DEFUN([AM_MAKE_INCLUDE],
+[am_make=${MAKE-make}
+cat > confinc << 'END'
+am__doit:
+       @echo this is the am__doit target
+.PHONY: am__doit
+END
+# If we don't find an include directive, just comment out the code.
+AC_MSG_CHECKING([for style of include used by $am_make])
+am__include="#"
+am__quote=
+_am_result=none
+# First try GNU make style include.
+echo "include confinc" > confmf
+# Ignore all kinds of additional output from 'make'.
+case `$am_make -s -f confmf 2> /dev/null` in #(
+*the\ am__doit\ target*)
+  am__include=include
+  am__quote=
+  _am_result=GNU
+  ;;
+esac
+# Now try BSD make style include.
+if test "$am__include" = "#"; then
+   echo '.include "confinc"' > confmf
+   case `$am_make -s -f confmf 2> /dev/null` in #(
+   *the\ am__doit\ target*)
+     am__include=.include
+     am__quote="\""
+     _am_result=BSD
+     ;;
+   esac
+fi
+AC_SUBST([am__include])
+AC_SUBST([am__quote])
+AC_MSG_RESULT([$_am_result])
+rm -f confinc confmf
+])
+
+# Fake the existence of programs that GNU maintainers use.  -*- Autoconf -*-
+
+# Copyright (C) 1997-2014 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_MISSING_PROG(NAME, PROGRAM)
+# ------------------------------
+AC_DEFUN([AM_MISSING_PROG],
+[AC_REQUIRE([AM_MISSING_HAS_RUN])
+$1=${$1-"${am_missing_run}$2"}
+AC_SUBST($1)])
+
+# AM_MISSING_HAS_RUN
+# ------------------
+# Define MISSING if not defined so far and test if it is modern enough.
+# If it is, set am_missing_run to use it, otherwise, to nothing.
+AC_DEFUN([AM_MISSING_HAS_RUN],
+[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl
+AC_REQUIRE_AUX_FILE([missing])dnl
+if test x"${MISSING+set}" != xset; then
+  case $am_aux_dir in
+  *\ * | *\    *)
+    MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;;
+  *)
+    MISSING="\${SHELL} $am_aux_dir/missing" ;;
+  esac
+fi
+# Use eval to expand $SHELL
+if eval "$MISSING --is-lightweight"; then
+  am_missing_run="$MISSING "
+else
+  am_missing_run=
+  AC_MSG_WARN(['missing' script is too old or missing])
+fi
+])
+
+# Helper functions for option handling.                     -*- Autoconf -*-
+
+# Copyright (C) 2001-2014 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# _AM_MANGLE_OPTION(NAME)
+# -----------------------
+AC_DEFUN([_AM_MANGLE_OPTION],
+[[_AM_OPTION_]m4_bpatsubst($1, [[^a-zA-Z0-9_]], [_])])
+
+# _AM_SET_OPTION(NAME)
+# --------------------
+# Set option NAME.  Presently that only means defining a flag for this option.
+AC_DEFUN([_AM_SET_OPTION],
+[m4_define(_AM_MANGLE_OPTION([$1]), [1])])
+
+# _AM_SET_OPTIONS(OPTIONS)
+# ------------------------
+# OPTIONS is a space-separated list of Automake options.
+AC_DEFUN([_AM_SET_OPTIONS],
+[m4_foreach_w([_AM_Option], [$1], [_AM_SET_OPTION(_AM_Option)])])
+
+# _AM_IF_OPTION(OPTION, IF-SET, [IF-NOT-SET])
+# -------------------------------------------
+# Execute IF-SET if OPTION is set, IF-NOT-SET otherwise.
+AC_DEFUN([_AM_IF_OPTION],
+[m4_ifset(_AM_MANGLE_OPTION([$1]), [$2], [$3])])
+
+# Copyright (C) 1999-2014 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# _AM_PROG_CC_C_O
+# ---------------
+# Like AC_PROG_CC_C_O, but changed for automake.  We rewrite AC_PROG_CC
+# to automatically call this.
+AC_DEFUN([_AM_PROG_CC_C_O],
+[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl
+AC_REQUIRE_AUX_FILE([compile])dnl
+AC_LANG_PUSH([C])dnl
+AC_CACHE_CHECK(
+  [whether $CC understands -c and -o together],
+  [am_cv_prog_cc_c_o],
+  [AC_LANG_CONFTEST([AC_LANG_PROGRAM([])])
+  # Make sure it works both with $CC and with simple cc.
+  # Following AC_PROG_CC_C_O, we do the test twice because some
+  # compilers refuse to overwrite an existing .o file with -o,
+  # though they will create one.
+  am_cv_prog_cc_c_o=yes
+  for am_i in 1 2; do
+    if AM_RUN_LOG([$CC -c conftest.$ac_ext -o conftest2.$ac_objext]) \
+         && test -f conftest2.$ac_objext; then
+      : OK
+    else
+      am_cv_prog_cc_c_o=no
+      break
+    fi
+  done
+  rm -f core conftest*
+  unset am_i])
+if test "$am_cv_prog_cc_c_o" != yes; then
+   # Losing compiler, so override with the script.
+   # FIXME: It is wrong to rewrite CC.
+   # But if we don't then we get into trouble of one sort or another.
+   # A longer-term fix would be to have automake use am__CC in this case,
+   # and then we could set am__CC="\$(top_srcdir)/compile \$(CC)"
+   CC="$am_aux_dir/compile $CC"
+fi
+AC_LANG_POP([C])])
+
+# For backward compatibility.
+AC_DEFUN_ONCE([AM_PROG_CC_C_O], [AC_REQUIRE([AC_PROG_CC])])
+
+# Copyright (C) 2001-2014 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_RUN_LOG(COMMAND)
+# -------------------
+# Run COMMAND, save the exit status in ac_status, and log it.
+# (This has been adapted from Autoconf's _AC_RUN_LOG macro.)
+AC_DEFUN([AM_RUN_LOG],
+[{ echo "$as_me:$LINENO: $1" >&AS_MESSAGE_LOG_FD
+   ($1) >&AS_MESSAGE_LOG_FD 2>&AS_MESSAGE_LOG_FD
+   ac_status=$?
+   echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD
+   (exit $ac_status); }])
+
+# Check to make sure that the build environment is sane.    -*- Autoconf -*-
+
+# Copyright (C) 1996-2014 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_SANITY_CHECK
+# ---------------
+AC_DEFUN([AM_SANITY_CHECK],
+[AC_MSG_CHECKING([whether build environment is sane])
+# Reject unsafe characters in $srcdir or the absolute working directory
+# name.  Accept space and tab only in the latter.
+am_lf='
+'
+case `pwd` in
+  *[[\\\"\#\$\&\'\`$am_lf]]*)
+    AC_MSG_ERROR([unsafe absolute working directory name]);;
+esac
+case $srcdir in
+  *[[\\\"\#\$\&\'\`$am_lf\ \   ]]*)
+    AC_MSG_ERROR([unsafe srcdir value: '$srcdir']);;
+esac
+
+# Do 'set' in a subshell so we don't clobber the current shell's
+# arguments.  Must try -L first in case configure is actually a
+# symlink; some systems play weird games with the mod time of symlinks
+# (eg FreeBSD returns the mod time of the symlink's containing
+# directory).
+if (
+   am_has_slept=no
+   for am_try in 1 2; do
+     echo "timestamp, slept: $am_has_slept" > conftest.file
+     set X `ls -Lt "$srcdir/configure" conftest.file 2> /dev/null`
+     if test "$[*]" = "X"; then
+       # -L didn't work.
+       set X `ls -t "$srcdir/configure" conftest.file`
+     fi
+     if test "$[*]" != "X $srcdir/configure conftest.file" \
+       && test "$[*]" != "X conftest.file $srcdir/configure"; then
+
+       # If neither matched, then we have a broken ls.  This can happen
+       # if, for instance, CONFIG_SHELL is bash and it inherits a
+       # broken ls alias from the environment.  This has actually
+       # happened.  Such a system could not be considered "sane".
+       AC_MSG_ERROR([ls -t appears to fail.  Make sure there is not a broken
+  alias in your environment])
+     fi
+     if test "$[2]" = conftest.file || test $am_try -eq 2; then
+       break
+     fi
+     # Just in case.
+     sleep 1
+     am_has_slept=yes
+   done
+   test "$[2]" = conftest.file
+   )
+then
+   # Ok.
+   :
+else
+   AC_MSG_ERROR([newly created file is older than distributed files!
+Check your system clock])
+fi
+AC_MSG_RESULT([yes])
+# If we didn't sleep, we still need to ensure time stamps of config.status and
+# generated files are strictly newer.
+am_sleep_pid=
+if grep 'slept: no' conftest.file >/dev/null 2>&1; then
+  ( sleep 1 ) &
+  am_sleep_pid=$!
+fi
+AC_CONFIG_COMMANDS_PRE(
+  [AC_MSG_CHECKING([that generated files are newer than configure])
+   if test -n "$am_sleep_pid"; then
+     # Hide warnings about reused PIDs.
+     wait $am_sleep_pid 2>/dev/null
+   fi
+   AC_MSG_RESULT([done])])
+rm -f conftest.file
+])
+
+# Copyright (C) 2009-2014 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_SILENT_RULES([DEFAULT])
+# --------------------------
+# Enable less verbose build rules; with the default set to DEFAULT
+# ("yes" being less verbose, "no" or empty being verbose).
+AC_DEFUN([AM_SILENT_RULES],
+[AC_ARG_ENABLE([silent-rules], [dnl
+AS_HELP_STRING(
+  [--enable-silent-rules],
+  [less verbose build output (undo: "make V=1")])
+AS_HELP_STRING(
+  [--disable-silent-rules],
+  [verbose build output (undo: "make V=0")])dnl
+])
+case $enable_silent_rules in @%:@ (((
+  yes) AM_DEFAULT_VERBOSITY=0;;
+   no) AM_DEFAULT_VERBOSITY=1;;
+    *) AM_DEFAULT_VERBOSITY=m4_if([$1], [yes], [0], [1]);;
+esac
+dnl
+dnl A few 'make' implementations (e.g., NonStop OS and NextStep)
+dnl do not support nested variable expansions.
+dnl See automake bug#9928 and bug#10237.
+am_make=${MAKE-make}
+AC_CACHE_CHECK([whether $am_make supports nested variables],
+   [am_cv_make_support_nested_variables],
+   [if AS_ECHO([['TRUE=$(BAR$(V))
+BAR0=false
+BAR1=true
+V=1
+am__doit:
+       @$(TRUE)
+.PHONY: am__doit']]) | $am_make -f - >/dev/null 2>&1; then
+  am_cv_make_support_nested_variables=yes
+else
+  am_cv_make_support_nested_variables=no
+fi])
+if test $am_cv_make_support_nested_variables = yes; then
+  dnl Using '$V' instead of '$(V)' breaks IRIX make.
+  AM_V='$(V)'
+  AM_DEFAULT_V='$(AM_DEFAULT_VERBOSITY)'
+else
+  AM_V=$AM_DEFAULT_VERBOSITY
+  AM_DEFAULT_V=$AM_DEFAULT_VERBOSITY
+fi
+AC_SUBST([AM_V])dnl
+AM_SUBST_NOTMAKE([AM_V])dnl
+AC_SUBST([AM_DEFAULT_V])dnl
+AM_SUBST_NOTMAKE([AM_DEFAULT_V])dnl
+AC_SUBST([AM_DEFAULT_VERBOSITY])dnl
+AM_BACKSLASH='\'
+AC_SUBST([AM_BACKSLASH])dnl
+_AM_SUBST_NOTMAKE([AM_BACKSLASH])dnl
+])
+
+# Copyright (C) 2001-2014 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_PROG_INSTALL_STRIP
+# ---------------------
+# One issue with vendor 'install' (even GNU) is that you can't
+# specify the program used to strip binaries.  This is especially
+# annoying in cross-compiling environments, where the build's strip
+# is unlikely to handle the host's binaries.
+# Fortunately install-sh will honor a STRIPPROG variable, so we
+# always use install-sh in "make install-strip", and initialize
+# STRIPPROG with the value of the STRIP variable (set by the user).
+AC_DEFUN([AM_PROG_INSTALL_STRIP],
+[AC_REQUIRE([AM_PROG_INSTALL_SH])dnl
+# Installed binaries are usually stripped using 'strip' when the user
+# run "make install-strip".  However 'strip' might not be the right
+# tool to use in cross-compilation environments, therefore Automake
+# will honor the 'STRIP' environment variable to overrule this program.
+dnl Don't test for $cross_compiling = yes, because it might be 'maybe'.
+if test "$cross_compiling" != no; then
+  AC_CHECK_TOOL([STRIP], [strip], :)
+fi
+INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s"
+AC_SUBST([INSTALL_STRIP_PROGRAM])])
+
+# Copyright (C) 2006-2014 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# _AM_SUBST_NOTMAKE(VARIABLE)
+# ---------------------------
+# Prevent Automake from outputting VARIABLE = @VARIABLE@ in Makefile.in.
+# This macro is traced by Automake.
+AC_DEFUN([_AM_SUBST_NOTMAKE])
+
+# AM_SUBST_NOTMAKE(VARIABLE)
+# --------------------------
+# Public sister of _AM_SUBST_NOTMAKE.
+AC_DEFUN([AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE($@)])
+
+# Check how to create a tarball.                            -*- Autoconf -*-
+
+# Copyright (C) 2004-2014 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# _AM_PROG_TAR(FORMAT)
+# --------------------
+# Check how to create a tarball in format FORMAT.
+# FORMAT should be one of 'v7', 'ustar', or 'pax'.
+#
+# Substitute a variable $(am__tar) that is a command
+# writing to stdout a FORMAT-tarball containing the directory
+# $tardir.
+#     tardir=directory && $(am__tar) > result.tar
+#
+# Substitute a variable $(am__untar) that extract such
+# a tarball read from stdin.
+#     $(am__untar) < result.tar
+#
+AC_DEFUN([_AM_PROG_TAR],
+[# Always define AMTAR for backward compatibility.  Yes, it's still used
+# in the wild :-(  We should find a proper way to deprecate it ...
+AC_SUBST([AMTAR], ['$${TAR-tar}'])
+
+# We'll loop over all known methods to create a tar archive until one works.
+_am_tools='gnutar m4_if([$1], [ustar], [plaintar]) pax cpio none'
+
+m4_if([$1], [v7],
+  [am__tar='$${TAR-tar} chof - "$$tardir"' am__untar='$${TAR-tar} xf -'],
+
+  [m4_case([$1],
+    [ustar],
+     [# The POSIX 1988 'ustar' format is defined with fixed-size fields.
+      # There is notably a 21 bits limit for the UID and the GID.  In fact,
+      # the 'pax' utility can hang on bigger UID/GID (see automake bug#8343
+      # and bug#13588).
+      am_max_uid=2097151 # 2^21 - 1
+      am_max_gid=$am_max_uid
+      # The $UID and $GID variables are not portable, so we need to resort
+      # to the POSIX-mandated id(1) utility.  Errors in the 'id' calls
+      # below are definitely unexpected, so allow the users to see them
+      # (that is, avoid stderr redirection).
+      am_uid=`id -u || echo unknown`
+      am_gid=`id -g || echo unknown`
+      AC_MSG_CHECKING([whether UID '$am_uid' is supported by ustar format])
+      if test $am_uid -le $am_max_uid; then
+         AC_MSG_RESULT([yes])
+      else
+         AC_MSG_RESULT([no])
+         _am_tools=none
+      fi
+      AC_MSG_CHECKING([whether GID '$am_gid' is supported by ustar format])
+      if test $am_gid -le $am_max_gid; then
+         AC_MSG_RESULT([yes])
+      else
+        AC_MSG_RESULT([no])
+        _am_tools=none
+      fi],
+
+  [pax],
+    [],
+
+  [m4_fatal([Unknown tar format])])
+
+  AC_MSG_CHECKING([how to create a $1 tar archive])
+
+  # Go ahead even if we have the value already cached.  We do so because we
+  # need to set the values for the 'am__tar' and 'am__untar' variables.
+  _am_tools=${am_cv_prog_tar_$1-$_am_tools}
+
+  for _am_tool in $_am_tools; do
+    case $_am_tool in
+    gnutar)
+      for _am_tar in tar gnutar gtar; do
+        AM_RUN_LOG([$_am_tar --version]) && break
+      done
+      am__tar="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$$tardir"'
+      am__tar_="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$tardir"'
+      am__untar="$_am_tar -xf -"
+      ;;
+    plaintar)
+      # Must skip GNU tar: if it does not support --format= it doesn't create
+      # ustar tarball either.
+      (tar --version) >/dev/null 2>&1 && continue
+      am__tar='tar chf - "$$tardir"'
+      am__tar_='tar chf - "$tardir"'
+      am__untar='tar xf -'
+      ;;
+    pax)
+      am__tar='pax -L -x $1 -w "$$tardir"'
+      am__tar_='pax -L -x $1 -w "$tardir"'
+      am__untar='pax -r'
+      ;;
+    cpio)
+      am__tar='find "$$tardir" -print | cpio -o -H $1 -L'
+      am__tar_='find "$tardir" -print | cpio -o -H $1 -L'
+      am__untar='cpio -i -H $1 -d'
+      ;;
+    none)
+      am__tar=false
+      am__tar_=false
+      am__untar=false
+      ;;
+    esac
+
+    # If the value was cached, stop now.  We just wanted to have am__tar
+    # and am__untar set.
+    test -n "${am_cv_prog_tar_$1}" && break
+
+    # tar/untar a dummy directory, and stop if the command works.
+    rm -rf conftest.dir
+    mkdir conftest.dir
+    echo GrepMe > conftest.dir/file
+    AM_RUN_LOG([tardir=conftest.dir && eval $am__tar_ >conftest.tar])
+    rm -rf conftest.dir
+    if test -s conftest.tar; then
+      AM_RUN_LOG([$am__untar <conftest.tar])
+      AM_RUN_LOG([cat conftest.dir/file])
+      grep GrepMe conftest.dir/file >/dev/null 2>&1 && break
+    fi
+  done
+  rm -rf conftest.dir
+
+  AC_CACHE_VAL([am_cv_prog_tar_$1], [am_cv_prog_tar_$1=$_am_tool])
+  AC_MSG_RESULT([$am_cv_prog_tar_$1])])
+
+AC_SUBST([am__tar])
+AC_SUBST([am__untar])
+]) # _AM_PROG_TAR
+
diff --git a/blackbox.R b/blackbox.R
new file mode 100644 (file)
index 0000000..1162252
--- /dev/null
@@ -0,0 +1,19 @@
+# -*- makefile -*-
+
+blackbox : [X] GTK COMMON blackbox blackbox-icon|no-icon
+
+blackbox : [G] WINDOWS COMMON blackbox blackbox.res|noicon.res
+
+ALL += blackbox[COMBINED]
+
+!begin am gtk
+GAMES += blackbox
+!end
+
+!begin >list.c
+    A(blackbox) \
+!end
+
+!begin >gamedesc.txt
+blackbox:blackbox.exe:Black Box:Ball-finding puzzle:Find the hidden balls in the box by bouncing laser beams off them.
+!end
diff --git a/blackbox.c b/blackbox.c
new file mode 100644 (file)
index 0000000..629b7ec
--- /dev/null
@@ -0,0 +1,1543 @@
+/*
+ * blackbox.c: implementation of 'Black Box'.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+
+#define PREFERRED_TILE_SIZE 32
+#define FLASH_FRAME 0.2F
+
+/* Terminology, for ease of reading various macros scattered about the place.
+ *
+ * The 'arena' is the inner area where the balls are placed. This is
+ *   indexed from (0,0) to (w-1,h-1) but its offset in the grid is (1,1).
+ *
+ * The 'range' (firing range) is the bit around the edge where
+ *   the lasers are fired from. This is indexed from 0 --> (2*(w+h) - 1),
+ *   starting at the top left ((1,0) on the grid) and moving clockwise.
+ *
+ * The 'grid' is just the big array containing arena and range;
+ *   locations (0,0), (0,w+1), (h+1,w+1) and (h+1,0) are unused.
+ */
+
+enum {
+    COL_BACKGROUND, COL_COVER, COL_LOCK,
+    COL_TEXT, COL_FLASHTEXT,
+    COL_HIGHLIGHT, COL_LOWLIGHT, COL_GRID,
+    COL_BALL, COL_WRONG, COL_BUTTON,
+    COL_CURSOR,
+    NCOLOURS
+};
+
+struct game_params {
+    int w, h;
+    int minballs, maxballs;
+};
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = ret->h = 8;
+    ret->minballs = ret->maxballs = 5;
+
+    return ret;
+}
+
+static const game_params blackbox_presets[] = {
+    { 5, 5, 3, 3 },
+    { 8, 8, 5, 5 },
+    { 8, 8, 3, 6 },
+    { 10, 10, 5, 5 },
+    { 10, 10, 4, 10 }
+};
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    char str[80];
+    game_params *ret;
+
+    if (i < 0 || i >= lenof(blackbox_presets))
+        return FALSE;
+
+    ret = snew(game_params);
+    *ret = blackbox_presets[i];
+
+    if (ret->minballs == ret->maxballs)
+        sprintf(str, "%dx%d, %d balls",
+                ret->w, ret->h, ret->minballs);
+    else
+        sprintf(str, "%dx%d, %d-%d balls",
+                ret->w, ret->h, ret->minballs, ret->maxballs);
+
+    *name = dupstr(str);
+    *params = ret;
+    return TRUE;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+static void decode_params(game_params *params, char const *string)
+{
+    char const *p = string;
+    game_params *defs = default_params();
+
+    *params = *defs; free_params(defs);
+
+    while (*p) {
+        switch (*p++) {
+        case 'w':
+            params->w = atoi(p);
+            while (*p && isdigit((unsigned char)*p)) p++;
+            break;
+
+        case 'h':
+            params->h = atoi(p);
+            while (*p && isdigit((unsigned char)*p)) p++;
+            break;
+
+        case 'm':
+            params->minballs = atoi(p);
+            while (*p && isdigit((unsigned char)*p)) p++;
+            break;
+
+        case 'M':
+            params->maxballs = atoi(p);
+            while (*p && isdigit((unsigned char)*p)) p++;
+            break;
+
+        default:
+            ;
+        }
+    }
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char str[256];
+
+    sprintf(str, "w%dh%dm%dM%d",
+            params->w, params->h, params->minballs, params->maxballs);
+    return dupstr(str);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+    char buf[80];
+
+    ret = snewn(4, config_item);
+
+    ret[0].name = "Width";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->w);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Height";
+    ret[1].type = C_STRING;
+    sprintf(buf, "%d", params->h);
+    ret[1].sval = dupstr(buf);
+    ret[1].ival = 0;
+
+    ret[2].name = "No. of balls";
+    ret[2].type = C_STRING;
+    if (params->minballs == params->maxballs)
+        sprintf(buf, "%d", params->minballs);
+    else
+        sprintf(buf, "%d-%d", params->minballs, params->maxballs);
+    ret[2].sval = dupstr(buf);
+    ret[2].ival = 0;
+
+    ret[3].name = NULL;
+    ret[3].type = C_END;
+    ret[3].sval = NULL;
+    ret[3].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = atoi(cfg[0].sval);
+    ret->h = atoi(cfg[1].sval);
+
+    /* Allow 'a-b' for a range, otherwise assume a single number. */
+    if (sscanf(cfg[2].sval, "%d-%d", &ret->minballs, &ret->maxballs) < 2)
+        ret->minballs = ret->maxballs = atoi(cfg[2].sval);
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    if (params->w < 2 || params->h < 2)
+        return "Width and height must both be at least two";
+    /* next one is just for ease of coding stuff into 'char'
+     * types, and could be worked around if required. */
+    if (params->w > 255 || params->h > 255)
+        return "Widths and heights greater than 255 are not supported";
+    if (params->minballs > params->maxballs)
+        return "Minimum number of balls may not be greater than maximum";
+    if (params->minballs >= params->w * params->h)
+        return "Too many balls to fit in grid";
+    return NULL;
+}
+
+/*
+ * We store: width | height | ball1x | ball1y | [ ball2x | ball2y | [...] ]
+ * all stored as unsigned chars; validate_params has already
+ * checked this won't overflow an 8-bit char.
+ * Then we obfuscate it.
+ */
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                          char **aux, int interactive)
+{
+    int nballs = params->minballs, i;
+    char *grid, *ret;
+    unsigned char *bmp;
+
+    if (params->maxballs > params->minballs)
+        nballs += random_upto(rs, params->maxballs - params->minballs + 1);
+
+    grid = snewn(params->w*params->h, char);
+    memset(grid, 0, params->w * params->h * sizeof(char));
+
+    bmp = snewn(nballs*2 + 2, unsigned char);
+    memset(bmp, 0, (nballs*2 + 2) * sizeof(unsigned char));
+
+    bmp[0] = params->w;
+    bmp[1] = params->h;
+
+    for (i = 0; i < nballs; i++) {
+        int x, y;
+
+        do {
+            x = random_upto(rs, params->w);
+            y = random_upto(rs, params->h);
+        } while (grid[y*params->w + x]);
+
+        grid[y*params->w + x] = 1;
+
+        bmp[(i+1)*2 + 0] = x;
+        bmp[(i+1)*2 + 1] = y;
+    }
+    sfree(grid);
+
+    obfuscate_bitmap(bmp, (nballs*2 + 2) * 8, FALSE);
+    ret = bin2hex(bmp, nballs*2 + 2);
+    sfree(bmp);
+
+    return ret;
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    int nballs, dlen = strlen(desc), i;
+    unsigned char *bmp;
+    char *ret;
+
+    /* the bitmap is 2+(nballs*2) long; the hex version is double that. */
+    nballs = ((dlen/2)-2)/2;
+
+    if (dlen < 4 || dlen % 4 ||
+        nballs < params->minballs || nballs > params->maxballs)
+        return "Game description is wrong length";
+
+    bmp = hex2bin(desc, nballs*2 + 2);
+    obfuscate_bitmap(bmp, (nballs*2 + 2) * 8, TRUE);
+    ret = "Game description is corrupted";
+    /* check general grid size */
+    if (bmp[0] != params->w || bmp[1] != params->h)
+        goto done;
+    /* check each ball will fit on that grid */
+    for (i = 0; i < nballs; i++) {
+        int x = bmp[(i+1)*2 + 0], y = bmp[(i+1)*2 + 1];
+        if (x < 0 || y < 0 || x >= params->w || y >= params->h)
+            goto done;
+    }
+    ret = NULL;
+
+done:
+    sfree(bmp);
+    return ret;
+}
+
+#define BALL_CORRECT    0x01
+#define BALL_GUESS      0x02
+#define BALL_LOCK       0x04
+
+#define LASER_FLAGMASK  0x1f800
+#define LASER_OMITTED    0x0800
+#define LASER_REFLECT    0x1000
+#define LASER_HIT        0x2000
+#define LASER_WRONG      0x4000
+#define LASER_FLASHED    0x8000
+#define LASER_EMPTY      (~0)
+
+#define FLAG_CURSOR     0x10000 /* needs to be disjoint from both sets */
+
+struct game_state {
+    int w, h, minballs, maxballs, nballs, nlasers;
+    unsigned int *grid; /* (w+2)x(h+2), to allow for laser firing range */
+    unsigned int *exits; /* one per laser */
+    int done;           /* user has finished placing his own balls. */
+    int laserno;        /* number of next laser to be fired. */
+    int nguesses, reveal, justwrong, nright, nwrong, nmissed;
+};
+
+#define GRID(s,x,y) ((s)->grid[(y)*((s)->w+2) + (x)])
+
+#define RANGECHECK(s,x) ((x) >= 0 && (x) <= (s)->nlasers)
+
+/* specify numbers because they must match array indexes. */
+enum { DIR_UP = 0, DIR_RIGHT = 1, DIR_DOWN = 2, DIR_LEFT = 3 };
+
+struct offset { int x, y; };
+
+static const struct offset offsets[] = {
+    {  0, -1 }, /* up */
+    {  1,  0 }, /* right */
+    {  0,  1 }, /* down */
+    { -1,  0 }  /* left */
+};
+
+#ifdef DEBUGGING
+static const char *dirstrs[] = {
+    "UP", "RIGHT", "DOWN", "LEFT"
+};
+#endif
+
+static int range2grid(const game_state *state, int rangeno, int *x, int *y,
+                      int *direction)
+{
+    if (rangeno < 0)
+        return 0;
+
+    if (rangeno < state->w) {
+        /* top row; from (1,0) to (w,0) */
+        *x = rangeno + 1;
+        *y = 0;
+        *direction = DIR_DOWN;
+        return 1;
+    }
+    rangeno -= state->w;
+    if (rangeno < state->h) {
+        /* RHS; from (w+1, 1) to (w+1, h) */
+        *x = state->w+1;
+        *y = rangeno + 1;
+        *direction = DIR_LEFT;
+        return 1;
+    }
+    rangeno -= state->h;
+    if (rangeno < state->w) {
+        /* bottom row; from (1, h+1) to (w, h+1); counts backwards */
+        *x = (state->w - rangeno);
+        *y = state->h+1;
+        *direction = DIR_UP;
+        return 1;
+    }
+    rangeno -= state->w;
+    if (rangeno < state->h) {
+        /* LHS; from (0, 1) to (0, h); counts backwards */
+        *x = 0;
+        *y = (state->h - rangeno);
+        *direction = DIR_RIGHT;
+        return 1;
+    }
+    return 0;
+}
+
+static int grid2range(const game_state *state, int x, int y, int *rangeno)
+{
+    int ret, x1 = state->w+1, y1 = state->h+1;
+
+    if (x > 0 && x < x1 && y > 0 && y < y1) return 0; /* in arena */
+    if (x < 0 || x > x1 || y < 0 || y > y1) return 0; /* outside grid */
+
+    if ((x == 0 || x == x1) && (y == 0 || y == y1))
+        return 0; /* one of 4 corners */
+
+    if (y == 0) {               /* top line */
+        ret = x - 1;
+    } else if (x == x1) {       /* RHS */
+        ret = y - 1 + state->w;
+    } else if (y == y1) {       /* Bottom [and counts backwards] */
+        ret = (state->w - x) + state->w + state->h;
+    } else {                    /* LHS [and counts backwards ] */
+        ret = (state->h-y) + state->w + state->w + state->h;
+    }
+    *rangeno = ret;
+    debug(("grid2range: (%d,%d) rangeno = %d\n", x, y, ret));
+    return 1;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    game_state *state = snew(game_state);
+    int dlen = strlen(desc), i;
+    unsigned char *bmp;
+
+    state->minballs = params->minballs;
+    state->maxballs = params->maxballs;
+    state->nballs = ((dlen/2)-2)/2;
+
+    bmp = hex2bin(desc, state->nballs*2 + 2);
+    obfuscate_bitmap(bmp, (state->nballs*2 + 2) * 8, TRUE);
+
+    state->w = bmp[0]; state->h = bmp[1];
+    state->nlasers = 2 * (state->w + state->h);
+
+    state->grid = snewn((state->w+2)*(state->h+2), unsigned int);
+    memset(state->grid, 0, (state->w+2)*(state->h+2) * sizeof(unsigned int));
+
+    state->exits = snewn(state->nlasers, unsigned int);
+    memset(state->exits, LASER_EMPTY, state->nlasers * sizeof(unsigned int));
+
+    for (i = 0; i < state->nballs; i++) {
+        GRID(state, bmp[(i+1)*2 + 0]+1, bmp[(i+1)*2 + 1]+1) = BALL_CORRECT;
+    }
+    sfree(bmp);
+
+    state->done = state->nguesses = state->reveal = state->justwrong =
+        state->nright = state->nwrong = state->nmissed = 0;
+    state->laserno = 1;
+
+    return state;
+}
+
+#define XFER(x) ret->x = state->x
+
+static game_state *dup_game(const game_state *state)
+{
+    game_state *ret = snew(game_state);
+
+    XFER(w); XFER(h);
+    XFER(minballs); XFER(maxballs);
+    XFER(nballs); XFER(nlasers);
+
+    ret->grid = snewn((ret->w+2)*(ret->h+2), unsigned int);
+    memcpy(ret->grid, state->grid, (ret->w+2)*(ret->h+2) * sizeof(unsigned int));
+    ret->exits = snewn(ret->nlasers, unsigned int);
+    memcpy(ret->exits, state->exits, ret->nlasers * sizeof(unsigned int));
+
+    XFER(done);
+    XFER(laserno);
+    XFER(nguesses);
+    XFER(reveal);
+    XFER(justwrong);
+    XFER(nright); XFER(nwrong); XFER(nmissed);
+
+    return ret;
+}
+
+#undef XFER
+
+static void free_game(game_state *state)
+{
+    sfree(state->exits);
+    sfree(state->grid);
+    sfree(state);
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    return dupstr("S");
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    return NULL;
+}
+
+struct game_ui {
+    int flash_laserno;
+    int errors, newmove;
+    int cur_x, cur_y, cur_visible;
+    int flash_laser; /* 0 = never, 1 = always, 2 = if anim. */
+};
+
+static game_ui *new_ui(const game_state *state)
+{
+    game_ui *ui = snew(game_ui);
+    ui->flash_laserno = LASER_EMPTY;
+    ui->errors = 0;
+    ui->newmove = FALSE;
+
+    ui->cur_x = ui->cur_y = 1;
+    ui->cur_visible = 0;
+
+    ui->flash_laser = 0;
+
+    return ui;
+}
+
+static void free_ui(game_ui *ui)
+{
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    char buf[80];
+    /*
+     * The error counter needs preserving across a serialisation.
+     */
+    sprintf(buf, "E%d", ui->errors);
+    return dupstr(buf);
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+    sscanf(encoding, "E%d", &ui->errors);
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+    /*
+     * If we've encountered a `justwrong' state as a result of
+     * actually making a move, increment the ui error counter.
+     */
+    if (newstate->justwrong && ui->newmove)
+       ui->errors++;
+    ui->newmove = FALSE;
+}
+
+#define OFFSET(gx,gy,o) do {                                    \
+    int off = (4 + (o) % 4) % 4;                                \
+    (gx) += offsets[off].x;                                     \
+    (gy) += offsets[off].y;                                     \
+} while(0)
+
+enum { LOOK_LEFT, LOOK_FORWARD, LOOK_RIGHT };
+
+/* Given a position and a direction, check whether we can see a ball in front
+ * of us, or to our front-left or front-right. */
+static int isball(game_state *state, int gx, int gy, int direction, int lookwhere)
+{
+    debug(("isball, (%d, %d), dir %s, lookwhere %s\n", gx, gy, dirstrs[direction],
+           lookwhere == LOOK_LEFT ? "LEFT" :
+           lookwhere == LOOK_FORWARD ? "FORWARD" : "RIGHT"));
+    OFFSET(gx,gy,direction);
+    if (lookwhere == LOOK_LEFT)
+        OFFSET(gx,gy,direction-1);
+    else if (lookwhere == LOOK_RIGHT)
+        OFFSET(gx,gy,direction+1);
+    else if (lookwhere != LOOK_FORWARD)
+        assert(!"unknown lookwhere");
+
+    debug(("isball, new (%d, %d)\n", gx, gy));
+
+    /* if we're off the grid (into the firing range) there's never a ball. */
+    if (gx < 1 || gy < 1 || gx > state->w || gy > state->h)
+        return 0;
+
+    if (GRID(state, gx,gy) & BALL_CORRECT)
+        return 1;
+
+    return 0;
+}
+
+static int fire_laser_internal(game_state *state, int x, int y, int direction)
+{
+    int unused, lno, tmp;
+
+    tmp = grid2range(state, x, y, &lno);
+    assert(tmp);
+
+    /* deal with strange initial reflection rules (that stop
+     * you turning down the laser range) */
+
+    /* I've just chosen to prioritise instant-hit over instant-reflection;
+     * I can't find anywhere that gives me a definite algorithm for this. */
+    if (isball(state, x, y, direction, LOOK_FORWARD)) {
+        debug(("Instant hit at (%d, %d)\n", x, y));
+       return LASER_HIT;              /* hit */
+    }
+
+    if (isball(state, x, y, direction, LOOK_LEFT) ||
+        isball(state, x, y, direction, LOOK_RIGHT)) {
+        debug(("Instant reflection at (%d, %d)\n", x, y));
+       return LASER_REFLECT;          /* reflection */
+    }
+    /* move us onto the grid. */
+    OFFSET(x, y, direction);
+
+    while (1) {
+        debug(("fire_laser: looping at (%d, %d) pointing %s\n",
+               x, y, dirstrs[direction]));
+        if (grid2range(state, x, y, &unused)) {
+            int exitno;
+
+           tmp = grid2range(state, x, y, &exitno);
+           assert(tmp);
+
+           return (lno == exitno ? LASER_REFLECT : exitno);
+        }
+        /* paranoia. This obviously should never happen */
+        assert(!(GRID(state, x, y) & BALL_CORRECT));
+
+        if (isball(state, x, y, direction, LOOK_FORWARD)) {
+            /* we're facing a ball; send back a reflection. */
+            debug(("Ball ahead of (%d, %d)", x, y));
+            return LASER_HIT;         /* hit */
+        }
+
+        if (isball(state, x, y, direction, LOOK_LEFT)) {
+            /* ball to our left; rotate clockwise and look again. */
+            debug(("Ball to left; turning clockwise.\n"));
+            direction += 1; direction %= 4;
+            continue;
+        }
+        if (isball(state, x, y, direction, LOOK_RIGHT)) {
+            /* ball to our right; rotate anti-clockwise and look again. */
+            debug(("Ball to rightl turning anti-clockwise.\n"));
+            direction += 3; direction %= 4;
+            continue;
+        }
+        /* ... otherwise, we have no balls ahead of us so just move one step. */
+        debug(("No balls; moving forwards.\n"));
+        OFFSET(x, y, direction);
+    }
+}
+
+static int laser_exit(game_state *state, int entryno)
+{
+    int tmp, x, y, direction;
+
+    tmp = range2grid(state, entryno, &x, &y, &direction);
+    assert(tmp);
+
+    return fire_laser_internal(state, x, y, direction);
+}
+
+static void fire_laser(game_state *state, int entryno)
+{
+    int tmp, exitno, x, y, direction;
+
+    tmp = range2grid(state, entryno, &x, &y, &direction);
+    assert(tmp);
+
+    exitno = fire_laser_internal(state, x, y, direction);
+
+    if (exitno == LASER_HIT || exitno == LASER_REFLECT) {
+       GRID(state, x, y) = state->exits[entryno] = exitno;
+    } else {
+       int newno = state->laserno++;
+       int xend, yend, unused;
+       tmp = range2grid(state, exitno, &xend, &yend, &unused);
+       assert(tmp);
+       GRID(state, x, y) = GRID(state, xend, yend) = newno;
+       state->exits[entryno] = exitno;
+       state->exits[exitno] = entryno;
+    }
+}
+
+/* Checks that the guessed balls in the state match up with the real balls
+ * for all possible lasers (i.e. not just the ones that the player might
+ * have already guessed). This is required because any layout with >4 balls
+ * might have multiple valid solutions. Returns non-zero for a 'correct'
+ * (i.e. consistent) layout. */
+static int check_guesses(game_state *state, int cagey)
+{
+    game_state *solution, *guesses;
+    int i, x, y, n, unused, tmp;
+    int ret = 0;
+
+    if (cagey) {
+       /*
+        * First, check that each laser the player has already
+        * fired is consistent with the layout. If not, show them
+        * one error they've made and reveal no further
+        * information.
+        *
+        * Failing that, check to see whether the player would have
+        * been able to fire any laser which distinguished the real
+        * solution from their guess. If so, show them one such
+        * laser and reveal no further information.
+        */
+       guesses = dup_game(state);
+       /* clear out BALL_CORRECT on guess, make BALL_GUESS BALL_CORRECT. */
+       for (x = 1; x <= state->w; x++) {
+           for (y = 1; y <= state->h; y++) {
+               GRID(guesses, x, y) &= ~BALL_CORRECT;
+               if (GRID(guesses, x, y) & BALL_GUESS)
+                   GRID(guesses, x, y) |= BALL_CORRECT;
+           }
+       }
+       n = 0;
+       for (i = 0; i < guesses->nlasers; i++) {
+           if (guesses->exits[i] != LASER_EMPTY &&
+               guesses->exits[i] != laser_exit(guesses, i))
+               n++;
+       }
+       if (n) {
+           /*
+            * At least one of the player's existing lasers
+            * contradicts their ball placement. Pick a random one,
+            * highlight it, and return.
+            *
+            * A temporary random state is created from the current
+            * grid, so that repeating the same marking will give
+            * the same answer instead of a different one.
+            */
+           random_state *rs = random_new((char *)guesses->grid,
+                                         (state->w+2)*(state->h+2) *
+                                         sizeof(unsigned int));
+           n = random_upto(rs, n);
+           random_free(rs);
+           for (i = 0; i < guesses->nlasers; i++) {
+               if (guesses->exits[i] != LASER_EMPTY &&
+                   guesses->exits[i] != laser_exit(guesses, i) &&
+                   n-- == 0) {
+                   state->exits[i] |= LASER_WRONG;
+                   tmp = laser_exit(state, i);
+                   if (RANGECHECK(state, tmp))
+                       state->exits[tmp] |= LASER_WRONG;
+                   state->justwrong = TRUE;
+                   free_game(guesses);
+                   return 0;
+               }
+           }
+       }
+       n = 0;
+       for (i = 0; i < guesses->nlasers; i++) {
+           if (guesses->exits[i] == LASER_EMPTY &&
+               laser_exit(state, i) != laser_exit(guesses, i))
+               n++;
+       }
+       if (n) {
+           /*
+            * At least one of the player's unfired lasers would
+            * demonstrate their ball placement to be wrong. Pick a
+            * random one, highlight it, and return.
+            *
+            * A temporary random state is created from the current
+            * grid, so that repeating the same marking will give
+            * the same answer instead of a different one.
+            */
+           random_state *rs = random_new((char *)guesses->grid,
+                                         (state->w+2)*(state->h+2) *
+                                         sizeof(unsigned int));
+           n = random_upto(rs, n);
+           random_free(rs);
+           for (i = 0; i < guesses->nlasers; i++) {
+               if (guesses->exits[i] == LASER_EMPTY &&
+                   laser_exit(state, i) != laser_exit(guesses, i) &&
+                   n-- == 0) {
+                   fire_laser(state, i);
+                   state->exits[i] |= LASER_OMITTED;
+                   tmp = laser_exit(state, i);
+                   if (RANGECHECK(state, tmp))
+                       state->exits[tmp] |= LASER_OMITTED;
+                   state->justwrong = TRUE;
+                   free_game(guesses);
+                   return 0;
+               }
+           }
+       }
+       free_game(guesses);
+    }
+
+    /* duplicate the state (to solution) */
+    solution = dup_game(state);
+
+    /* clear out the lasers of solution */
+    for (i = 0; i < solution->nlasers; i++) {
+        tmp = range2grid(solution, i, &x, &y, &unused);
+        assert(tmp);
+        GRID(solution, x, y) = 0;
+        solution->exits[i] = LASER_EMPTY;
+    }
+
+    /* duplicate solution to guess. */
+    guesses = dup_game(solution);
+
+    /* clear out BALL_CORRECT on guess, make BALL_GUESS BALL_CORRECT. */
+    for (x = 1; x <= state->w; x++) {
+        for (y = 1; y <= state->h; y++) {
+            GRID(guesses, x, y) &= ~BALL_CORRECT;
+            if (GRID(guesses, x, y) & BALL_GUESS)
+                GRID(guesses, x, y) |= BALL_CORRECT;
+        }
+    }
+
+    /* for each laser (on both game_states), fire it if it hasn't been fired.
+     * If one has been fired (or received a hit) and another hasn't, we know
+     * the ball layouts didn't match and can short-circuit return. */
+    for (i = 0; i < solution->nlasers; i++) {
+        if (solution->exits[i] == LASER_EMPTY)
+            fire_laser(solution, i);
+        if (guesses->exits[i] == LASER_EMPTY)
+            fire_laser(guesses, i);
+    }
+
+    /* check each game_state's laser against the other; if any differ, return 0 */
+    ret = 1;
+    for (i = 0; i < solution->nlasers; i++) {
+        tmp = range2grid(solution, i, &x, &y, &unused);
+        assert(tmp);
+
+        if (solution->exits[i] != guesses->exits[i]) {
+            /* If the original state didn't have this shot fired,
+             * and it would be wrong between the guess and the solution,
+             * add it. */
+            if (state->exits[i] == LASER_EMPTY) {
+                state->exits[i] = solution->exits[i];
+                if (state->exits[i] == LASER_REFLECT ||
+                    state->exits[i] == LASER_HIT)
+                    GRID(state, x, y) = state->exits[i];
+                else {
+                    /* add a new shot, incrementing state's laser count. */
+                    int ex, ey, newno = state->laserno++;
+                    tmp = range2grid(state, state->exits[i], &ex, &ey, &unused);
+                    assert(tmp);
+                    GRID(state, x, y) = newno;
+                    GRID(state, ex, ey) = newno;
+                }
+               state->exits[i] |= LASER_OMITTED;
+            } else {
+               state->exits[i] |= LASER_WRONG;
+           }
+            ret = 0;
+        }
+    }
+    if (ret == 0 ||
+       state->nguesses < state->minballs ||
+       state->nguesses > state->maxballs) goto done;
+
+    /* fix up original state so the 'correct' balls end up matching the guesses,
+     * as we've just proved that they were equivalent. */
+    for (x = 1; x <= state->w; x++) {
+        for (y = 1; y <= state->h; y++) {
+            if (GRID(state, x, y) & BALL_GUESS)
+                GRID(state, x, y) |= BALL_CORRECT;
+            else
+                GRID(state, x, y) &= ~BALL_CORRECT;
+        }
+    }
+
+done:
+    /* fill in nright and nwrong. */
+    state->nright = state->nwrong = state->nmissed = 0;
+    for (x = 1; x <= state->w; x++) {
+        for (y = 1; y <= state->h; y++) {
+            int bs = GRID(state, x, y) & (BALL_GUESS | BALL_CORRECT);
+            if (bs == (BALL_GUESS | BALL_CORRECT))
+                state->nright++;
+            else if (bs == BALL_GUESS)
+                state->nwrong++;
+            else if (bs == BALL_CORRECT)
+                state->nmissed++;
+        }
+    }
+    free_game(solution);
+    free_game(guesses);
+    state->reveal = 1;
+    return ret;
+}
+
+#define TILE_SIZE (ds->tilesize)
+
+#define TODRAW(x) ((TILE_SIZE * (x)) + (TILE_SIZE / 2))
+#define FROMDRAW(x) (((x) - (TILE_SIZE / 2)) / TILE_SIZE)
+
+#define CAN_REVEAL(state) ((state)->nguesses >= (state)->minballs && \
+                          (state)->nguesses <= (state)->maxballs && \
+                          !(state)->reveal && !(state)->justwrong)
+
+struct game_drawstate {
+    int tilesize, crad, rrad, w, h; /* w and h to make macros work... */
+    unsigned int *grid;          /* as the game_state grid */
+    int started, reveal;
+    int flash_laserno, isflash;
+};
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    int gx = -1, gy = -1, rangeno = -1, wouldflash = 0;
+    enum { NONE, TOGGLE_BALL, TOGGLE_LOCK, FIRE, REVEAL,
+           TOGGLE_COLUMN_LOCK, TOGGLE_ROW_LOCK} action = NONE;
+    char buf[80], *nullret = NULL;
+
+    if (IS_CURSOR_MOVE(button)) {
+        int cx = ui->cur_x, cy = ui->cur_y;
+
+        move_cursor(button, &cx, &cy, state->w+2, state->h+2, 0);
+        if ((cx == 0 && cy == 0 && !CAN_REVEAL(state)) ||
+            (cx == 0 && cy == state->h+1) ||
+            (cx == state->w+1 && cy == 0) ||
+            (cx == state->w+1 && cy == state->h+1))
+            return NULL; /* disallow moving cursor to corners. */
+        ui->cur_x = cx;
+        ui->cur_y = cy;
+        ui->cur_visible = 1;
+        return "";
+    }
+
+    if (button == LEFT_BUTTON || button == RIGHT_BUTTON) {
+        gx = FROMDRAW(x);
+        gy = FROMDRAW(y);
+        ui->cur_visible = 0;
+        wouldflash = 1;
+    } else if (button == LEFT_RELEASE) {
+        ui->flash_laser = 0;
+        return "";
+    } else if (IS_CURSOR_SELECT(button)) {
+        if (ui->cur_visible) {
+            gx = ui->cur_x;
+            gy = ui->cur_y;
+            ui->flash_laser = 0;
+            wouldflash = 2;
+        } else {
+            ui->cur_visible = 1;
+            return "";
+        }
+        /* Fix up 'button' for the below logic. */
+        if (button == CURSOR_SELECT2) button = RIGHT_BUTTON;
+        else button = LEFT_BUTTON;
+    }
+
+    if (gx != -1 && gy != -1) {
+        if (gx == 0 && gy == 0 && button == LEFT_BUTTON)
+            action = REVEAL;
+        if (gx >= 1 && gx <= state->w && gy >= 1 && gy <= state->h) {
+            if (button == LEFT_BUTTON) {
+                if (!(GRID(state, gx,gy) & BALL_LOCK))
+                    action = TOGGLE_BALL;
+            } else
+                action = TOGGLE_LOCK;
+        }
+        if (grid2range(state, gx, gy, &rangeno)) {
+            if (button == LEFT_BUTTON)
+                action = FIRE;
+            else if (gy == 0 || gy > state->h)
+                action = TOGGLE_COLUMN_LOCK; /* and use gx */
+            else
+                action = TOGGLE_ROW_LOCK;    /* and use gy */
+        }
+    }
+
+    switch (action) {
+    case TOGGLE_BALL:
+        sprintf(buf, "T%d,%d", gx, gy);
+        break;
+
+    case TOGGLE_LOCK:
+        sprintf(buf, "LB%d,%d", gx, gy);
+        break;
+
+    case TOGGLE_COLUMN_LOCK:
+        sprintf(buf, "LC%d", gx);
+        break;
+
+    case TOGGLE_ROW_LOCK:
+        sprintf(buf, "LR%d", gy);
+        break;
+
+    case FIRE:
+       if (state->reveal && state->exits[rangeno] == LASER_EMPTY)
+           return nullret;
+        ui->flash_laserno = rangeno;
+        ui->flash_laser = wouldflash;
+        nullret = "";
+        if (state->exits[rangeno] != LASER_EMPTY)
+            return "";
+        sprintf(buf, "F%d", rangeno);
+        break;
+
+    case REVEAL:
+        if (!CAN_REVEAL(state)) return nullret;
+        if (ui->cur_visible == 1) ui->cur_x = ui->cur_y = 1;
+        sprintf(buf, "R");
+        break;
+
+    default:
+        return nullret;
+    }
+    if (state->reveal) return nullret;
+    ui->newmove = TRUE;
+    return dupstr(buf);
+}
+
+static game_state *execute_move(const game_state *from, const char *move)
+{
+    game_state *ret = dup_game(from);
+    int gx = -1, gy = -1, rangeno = -1;
+
+    if (ret->justwrong) {
+       int i;
+       ret->justwrong = FALSE;
+       for (i = 0; i < ret->nlasers; i++)
+           if (ret->exits[i] != LASER_EMPTY)
+               ret->exits[i] &= ~(LASER_OMITTED | LASER_WRONG);
+    }
+
+    if (!strcmp(move, "S")) {
+        check_guesses(ret, FALSE);
+        return ret;
+    }
+
+    if (from->reveal) goto badmove;
+    if (!*move) goto badmove;
+
+    switch (move[0]) {
+    case 'T':
+        sscanf(move+1, "%d,%d", &gx, &gy);
+        if (gx < 1 || gy < 1 || gx > ret->w || gy > ret->h)
+            goto badmove;
+        if (GRID(ret, gx, gy) & BALL_GUESS) {
+            ret->nguesses--;
+            GRID(ret, gx, gy) &= ~BALL_GUESS;
+        } else {
+            ret->nguesses++;
+            GRID(ret, gx, gy) |= BALL_GUESS;
+        }
+        break;
+
+    case 'F':
+        sscanf(move+1, "%d", &rangeno);
+        if (ret->exits[rangeno] != LASER_EMPTY)
+            goto badmove;
+        if (!RANGECHECK(ret, rangeno))
+            goto badmove;
+        fire_laser(ret, rangeno);
+        break;
+
+    case 'R':
+        if (ret->nguesses < ret->minballs ||
+            ret->nguesses > ret->maxballs)
+            goto badmove;
+        check_guesses(ret, TRUE);
+        break;
+
+    case 'L':
+        {
+            int lcount = 0;
+            if (strlen(move) < 2) goto badmove;
+            switch (move[1]) {
+            case 'B':
+                sscanf(move+2, "%d,%d", &gx, &gy);
+                if (gx < 1 || gy < 1 || gx > ret->w || gy > ret->h)
+                    goto badmove;
+                GRID(ret, gx, gy) ^= BALL_LOCK;
+                break;
+
+#define COUNTLOCK do { if (GRID(ret, gx, gy) & BALL_LOCK) lcount++; } while (0)
+#define SETLOCKIF(c) do {                                       \
+    if (lcount > (c)) GRID(ret, gx, gy) &= ~BALL_LOCK;          \
+    else              GRID(ret, gx, gy) |= BALL_LOCK;           \
+} while(0)
+
+            case 'C':
+                sscanf(move+2, "%d", &gx);
+                if (gx < 1 || gx > ret->w) goto badmove;
+                for (gy = 1; gy <= ret->h; gy++) { COUNTLOCK; }
+                for (gy = 1; gy <= ret->h; gy++) { SETLOCKIF(ret->h/2); }
+                break;
+
+            case 'R':
+                sscanf(move+2, "%d", &gy);
+                if (gy < 1 || gy > ret->h) goto badmove;
+                for (gx = 1; gx <= ret->w; gx++) { COUNTLOCK; }
+                for (gx = 1; gx <= ret->w; gx++) { SETLOCKIF(ret->w/2); }
+                break;
+
+#undef COUNTLOCK
+#undef SETLOCKIF
+
+            default:
+                goto badmove;
+            }
+        }
+        break;
+
+    default:
+        goto badmove;
+    }
+
+    return ret;
+
+badmove:
+    free_game(ret);
+    return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    /* Border is ts/2, to make things easier.
+     * Thus we have (width) + 2 (firing range*2) + 1 (border*2) tiles
+     * across, and similarly height + 2 + 1 tiles down. */
+    *x = (params->w + 3) * tilesize;
+    *y = (params->h + 3) * tilesize;
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+    ds->crad = (tilesize-1)/2;
+    ds->rrad = (3*tilesize)/8;
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+    int i;
+
+    game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT);
+
+    ret[COL_BALL * 3 + 0] = 0.0F;
+    ret[COL_BALL * 3 + 1] = 0.0F;
+    ret[COL_BALL * 3 + 2] = 0.0F;
+
+    ret[COL_WRONG * 3 + 0] = 1.0F;
+    ret[COL_WRONG * 3 + 1] = 0.0F;
+    ret[COL_WRONG * 3 + 2] = 0.0F;
+
+    ret[COL_BUTTON * 3 + 0] = 0.0F;
+    ret[COL_BUTTON * 3 + 1] = 1.0F;
+    ret[COL_BUTTON * 3 + 2] = 0.0F;
+
+    ret[COL_CURSOR * 3 + 0] = 1.0F;
+    ret[COL_CURSOR * 3 + 1] = 0.0F;
+    ret[COL_CURSOR * 3 + 2] = 0.0F;
+
+    for (i = 0; i < 3; i++) {
+        ret[COL_GRID * 3 + i] = ret[COL_BACKGROUND * 3 + i] * 0.9F;
+        ret[COL_LOCK * 3 + i] = ret[COL_BACKGROUND * 3 + i] * 0.7F;
+        ret[COL_COVER * 3 + i] = ret[COL_BACKGROUND * 3 + i] * 0.5F;
+        ret[COL_TEXT * 3 + i] = 0.0F;
+    }
+
+    ret[COL_FLASHTEXT * 3 + 0] = 0.0F;
+    ret[COL_FLASHTEXT * 3 + 1] = 1.0F;
+    ret[COL_FLASHTEXT * 3 + 2] = 0.0F;
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    struct game_drawstate *ds = snew(struct game_drawstate);
+
+    ds->tilesize = 0;
+    ds->w = state->w; ds->h = state->h;
+    ds->grid = snewn((state->w+2)*(state->h+2), unsigned int);
+    memset(ds->grid, 0, (state->w+2)*(state->h+2)*sizeof(unsigned int));
+    ds->started = ds->reveal = 0;
+    ds->flash_laserno = LASER_EMPTY;
+    ds->isflash = 0;
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds->grid);
+    sfree(ds);
+}
+
+static void draw_square_cursor(drawing *dr, game_drawstate *ds, int dx, int dy)
+{
+    int coff = TILE_SIZE/8;
+    draw_rect_outline(dr, dx + coff, dy + coff,
+                      TILE_SIZE - coff*2,
+                      TILE_SIZE - coff*2,
+                      COL_CURSOR);
+}
+
+
+static void draw_arena_tile(drawing *dr, const game_state *gs,
+                            game_drawstate *ds, const game_ui *ui,
+                            int ax, int ay, int force, int isflash)
+{
+    int gx = ax+1, gy = ay+1;
+    int gs_tile = GRID(gs, gx, gy), ds_tile = GRID(ds, gx, gy);
+    int dx = TODRAW(gx), dy = TODRAW(gy);
+
+    if (ui->cur_visible && ui->cur_x == gx && ui->cur_y == gy)
+        gs_tile |= FLAG_CURSOR;
+
+    if (gs_tile != ds_tile || gs->reveal != ds->reveal || force) {
+        int bcol, ocol, bg;
+
+        bg = (gs->reveal ? COL_BACKGROUND :
+              (gs_tile & BALL_LOCK) ? COL_LOCK : COL_COVER);
+
+        draw_rect(dr, dx, dy, TILE_SIZE, TILE_SIZE, bg);
+        draw_rect_outline(dr, dx, dy, TILE_SIZE, TILE_SIZE, COL_GRID);
+
+        if (gs->reveal) {
+            /* Guessed balls are always black; if they're incorrect they'll
+             * have a red cross added later.
+             * Missing balls are red. */
+            if (gs_tile & BALL_GUESS) {
+                bcol = isflash ? bg : COL_BALL;
+            } else if (gs_tile & BALL_CORRECT) {
+                bcol = isflash ? bg : COL_WRONG;
+            } else {
+                bcol = bg;
+            }
+        } else {
+            /* guesses are black/black, all else background. */
+            if (gs_tile & BALL_GUESS) {
+                bcol = COL_BALL;
+            } else {
+                bcol = bg;
+            }
+        }
+        ocol = (gs_tile & FLAG_CURSOR && bcol != bg) ? COL_CURSOR : bcol;
+
+        draw_circle(dr, dx + TILE_SIZE/2, dy + TILE_SIZE/2, ds->crad-1,
+                    ocol, ocol);
+        draw_circle(dr, dx + TILE_SIZE/2, dy + TILE_SIZE/2, ds->crad-3,
+                    bcol, bcol);
+
+
+        if (gs_tile & FLAG_CURSOR && bcol == bg)
+            draw_square_cursor(dr, ds, dx, dy);
+
+        if (gs->reveal &&
+            (gs_tile & BALL_GUESS) &&
+            !(gs_tile & BALL_CORRECT)) {
+            int x1 = dx + 3, y1 = dy + 3;
+            int x2 = dx + TILE_SIZE - 3, y2 = dy + TILE_SIZE-3;
+           int coords[8];
+
+            /* Incorrect guess; draw a red cross over the ball. */
+           coords[0] = x1-1;
+           coords[1] = y1+1;
+           coords[2] = x1+1;
+           coords[3] = y1-1;
+           coords[4] = x2+1;
+           coords[5] = y2-1;
+           coords[6] = x2-1;
+           coords[7] = y2+1;
+           draw_polygon(dr, coords, 4, COL_WRONG, COL_WRONG);
+           coords[0] = x2+1;
+           coords[1] = y1+1;
+           coords[2] = x2-1;
+           coords[3] = y1-1;
+           coords[4] = x1-1;
+           coords[5] = y2-1;
+           coords[6] = x1+1;
+           coords[7] = y2+1;
+           draw_polygon(dr, coords, 4, COL_WRONG, COL_WRONG);
+        }
+        draw_update(dr, dx, dy, TILE_SIZE, TILE_SIZE);
+    }
+    GRID(ds,gx,gy) = gs_tile;
+}
+
+static void draw_laser_tile(drawing *dr, const game_state *gs,
+                            game_drawstate *ds, const game_ui *ui,
+                            int lno, int force)
+{
+    int gx, gy, dx, dy, unused;
+    int wrong, omitted, reflect, hit, laserval, flash = 0, tmp;
+    unsigned int gs_tile, ds_tile, exitno;
+
+    tmp = range2grid(gs, lno, &gx, &gy, &unused);
+    assert(tmp);
+    gs_tile = GRID(gs, gx, gy);
+    ds_tile = GRID(ds, gx, gy);
+    dx = TODRAW(gx);
+    dy = TODRAW(gy);
+
+    wrong = gs->exits[lno] & LASER_WRONG;
+    omitted = gs->exits[lno] & LASER_OMITTED;
+    exitno = gs->exits[lno] & ~LASER_FLAGMASK;
+
+    reflect = gs_tile & LASER_REFLECT;
+    hit = gs_tile & LASER_HIT;
+    laserval = gs_tile & ~LASER_FLAGMASK;
+
+    if (lno == ds->flash_laserno)
+        gs_tile |= LASER_FLASHED;
+    else if (!(gs->exits[lno] & (LASER_HIT | LASER_REFLECT))) {
+        if (exitno == ds->flash_laserno)
+            gs_tile |= LASER_FLASHED;
+    }
+    if (gs_tile & LASER_FLASHED) flash = 1;
+
+    gs_tile |= wrong | omitted;
+
+    if (ui->cur_visible && ui->cur_x == gx && ui->cur_y == gy)
+        gs_tile |= FLAG_CURSOR;
+
+    if (gs_tile != ds_tile || force) {
+        draw_rect(dr, dx, dy, TILE_SIZE, TILE_SIZE, COL_BACKGROUND);
+        draw_rect_outline(dr, dx, dy, TILE_SIZE, TILE_SIZE, COL_GRID);
+
+        if (gs_tile &~ (LASER_WRONG | LASER_OMITTED | FLAG_CURSOR)) {
+            char str[32];
+            int tcol = flash ? COL_FLASHTEXT : omitted ? COL_WRONG : COL_TEXT;
+
+            if (reflect || hit)
+                sprintf(str, "%s", reflect ? "R" : "H");
+            else
+                sprintf(str, "%d", laserval);
+
+            if (wrong) {
+                draw_circle(dr, dx + TILE_SIZE/2, dy + TILE_SIZE/2,
+                            ds->rrad,
+                            COL_WRONG, COL_WRONG);
+                draw_circle(dr, dx + TILE_SIZE/2, dy + TILE_SIZE/2,
+                            ds->rrad - TILE_SIZE/16,
+                            COL_BACKGROUND, COL_WRONG);
+            }
+
+            draw_text(dr, dx + TILE_SIZE/2, dy + TILE_SIZE/2,
+                      FONT_VARIABLE, TILE_SIZE/2, ALIGN_VCENTRE | ALIGN_HCENTRE,
+                      tcol, str);
+        }
+        if (gs_tile & FLAG_CURSOR)
+            draw_square_cursor(dr, ds, dx, dy);
+
+        draw_update(dr, dx, dy, TILE_SIZE, TILE_SIZE);
+    }
+    GRID(ds, gx, gy) = gs_tile;
+}
+
+#define CUR_ANIM 0.2F
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int i, x, y, ts = TILE_SIZE, isflash = 0, force = 0;
+
+    if (flashtime > 0) {
+        int frame = (int)(flashtime / FLASH_FRAME);
+        isflash = (frame % 2) == 0;
+        debug(("game_redraw: flashtime = %f", flashtime));
+    }
+
+    if (!ds->started) {
+        int x0 = TODRAW(0)-1, y0 = TODRAW(0)-1;
+        int x1 = TODRAW(state->w+2), y1 = TODRAW(state->h+2);
+
+        draw_rect(dr, 0, 0,
+                  TILE_SIZE * (state->w+3), TILE_SIZE * (state->h+3),
+                  COL_BACKGROUND);
+
+        /* clockwise around the outline starting at pt behind (1,1). */
+        draw_line(dr, x0+ts, y0+ts, x0+ts, y0,    COL_HIGHLIGHT);
+        draw_line(dr, x0+ts, y0,    x1-ts, y0,    COL_HIGHLIGHT);
+        draw_line(dr, x1-ts, y0,    x1-ts, y0+ts, COL_LOWLIGHT);
+        draw_line(dr, x1-ts, y0+ts, x1,    y0+ts, COL_HIGHLIGHT);
+        draw_line(dr, x1,    y0+ts, x1,    y1-ts, COL_LOWLIGHT);
+        draw_line(dr, x1,    y1-ts, x1-ts, y1-ts, COL_LOWLIGHT);
+        draw_line(dr, x1-ts, y1-ts, x1-ts, y1,    COL_LOWLIGHT);
+        draw_line(dr, x1-ts, y1,    x0+ts, y1,    COL_LOWLIGHT);
+        draw_line(dr, x0+ts, y1,    x0+ts, y1-ts, COL_HIGHLIGHT);
+        draw_line(dr, x0+ts, y1-ts, x0,    y1-ts, COL_LOWLIGHT);
+        draw_line(dr, x0,    y1-ts, x0,    y0+ts, COL_HIGHLIGHT);
+        draw_line(dr, x0,    y0+ts, x0+ts, y0+ts, COL_HIGHLIGHT);
+        /* phew... */
+
+        draw_update(dr, 0, 0,
+                    TILE_SIZE * (state->w+3), TILE_SIZE * (state->h+3));
+        force = 1;
+        ds->started = 1;
+    }
+
+    if (isflash != ds->isflash) force = 1;
+
+    /* draw the arena */
+    for (x = 0; x < state->w; x++) {
+        for (y = 0; y < state->h; y++) {
+            draw_arena_tile(dr, state, ds, ui, x, y, force, isflash);
+        }
+    }
+
+    /* draw the lasers */
+    ds->flash_laserno = LASER_EMPTY;
+    if (ui->flash_laser == 1)
+        ds->flash_laserno = ui->flash_laserno;
+    else if (ui->flash_laser == 2 && animtime > 0)
+        ds->flash_laserno = ui->flash_laserno;
+
+    for (i = 0; i < 2*(state->w+state->h); i++) {
+        draw_laser_tile(dr, state, ds, ui, i, force);
+    }
+
+    /* draw the 'finish' button */
+    if (CAN_REVEAL(state)) {
+        int outline = (ui->cur_visible && ui->cur_x == 0 && ui->cur_y == 0)
+            ? COL_CURSOR : COL_BALL;
+        clip(dr, TODRAW(0)-1, TODRAW(0)-1, TILE_SIZE+1, TILE_SIZE+1);
+        draw_circle(dr, TODRAW(0) + ds->crad, TODRAW(0) + ds->crad, ds->crad,
+                    outline, outline);
+        draw_circle(dr, TODRAW(0) + ds->crad, TODRAW(0) + ds->crad, ds->crad-2,
+                    COL_BUTTON, COL_BUTTON);
+       unclip(dr);
+    } else {
+        draw_rect(dr, TODRAW(0)-1, TODRAW(0)-1,
+                 TILE_SIZE+1, TILE_SIZE+1, COL_BACKGROUND);
+    }
+    draw_update(dr, TODRAW(0), TODRAW(0), TILE_SIZE, TILE_SIZE);
+    ds->reveal = state->reveal;
+    ds->isflash = isflash;
+
+    {
+        char buf[256];
+
+        if (ds->reveal) {
+            if (state->nwrong == 0 &&
+                state->nmissed == 0 &&
+                state->nright >= state->minballs)
+                sprintf(buf, "CORRECT!");
+            else
+                sprintf(buf, "%d wrong and %d missed balls.",
+                        state->nwrong, state->nmissed);
+        } else if (state->justwrong) {
+           sprintf(buf, "Wrong! Guess again.");
+       } else {
+            if (state->nguesses > state->maxballs)
+                sprintf(buf, "%d too many balls marked.",
+                        state->nguesses - state->maxballs);
+            else if (state->nguesses <= state->maxballs &&
+                     state->nguesses >= state->minballs)
+                sprintf(buf, "Click button to verify guesses.");
+            else if (state->maxballs == state->minballs)
+                sprintf(buf, "Balls marked: %d / %d",
+                        state->nguesses, state->minballs);
+            else
+                sprintf(buf, "Balls marked: %d / %d-%d.",
+                        state->nguesses, state->minballs, state->maxballs);
+        }
+       if (ui->errors) {
+           sprintf(buf + strlen(buf), " (%d error%s)",
+                   ui->errors, ui->errors > 1 ? "s" : "");
+       }
+        status_bar(dr, buf);
+    }
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return (ui->flash_laser == 2) ? CUR_ANIM : 0.0F;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    if (!oldstate->reveal && newstate->reveal)
+        return 4.0F * FLASH_FRAME;
+    else
+        return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    if (state->reveal) {
+        /*
+         * We return nonzero whenever the solution has been revealed,
+         * even (on spoiler grounds) if it wasn't guessed correctly.
+         */
+        if (state->nwrong == 0 &&
+            state->nmissed == 0 &&
+            state->nright >= state->minballs)
+            return +1;
+        else
+            return -1;
+    }
+    return 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+}
+
+#ifdef COMBINED
+#define thegame blackbox
+#endif
+
+const struct game thegame = {
+    "Black Box", "games.blackbox", "blackbox",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    FALSE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    FALSE, FALSE, game_print_size, game_print,
+    TRUE,                             /* wants_statusbar */
+    FALSE, game_timing_state,
+    REQUIRE_RBUTTON,                  /* flags */
+};
+
+/* vim: set shiftwidth=4 tabstop=8: */
diff --git a/bridges.R b/bridges.R
new file mode 100644 (file)
index 0000000..75df309
--- /dev/null
+++ b/bridges.R
@@ -0,0 +1,21 @@
+# -*- makefile -*-
+
+BRIDGES_EXTRA = dsf findloop
+
+bridges  : [X] GTK COMMON bridges BRIDGES_EXTRA bridges-icon|no-icon
+
+bridges  : [G] WINDOWS COMMON bridges BRIDGES_EXTRA bridges.res|noicon.res
+
+ALL += bridges[COMBINED] BRIDGES_EXTRA
+
+!begin am gtk
+GAMES += bridges
+!end
+
+!begin >list.c
+    A(bridges) \
+!end
+
+!begin >gamedesc.txt
+bridges:bridges.exe:Bridges:Bridge-placing puzzle:Connect all the islands with a network of bridges.
+!end
diff --git a/bridges.c b/bridges.c
new file mode 100644 (file)
index 0000000..de7403e
--- /dev/null
+++ b/bridges.c
@@ -0,0 +1,3262 @@
+/*
+ * bridges.c: Implementation of the Nikoli game 'Bridges'.
+ *
+ * Things still to do:
+ *
+ *  - The solver's algorithmic design is not really ideal. It makes
+ *    use of the same data representation as gameplay uses, which
+ *    often looks like a tempting reuse of code but isn't always a
+ *    good idea. In this case, it's unpleasant that each edge of the
+ *    graph ends up represented as multiple squares on a grid, with
+ *    flags indicating when edges and non-edges cross; that's useful
+ *    when the result can be directly translated into positions of
+ *    graphics on the display, but in purely internal work it makes
+ *    even simple manipulations during solving more painful than they
+ *    should be, and complex ones have no choice but to modify the
+ *    data structures temporarily, test things, and put them back. I
+ *    envisage a complete solver rewrite along the following lines:
+ *     + We have a collection of vertices (islands) and edges
+ *       (potential bridge locations, i.e. pairs of horizontal or
+ *       vertical islands with no other island in between).
+ *     + Each edge has an associated list of edges that cross it, and
+ *       hence with which it is mutually exclusive.
+ *     + For each edge, we track the min and max number of bridges we
+ *       currently think possible.
+ *     + For each vertex, we track the number of _liberties_ it has,
+ *       i.e. its clue number minus the min bridge count for each edge
+ *       out of it.
+ *     + We also maintain a dsf that identifies sets of vertices which
+ *       are connected components of the puzzle so far, and for each
+ *       equivalence class we track the total number of liberties for
+ *       that component. (The dsf mechanism will also already track
+ *       the size of each component, i.e. number of islands.)
+ *     + So incrementing the min for an edge requires processing along
+ *       the lines of:
+ *        - set the max for all edges crossing that one to zero
+ *        - decrement the liberty count for the vertex at each end,
+ *          and also for each vertex's equivalence class (NB they may
+ *          be the same class)
+ *        - unify the two equivalence classes if they're not already,
+ *          and if so, set the liberty count for the new class to be
+ *          the sum of the previous two.
+ *     + Decrementing the max is much easier, however.
+ *     + With this data structure the really fiddly stuff in stage3()
+ *       becomes more or less trivial, because it's now a quick job to
+ *       find out whether an island would form an isolated subgraph if
+ *       connected to a given subset of its neighbours:
+ *        - identify the connected components containing the test
+ *          vertex and its putative new neighbours (but be careful not
+ *          to count a component more than once if two or more of the
+ *          vertices involved are already in the same one)
+ *        - find the sum of those components' liberty counts, and also
+ *          the total number of islands involved
+ *        - if the total liberty count of the connected components is
+ *          exactly equal to twice the number of edges we'd be adding
+ *          (of course each edge destroys two liberties, one at each
+ *          end) then these components would become a subgraph with
+ *          zero liberties if connected together.
+ *        - therefore, if that subgraph also contains fewer than the
+ *          total number of islands, it's disallowed.
+ *        - As mentioned in stage3(), once we've identified such a
+ *          disallowed pattern, we have two choices for what to do
+ *          with it: if the candidate set of neighbours has size 1 we
+ *          can reduce the max for the edge to that one neighbour,
+ *          whereas if its complement has size 1 we can increase the
+ *          min for the edge to the _omitted_ neighbour.
+ *
+ *  - write a recursive solver?
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+
+/* Turn this on for hints about which lines are considered possibilities. */
+#undef DRAW_GRID
+
+/* --- structures for params, state, etc. --- */
+
+#define MAX_BRIDGES     4
+
+#define PREFERRED_TILE_SIZE 24
+#define TILE_SIZE       (ds->tilesize)
+#define BORDER          (TILE_SIZE / 2)
+
+#define COORD(x)  ( (x) * TILE_SIZE + BORDER )
+#define FROMCOORD(x)  ( ((x) - BORDER + TILE_SIZE) / TILE_SIZE - 1 )
+
+#define FLASH_TIME 0.50F
+
+enum {
+    COL_BACKGROUND,
+    COL_FOREGROUND,
+    COL_HIGHLIGHT, COL_LOWLIGHT,
+    COL_SELECTED, COL_MARK,
+    COL_HINT, COL_GRID,
+    COL_WARNING,
+    COL_CURSOR,
+    NCOLOURS
+};
+
+struct game_params {
+    int w, h, maxb;
+    int islands, expansion;     /* %age of island squares, %age chance of expansion */
+    int allowloops, difficulty;
+};
+
+/* general flags used by all structs */
+#define G_ISLAND        0x0001
+#define G_LINEV         0x0002     /* contains a vert. line */
+#define G_LINEH         0x0004     /* contains a horiz. line (mutex with LINEV) */
+#define G_LINE          (G_LINEV|G_LINEH)
+#define G_MARKV         0x0008
+#define G_MARKH         0x0010
+#define G_MARK          (G_MARKV|G_MARKH)
+#define G_NOLINEV       0x0020
+#define G_NOLINEH       0x0040
+#define G_NOLINE        (G_NOLINEV|G_NOLINEH)
+
+/* flags used by the error checker */
+#define G_WARN          0x0080
+
+/* flags used by the solver etc. */
+#define G_SWEEP         0x1000
+
+#define G_FLAGSH        (G_LINEH|G_MARKH|G_NOLINEH)
+#define G_FLAGSV        (G_LINEV|G_MARKV|G_NOLINEV)
+
+typedef unsigned int grid_type; /* change me later if we invent > 16 bits of flags. */
+
+struct solver_state {
+    int *dsf, *comptspaces;
+    int *tmpdsf, *tmpcompspaces;
+    int refcount;
+};
+
+/* state->gridi is an optimisation; it stores the pointer to the island
+ * structs indexed by (x,y). It's not strictly necessary (we could use
+ * find234 instead), but Purify showed that board generation (mostly the solver)
+ * was spending 60% of its time in find234. */
+
+struct surrounds { /* cloned from lightup.c */
+    struct { int x, y, dx, dy, off; } points[4];
+    int npoints, nislands;
+};
+
+struct island {
+  game_state *state;
+  int x, y, count;
+  struct surrounds adj;
+};
+
+struct game_state {
+    int w, h, completed, solved, allowloops, maxb;
+    grid_type *grid;
+    struct island *islands;
+    int n_islands, n_islands_alloc;
+    game_params params; /* used by the aux solver. */
+#define N_WH_ARRAYS 5
+    char *wha, *possv, *possh, *lines, *maxv, *maxh;
+    struct island **gridi;
+    struct solver_state *solver; /* refcounted */
+};
+
+#define GRIDSZ(s) ((s)->w * (s)->h * sizeof(grid_type))
+
+#define INGRID(s,x,y) ((x) >= 0 && (x) < (s)->w && (y) >= 0 && (y) < (s)->h)
+
+#define DINDEX(x,y) ((y)*state->w + (x))
+
+#define INDEX(s,g,x,y) ((s)->g[(y)*((s)->w) + (x)])
+#define IDX(s,g,i) ((s)->g[(i)])
+#define GRID(s,x,y) INDEX(s,grid,x,y)
+#define POSSIBLES(s,dx,x,y) ((dx) ? (INDEX(s,possh,x,y)) : (INDEX(s,possv,x,y)))
+#define MAXIMUM(s,dx,x,y) ((dx) ? (INDEX(s,maxh,x,y)) : (INDEX(s,maxv,x,y)))
+
+#define GRIDCOUNT(s,x,y,f) ((GRID(s,x,y) & (f)) ? (INDEX(s,lines,x,y)) : 0)
+
+#define WITHIN2(x,min,max) (((x) < (min)) ? 0 : (((x) > (max)) ? 0 : 1))
+#define WITHIN(x,min,max) ((min) > (max) ? \
+                           WITHIN2(x,max,min) : WITHIN2(x,min,max))
+
+/* --- island struct and tree support functions --- */
+
+#define ISLAND_ORTH(is,j,f,df) \
+    (is->f + (is->adj.points[(j)].off*is->adj.points[(j)].df))
+
+#define ISLAND_ORTHX(is,j) ISLAND_ORTH(is,j,x,dx)
+#define ISLAND_ORTHY(is,j) ISLAND_ORTH(is,j,y,dy)
+
+static void fixup_islands_for_realloc(game_state *state)
+{
+    int i;
+
+    for (i = 0; i < state->w*state->h; i++) state->gridi[i] = NULL;
+    for (i = 0; i < state->n_islands; i++) {
+        struct island *is = &state->islands[i];
+        is->state = state;
+        INDEX(state, gridi, is->x, is->y) = is;
+    }
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    int x, y, len, nl;
+    char *ret, *p;
+    struct island *is;
+    grid_type grid;
+
+    len = (state->h) * (state->w+1) + 1;
+    ret = snewn(len, char);
+    p = ret;
+
+    for (y = 0; y < state->h; y++) {
+        for (x = 0; x < state->w; x++) {
+            grid = GRID(state,x,y);
+            nl = INDEX(state,lines,x,y);
+            is = INDEX(state, gridi, x, y);
+            if (is) {
+                *p++ = '0' + is->count;
+            } else if (grid & G_LINEV) {
+                *p++ = (nl > 1) ? '"' : (nl == 1) ? '|' : '!'; /* gaah, want a double-bar. */
+            } else if (grid & G_LINEH) {
+                *p++ = (nl > 1) ? '=' : (nl == 1) ? '-' : '~';
+            } else {
+                *p++ = '.';
+            }
+        }
+        *p++ = '\n';
+    }
+    *p++ = '\0';
+
+    assert(p - ret == len);
+    return ret;
+}
+
+static void debug_state(game_state *state)
+{
+    char *textversion = game_text_format(state);
+    debug(("%s", textversion));
+    sfree(textversion);
+}
+
+/*static void debug_possibles(game_state *state)
+{
+    int x, y;
+    debug(("possh followed by possv\n"));
+    for (y = 0; y < state->h; y++) {
+        for (x = 0; x < state->w; x++) {
+            debug(("%d", POSSIBLES(state, 1, x, y)));
+        }
+        debug((" "));
+        for (x = 0; x < state->w; x++) {
+            debug(("%d", POSSIBLES(state, 0, x, y)));
+        }
+        debug(("\n"));
+    }
+    debug(("\n"));
+        for (y = 0; y < state->h; y++) {
+        for (x = 0; x < state->w; x++) {
+            debug(("%d", MAXIMUM(state, 1, x, y)));
+        }
+        debug((" "));
+        for (x = 0; x < state->w; x++) {
+            debug(("%d", MAXIMUM(state, 0, x, y)));
+        }
+        debug(("\n"));
+    }
+    debug(("\n"));
+}*/
+
+static void island_set_surrounds(struct island *is)
+{
+    assert(INGRID(is->state,is->x,is->y));
+    is->adj.npoints = is->adj.nislands = 0;
+#define ADDPOINT(cond,ddx,ddy) do {\
+    if (cond) { \
+        is->adj.points[is->adj.npoints].x = is->x+(ddx); \
+        is->adj.points[is->adj.npoints].y = is->y+(ddy); \
+        is->adj.points[is->adj.npoints].dx = (ddx); \
+        is->adj.points[is->adj.npoints].dy = (ddy); \
+        is->adj.points[is->adj.npoints].off = 0; \
+        is->adj.npoints++; \
+    } } while(0)
+    ADDPOINT(is->x > 0,                -1,  0);
+    ADDPOINT(is->x < (is->state->w-1), +1,  0);
+    ADDPOINT(is->y > 0,                 0, -1);
+    ADDPOINT(is->y < (is->state->h-1),  0, +1);
+}
+
+static void island_find_orthogonal(struct island *is)
+{
+    /* fills in the rest of the 'surrounds' structure, assuming
+     * all other islands are now in place. */
+    int i, x, y, dx, dy, off;
+
+    is->adj.nislands = 0;
+    for (i = 0; i < is->adj.npoints; i++) {
+        dx = is->adj.points[i].dx;
+        dy = is->adj.points[i].dy;
+        x = is->x + dx;
+        y = is->y + dy;
+        off = 1;
+        is->adj.points[i].off = 0;
+        while (INGRID(is->state, x, y)) {
+            if (GRID(is->state, x, y) & G_ISLAND) {
+                is->adj.points[i].off = off;
+                is->adj.nislands++;
+                /*debug(("island (%d,%d) has orth is. %d*(%d,%d) away at (%d,%d).\n",
+                       is->x, is->y, off, dx, dy,
+                       ISLAND_ORTHX(is,i), ISLAND_ORTHY(is,i)));*/
+                goto foundisland;
+            }
+            off++; x += dx; y += dy;
+        }
+foundisland:
+        ;
+    }
+}
+
+static int island_hasbridge(struct island *is, int direction)
+{
+    int x = is->adj.points[direction].x;
+    int y = is->adj.points[direction].y;
+    grid_type gline = is->adj.points[direction].dx ? G_LINEH : G_LINEV;
+
+    if (GRID(is->state, x, y) & gline) return 1;
+    return 0;
+}
+
+static struct island *island_find_connection(struct island *is, int adjpt)
+{
+    struct island *is_r;
+
+    assert(adjpt < is->adj.npoints);
+    if (!is->adj.points[adjpt].off) return NULL;
+    if (!island_hasbridge(is, adjpt)) return NULL;
+
+    is_r = INDEX(is->state, gridi,
+                 ISLAND_ORTHX(is, adjpt), ISLAND_ORTHY(is, adjpt));
+    assert(is_r);
+
+    return is_r;
+}
+
+static struct island *island_add(game_state *state, int x, int y, int count)
+{
+    struct island *is;
+    int realloced = 0;
+
+    assert(!(GRID(state,x,y) & G_ISLAND));
+    GRID(state,x,y) |= G_ISLAND;
+
+    state->n_islands++;
+    if (state->n_islands > state->n_islands_alloc) {
+        state->n_islands_alloc = state->n_islands * 2;
+        state->islands =
+            sresize(state->islands, state->n_islands_alloc, struct island);
+        realloced = 1;
+    }
+    is = &state->islands[state->n_islands-1];
+
+    memset(is, 0, sizeof(struct island));
+    is->state = state;
+    is->x = x;
+    is->y = y;
+    is->count = count;
+    island_set_surrounds(is);
+
+    if (realloced)
+        fixup_islands_for_realloc(state);
+    else
+        INDEX(state, gridi, x, y) = is;
+
+    return is;
+}
+
+
+/* n = -1 means 'flip NOLINE flags [and set line to 0].' */
+static void island_join(struct island *i1, struct island *i2, int n, int is_max)
+{
+    game_state *state = i1->state;
+    int s, e, x, y;
+
+    assert(i1->state == i2->state);
+    assert(n >= -1 && n <= i1->state->maxb);
+
+    if (i1->x == i2->x) {
+        x = i1->x;
+        if (i1->y < i2->y) {
+            s = i1->y+1; e = i2->y-1;
+        } else {
+            s = i2->y+1; e = i1->y-1;
+        }
+        for (y = s; y <= e; y++) {
+            if (is_max) {
+                INDEX(state,maxv,x,y) = n;
+            } else {
+                if (n < 0) {
+                    GRID(state,x,y) ^= G_NOLINEV;
+                } else if (n == 0) {
+                    GRID(state,x,y) &= ~G_LINEV;
+                } else {
+                    GRID(state,x,y) |= G_LINEV;
+                    INDEX(state,lines,x,y) = n;
+                }
+            }
+        }
+    } else if (i1->y == i2->y) {
+        y = i1->y;
+        if (i1->x < i2->x) {
+            s = i1->x+1; e = i2->x-1;
+        } else {
+            s = i2->x+1; e = i1->x-1;
+        }
+        for (x = s; x <= e; x++) {
+            if (is_max) {
+                INDEX(state,maxh,x,y) = n;
+            } else {
+                if (n < 0) {
+                    GRID(state,x,y) ^= G_NOLINEH;
+                } else if (n == 0) {
+                    GRID(state,x,y) &= ~G_LINEH;
+                } else {
+                    GRID(state,x,y) |= G_LINEH;
+                    INDEX(state,lines,x,y) = n;
+                }
+            }
+        }
+    } else {
+        assert(!"island_join: islands not orthogonal.");
+    }
+}
+
+/* Counts the number of bridges currently attached to the island. */
+static int island_countbridges(struct island *is)
+{
+    int i, c = 0;
+
+    for (i = 0; i < is->adj.npoints; i++) {
+        c += GRIDCOUNT(is->state,
+                       is->adj.points[i].x, is->adj.points[i].y,
+                       is->adj.points[i].dx ? G_LINEH : G_LINEV);
+    }
+    /*debug(("island count for (%d,%d) is %d.\n", is->x, is->y, c));*/
+    return c;
+}
+
+static int island_adjspace(struct island *is, int marks, int missing,
+                           int direction)
+{
+    int x, y, poss, curr, dx;
+    grid_type gline, mline;
+
+    x = is->adj.points[direction].x;
+    y = is->adj.points[direction].y;
+    dx = is->adj.points[direction].dx;
+    gline = dx ? G_LINEH : G_LINEV;
+
+    if (marks) {
+        mline = dx ? G_MARKH : G_MARKV;
+        if (GRID(is->state,x,y) & mline) return 0;
+    }
+    poss = POSSIBLES(is->state, dx, x, y);
+    poss = min(poss, missing);
+
+    curr = GRIDCOUNT(is->state, x, y, gline);
+    poss = min(poss, MAXIMUM(is->state, dx, x, y) - curr);
+
+    return poss;
+}
+
+/* Counts the number of bridge spaces left around the island;
+ * expects the possibles to be up-to-date. */
+static int island_countspaces(struct island *is, int marks)
+{
+    int i, c = 0, missing;
+
+    missing = is->count - island_countbridges(is);
+    if (missing < 0) return 0;
+
+    for (i = 0; i < is->adj.npoints; i++) {
+        c += island_adjspace(is, marks, missing, i);
+    }
+    return c;
+}
+
+static int island_isadj(struct island *is, int direction)
+{
+    int x, y;
+    grid_type gline, mline;
+
+    x = is->adj.points[direction].x;
+    y = is->adj.points[direction].y;
+
+    mline = is->adj.points[direction].dx ? G_MARKH : G_MARKV;
+    gline = is->adj.points[direction].dx ? G_LINEH : G_LINEV;
+    if (GRID(is->state, x, y) & mline) {
+        /* If we're marked (i.e. the thing to attach to is complete)
+         * only count an adjacency if we're already attached. */
+        return GRIDCOUNT(is->state, x, y, gline);
+    } else {
+        /* If we're unmarked, count possible adjacency iff it's
+         * flagged as POSSIBLE. */
+        return POSSIBLES(is->state, is->adj.points[direction].dx, x, y);
+    }
+    return 0;
+}
+
+/* Counts the no. of possible adjacent islands (including islands
+ * we're already connected to). */
+static int island_countadj(struct island *is)
+{
+    int i, nadj = 0;
+
+    for (i = 0; i < is->adj.npoints; i++) {
+        if (island_isadj(is, i)) nadj++;
+    }
+    return nadj;
+}
+
+static void island_togglemark(struct island *is)
+{
+    int i, j, x, y, o;
+    struct island *is_loop;
+
+    /* mark the island... */
+    GRID(is->state, is->x, is->y) ^= G_MARK;
+
+    /* ...remove all marks on non-island squares... */
+    for (x = 0; x < is->state->w; x++) {
+        for (y = 0; y < is->state->h; y++) {
+            if (!(GRID(is->state, x, y) & G_ISLAND))
+                GRID(is->state, x, y) &= ~G_MARK;
+        }
+    }
+
+    /* ...and add marks to squares around marked islands. */
+    for (i = 0; i < is->state->n_islands; i++) {
+        is_loop = &is->state->islands[i];
+        if (!(GRID(is_loop->state, is_loop->x, is_loop->y) & G_MARK))
+            continue;
+
+        for (j = 0; j < is_loop->adj.npoints; j++) {
+            /* if this direction takes us to another island, mark all
+             * squares between the two islands. */
+            if (!is_loop->adj.points[j].off) continue;
+            assert(is_loop->adj.points[j].off > 1);
+            for (o = 1; o < is_loop->adj.points[j].off; o++) {
+                GRID(is_loop->state,
+                     is_loop->x + is_loop->adj.points[j].dx*o,
+                     is_loop->y + is_loop->adj.points[j].dy*o) |=
+                    is_loop->adj.points[j].dy ? G_MARKV : G_MARKH;
+            }
+        }
+    }
+}
+
+static int island_impossible(struct island *is, int strict)
+{
+    int curr = island_countbridges(is), nspc = is->count - curr, nsurrspc;
+    int i, poss;
+    struct island *is_orth;
+
+    if (nspc < 0) {
+        debug(("island at (%d,%d) impossible because full.\n", is->x, is->y));
+        return 1;        /* too many bridges */
+    } else if ((curr + island_countspaces(is, 0)) < is->count) {
+        debug(("island at (%d,%d) impossible because not enough spaces.\n", is->x, is->y));
+        return 1;        /* impossible to create enough bridges */
+    } else if (strict && curr < is->count) {
+        debug(("island at (%d,%d) impossible because locked.\n", is->x, is->y));
+        return 1;        /* not enough bridges and island is locked */
+    }
+
+    /* Count spaces in surrounding islands. */
+    nsurrspc = 0;
+    for (i = 0; i < is->adj.npoints; i++) {
+        int ifree, dx = is->adj.points[i].dx;
+
+        if (!is->adj.points[i].off) continue;
+        poss = POSSIBLES(is->state, dx,
+                         is->adj.points[i].x, is->adj.points[i].y);
+        if (poss == 0) continue;
+        is_orth = INDEX(is->state, gridi,
+                        ISLAND_ORTHX(is,i), ISLAND_ORTHY(is,i));
+        assert(is_orth);
+
+        ifree = is_orth->count - island_countbridges(is_orth);
+        if (ifree > 0) {
+           /*
+            * ifree is the number of bridges unfilled in the other
+            * island, which is clearly an upper bound on the number
+            * of extra bridges this island may run to it.
+            *
+            * Another upper bound is the number of bridges unfilled
+            * on the specific line between here and there. We must
+            * take the minimum of both.
+            */
+           int bmax = MAXIMUM(is->state, dx,
+                              is->adj.points[i].x, is->adj.points[i].y);
+           int bcurr = GRIDCOUNT(is->state,
+                                 is->adj.points[i].x, is->adj.points[i].y,
+                                 dx ? G_LINEH : G_LINEV);
+           assert(bcurr <= bmax);
+            nsurrspc += min(ifree, bmax - bcurr);
+       }
+    }
+    if (nsurrspc < nspc) {
+        debug(("island at (%d,%d) impossible: surr. islands %d spc, need %d.\n",
+               is->x, is->y, nsurrspc, nspc));
+        return 1;       /* not enough spaces around surrounding islands to fill this one. */
+    }
+
+    return 0;
+}
+
+/* --- Game parameter functions --- */
+
+#define DEFAULT_PRESET 0
+
+const struct game_params bridges_presets[] = {
+  { 7, 7, 2, 30, 10, 1, 0 },
+  { 7, 7, 2, 30, 10, 1, 1 },
+  { 7, 7, 2, 30, 10, 1, 2 },
+  { 10, 10, 2, 30, 10, 1, 0 },
+  { 10, 10, 2, 30, 10, 1, 1 },
+  { 10, 10, 2, 30, 10, 1, 2 },
+  { 15, 15, 2, 30, 10, 1, 0 },
+  { 15, 15, 2, 30, 10, 1, 1 },
+  { 15, 15, 2, 30, 10, 1, 2 },
+};
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+    *ret = bridges_presets[DEFAULT_PRESET];
+
+    return ret;
+}
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params *ret;
+    char buf[80];
+
+    if (i < 0 || i >= lenof(bridges_presets))
+        return FALSE;
+
+    ret = default_params();
+    *ret = bridges_presets[i];
+    *params = ret;
+
+    sprintf(buf, "%dx%d %s", ret->w, ret->h,
+            ret->difficulty == 0 ? "easy" :
+            ret->difficulty == 1 ? "medium" : "hard");
+    *name = dupstr(buf);
+
+    return TRUE;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+#define EATNUM(x) do { \
+    (x) = atoi(string); \
+    while (*string && isdigit((unsigned char)*string)) string++; \
+} while(0)
+
+static void decode_params(game_params *params, char const *string)
+{
+    EATNUM(params->w);
+    params->h = params->w;
+    if (*string == 'x') {
+        string++;
+        EATNUM(params->h);
+    }
+    if (*string == 'i') {
+        string++;
+        EATNUM(params->islands);
+    }
+    if (*string == 'e') {
+        string++;
+        EATNUM(params->expansion);
+    }
+    if (*string == 'm') {
+        string++;
+        EATNUM(params->maxb);
+    }
+    params->allowloops = 1;
+    if (*string == 'L') {
+        string++;
+        params->allowloops = 0;
+    }
+    if (*string == 'd') {
+        string++;
+        EATNUM(params->difficulty);
+    }
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char buf[80];
+
+    if (full) {
+        sprintf(buf, "%dx%di%de%dm%d%sd%d",
+                params->w, params->h, params->islands, params->expansion,
+                params->maxb, params->allowloops ? "" : "L",
+                params->difficulty);
+    } else {
+        sprintf(buf, "%dx%dm%d%s", params->w, params->h,
+                params->maxb, params->allowloops ? "" : "L");
+    }
+    return dupstr(buf);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+    char buf[80];
+
+    ret = snewn(8, config_item);
+
+    ret[0].name = "Width";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->w);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Height";
+    ret[1].type = C_STRING;
+    sprintf(buf, "%d", params->h);
+    ret[1].sval = dupstr(buf);
+    ret[1].ival = 0;
+
+    ret[2].name = "Difficulty";
+    ret[2].type = C_CHOICES;
+    ret[2].sval = ":Easy:Medium:Hard";
+    ret[2].ival = params->difficulty;
+
+    ret[3].name = "Allow loops";
+    ret[3].type = C_BOOLEAN;
+    ret[3].sval = NULL;
+    ret[3].ival = params->allowloops;
+
+    ret[4].name = "Max. bridges per direction";
+    ret[4].type = C_CHOICES;
+    ret[4].sval = ":1:2:3:4"; /* keep up-to-date with MAX_BRIDGES */
+    ret[4].ival = params->maxb - 1;
+
+    ret[5].name = "%age of island squares";
+    ret[5].type = C_CHOICES;
+    ret[5].sval = ":5%:10%:15%:20%:25%:30%";
+    ret[5].ival = (params->islands / 5)-1;
+
+    ret[6].name = "Expansion factor (%age)";
+    ret[6].type = C_CHOICES;
+    ret[6].sval = ":0%:10%:20%:30%:40%:50%:60%:70%:80%:90%:100%";
+    ret[6].ival = params->expansion / 10;
+
+    ret[7].name = NULL;
+    ret[7].type = C_END;
+    ret[7].sval = NULL;
+    ret[7].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w          = atoi(cfg[0].sval);
+    ret->h          = atoi(cfg[1].sval);
+    ret->difficulty = cfg[2].ival;
+    ret->allowloops = cfg[3].ival;
+    ret->maxb       = cfg[4].ival + 1;
+    ret->islands    = (cfg[5].ival + 1) * 5;
+    ret->expansion  = cfg[6].ival * 10;
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    if (params->w < 3 || params->h < 3)
+        return "Width and height must be at least 3";
+    if (params->maxb < 1 || params->maxb > MAX_BRIDGES)
+        return "Too many bridges.";
+    if (full) {
+        if (params->islands <= 0 || params->islands > 30)
+            return "%age of island squares must be between 1% and 30%";
+        if (params->expansion < 0 || params->expansion > 100)
+            return "Expansion factor must be between 0 and 100";
+    }
+    return NULL;
+}
+
+/* --- Game encoding and differences --- */
+
+static char *encode_game(game_state *state)
+{
+    char *ret, *p;
+    int wh = state->w*state->h, run, x, y;
+    struct island *is;
+
+    ret = snewn(wh + 1, char);
+    p = ret;
+    run = 0;
+    for (y = 0; y < state->h; y++) {
+        for (x = 0; x < state->w; x++) {
+            is = INDEX(state, gridi, x, y);
+            if (is) {
+                if (run) {
+                    *p++ = ('a'-1) + run;
+                    run = 0;
+                }
+                if (is->count < 10)
+                    *p++ = '0' + is->count;
+                else
+                    *p++ = 'A' + (is->count - 10);
+            } else {
+                if (run == 26) {
+                    *p++ = ('a'-1) + run;
+                    run = 0;
+                }
+                run++;
+            }
+        }
+    }
+    if (run) {
+        *p++ = ('a'-1) + run;
+        run = 0;
+    }
+    *p = '\0';
+    assert(p - ret <= wh);
+
+    return ret;
+}
+
+static char *game_state_diff(const game_state *src, const game_state *dest)
+{
+    int movesize = 256, movelen = 0;
+    char *move = snewn(movesize, char), buf[80];
+    int i, d, x, y, len;
+    grid_type gline, nline;
+    struct island *is_s, *is_d, *is_orth;
+
+#define APPEND do {                                     \
+    if (movelen + len >= movesize) {                    \
+        movesize = movelen + len + 256;                 \
+        move = sresize(move, movesize, char);           \
+    }                                                   \
+    strcpy(move + movelen, buf);                        \
+    movelen += len;                                     \
+} while(0)
+
+    move[movelen++] = 'S';
+    move[movelen] = '\0';
+
+    assert(src->n_islands == dest->n_islands);
+
+    for (i = 0; i < src->n_islands; i++) {
+        is_s = &src->islands[i];
+        is_d = &dest->islands[i];
+        assert(is_s->x == is_d->x);
+        assert(is_s->y == is_d->y);
+        assert(is_s->adj.npoints == is_d->adj.npoints); /* more paranoia */
+
+        for (d = 0; d < is_s->adj.npoints; d++) {
+            if (is_s->adj.points[d].dx == -1 ||
+                is_s->adj.points[d].dy == -1) continue;
+
+            x = is_s->adj.points[d].x;
+            y = is_s->adj.points[d].y;
+            gline = is_s->adj.points[d].dx ? G_LINEH : G_LINEV;
+            nline = is_s->adj.points[d].dx ? G_NOLINEH : G_NOLINEV;
+            is_orth = INDEX(dest, gridi,
+                            ISLAND_ORTHX(is_d, d), ISLAND_ORTHY(is_d, d));
+
+            if (GRIDCOUNT(src, x, y, gline) != GRIDCOUNT(dest, x, y, gline)) {
+                assert(is_orth);
+                len = sprintf(buf, ";L%d,%d,%d,%d,%d",
+                              is_s->x, is_s->y, is_orth->x, is_orth->y,
+                              GRIDCOUNT(dest, x, y, gline));
+                APPEND;
+            }
+            if ((GRID(src,x,y) & nline) != (GRID(dest, x, y) & nline)) {
+                assert(is_orth);
+                len = sprintf(buf, ";N%d,%d,%d,%d",
+                              is_s->x, is_s->y, is_orth->x, is_orth->y);
+                APPEND;
+            }
+        }
+        if ((GRID(src, is_s->x, is_s->y) & G_MARK) !=
+            (GRID(dest, is_d->x, is_d->y) & G_MARK)) {
+            len = sprintf(buf, ";M%d,%d", is_s->x, is_s->y);
+            APPEND;
+        }
+    }
+    return move;
+}
+
+/* --- Game setup and solving utilities --- */
+
+/* This function is optimised; a Quantify showed that lots of grid-generation time
+ * (>50%) was spent in here. Hence the IDX() stuff. */
+
+static void map_update_possibles(game_state *state)
+{
+    int x, y, s, e, bl, i, np, maxb, w = state->w, idx;
+    struct island *is_s = NULL, *is_f = NULL;
+
+    /* Run down vertical stripes [un]setting possv... */
+    for (x = 0; x < state->w; x++) {
+        idx = x;
+        s = e = -1;
+        bl = 0;
+        maxb = state->params.maxb;     /* placate optimiser */
+        /* Unset possible flags until we find an island. */
+        for (y = 0; y < state->h; y++) {
+            is_s = IDX(state, gridi, idx);
+            if (is_s) {
+                maxb = is_s->count;
+                break;
+            }
+
+            IDX(state, possv, idx) = 0;
+            idx += w;
+        }
+        for (; y < state->h; y++) {
+            maxb = min(maxb, IDX(state, maxv, idx));
+            is_f = IDX(state, gridi, idx);
+            if (is_f) {
+                assert(is_s);
+                np = min(maxb, is_f->count);
+
+                if (s != -1) {
+                    for (i = s; i <= e; i++) {
+                        INDEX(state, possv, x, i) = bl ? 0 : np;
+                    }
+                }
+                s = y+1;
+                bl = 0;
+                is_s = is_f;
+                maxb = is_s->count;
+            } else {
+                e = y;
+                if (IDX(state,grid,idx) & (G_LINEH|G_NOLINEV)) bl = 1;
+            }
+            idx += w;
+        }
+        if (s != -1) {
+            for (i = s; i <= e; i++)
+                INDEX(state, possv, x, i) = 0;
+        }
+    }
+
+    /* ...and now do horizontal stripes [un]setting possh. */
+    /* can we lose this clone'n'hack? */
+    for (y = 0; y < state->h; y++) {
+        idx = y*w;
+        s = e = -1;
+        bl = 0;
+        maxb = state->params.maxb;     /* placate optimiser */
+        for (x = 0; x < state->w; x++) {
+            is_s = IDX(state, gridi, idx);
+            if (is_s) {
+                maxb = is_s->count;
+                break;
+            }
+
+            IDX(state, possh, idx) = 0;
+            idx += 1;
+        }
+        for (; x < state->w; x++) {
+            maxb = min(maxb, IDX(state, maxh, idx));
+            is_f = IDX(state, gridi, idx);
+            if (is_f) {
+                assert(is_s);
+                np = min(maxb, is_f->count);
+
+                if (s != -1) {
+                    for (i = s; i <= e; i++) {
+                        INDEX(state, possh, i, y) = bl ? 0 : np;
+                    }
+                }
+                s = x+1;
+                bl = 0;
+                is_s = is_f;
+                maxb = is_s->count;
+            } else {
+                e = x;
+                if (IDX(state,grid,idx) & (G_LINEV|G_NOLINEH)) bl = 1;
+            }
+            idx += 1;
+        }
+        if (s != -1) {
+            for (i = s; i <= e; i++)
+                INDEX(state, possh, i, y) = 0;
+        }
+    }
+}
+
+static void map_count(game_state *state)
+{
+    int i, n, ax, ay;
+    grid_type flag, grid;
+    struct island *is;
+
+    for (i = 0; i < state->n_islands; i++) {
+        is = &state->islands[i];
+        is->count = 0;
+        for (n = 0; n < is->adj.npoints; n++) {
+            ax = is->adj.points[n].x;
+            ay = is->adj.points[n].y;
+            flag = (ax == is->x) ? G_LINEV : G_LINEH;
+            grid = GRID(state,ax,ay);
+            if (grid & flag) {
+                is->count += INDEX(state,lines,ax,ay);
+            }
+        }
+    }
+}
+
+static void map_find_orthogonal(game_state *state)
+{
+    int i;
+
+    for (i = 0; i < state->n_islands; i++) {
+        island_find_orthogonal(&state->islands[i]);
+    }
+}
+
+struct bridges_neighbour_ctx {
+    game_state *state;
+    int i, n, neighbours[4];
+};
+static int bridges_neighbour(int vertex, void *vctx)
+{
+    struct bridges_neighbour_ctx *ctx = (struct bridges_neighbour_ctx *)vctx;
+    if (vertex >= 0) {
+        game_state *state = ctx->state;
+        int w = state->w, x = vertex % w, y = vertex / w;
+        grid_type grid = GRID(state, x, y), gline = grid & G_LINE;
+        struct island *is;
+        int x1, y1, x2, y2, i;
+
+        ctx->i = ctx->n = 0;
+
+        is = INDEX(state, gridi, x, y);
+        if (is) {
+            for (i = 0; i < is->adj.npoints; i++) {
+                gline = is->adj.points[i].dx ? G_LINEH : G_LINEV;
+                if (GRID(state, is->adj.points[i].x,
+                         is->adj.points[i].y) & gline) {
+                    ctx->neighbours[ctx->n++] =
+                        (is->adj.points[i].y * w + is->adj.points[i].x);
+                }
+            }
+        } else if (gline) {
+            if (gline & G_LINEV) {
+                x1 = x2 = x;
+                y1 = y-1; y2 = y+1;
+            } else {
+                x1 = x-1; x2 = x+1;
+                y1 = y2 = y;
+            }
+            /* Non-island squares with edges in should never be
+             * pointing off the edge of the grid. */
+            assert(INGRID(state, x1, y1));
+            assert(INGRID(state, x2, y2));
+            if (GRID(state, x1, y1) & (gline | G_ISLAND))
+                ctx->neighbours[ctx->n++] = y1 * w + x1;
+            if (GRID(state, x2, y2) & (gline | G_ISLAND))
+                ctx->neighbours[ctx->n++] = y2 * w + x2;
+        }
+    }
+
+    if (ctx->i < ctx->n)
+        return ctx->neighbours[ctx->i++];
+    else
+        return -1;
+}
+
+static int map_hasloops(game_state *state, int mark)
+{
+    int x, y;
+    struct findloopstate *fls;
+    struct bridges_neighbour_ctx ctx;
+    int ret;
+
+    fls = findloop_new_state(state->w * state->h);
+    ctx.state = state;
+    ret = findloop_run(fls, state->w * state->h, bridges_neighbour, &ctx);
+
+    if (mark) {
+        for (y = 0; y < state->h; y++) {
+            for (x = 0; x < state->w; x++) {
+                int u, v;
+
+                u = y * state->w + x;
+                for (v = bridges_neighbour(u, &ctx); v >= 0;
+                     v = bridges_neighbour(-1, &ctx))
+                    if (findloop_is_loop_edge(fls, u, v))
+                        GRID(state,x,y) |= G_WARN;
+            }
+        }
+    }
+
+    findloop_free_state(fls);
+    return ret;
+}
+
+static void map_group(game_state *state)
+{
+    int i, wh = state->w*state->h, d1, d2;
+    int x, y, x2, y2;
+    int *dsf = state->solver->dsf;
+    struct island *is, *is_join;
+
+    /* Initialise dsf. */
+    dsf_init(dsf, wh);
+
+    /* For each island, find connected islands right or down
+     * and merge the dsf for the island squares as well as the
+     * bridge squares. */
+    for (x = 0; x < state->w; x++) {
+        for (y = 0; y < state->h; y++) {
+            GRID(state,x,y) &= ~(G_SWEEP|G_WARN); /* for group_full. */
+
+            is = INDEX(state, gridi, x, y);
+            if (!is) continue;
+            d1 = DINDEX(x,y);
+            for (i = 0; i < is->adj.npoints; i++) {
+                /* only want right/down */
+                if (is->adj.points[i].dx == -1 ||
+                    is->adj.points[i].dy == -1) continue;
+
+                is_join = island_find_connection(is, i);
+                if (!is_join) continue;
+
+                d2 = DINDEX(is_join->x, is_join->y);
+                if (dsf_canonify(dsf,d1) == dsf_canonify(dsf,d2)) {
+                    ; /* we have a loop. See comment in map_hasloops. */
+                    /* However, we still want to merge all squares joining
+                     * this side-that-makes-a-loop. */
+                }
+                /* merge all squares between island 1 and island 2. */
+                for (x2 = x; x2 <= is_join->x; x2++) {
+                    for (y2 = y; y2 <= is_join->y; y2++) {
+                        d2 = DINDEX(x2,y2);
+                        if (d1 != d2) dsf_merge(dsf,d1,d2);
+                    }
+                }
+            }
+        }
+    }
+}
+
+static int map_group_check(game_state *state, int canon, int warn,
+                           int *nislands_r)
+{
+    int *dsf = state->solver->dsf, nislands = 0;
+    int x, y, i, allfull = 1;
+    struct island *is;
+
+    for (i = 0; i < state->n_islands; i++) {
+        is = &state->islands[i];
+        if (dsf_canonify(dsf, DINDEX(is->x,is->y)) != canon) continue;
+
+        GRID(state, is->x, is->y) |= G_SWEEP;
+        nislands++;
+        if (island_countbridges(is) != is->count)
+            allfull = 0;
+    }
+    if (warn && allfull && nislands != state->n_islands) {
+        /* we're full and this island group isn't the whole set.
+         * Mark all squares with this dsf canon as ERR. */
+        for (x = 0; x < state->w; x++) {
+            for (y = 0; y < state->h; y++) {
+                if (dsf_canonify(dsf, DINDEX(x,y)) == canon) {
+                    GRID(state,x,y) |= G_WARN;
+                }
+            }
+        }
+
+    }
+    if (nislands_r) *nislands_r = nislands;
+    return allfull;
+}
+
+static int map_group_full(game_state *state, int *ngroups_r)
+{
+    int *dsf = state->solver->dsf, ngroups = 0;
+    int i, anyfull = 0;
+    struct island *is;
+
+    /* NB this assumes map_group (or sth else) has cleared G_SWEEP. */
+
+    for (i = 0; i < state->n_islands; i++) {
+        is = &state->islands[i];
+        if (GRID(state,is->x,is->y) & G_SWEEP) continue;
+
+        ngroups++;
+        if (map_group_check(state, dsf_canonify(dsf, DINDEX(is->x,is->y)),
+                            1, NULL))
+            anyfull = 1;
+    }
+
+    *ngroups_r = ngroups;
+    return anyfull;
+}
+
+static int map_check(game_state *state)
+{
+    int ngroups;
+
+    /* Check for loops, if necessary. */
+    if (!state->allowloops) {
+        if (map_hasloops(state, 1))
+            return 0;
+    }
+
+    /* Place islands into island groups and check for early
+     * satisfied-groups. */
+    map_group(state); /* clears WARN and SWEEP */
+    if (map_group_full(state, &ngroups)) {
+        if (ngroups == 1) return 1;
+    }
+    return 0;
+}
+
+static void map_clear(game_state *state)
+{
+    int x, y;
+
+    for (x = 0; x < state->w; x++) {
+        for (y = 0; y < state->h; y++) {
+            /* clear most flags; might want to be slightly more careful here. */
+            GRID(state,x,y) &= G_ISLAND;
+        }
+    }
+}
+
+static void solve_join(struct island *is, int direction, int n, int is_max)
+{
+    struct island *is_orth;
+    int d1, d2, *dsf = is->state->solver->dsf;
+    game_state *state = is->state; /* for DINDEX */
+
+    is_orth = INDEX(is->state, gridi,
+                    ISLAND_ORTHX(is, direction),
+                    ISLAND_ORTHY(is, direction));
+    assert(is_orth);
+    /*debug(("...joining (%d,%d) to (%d,%d) with %d bridge(s).\n",
+           is->x, is->y, is_orth->x, is_orth->y, n));*/
+    island_join(is, is_orth, n, is_max);
+
+    if (n > 0 && !is_max) {
+        d1 = DINDEX(is->x, is->y);
+        d2 = DINDEX(is_orth->x, is_orth->y);
+        if (dsf_canonify(dsf, d1) != dsf_canonify(dsf, d2))
+            dsf_merge(dsf, d1, d2);
+    }
+}
+
+static int solve_fillone(struct island *is)
+{
+    int i, nadded = 0;
+
+    debug(("solve_fillone for island (%d,%d).\n", is->x, is->y));
+
+    for (i = 0; i < is->adj.npoints; i++) {
+        if (island_isadj(is, i)) {
+            if (island_hasbridge(is, i)) {
+                /* already attached; do nothing. */;
+            } else {
+                solve_join(is, i, 1, 0);
+                nadded++;
+            }
+        }
+    }
+    return nadded;
+}
+
+static int solve_fill(struct island *is)
+{
+    /* for each unmarked adjacent, make sure we convert every possible bridge
+     * to a real one, and then work out the possibles afresh. */
+    int i, nnew, ncurr, nadded = 0, missing;
+
+    debug(("solve_fill for island (%d,%d).\n", is->x, is->y));
+
+    missing = is->count - island_countbridges(is);
+    if (missing < 0) return 0;
+
+    /* very like island_countspaces. */
+    for (i = 0; i < is->adj.npoints; i++) {
+        nnew = island_adjspace(is, 1, missing, i);
+        if (nnew) {
+            ncurr = GRIDCOUNT(is->state,
+                              is->adj.points[i].x, is->adj.points[i].y,
+                              is->adj.points[i].dx ? G_LINEH : G_LINEV);
+
+            solve_join(is, i, nnew + ncurr, 0);
+            nadded += nnew;
+        }
+    }
+    return nadded;
+}
+
+static int solve_island_stage1(struct island *is, int *didsth_r)
+{
+    int bridges = island_countbridges(is);
+    int nspaces = island_countspaces(is, 1);
+    int nadj = island_countadj(is);
+    int didsth = 0;
+
+    assert(didsth_r);
+
+    /*debug(("island at (%d,%d) filled %d/%d (%d spc) nadj %d\n",
+           is->x, is->y, bridges, is->count, nspaces, nadj));*/
+    if (bridges > is->count) {
+        /* We only ever add bridges when we're sure they fit, or that's
+         * the only place they can go. If we've added bridges such that
+         * another island has become wrong, the puzzle must not have had
+         * a solution. */
+        debug(("...island at (%d,%d) is overpopulated!\n", is->x, is->y));
+        return 0;
+    } else if (bridges == is->count) {
+        /* This island is full. Make sure it's marked (and update
+         * possibles if we did). */
+        if (!(GRID(is->state, is->x, is->y) & G_MARK)) {
+            debug(("...marking island (%d,%d) as full.\n", is->x, is->y));
+            island_togglemark(is);
+            didsth = 1;
+        }
+    } else if (GRID(is->state, is->x, is->y) & G_MARK) {
+        debug(("...island (%d,%d) is marked but unfinished!\n",
+               is->x, is->y));
+        return 0; /* island has been marked unfinished; no solution from here. */
+    } else {
+        /* This is the interesting bit; we try and fill in more information
+         * about this island. */
+        if (is->count == bridges + nspaces) {
+            if (solve_fill(is) > 0) didsth = 1;
+        } else if (is->count > ((nadj-1) * is->state->maxb)) {
+            /* must have at least one bridge in each possible direction. */
+            if (solve_fillone(is) > 0) didsth = 1;
+        }
+    }
+    if (didsth) {
+        map_update_possibles(is->state);
+        *didsth_r = 1;
+    }
+    return 1;
+}
+
+/* returns non-zero if a new line here would cause a loop. */
+static int solve_island_checkloop(struct island *is, int direction)
+{
+    struct island *is_orth;
+    int *dsf = is->state->solver->dsf, d1, d2;
+    game_state *state = is->state;
+
+    if (is->state->allowloops) return 0; /* don't care anyway */
+    if (island_hasbridge(is, direction)) return 0; /* already has a bridge */
+    if (island_isadj(is, direction) == 0) return 0; /* no adj island */
+
+    is_orth = INDEX(is->state, gridi,
+                    ISLAND_ORTHX(is,direction),
+                    ISLAND_ORTHY(is,direction));
+    if (!is_orth) return 0;
+
+    d1 = DINDEX(is->x, is->y);
+    d2 = DINDEX(is_orth->x, is_orth->y);
+    if (dsf_canonify(dsf, d1) == dsf_canonify(dsf, d2)) {
+        /* two islands are connected already; don't join them. */
+        return 1;
+    }
+    return 0;
+}
+
+static int solve_island_stage2(struct island *is, int *didsth_r)
+{
+    int added = 0, removed = 0, navail = 0, nadj, i;
+
+    assert(didsth_r);
+
+    for (i = 0; i < is->adj.npoints; i++) {
+        if (solve_island_checkloop(is, i)) {
+            debug(("removing possible loop at (%d,%d) direction %d.\n",
+                   is->x, is->y, i));
+            solve_join(is, i, -1, 0);
+            map_update_possibles(is->state);
+            removed = 1;
+        } else {
+            navail += island_isadj(is, i);
+            /*debug(("stage2: navail for (%d,%d) direction (%d,%d) is %d.\n",
+                   is->x, is->y,
+                   is->adj.points[i].dx, is->adj.points[i].dy,
+                   island_isadj(is, i)));*/
+        }
+    }
+
+    /*debug(("island at (%d,%d) navail %d: checking...\n", is->x, is->y, navail));*/
+
+    for (i = 0; i < is->adj.npoints; i++) {
+        if (!island_hasbridge(is, i)) {
+            nadj = island_isadj(is, i);
+            if (nadj > 0 && (navail - nadj) < is->count) {
+                /* we couldn't now complete the island without at
+                 * least one bridge here; put it in. */
+                /*debug(("nadj %d, navail %d, is->count %d.\n",
+                       nadj, navail, is->count));*/
+                debug(("island at (%d,%d) direction (%d,%d) must have 1 bridge\n",
+                       is->x, is->y,
+                       is->adj.points[i].dx, is->adj.points[i].dy));
+                solve_join(is, i, 1, 0);
+                added = 1;
+                /*debug_state(is->state);
+                debug_possibles(is->state);*/
+            }
+        }
+    }
+    if (added) map_update_possibles(is->state);
+    if (added || removed) *didsth_r = 1;
+    return 1;
+}
+
+static int solve_island_subgroup(struct island *is, int direction)
+{
+    struct island *is_join;
+    int nislands, *dsf = is->state->solver->dsf;
+    game_state *state = is->state;
+
+    debug(("..checking subgroups.\n"));
+
+    /* if is isn't full, return 0. */
+    if (island_countbridges(is) < is->count) {
+        debug(("...orig island (%d,%d) not full.\n", is->x, is->y));
+        return 0;
+    }
+
+    if (direction >= 0) {
+        is_join = INDEX(state, gridi,
+                        ISLAND_ORTHX(is, direction),
+                        ISLAND_ORTHY(is, direction));
+        assert(is_join);
+
+        /* if is_join isn't full, return 0. */
+        if (island_countbridges(is_join) < is_join->count) {
+            debug(("...dest island (%d,%d) not full.\n",
+                   is_join->x, is_join->y));
+            return 0;
+        }
+    }
+
+    /* Check group membership for is->dsf; if it's full return 1. */
+    if (map_group_check(state, dsf_canonify(dsf, DINDEX(is->x,is->y)),
+                        0, &nislands)) {
+        if (nislands < state->n_islands) {
+            /* we have a full subgroup that isn't the whole set.
+             * This isn't allowed. */
+            debug(("island at (%d,%d) makes full subgroup, disallowing.\n",
+                   is->x, is->y));
+            return 1;
+        } else {
+            debug(("...has finished puzzle.\n"));
+        }
+    }
+    return 0;
+}
+
+static int solve_island_impossible(game_state *state)
+{
+    struct island *is;
+    int i;
+
+    /* If any islands are impossible, return 1. */
+    for (i = 0; i < state->n_islands; i++) {
+        is = &state->islands[i];
+        if (island_impossible(is, 0)) {
+            debug(("island at (%d,%d) has become impossible, disallowing.\n",
+                   is->x, is->y));
+            return 1;
+        }
+    }
+    return 0;
+}
+
+/* Bear in mind that this function is really rather inefficient. */
+static int solve_island_stage3(struct island *is, int *didsth_r)
+{
+    int i, n, x, y, missing, spc, curr, maxb, didsth = 0;
+    int wh = is->state->w * is->state->h;
+    struct solver_state *ss = is->state->solver;
+
+    assert(didsth_r);
+
+    missing = is->count - island_countbridges(is);
+    if (missing <= 0) return 1;
+
+    for (i = 0; i < is->adj.npoints; i++) {
+        x = is->adj.points[i].x;
+        y = is->adj.points[i].y;
+        spc = island_adjspace(is, 1, missing, i);
+        if (spc == 0) continue;
+
+        curr = GRIDCOUNT(is->state, x, y,
+                         is->adj.points[i].dx ? G_LINEH : G_LINEV);
+        debug(("island at (%d,%d) s3, trying %d - %d bridges.\n",
+               is->x, is->y, curr+1, curr+spc));
+
+        /* Now we know that this island could have more bridges,
+         * to bring the total from curr+1 to curr+spc. */
+        maxb = -1;
+        /* We have to squirrel the dsf away and restore it afterwards;
+         * it is additive only, and can't be removed from. */
+        memcpy(ss->tmpdsf, ss->dsf, wh*sizeof(int));
+        for (n = curr+1; n <= curr+spc; n++) {
+            solve_join(is, i, n, 0);
+            map_update_possibles(is->state);
+
+            if (solve_island_subgroup(is, i) ||
+                solve_island_impossible(is->state)) {
+                maxb = n-1;
+                debug(("island at (%d,%d) d(%d,%d) new max of %d bridges:\n",
+                       is->x, is->y,
+                       is->adj.points[i].dx, is->adj.points[i].dy,
+                       maxb));
+                break;
+            }
+        }
+        solve_join(is, i, curr, 0); /* put back to before. */
+        memcpy(ss->dsf, ss->tmpdsf, wh*sizeof(int));
+
+        if (maxb != -1) {
+            /*debug_state(is->state);*/
+            if (maxb == 0) {
+                debug(("...adding NOLINE.\n"));
+                solve_join(is, i, -1, 0); /* we can't have any bridges here. */
+            } else {
+                debug(("...setting maximum\n"));
+                solve_join(is, i, maxb, 1);
+            }
+            didsth = 1;
+        }
+        map_update_possibles(is->state);
+    }
+
+    for (i = 0; i < is->adj.npoints; i++) {
+        /*
+         * Now check to see if any currently empty direction must have
+         * at least one bridge in order to avoid forming an isolated
+         * subgraph. This differs from the check above in that it
+         * considers multiple target islands. For example:
+         *
+         *   2   2    4
+         *                                  1     3     2
+         *       3
+         *                                        4
+         *
+         * The example on the left can be handled by the above loop:
+         * it will observe that connecting the central 2 twice to the
+         * left would form an isolated subgraph, and hence it will
+         * restrict that 2 to at most one bridge in that direction.
+         * But the example on the right won't be handled by that loop,
+         * because the deduction requires us to imagine connecting the
+         * 3 to _both_ the 1 and 2 at once to form an isolated
+         * subgraph.
+         *
+         * This pass is necessary _as well_ as the above one, because
+         * neither can do the other's job. In the left one,
+         * restricting the direction which _would_ cause trouble can
+         * be done even if it's not yet clear which of the remaining
+         * directions has to have a compensatory bridge; whereas the
+         * pass below that can handle the right-hand example does need
+         * to know what direction to point the necessary bridge in.
+         *
+         * Neither pass can handle the most general case, in which we
+         * observe that an arbitrary subset of an island's neighbours
+         * would form an isolated subgraph with it if it connected
+         * maximally to them, and hence that at least one bridge must
+         * point to some neighbour outside that subset but we don't
+         * know which neighbour. To handle that, we'd have to have a
+         * richer data format for the solver, which could cope with
+         * recording the idea that at least one of two edges must have
+         * a bridge.
+         */
+        int got = 0;
+        int before[4];
+        int j;
+
+        spc = island_adjspace(is, 1, missing, i);
+        if (spc == 0) continue;
+
+        for (j = 0; j < is->adj.npoints; j++)
+            before[j] = GRIDCOUNT(is->state,
+                                  is->adj.points[j].x,
+                                  is->adj.points[j].y,
+                                  is->adj.points[j].dx ? G_LINEH : G_LINEV);
+        if (before[i] != 0) continue;  /* this idea is pointless otherwise */
+
+        memcpy(ss->tmpdsf, ss->dsf, wh*sizeof(int));
+
+        for (j = 0; j < is->adj.npoints; j++) {
+            spc = island_adjspace(is, 1, missing, j);
+            if (spc == 0) continue;
+            if (j == i) continue;
+            solve_join(is, j, before[j] + spc, 0);
+        }
+        map_update_possibles(is->state);
+
+        if (solve_island_subgroup(is, -1))
+            got = 1;
+
+        for (j = 0; j < is->adj.npoints; j++)
+            solve_join(is, j, before[j], 0);
+        memcpy(ss->dsf, ss->tmpdsf, wh*sizeof(int));
+
+        if (got) {
+            debug(("island at (%d,%d) must connect in direction (%d,%d) to"
+                   " avoid full subgroup.\n",
+                   is->x, is->y, is->adj.points[i].dx, is->adj.points[i].dy));
+            solve_join(is, i, 1, 0);
+            didsth = 1;
+        }
+
+        map_update_possibles(is->state);
+    }
+
+    if (didsth) *didsth_r = didsth;
+    return 1;
+}
+
+#define CONTINUE_IF_FULL do {                           \
+if (GRID(state, is->x, is->y) & G_MARK) {            \
+    /* island full, don't try fixing it */           \
+    continue;                                        \
+} } while(0)
+
+static int solve_sub(game_state *state, int difficulty, int depth)
+{
+    struct island *is;
+    int i, didsth;
+
+    while (1) {
+        didsth = 0;
+
+        /* First island iteration: things we can work out by looking at
+         * properties of the island as a whole. */
+        for (i = 0; i < state->n_islands; i++) {
+            is = &state->islands[i];
+            if (!solve_island_stage1(is, &didsth)) return 0;
+        }
+        if (didsth) continue;
+        else if (difficulty < 1) break;
+
+        /* Second island iteration: thing we can work out by looking at
+         * properties of individual island connections. */
+        for (i = 0; i < state->n_islands; i++) {
+            is = &state->islands[i];
+            CONTINUE_IF_FULL;
+            if (!solve_island_stage2(is, &didsth)) return 0;
+        }
+        if (didsth) continue;
+        else if (difficulty < 2) break;
+
+        /* Third island iteration: things we can only work out by looking
+         * at groups of islands. */
+        for (i = 0; i < state->n_islands; i++) {
+            is = &state->islands[i];
+            if (!solve_island_stage3(is, &didsth)) return 0;
+        }
+        if (didsth) continue;
+        else if (difficulty < 3) break;
+
+        /* If we can be bothered, write a recursive solver to finish here. */
+        break;
+    }
+    if (map_check(state)) return 1; /* solved it */
+    return 0;
+}
+
+static void solve_for_hint(game_state *state)
+{
+    map_group(state);
+    solve_sub(state, 10, 0);
+}
+
+static int solve_from_scratch(game_state *state, int difficulty)
+{
+    map_clear(state);
+    map_group(state);
+    map_update_possibles(state);
+    return solve_sub(state, difficulty, 0);
+}
+
+/* --- New game functions --- */
+
+static game_state *new_state(const game_params *params)
+{
+    game_state *ret = snew(game_state);
+    int wh = params->w * params->h, i;
+
+    ret->w = params->w;
+    ret->h = params->h;
+    ret->allowloops = params->allowloops;
+    ret->maxb = params->maxb;
+    ret->params = *params;
+
+    ret->grid = snewn(wh, grid_type);
+    memset(ret->grid, 0, GRIDSZ(ret));
+
+    ret->wha = snewn(wh*N_WH_ARRAYS, char);
+    memset(ret->wha, 0, wh*N_WH_ARRAYS*sizeof(char));
+
+    ret->possv = ret->wha;
+    ret->possh = ret->wha + wh;
+    ret->lines = ret->wha + wh*2;
+    ret->maxv = ret->wha + wh*3;
+    ret->maxh = ret->wha + wh*4;
+
+    memset(ret->maxv, ret->maxb, wh*sizeof(char));
+    memset(ret->maxh, ret->maxb, wh*sizeof(char));
+
+    ret->islands = NULL;
+    ret->n_islands = 0;
+    ret->n_islands_alloc = 0;
+
+    ret->gridi = snewn(wh, struct island *);
+    for (i = 0; i < wh; i++) ret->gridi[i] = NULL;
+
+    ret->solved = ret->completed = 0;
+
+    ret->solver = snew(struct solver_state);
+    ret->solver->dsf = snew_dsf(wh);
+    ret->solver->tmpdsf = snewn(wh, int);
+
+    ret->solver->refcount = 1;
+
+    return ret;
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    game_state *ret = snew(game_state);
+    int wh = state->w*state->h;
+
+    ret->w = state->w;
+    ret->h = state->h;
+    ret->allowloops = state->allowloops;
+    ret->maxb = state->maxb;
+    ret->params = state->params;
+
+    ret->grid = snewn(wh, grid_type);
+    memcpy(ret->grid, state->grid, GRIDSZ(ret));
+
+    ret->wha = snewn(wh*N_WH_ARRAYS, char);
+    memcpy(ret->wha, state->wha, wh*N_WH_ARRAYS*sizeof(char));
+
+    ret->possv = ret->wha;
+    ret->possh = ret->wha + wh;
+    ret->lines = ret->wha + wh*2;
+    ret->maxv = ret->wha + wh*3;
+    ret->maxh = ret->wha + wh*4;
+
+    ret->islands = snewn(state->n_islands, struct island);
+    memcpy(ret->islands, state->islands, state->n_islands * sizeof(struct island));
+    ret->n_islands = ret->n_islands_alloc = state->n_islands;
+
+    ret->gridi = snewn(wh, struct island *);
+    fixup_islands_for_realloc(ret);
+
+    ret->solved = state->solved;
+    ret->completed = state->completed;
+
+    ret->solver = state->solver;
+    ret->solver->refcount++;
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    if (--state->solver->refcount <= 0) {
+        sfree(state->solver->dsf);
+        sfree(state->solver->tmpdsf);
+        sfree(state->solver);
+    }
+
+    sfree(state->islands);
+    sfree(state->gridi);
+
+    sfree(state->wha);
+
+    sfree(state->grid);
+    sfree(state);
+}
+
+#define MAX_NEWISLAND_TRIES     50
+#define MIN_SENSIBLE_ISLANDS    3
+
+#define ORDER(a,b) do { if (a < b) { int tmp=a; int a=b; int b=tmp; } } while(0)
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                          char **aux, int interactive)
+{
+    game_state *tobuild  = NULL;
+    int i, j, wh = params->w * params->h, x, y, dx, dy;
+    int minx, miny, maxx, maxy, joinx, joiny, newx, newy, diffx, diffy;
+    int ni_req = max((params->islands * wh) / 100, MIN_SENSIBLE_ISLANDS), ni_curr, ni_bad;
+    struct island *is, *is2;
+    char *ret;
+    unsigned int echeck;
+
+    /* pick a first island position randomly. */
+generate:
+    if (tobuild) free_game(tobuild);
+    tobuild = new_state(params);
+
+    x = random_upto(rs, params->w);
+    y = random_upto(rs, params->h);
+    island_add(tobuild, x, y, 0);
+    ni_curr = 1;
+    ni_bad = 0;
+    debug(("Created initial island at (%d,%d).\n", x, y));
+
+    while (ni_curr < ni_req) {
+        /* Pick a random island to try and extend from. */
+        i = random_upto(rs, tobuild->n_islands);
+        is = &tobuild->islands[i];
+
+        /* Pick a random direction to extend in. */
+        j = random_upto(rs, is->adj.npoints);
+        dx = is->adj.points[j].x - is->x;
+        dy = is->adj.points[j].y - is->y;
+
+        /* Find out limits of where we could put a new island. */
+        joinx = joiny = -1;
+        minx = is->x + 2*dx; miny = is->y + 2*dy; /* closest is 2 units away. */
+        x = is->x+dx; y = is->y+dy;
+        if (GRID(tobuild,x,y) & (G_LINEV|G_LINEH)) {
+            /* already a line next to the island, continue. */
+            goto bad;
+        }
+        while (1) {
+            if (x < 0 || x >= params->w || y < 0 || y >= params->h) {
+                /* got past the edge; put a possible at the island
+                 * and exit. */
+                maxx = x-dx; maxy = y-dy;
+                goto foundmax;
+            }
+            if (GRID(tobuild,x,y) & G_ISLAND) {
+                /* could join up to an existing island... */
+                joinx = x; joiny = y;
+                /* ... or make a new one 2 spaces away. */
+                maxx = x - 2*dx; maxy = y - 2*dy;
+                goto foundmax;
+            } else if (GRID(tobuild,x,y) & (G_LINEV|G_LINEH)) {
+                /* could make a new one 1 space away from the line. */
+                maxx = x - dx; maxy = y - dy;
+                goto foundmax;
+            }
+            x += dx; y += dy;
+        }
+
+foundmax:
+        debug(("Island at (%d,%d) with d(%d,%d) has new positions "
+               "(%d,%d) -> (%d,%d), join (%d,%d).\n",
+               is->x, is->y, dx, dy, minx, miny, maxx, maxy, joinx, joiny));
+        /* Now we know where we could either put a new island
+         * (between min and max), or (if loops are allowed) could join on
+         * to an existing island (at join). */
+        if (params->allowloops && joinx != -1 && joiny != -1) {
+            if (random_upto(rs, 100) < (unsigned long)params->expansion) {
+                is2 = INDEX(tobuild, gridi, joinx, joiny);
+                debug(("Joining island at (%d,%d) to (%d,%d).\n",
+                       is->x, is->y, is2->x, is2->y));
+                goto join;
+            }
+        }
+        diffx = (maxx - minx) * dx;
+        diffy = (maxy - miny) * dy;
+        if (diffx < 0 || diffy < 0)  goto bad;
+        if (random_upto(rs,100) < (unsigned long)params->expansion) {
+            newx = maxx; newy = maxy;
+            debug(("Creating new island at (%d,%d) (expanded).\n", newx, newy));
+        } else {
+            newx = minx + random_upto(rs,diffx+1)*dx;
+            newy = miny + random_upto(rs,diffy+1)*dy;
+            debug(("Creating new island at (%d,%d).\n", newx, newy));
+        }
+        /* check we're not next to island in the other orthogonal direction. */
+        if ((INGRID(tobuild,newx+dy,newy+dx) && (GRID(tobuild,newx+dy,newy+dx) & G_ISLAND)) ||
+            (INGRID(tobuild,newx-dy,newy-dx) && (GRID(tobuild,newx-dy,newy-dx) & G_ISLAND))) {
+            debug(("New location is adjacent to island, skipping.\n"));
+            goto bad;
+        }
+        is2 = island_add(tobuild, newx, newy, 0);
+        /* Must get is again at this point; the array might have
+         * been realloced by island_add... */
+        is = &tobuild->islands[i]; /* ...but order will not change. */
+
+        ni_curr++; ni_bad = 0;
+join:
+        island_join(is, is2, random_upto(rs, tobuild->maxb)+1, 0);
+        debug_state(tobuild);
+        continue;
+
+bad:
+        ni_bad++;
+        if (ni_bad > MAX_NEWISLAND_TRIES) {
+            debug(("Unable to create any new islands after %d tries; "
+                   "created %d [%d%%] (instead of %d [%d%%] requested).\n",
+                   MAX_NEWISLAND_TRIES,
+                   ni_curr, ni_curr * 100 / wh,
+                   ni_req, ni_req * 100 / wh));
+            goto generated;
+        }
+    }
+
+generated:
+    if (ni_curr == 1) {
+        debug(("Only generated one island (!), retrying.\n"));
+        goto generate;
+    }
+    /* Check we have at least one island on each extremity of the grid. */
+    echeck = 0;
+    for (x = 0; x < params->w; x++) {
+        if (INDEX(tobuild, gridi, x, 0))           echeck |= 1;
+        if (INDEX(tobuild, gridi, x, params->h-1)) echeck |= 2;
+    }
+    for (y = 0; y < params->h; y++) {
+        if (INDEX(tobuild, gridi, 0,           y)) echeck |= 4;
+        if (INDEX(tobuild, gridi, params->w-1, y)) echeck |= 8;
+    }
+    if (echeck != 15) {
+        debug(("Generated grid doesn't fill to sides, retrying.\n"));
+        goto generate;
+    }
+
+    map_count(tobuild);
+    map_find_orthogonal(tobuild);
+
+    if (params->difficulty > 0) {
+        if ((ni_curr > MIN_SENSIBLE_ISLANDS) &&
+            (solve_from_scratch(tobuild, params->difficulty-1) > 0)) {
+            debug(("Grid is solvable at difficulty %d (too easy); retrying.\n",
+                   params->difficulty-1));
+            goto generate;
+        }
+    }
+
+    if (solve_from_scratch(tobuild, params->difficulty) == 0) {
+        debug(("Grid not solvable at difficulty %d, (too hard); retrying.\n",
+               params->difficulty));
+        goto generate;
+    }
+
+    /* ... tobuild is now solved. We rely on this making the diff for aux. */
+    debug_state(tobuild);
+    ret = encode_game(tobuild);
+    {
+        game_state *clean = dup_game(tobuild);
+        map_clear(clean);
+        map_update_possibles(clean);
+        *aux = game_state_diff(clean, tobuild);
+        free_game(clean);
+    }
+    free_game(tobuild);
+
+    return ret;
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    int i, wh = params->w * params->h;
+
+    for (i = 0; i < wh; i++) {
+        if (*desc >= '1' && *desc <= '9')
+            /* OK */;
+        else if (*desc >= 'a' && *desc <= 'z')
+            i += *desc - 'a'; /* plus the i++ */
+        else if (*desc >= 'A' && *desc <= 'G')
+            /* OK */;
+        else if (*desc == 'V' || *desc == 'W' ||
+                 *desc == 'X' || *desc == 'Y' ||
+                 *desc == 'H' || *desc == 'I' ||
+                 *desc == 'J' || *desc == 'K')
+            /* OK */;
+        else if (!*desc)
+            return "Game description shorter than expected";
+        else
+            return "Game description contains unexpected character";
+        desc++;
+    }
+    if (*desc || i > wh)
+        return "Game description longer than expected";
+
+    return NULL;
+}
+
+static game_state *new_game_sub(const game_params *params, const char *desc)
+{
+    game_state *state = new_state(params);
+    int x, y, run = 0;
+
+    debug(("new_game[_sub]: desc = '%s'.\n", desc));
+
+    for (y = 0; y < params->h; y++) {
+        for (x = 0; x < params->w; x++) {
+            char c = '\0';
+
+            if (run == 0) {
+                c = *desc++;
+                assert(c != 'S');
+                if (c >= 'a' && c <= 'z')
+                    run = c - 'a' + 1;
+            }
+
+            if (run > 0) {
+                c = 'S';
+                run--;
+            }
+
+            switch (c) {
+            case '1': case '2': case '3': case '4':
+            case '5': case '6': case '7': case '8': case '9':
+                island_add(state, x, y, (c - '0'));
+                break;
+
+            case 'A': case 'B': case 'C': case 'D':
+            case 'E': case 'F': case 'G':
+                island_add(state, x, y, (c - 'A') + 10);
+                break;
+
+            case 'S':
+                /* empty square */
+                break;
+
+            default:
+                assert(!"Malformed desc.");
+                break;
+            }
+        }
+    }
+    if (*desc) assert(!"Over-long desc.");
+
+    map_find_orthogonal(state);
+    map_update_possibles(state);
+
+    return state;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    return new_game_sub(params, desc);
+}
+
+struct game_ui {
+    int dragx_src, dragy_src;   /* source; -1 means no drag */
+    int dragx_dst, dragy_dst;   /* src's closest orth island. */
+    grid_type todraw;
+    int dragging, drag_is_noline, nlines;
+
+    int cur_x, cur_y, cur_visible;      /* cursor position */
+    int show_hints;
+};
+
+static char *ui_cancel_drag(game_ui *ui)
+{
+    ui->dragx_src = ui->dragy_src = -1;
+    ui->dragx_dst = ui->dragy_dst = -1;
+    ui->dragging = 0;
+    return "";
+}
+
+static game_ui *new_ui(const game_state *state)
+{
+    game_ui *ui = snew(game_ui);
+    ui_cancel_drag(ui);
+    ui->cur_x = state->islands[0].x;
+    ui->cur_y = state->islands[0].y;
+    ui->cur_visible = 0;
+    ui->show_hints = 0;
+    return ui;
+}
+
+static void free_ui(game_ui *ui)
+{
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+}
+
+struct game_drawstate {
+    int tilesize;
+    int w, h;
+    unsigned long *grid, *newgrid;
+    int *lv, *lh;
+    int started, dragging;
+};
+
+/*
+ * The contents of ds->grid are complicated, because of the circular
+ * islands which overlap their own grid square into neighbouring
+ * squares. An island square can contain pieces of the bridges in all
+ * directions, and conversely a bridge square can be intruded on by
+ * islands from any direction.
+ *
+ * So we define one group of flags describing what's important about
+ * an island, and another describing a bridge. Island squares' entries
+ * in ds->grid contain one of the former and four of the latter; bridge
+ * squares, four of the former and _two_ of the latter - because a
+ * horizontal and vertical 'bridge' can cross, when one of them is a
+ * 'no bridge here' pencil mark.
+ *
+ * Bridge flags need to indicate 0-4 actual bridges (3 bits), a 'no
+ * bridge' row of crosses, or a grey hint line; that's 7
+ * possibilities, so 3 bits suffice. But then we also need to vary the
+ * colours: the bridges can turn COL_WARNING if they're part of a loop
+ * in no-loops mode, COL_HIGHLIGHT during a victory flash, or
+ * COL_SELECTED if they're the bridge the user is currently dragging,
+ * so that's 2 more bits for foreground colour. Also bridges can be
+ * backed by COL_MARK if they're locked by the user, so that's one
+ * more bit, making 6 bits per bridge direction.
+ *
+ * Island flags omit the actual island clue (it never changes during
+ * the game, so doesn't have to be stored in ds->grid to check against
+ * the previous version), so they just need to include 2 bits for
+ * foreground colour (an island can be normal, COL_HIGHLIGHT during
+ * victory, COL_WARNING if its clue is unsatisfiable, or COL_SELECTED
+ * if it's part of the user's drag) and 2 bits for background (normal,
+ * COL_MARK for a locked island, COL_CURSOR for the keyboard cursor).
+ * That's 4 bits per island direction. We must also indicate whether
+ * no island is present at all (in the case where the island is
+ * potentially intruding into the side of a line square), which we do
+ * using the unused 4th value of the background field.
+ *
+ * So an island square needs 4 + 4*6 = 28 bits, while a bridge square
+ * needs 4*4 + 2*6 = 28 bits too. Both only just fit in 32 bits, which
+ * is handy, because otherwise we'd have to faff around forever with
+ * little structs!
+ */
+/* Flags for line data */
+#define DL_COUNTMASK    0x07
+#define DL_COUNT_CROSS  0x06
+#define DL_COUNT_HINT   0x07
+#define DL_COLMASK      0x18
+#define DL_COL_NORMAL   0x00
+#define DL_COL_WARNING  0x08
+#define DL_COL_FLASH    0x10
+#define DL_COL_SELECTED 0x18
+#define DL_LOCK         0x20
+#define DL_MASK         0x3F
+/* Flags for island data */
+#define DI_COLMASK      0x03
+#define DI_COL_NORMAL   0x00
+#define DI_COL_FLASH    0x01
+#define DI_COL_WARNING  0x02
+#define DI_COL_SELECTED 0x03
+#define DI_BGMASK       0x0C
+#define DI_BG_NO_ISLAND 0x00
+#define DI_BG_NORMAL    0x04
+#define DI_BG_MARK      0x08
+#define DI_BG_CURSOR    0x0C
+#define DI_MASK         0x0F
+/* Shift counts for the format of a 32-bit word in an island square */
+#define D_I_ISLAND_SHIFT 0
+#define D_I_LINE_SHIFT_L 4
+#define D_I_LINE_SHIFT_R 10
+#define D_I_LINE_SHIFT_U 16
+#define D_I_LINE_SHIFT_D 24
+/* Shift counts for the format of a 32-bit word in a line square */
+#define D_L_ISLAND_SHIFT_L 0
+#define D_L_ISLAND_SHIFT_R 4
+#define D_L_ISLAND_SHIFT_U 8
+#define D_L_ISLAND_SHIFT_D 12
+#define D_L_LINE_SHIFT_H 16
+#define D_L_LINE_SHIFT_V 22
+
+static char *update_drag_dst(const game_state *state, game_ui *ui,
+                             const game_drawstate *ds, int nx, int ny)
+{
+    int ox, oy, dx, dy, i, currl, maxb;
+    struct island *is;
+    grid_type gtype, ntype, mtype, curr;
+
+    if (ui->dragx_src == -1 || ui->dragy_src == -1) return NULL;
+
+    ui->dragx_dst = -1;
+    ui->dragy_dst = -1;
+
+    /* work out which of the four directions we're closest to... */
+    ox = COORD(ui->dragx_src) + TILE_SIZE/2;
+    oy = COORD(ui->dragy_src) + TILE_SIZE/2;
+
+    if (abs(nx-ox) < abs(ny-oy)) {
+        dx = 0;
+        dy = (ny-oy) < 0 ? -1 : 1;
+        gtype = G_LINEV; ntype = G_NOLINEV; mtype = G_MARKV;
+        maxb = INDEX(state, maxv, ui->dragx_src+dx, ui->dragy_src+dy);
+    } else {
+        dy = 0;
+        dx = (nx-ox) < 0 ? -1 : 1;
+        gtype = G_LINEH; ntype = G_NOLINEH; mtype = G_MARKH;
+        maxb = INDEX(state, maxh, ui->dragx_src+dx, ui->dragy_src+dy);
+    }
+    if (ui->drag_is_noline) {
+        ui->todraw = ntype;
+    } else {
+        curr = GRID(state, ui->dragx_src+dx, ui->dragy_src+dy);
+        currl = INDEX(state, lines, ui->dragx_src+dx, ui->dragy_src+dy);
+
+        if (curr & gtype) {
+            if (currl == maxb) {
+                ui->todraw = 0;
+                ui->nlines = 0;
+            } else {
+                ui->todraw = gtype;
+                ui->nlines = currl + 1;
+            }
+        } else {
+            ui->todraw = gtype;
+            ui->nlines = 1;
+        }
+    }
+
+    /* ... and see if there's an island off in that direction. */
+    is = INDEX(state, gridi, ui->dragx_src, ui->dragy_src);
+    for (i = 0; i < is->adj.npoints; i++) {
+        if (is->adj.points[i].off == 0) continue;
+        curr = GRID(state, is->x+dx, is->y+dy);
+        if (curr & mtype) continue; /* don't allow changes to marked lines. */
+        if (ui->drag_is_noline) {
+            if (curr & gtype) continue; /* no no-line where already a line */
+        } else {
+            if (POSSIBLES(state, dx, is->x+dx, is->y+dy) == 0) continue; /* no line if !possible. */
+            if (curr & ntype) continue; /* can't have a bridge where there's a no-line. */
+        }
+
+        if (is->adj.points[i].dx == dx &&
+            is->adj.points[i].dy == dy) {
+            ui->dragx_dst = ISLAND_ORTHX(is,i);
+            ui->dragy_dst = ISLAND_ORTHY(is,i);
+        }
+    }
+    /*debug(("update_drag src (%d,%d) d(%d,%d) dst (%d,%d)\n",
+           ui->dragx_src, ui->dragy_src, dx, dy,
+           ui->dragx_dst, ui->dragy_dst));*/
+    return "";
+}
+
+static char *finish_drag(const game_state *state, game_ui *ui)
+{
+    char buf[80];
+
+    if (ui->dragx_src == -1 || ui->dragy_src == -1)
+        return NULL;
+    if (ui->dragx_dst == -1 || ui->dragy_dst == -1)
+        return ui_cancel_drag(ui);
+
+    if (ui->drag_is_noline) {
+        sprintf(buf, "N%d,%d,%d,%d",
+                ui->dragx_src, ui->dragy_src,
+                ui->dragx_dst, ui->dragy_dst);
+    } else {
+        sprintf(buf, "L%d,%d,%d,%d,%d",
+                ui->dragx_src, ui->dragy_src,
+                ui->dragx_dst, ui->dragy_dst, ui->nlines);
+    }
+
+    ui_cancel_drag(ui);
+
+    return dupstr(buf);
+}
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    int gx = FROMCOORD(x), gy = FROMCOORD(y);
+    char buf[80], *ret;
+    grid_type ggrid = INGRID(state,gx,gy) ? GRID(state,gx,gy) : 0;
+    int shift = button & MOD_SHFT, control = button & MOD_CTRL;
+    button &= ~MOD_MASK;
+
+    if (button == LEFT_BUTTON || button == RIGHT_BUTTON) {
+        if (!INGRID(state, gx, gy)) return NULL;
+        ui->cur_visible = 0;
+        if (ggrid & G_ISLAND) {
+            ui->dragx_src = gx;
+            ui->dragy_src = gy;
+            return "";
+        } else
+            return ui_cancel_drag(ui);
+    } else if (button == LEFT_DRAG || button == RIGHT_DRAG) {
+        if (INGRID(state, ui->dragx_src, ui->dragy_src)
+                && (gx != ui->dragx_src || gy != ui->dragy_src)
+                && !(GRID(state,ui->dragx_src,ui->dragy_src) & G_MARK)) {
+            ui->dragging = 1;
+            ui->drag_is_noline = (button == RIGHT_DRAG) ? 1 : 0;
+            return update_drag_dst(state, ui, ds, x, y);
+        } else {
+            /* cancel a drag when we go back to the starting point */
+            ui->dragx_dst = -1;
+            ui->dragy_dst = -1;
+            return "";
+        }
+    } else if (button == LEFT_RELEASE || button == RIGHT_RELEASE) {
+        if (ui->dragging) {
+            return finish_drag(state, ui);
+        } else {
+            if (!INGRID(state, ui->dragx_src, ui->dragy_src)
+                    || gx != ui->dragx_src || gy != ui->dragy_src) {
+                return ui_cancel_drag(ui);
+            }
+            ui_cancel_drag(ui);
+            if (!INGRID(state, gx, gy)) return NULL;
+            if (!(GRID(state, gx, gy) & G_ISLAND)) return NULL;
+            sprintf(buf, "M%d,%d", gx, gy);
+            return dupstr(buf);
+        }
+    } else if (button == 'h' || button == 'H') {
+        game_state *solved = dup_game(state);
+        solve_for_hint(solved);
+        ret = game_state_diff(state, solved);
+        free_game(solved);
+        return ret;
+    } else if (IS_CURSOR_MOVE(button)) {
+        ui->cur_visible = 1;
+        if (control || shift) {
+            ui->dragx_src = ui->cur_x;
+            ui->dragy_src = ui->cur_y;
+            ui->dragging = TRUE;
+            ui->drag_is_noline = !control;
+        }
+        if (ui->dragging) {
+            int nx = ui->cur_x, ny = ui->cur_y;
+
+            move_cursor(button, &nx, &ny, state->w, state->h, 0);
+            if (nx == ui->cur_x && ny == ui->cur_y)
+                return NULL;
+            update_drag_dst(state, ui, ds,
+                             COORD(nx)+TILE_SIZE/2,
+                             COORD(ny)+TILE_SIZE/2);
+            return finish_drag(state, ui);
+        } else {
+            int dx = (button == CURSOR_RIGHT) ? +1 : (button == CURSOR_LEFT) ? -1 : 0;
+            int dy = (button == CURSOR_DOWN)  ? +1 : (button == CURSOR_UP)   ? -1 : 0;
+            int dorthx = 1 - abs(dx), dorthy = 1 - abs(dy);
+            int dir, orth, nx = x, ny = y;
+
+            /* 'orthorder' is a tweak to ensure that if you press RIGHT and
+             * happen to move upwards, when you press LEFT you then tend
+             * downwards (rather than upwards again). */
+            int orthorder = (button == CURSOR_LEFT || button == CURSOR_UP) ? 1 : -1;
+
+            /* This attempts to find an island in the direction you're
+             * asking for, broadly speaking. If you ask to go right, for
+             * example, it'll look for islands to the right and slightly
+             * above or below your current horiz. position, allowing
+             * further above/below the further away it searches. */
+
+            assert(GRID(state, ui->cur_x, ui->cur_y) & G_ISLAND);
+            /* currently this is depth-first (so orthogonally-adjacent
+             * islands across the other side of the grid will be moved to
+             * before closer islands slightly offset). Swap the order of
+             * these two loops to change to breadth-first search. */
+            for (orth = 0; ; orth++) {
+                int oingrid = 0;
+                for (dir = 1; ; dir++) {
+                    int dingrid = 0;
+
+                    if (orth > dir) continue; /* only search in cone outwards. */
+
+                    nx = ui->cur_x + dir*dx + orth*dorthx*orthorder;
+                    ny = ui->cur_y + dir*dy + orth*dorthy*orthorder;
+                    if (INGRID(state, nx, ny)) {
+                        dingrid = oingrid = 1;
+                        if (GRID(state, nx, ny) & G_ISLAND) goto found;
+                    }
+
+                    nx = ui->cur_x + dir*dx - orth*dorthx*orthorder;
+                    ny = ui->cur_y + dir*dy - orth*dorthy*orthorder;
+                    if (INGRID(state, nx, ny)) {
+                        dingrid = oingrid = 1;
+                        if (GRID(state, nx, ny) & G_ISLAND) goto found;
+                    }
+
+                    if (!dingrid) break;
+                }
+                if (!oingrid) return "";
+            }
+            /* not reached */
+
+found:
+            ui->cur_x = nx;
+            ui->cur_y = ny;
+            return "";
+        }
+    } else if (IS_CURSOR_SELECT(button)) {
+        if (!ui->cur_visible) {
+            ui->cur_visible = 1;
+            return "";
+        }
+        if (ui->dragging || button == CURSOR_SELECT2) {
+            ui_cancel_drag(ui);
+            if (ui->dragx_dst == -1 && ui->dragy_dst == -1) {
+                sprintf(buf, "M%d,%d", ui->cur_x, ui->cur_y);
+                return dupstr(buf);
+            } else
+                return "";
+        } else {
+            grid_type v = GRID(state, ui->cur_x, ui->cur_y);
+            if (v & G_ISLAND) {
+                ui->dragging = 1;
+                ui->dragx_src = ui->cur_x;
+                ui->dragy_src = ui->cur_y;
+                ui->dragx_dst = ui->dragy_dst = -1;
+                ui->drag_is_noline = (button == CURSOR_SELECT2) ? 1 : 0;
+                return "";
+            }
+        }
+    } else if ((button >= '0' && button <= '9') ||
+               (button >= 'a' && button <= 'f') ||
+               (button >= 'A' && button <= 'F')) {
+        /* jump to island with .count == number closest to cur_{x,y} */
+        int best_x = -1, best_y = -1, best_sqdist = -1, number = -1, i;
+
+        if (button >= '0' && button <= '9')
+            number = (button == '0' ? 16 : button - '0');
+        else if (button >= 'a' && button <= 'f')
+            number = 10 + button - 'a';
+        else if (button >= 'A' && button <= 'F')
+            number = 10 + button - 'A';
+
+        if (!ui->cur_visible) {
+            ui->cur_visible = 1;
+            return "";
+        }
+
+        for (i = 0; i < state->n_islands; ++i) {
+            int x = state->islands[i].x, y = state->islands[i].y;
+            int dx = x - ui->cur_x, dy = y - ui->cur_y;
+            int sqdist = dx*dx + dy*dy;
+
+            if (state->islands[i].count != number)
+                continue;
+            if (x == ui->cur_x && y == ui->cur_y)
+                continue;
+
+            /* new_game() reads the islands in row-major order, so by
+             * breaking ties in favor of `first in state->islands' we
+             * also break ties by `lexicographically smallest (y, x)'.
+             * Thus, there's a stable pattern to how ties are broken
+             * which the user can learn and use to navigate faster. */
+            if (best_sqdist == -1 || sqdist < best_sqdist) {
+                best_x = x;
+                best_y = y;
+                best_sqdist = sqdist;
+            }
+        }
+        if (best_x != -1 && best_y != -1) {
+            ui->cur_x = best_x;
+            ui->cur_y = best_y;
+            return "";
+        } else
+            return NULL;
+    } else if (button == 'g' || button == 'G') {
+        ui->show_hints = 1 - ui->show_hints;
+        return "";
+    }
+
+    return NULL;
+}
+
+static game_state *execute_move(const game_state *state, const char *move)
+{
+    game_state *ret = dup_game(state);
+    int x1, y1, x2, y2, nl, n;
+    struct island *is1, *is2;
+    char c;
+
+    debug(("execute_move: %s\n", move));
+
+    if (!*move) goto badmove;
+    while (*move) {
+        c = *move++;
+        if (c == 'S') {
+            ret->solved = TRUE;
+            n = 0;
+        } else if (c == 'L') {
+            if (sscanf(move, "%d,%d,%d,%d,%d%n",
+                       &x1, &y1, &x2, &y2, &nl, &n) != 5)
+                goto badmove;
+            if (!INGRID(ret, x1, y1) || !INGRID(ret, x2, y2))
+                goto badmove;
+            is1 = INDEX(ret, gridi, x1, y1);
+            is2 = INDEX(ret, gridi, x2, y2);
+            if (!is1 || !is2) goto badmove;
+            if (nl < 0 || nl > state->maxb) goto badmove;
+            island_join(is1, is2, nl, 0);
+        } else if (c == 'N') {
+            if (sscanf(move, "%d,%d,%d,%d%n",
+                       &x1, &y1, &x2, &y2, &n) != 4)
+                goto badmove;
+            if (!INGRID(ret, x1, y1) || !INGRID(ret, x2, y2))
+                goto badmove;
+            is1 = INDEX(ret, gridi, x1, y1);
+            is2 = INDEX(ret, gridi, x2, y2);
+            if (!is1 || !is2) goto badmove;
+            island_join(is1, is2, -1, 0);
+        } else if (c == 'M') {
+            if (sscanf(move, "%d,%d%n",
+                       &x1, &y1, &n) != 2)
+                goto badmove;
+            if (!INGRID(ret, x1, y1))
+                goto badmove;
+            is1 = INDEX(ret, gridi, x1, y1);
+            if (!is1) goto badmove;
+            island_togglemark(is1);
+        } else
+            goto badmove;
+
+        move += n;
+        if (*move == ';')
+            move++;
+        else if (*move) goto badmove;
+    }
+
+    map_update_possibles(ret);
+    if (map_check(ret)) {
+        debug(("Game completed.\n"));
+        ret->completed = 1;
+    }
+    return ret;
+
+badmove:
+    debug(("%s: unrecognised move.\n", move));
+    free_game(ret);
+    return NULL;
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    char *ret;
+    game_state *solved;
+
+    if (aux) {
+        debug(("solve_game: aux = %s\n", aux));
+        solved = execute_move(state, aux);
+        if (!solved) {
+            *error = "Generated aux string is not a valid move (!).";
+            return NULL;
+        }
+    } else {
+        solved = dup_game(state);
+        /* solve with max strength... */
+        if (solve_from_scratch(solved, 10) == 0) {
+            free_game(solved);
+            *error = "Game does not have a (non-recursive) solution.";
+            return NULL;
+        }
+    }
+    ret = game_state_diff(currstate, solved);
+    free_game(solved);
+    debug(("solve_game: ret = %s\n", ret));
+    return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    struct { int tilesize; } ads, *ds = &ads;
+    ads.tilesize = tilesize;
+
+    *x = TILE_SIZE * params->w + 2 * BORDER;
+    *y = TILE_SIZE * params->h + 2 * BORDER;
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+    int i;
+
+    game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT);
+
+    for (i = 0; i < 3; i++) {
+        ret[COL_FOREGROUND * 3 + i] = 0.0F;
+        ret[COL_HINT * 3 + i] = ret[COL_LOWLIGHT * 3 + i];
+        ret[COL_GRID * 3 + i] =
+            (ret[COL_HINT * 3 + i] + ret[COL_BACKGROUND * 3 + i]) * 0.5F;
+        ret[COL_MARK * 3 + i] = ret[COL_HIGHLIGHT * 3 + i];
+    }
+    ret[COL_WARNING * 3 + 0] = 1.0F;
+    ret[COL_WARNING * 3 + 1] = 0.25F;
+    ret[COL_WARNING * 3 + 2] = 0.25F;
+
+    ret[COL_SELECTED * 3 + 0] = 0.25F;
+    ret[COL_SELECTED * 3 + 1] = 1.00F;
+    ret[COL_SELECTED * 3 + 2] = 0.25F;
+
+    ret[COL_CURSOR * 3 + 0] = min(ret[COL_BACKGROUND * 3 + 0] * 1.4F, 1.0F);
+    ret[COL_CURSOR * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 0.8F;
+    ret[COL_CURSOR * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] * 0.8F;
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    struct game_drawstate *ds = snew(struct game_drawstate);
+    int wh = state->w*state->h;
+    int i;
+
+    ds->tilesize = 0;
+    ds->w = state->w;
+    ds->h = state->h;
+    ds->started = 0;
+    ds->dragging = 0;
+    ds->grid = snewn(wh, unsigned long);
+    for (i = 0; i < wh; i++)
+        ds->grid[i] = ~0UL;
+    ds->newgrid = snewn(wh, unsigned long);
+    ds->lv = snewn(wh, int);
+    ds->lh = snewn(wh, int);
+    memset(ds->lv, 0, wh*sizeof(int));
+    memset(ds->lh, 0, wh*sizeof(int));
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds->lv);
+    sfree(ds->lh);
+    sfree(ds->newgrid);
+    sfree(ds->grid);
+    sfree(ds);
+}
+
+#define LINE_WIDTH (TILE_SIZE/8)
+#define TS8(x) (((x)*TILE_SIZE)/8)
+
+#define OFFSET(thing) ((TILE_SIZE/2) - ((thing)/2))
+
+static int between_island(const game_state *state, int sx, int sy,
+                          int dx, int dy)
+{
+    int x = sx - dx, y = sy - dy;
+
+    while (INGRID(state, x, y)) {
+        if (GRID(state, x, y) & G_ISLAND) goto found;
+        x -= dx; y -= dy;
+    }
+    return 0;
+found:
+    x = sx + dx, y = sy + dy;
+    while (INGRID(state, x, y)) {
+        if (GRID(state, x, y) & G_ISLAND) return 1;
+        x += dx; y += dy;
+    }
+    return 0;
+}
+
+static void lines_lvlh(const game_state *state, const game_ui *ui,
+                       int x, int y, grid_type v, int *lv_r, int *lh_r)
+{
+    int lh = 0, lv = 0;
+
+    if (v & G_LINEV) lv = INDEX(state,lines,x,y);
+    if (v & G_LINEH) lh = INDEX(state,lines,x,y);
+
+    if (ui->show_hints) {
+        if (between_island(state, x, y, 0, 1) && !lv) lv = 1;
+        if (between_island(state, x, y, 1, 0) && !lh) lh = 1;
+    }
+    /*debug(("lvlh: (%d,%d) v 0x%x lv %d lh %d.\n", x, y, v, lv, lh));*/
+    *lv_r = lv; *lh_r = lh;
+}
+
+static void draw_cross(drawing *dr, game_drawstate *ds,
+                       int ox, int oy, int col)
+{
+    int off = TS8(2);
+    draw_line(dr, ox,     oy, ox+off, oy+off, col);
+    draw_line(dr, ox+off, oy, ox,     oy+off, col);
+}
+
+static void draw_general_line(drawing *dr, game_drawstate *ds,
+                              int ox, int oy, int fx, int fy, int ax, int ay,
+                              int len, unsigned long ldata, int which)
+{
+    /*
+     * Draw one direction of lines in a square. To permit the same
+     * code to handle horizontal and vertical lines, fx,fy are the
+     * 'forward' direction (along the lines) and ax,ay are the
+     * 'across' direction.
+     *
+     * We draw the white background for a locked bridge if (which &
+     * 1), and draw the bridges themselves if (which & 2). This
+     * permits us to get two overlapping locked bridges right without
+     * one of them erasing part of the other.
+     */
+    int fg;
+
+    fg = ((ldata & DL_COUNTMASK) == DL_COUNT_HINT ? COL_HINT :
+          (ldata & DL_COLMASK) == DL_COL_SELECTED ? COL_SELECTED :
+          (ldata & DL_COLMASK) == DL_COL_FLASH ? COL_HIGHLIGHT :
+          (ldata & DL_COLMASK) == DL_COL_WARNING ? COL_WARNING :
+          COL_FOREGROUND);
+
+    if ((ldata & DL_COUNTMASK) == DL_COUNT_CROSS) {
+        draw_cross(dr, ds,
+                   ox + TS8(1)*fx + TS8(3)*ax,
+                   oy + TS8(1)*fy + TS8(3)*ay, fg);
+        draw_cross(dr, ds,
+                   ox + TS8(5)*fx + TS8(3)*ax,
+                   oy + TS8(5)*fy + TS8(3)*ay, fg);
+    } else if ((ldata & DL_COUNTMASK) != 0) {
+        int lh, lw, gw, bw, i, loff;
+
+        lh = (ldata & DL_COUNTMASK);
+        if (lh == DL_COUNT_HINT)
+            lh = 1;
+
+        lw = gw = LINE_WIDTH;
+        while ((bw = lw * lh + gw * (lh+1)) > TILE_SIZE)
+            gw--;
+
+        loff = OFFSET(bw);
+
+        if (which & 1) {
+            if ((ldata & DL_LOCK) && fg != COL_HINT)
+                draw_rect(dr, ox + loff*ax, oy + loff*ay,
+                          len*fx+bw*ax, len*fy+bw*ay, COL_MARK);
+        }
+        if (which & 2) {
+            for (i = 0; i < lh; i++, loff += lw + gw)
+                draw_rect(dr, ox + (loff+gw)*ax, oy + (loff+gw)*ay,
+                          len*fx+lw*ax, len*fy+lw*ay, fg);
+        }
+    }
+}
+
+static void draw_hline(drawing *dr, game_drawstate *ds,
+                       int ox, int oy, int w, unsigned long vdata, int which)
+{
+    draw_general_line(dr, ds, ox, oy, 1, 0, 0, 1, w, vdata, which);
+}
+
+static void draw_vline(drawing *dr, game_drawstate *ds,
+                       int ox, int oy, int h, unsigned long vdata, int which)
+{
+    draw_general_line(dr, ds, ox, oy, 0, 1, 1, 0, h, vdata, which);
+}
+
+#define ISLAND_RADIUS ((TILE_SIZE*12)/20)
+#define ISLAND_NUMSIZE(clue) \
+    (((clue) < 10) ? (TILE_SIZE*7)/10 : (TILE_SIZE*5)/10)
+
+static void draw_island(drawing *dr, game_drawstate *ds,
+                        int ox, int oy, int clue, unsigned long idata)
+{
+    int half, orad, irad, fg, bg;
+
+    if ((idata & DI_BGMASK) == DI_BG_NO_ISLAND)
+        return;
+
+    half = TILE_SIZE/2;
+    orad = ISLAND_RADIUS;
+    irad = orad - LINE_WIDTH;
+    fg = ((idata & DI_COLMASK) == DI_COL_SELECTED ? COL_SELECTED :
+          (idata & DI_COLMASK) == DI_COL_WARNING ? COL_WARNING :
+          (idata & DI_COLMASK) == DI_COL_FLASH ? COL_HIGHLIGHT :
+          COL_FOREGROUND);
+    bg = ((idata & DI_BGMASK) == DI_BG_CURSOR ? COL_CURSOR :
+          (idata & DI_BGMASK) == DI_BG_MARK ? COL_MARK :
+          COL_BACKGROUND);
+
+    /* draw a thick circle */
+    draw_circle(dr, ox+half, oy+half, orad, fg, fg);
+    draw_circle(dr, ox+half, oy+half, irad, bg, bg);
+
+    if (clue > 0) {
+        char str[32];
+        int textcolour = (fg == COL_SELECTED ? COL_FOREGROUND : fg);
+        sprintf(str, "%d", clue);
+        draw_text(dr, ox+half, oy+half, FONT_VARIABLE, ISLAND_NUMSIZE(clue),
+                  ALIGN_VCENTRE | ALIGN_HCENTRE, textcolour, str);
+    }
+}
+
+static void draw_island_tile(drawing *dr, game_drawstate *ds,
+                             int x, int y, int clue, unsigned long data)
+{
+    int ox = COORD(x), oy = COORD(y);
+    int which;
+
+    clip(dr, ox, oy, TILE_SIZE, TILE_SIZE);
+    draw_rect(dr, ox, oy, TILE_SIZE, TILE_SIZE, COL_BACKGROUND);
+
+    /*
+     * Because of the possibility of incoming bridges just about
+     * meeting at one corner, we must split the line-drawing into
+     * background and foreground segments.
+     */
+    for (which = 1; which <= 2; which <<= 1) {
+        draw_hline(dr, ds, ox, oy, TILE_SIZE/2,
+                   (data >> D_I_LINE_SHIFT_L) & DL_MASK, which);
+        draw_hline(dr, ds, ox + TILE_SIZE - TILE_SIZE/2, oy, TILE_SIZE/2,
+                   (data >> D_I_LINE_SHIFT_R) & DL_MASK, which);
+        draw_vline(dr, ds, ox, oy, TILE_SIZE/2,
+                   (data >> D_I_LINE_SHIFT_U) & DL_MASK, which);
+        draw_vline(dr, ds, ox, oy + TILE_SIZE - TILE_SIZE/2, TILE_SIZE/2,
+                   (data >> D_I_LINE_SHIFT_D) & DL_MASK, which);
+    }
+    draw_island(dr, ds, ox, oy, clue, (data >> D_I_ISLAND_SHIFT) & DI_MASK);
+
+    unclip(dr);
+    draw_update(dr, ox, oy, TILE_SIZE, TILE_SIZE);
+}
+
+static void draw_line_tile(drawing *dr, game_drawstate *ds,
+                           int x, int y, unsigned long data)
+{
+    int ox = COORD(x), oy = COORD(y);
+    unsigned long hdata, vdata;
+
+    clip(dr, ox, oy, TILE_SIZE, TILE_SIZE);
+    draw_rect(dr, ox, oy, TILE_SIZE, TILE_SIZE, COL_BACKGROUND);
+
+    /*
+     * We have to think about which of the horizontal and vertical
+     * line to draw first, if both exist.
+     *
+     * The rule is that hint lines are drawn at the bottom, then
+     * NOLINE crosses, then actual bridges. The enumeration in the
+     * DL_COUNTMASK field is set up so that this drops out of a
+     * straight comparison between the two.
+     *
+     * Since lines crossing in this type of square cannot both be
+     * actual bridges, there's no need to pass a nontrivial 'which'
+     * parameter to draw_[hv]line.
+     */
+    hdata = (data >> D_L_LINE_SHIFT_H) & DL_MASK;
+    vdata = (data >> D_L_LINE_SHIFT_V) & DL_MASK;
+    if ((hdata & DL_COUNTMASK) > (vdata & DL_COUNTMASK)) {
+        draw_hline(dr, ds, ox, oy, TILE_SIZE, hdata, 3);
+        draw_vline(dr, ds, ox, oy, TILE_SIZE, vdata, 3);
+    } else {
+        draw_vline(dr, ds, ox, oy, TILE_SIZE, vdata, 3);
+        draw_hline(dr, ds, ox, oy, TILE_SIZE, hdata, 3);
+    }
+
+    /*
+     * The islands drawn at the edges of a line tile don't need clue
+     * numbers.
+     */
+    draw_island(dr, ds, ox - TILE_SIZE, oy, -1,
+                (data >> D_L_ISLAND_SHIFT_L) & DI_MASK);
+    draw_island(dr, ds, ox + TILE_SIZE, oy, -1,
+                (data >> D_L_ISLAND_SHIFT_R) & DI_MASK);
+    draw_island(dr, ds, ox, oy - TILE_SIZE, -1,
+                (data >> D_L_ISLAND_SHIFT_U) & DI_MASK);
+    draw_island(dr, ds, ox, oy + TILE_SIZE, -1,
+                (data >> D_L_ISLAND_SHIFT_D) & DI_MASK);
+
+    unclip(dr);
+    draw_update(dr, ox, oy, TILE_SIZE, TILE_SIZE);
+}
+
+static void draw_edge_tile(drawing *dr, game_drawstate *ds,
+                           int x, int y, int dx, int dy, unsigned long data)
+{
+    int ox = COORD(x), oy = COORD(y);
+    int cx = ox, cy = oy, cw = TILE_SIZE, ch = TILE_SIZE;
+
+    if (dy) {
+        if (dy > 0)
+            cy += TILE_SIZE/2;
+        ch -= TILE_SIZE/2;
+    } else {
+        if (dx > 0)
+            cx += TILE_SIZE/2;
+        cw -= TILE_SIZE/2;
+    }
+    clip(dr, cx, cy, cw, ch);
+    draw_rect(dr, cx, cy, cw, ch, COL_BACKGROUND);
+
+    draw_island(dr, ds, ox + TILE_SIZE*dx, oy + TILE_SIZE*dy, -1,
+                (data >> D_I_ISLAND_SHIFT) & DI_MASK);
+
+    unclip(dr);
+    draw_update(dr, cx, cy, cw, ch);
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int x, y, lv, lh;
+    grid_type v, flash = 0;
+    struct island *is, *is_drag_src = NULL, *is_drag_dst = NULL;
+
+    if (flashtime) {
+        int f = (int)(flashtime * 5 / FLASH_TIME);
+        if (f == 1 || f == 3) flash = TRUE;
+    }
+
+    /* Clear screen, if required. */
+    if (!ds->started) {
+        draw_rect(dr, 0, 0,
+                  TILE_SIZE * ds->w + 2 * BORDER,
+                  TILE_SIZE * ds->h + 2 * BORDER, COL_BACKGROUND);
+#ifdef DRAW_GRID
+        draw_rect_outline(dr,
+                          COORD(0)-1, COORD(0)-1,
+                          TILE_SIZE * ds->w + 2, TILE_SIZE * ds->h + 2,
+                          COL_GRID);
+#endif
+        draw_update(dr, 0, 0,
+                    TILE_SIZE * ds->w + 2 * BORDER,
+                    TILE_SIZE * ds->h + 2 * BORDER);
+        ds->started = 1;
+    }
+
+    if (ui->dragx_src != -1 && ui->dragy_src != -1) {
+        ds->dragging = 1;
+        is_drag_src = INDEX(state, gridi, ui->dragx_src, ui->dragy_src);
+        assert(is_drag_src);
+        if (ui->dragx_dst != -1 && ui->dragy_dst != -1) {
+            is_drag_dst = INDEX(state, gridi, ui->dragx_dst, ui->dragy_dst);
+            assert(is_drag_dst);
+        }
+    } else
+        ds->dragging = 0;
+
+    /*
+     * Set up ds->newgrid with the current grid contents.
+     */
+    for (x = 0; x < ds->w; x++)
+        for (y = 0; y < ds->h; y++)
+            INDEX(ds,newgrid,x,y) = 0;
+
+    for (x = 0; x < ds->w; x++) {
+        for (y = 0; y < ds->h; y++) {
+            v = GRID(state, x, y);
+
+            if (v & G_ISLAND) {
+                /*
+                 * An island square. Compute the drawing data for the
+                 * island, and put it in this square and surrounding
+                 * squares.
+                 */
+                unsigned long idata = 0;
+
+                is = INDEX(state, gridi, x, y);
+
+                if (flash)
+                    idata |= DI_COL_FLASH;
+                if (is_drag_src && (is == is_drag_src ||
+                                    (is_drag_dst && is == is_drag_dst)))
+                    idata |= DI_COL_SELECTED;
+                else if (island_impossible(is, v & G_MARK) || (v & G_WARN))
+                    idata |= DI_COL_WARNING;
+                else
+                    idata |= DI_COL_NORMAL;
+
+                if (ui->cur_visible &&
+                    ui->cur_x == is->x && ui->cur_y == is->y)
+                    idata |= DI_BG_CURSOR;
+                else if (v & G_MARK)
+                    idata |= DI_BG_MARK;
+                else
+                    idata |= DI_BG_NORMAL;
+
+                INDEX(ds,newgrid,x,y) |= idata << D_I_ISLAND_SHIFT;
+                if (x > 0 && !(GRID(state,x-1,y) & G_ISLAND))
+                    INDEX(ds,newgrid,x-1,y) |= idata << D_L_ISLAND_SHIFT_R;
+                if (x+1 < state->w && !(GRID(state,x+1,y) & G_ISLAND))
+                    INDEX(ds,newgrid,x+1,y) |= idata << D_L_ISLAND_SHIFT_L;
+                if (y > 0 && !(GRID(state,x,y-1) & G_ISLAND))
+                    INDEX(ds,newgrid,x,y-1) |= idata << D_L_ISLAND_SHIFT_D;
+                if (y+1 < state->h && !(GRID(state,x,y+1) & G_ISLAND))
+                    INDEX(ds,newgrid,x,y+1) |= idata << D_L_ISLAND_SHIFT_U;
+            } else {
+                unsigned long hdata, vdata;
+                int selh = FALSE, selv = FALSE;
+
+                /*
+                 * A line (non-island) square. Compute the drawing
+                 * data for any horizontal and vertical lines in the
+                 * square, and put them in this square's entry and
+                 * optionally those for neighbouring islands too.
+                 */
+
+                if (is_drag_dst &&
+                    WITHIN(x,is_drag_src->x, is_drag_dst->x) &&
+                    WITHIN(y,is_drag_src->y, is_drag_dst->y)) {
+                    if (is_drag_src->x != is_drag_dst->x)
+                        selh = TRUE;
+                    else
+                        selv = TRUE;
+                }
+                lines_lvlh(state, ui, x, y, v, &lv, &lh);
+
+                hdata = (v & G_NOLINEH ? DL_COUNT_CROSS :
+                         v & G_LINEH ? lh :
+                         (ui->show_hints &&
+                          between_island(state,x,y,1,0)) ? DL_COUNT_HINT : 0);
+                vdata = (v & G_NOLINEV ? DL_COUNT_CROSS :
+                         v & G_LINEV ? lv :
+                         (ui->show_hints &&
+                          between_island(state,x,y,0,1)) ? DL_COUNT_HINT : 0);
+
+                hdata |= (flash ? DL_COL_FLASH :
+                          v & G_WARN ? DL_COL_WARNING :
+                          selh ? DL_COL_SELECTED :
+                          DL_COL_NORMAL);
+                vdata |= (flash ? DL_COL_FLASH :
+                          v & G_WARN ? DL_COL_WARNING :
+                          selv ? DL_COL_SELECTED :
+                          DL_COL_NORMAL);
+
+                if (v & G_MARKH)
+                    hdata |= DL_LOCK;
+                if (v & G_MARKV)
+                    vdata |= DL_LOCK;
+
+                INDEX(ds,newgrid,x,y) |= hdata << D_L_LINE_SHIFT_H;
+                INDEX(ds,newgrid,x,y) |= vdata << D_L_LINE_SHIFT_V;
+                if (x > 0 && (GRID(state,x-1,y) & G_ISLAND))
+                    INDEX(ds,newgrid,x-1,y) |= hdata << D_I_LINE_SHIFT_R;
+                if (x+1 < state->w && (GRID(state,x+1,y) & G_ISLAND))
+                    INDEX(ds,newgrid,x+1,y) |= hdata << D_I_LINE_SHIFT_L;
+                if (y > 0 && (GRID(state,x,y-1) & G_ISLAND))
+                    INDEX(ds,newgrid,x,y-1) |= vdata << D_I_LINE_SHIFT_D;
+                if (y+1 < state->h && (GRID(state,x,y+1) & G_ISLAND))
+                    INDEX(ds,newgrid,x,y+1) |= vdata << D_I_LINE_SHIFT_U;
+            }
+        }
+    }
+
+    /*
+     * Now go through and draw any changed grid square.
+     */
+    for (x = 0; x < ds->w; x++) {
+        for (y = 0; y < ds->h; y++) {
+            unsigned long newval = INDEX(ds,newgrid,x,y);
+            if (INDEX(ds,grid,x,y) != newval) {
+                v = GRID(state, x, y);
+                if (v & G_ISLAND) {
+                    is = INDEX(state, gridi, x, y);
+                    draw_island_tile(dr, ds, x, y, is->count, newval);
+
+                    /*
+                     * If this tile is right at the edge of the grid,
+                     * we must also draw the part of the island that
+                     * goes completely out of bounds. We don't bother
+                     * keeping separate entries in ds->newgrid for
+                     * these tiles; it's easier just to redraw them
+                     * iff we redraw their parent island tile.
+                     */
+                    if (x == 0)
+                        draw_edge_tile(dr, ds, x-1, y, +1, 0, newval);
+                    if (y == 0)
+                        draw_edge_tile(dr, ds, x, y-1, 0, +1, newval);
+                    if (x == state->w-1)
+                        draw_edge_tile(dr, ds, x+1, y, -1, 0, newval);
+                    if (y == state->h-1)
+                        draw_edge_tile(dr, ds, x, y+1, 0, -1, newval);
+                } else {
+                    draw_line_tile(dr, ds, x, y, newval);
+                }
+                INDEX(ds,grid,x,y) = newval;
+            }
+        }
+    }
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return 0.0F;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    if (!oldstate->completed && newstate->completed &&
+        !oldstate->solved && !newstate->solved)
+        return FLASH_TIME;
+
+    return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    return state->completed ? +1 : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+    int pw, ph;
+
+    /* 10mm squares by default. */
+    game_compute_size(params, 1000, &pw, &ph);
+    *x = pw / 100.0F;
+    *y = ph / 100.0F;
+}
+
+static void game_print(drawing *dr, const game_state *state, int ts)
+{
+    int ink = print_mono_colour(dr, 0);
+    int paper = print_mono_colour(dr, 1);
+    int x, y, cx, cy, i, nl;
+    int loff;
+    grid_type grid;
+
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    game_drawstate ads, *ds = &ads;
+    ads.tilesize = ts;
+
+    /* I don't think this wants a border. */
+
+    /* Bridges */
+    loff = ts / (8 * sqrt((state->params.maxb - 1)));
+    print_line_width(dr, ts / 12);
+    for (x = 0; x < state->w; x++) {
+        for (y = 0; y < state->h; y++) {
+            cx = COORD(x); cy = COORD(y);
+            grid = GRID(state,x,y);
+            nl = INDEX(state,lines,x,y);
+
+            if (grid & G_ISLAND) continue;
+            if (grid & G_LINEV) {
+                for (i = 0; i < nl; i++)
+                    draw_line(dr, cx+ts/2+(2*i-nl+1)*loff, cy,
+                              cx+ts/2+(2*i-nl+1)*loff, cy+ts, ink);
+            }
+            if (grid & G_LINEH) {
+                for (i = 0; i < nl; i++)
+                    draw_line(dr, cx, cy+ts/2+(2*i-nl+1)*loff,
+                              cx+ts, cy+ts/2+(2*i-nl+1)*loff, ink);
+            }
+        }
+    }
+
+    /* Islands */
+    for (i = 0; i < state->n_islands; i++) {
+        char str[32];
+        struct island *is = &state->islands[i];
+        grid = GRID(state, is->x, is->y);
+        cx = COORD(is->x) + ts/2;
+        cy = COORD(is->y) + ts/2;
+
+        draw_circle(dr, cx, cy, ISLAND_RADIUS, paper, ink);
+
+        sprintf(str, "%d", is->count);
+        draw_text(dr, cx, cy, FONT_VARIABLE, ISLAND_NUMSIZE(is->count),
+                  ALIGN_VCENTRE | ALIGN_HCENTRE, ink, str);
+    }
+}
+
+#ifdef COMBINED
+#define thegame bridges
+#endif
+
+const struct game thegame = {
+    "Bridges", "games.bridges", "bridges",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    TRUE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    TRUE, FALSE, game_print_size, game_print,
+    FALSE,                            /* wants_statusbar */
+    FALSE, game_timing_state,
+    REQUIRE_RBUTTON,                  /* flags */
+};
+
+/* vim: set shiftwidth=4 tabstop=8: */
diff --git a/chm.but b/chm.but
new file mode 100644 (file)
index 0000000..e023704
--- /dev/null
+++ b/chm.but
@@ -0,0 +1,21 @@
+\# File containing the magic HTML configuration directives to create
+\# an MS HTML Help project. We put this on the end of the Puzzles
+\# docs build command line to build the HHP and friends.
+
+\cfg{html-leaf-level}{infinite}
+\cfg{html-leaf-contains-contents}{false}
+\cfg{html-suppress-navlinks}{true}
+\cfg{html-suppress-address}{true}
+
+\cfg{html-contents-filename}{index.html}
+\cfg{html-template-filename}{%k.html}
+\cfg{html-template-fragment}{%k}
+
+\cfg{html-mshtmlhelp-chm}{puzzles.chm}
+\cfg{html-mshtmlhelp-project}{puzzles.hhp}
+\cfg{html-mshtmlhelp-contents}{puzzles.hhc}
+\cfg{html-mshtmlhelp-index}{puzzles.hhk}
+
+\cfg{html-body-end}{}
+
+\cfg{html-head-end}{<link rel="stylesheet" type="text/css" href="chm.css">}
diff --git a/combi.c b/combi.c
new file mode 100644 (file)
index 0000000..4c5d107
--- /dev/null
+++ b/combi.c
@@ -0,0 +1,110 @@
+#include <assert.h>
+#include <string.h>
+
+#include "puzzles.h"
+
+/* horrific and doesn't check overflow. */
+static long factx(long x, long y)
+{
+    long acc = 1, i;
+
+    for (i = y; i <= x; i++)
+        acc *= i;
+    return acc;
+}
+
+void reset_combi(combi_ctx *combi)
+{
+    int i;
+    combi->nleft = combi->total;
+    for (i = 0; i < combi->r; i++)
+        combi->a[i] = i;
+}
+
+combi_ctx *new_combi(int r, int n)
+{
+    long nfr, nrf;
+    combi_ctx *combi;
+
+    assert(r <= n);
+    assert(n >= 1);
+
+    combi = snew(combi_ctx);
+    memset(combi, 0, sizeof(combi_ctx));
+    combi->r = r;
+    combi->n = n;
+
+    combi->a = snewn(r, int);
+    memset(combi->a, 0, r * sizeof(int));
+
+    nfr = factx(n, r+1);
+    nrf = factx(n-r, 1);
+    combi->total = (int)(nfr / nrf);
+
+    reset_combi(combi);
+    return combi;
+}
+
+/* returns NULL when we're done otherwise returns input. */
+combi_ctx *next_combi(combi_ctx *combi)
+{
+    int i = combi->r - 1, j;
+
+    if (combi->nleft == combi->total)
+        goto done;
+    else if (combi->nleft <= 0)
+        return NULL;
+
+    while (combi->a[i] == combi->n - combi->r + i)
+        i--;
+    combi->a[i] += 1;
+    for (j = i+1; j < combi->r; j++)
+        combi->a[j] = combi->a[i] + j - i;
+
+    done:
+    combi->nleft--;
+    return combi;
+}
+
+void free_combi(combi_ctx *combi)
+{
+    sfree(combi->a);
+    sfree(combi);
+}
+
+/* compile this with:
+ *   gcc -o combi.exe -DSTANDALONE_COMBI_TEST combi.c malloc.c
+ */
+#ifdef STANDALONE_COMBI_TEST
+
+#include <stdio.h>
+
+void fatal(char *fmt, ...)
+{
+    abort();
+}
+
+int main(int argc, char *argv[])
+{
+    combi_ctx *c;
+    int i, r, n;
+
+    if (argc < 3) {
+        fprintf(stderr, "Usage: combi R N\n");
+        exit(1);
+    }
+
+    r = atoi(argv[1]); n = atoi(argv[2]);
+    c = new_combi(r, n);
+    printf("combi %d of %d, %d elements.\n", c->r, c->n, c->total);
+
+    while (next_combi(c)) {
+        for (i = 0; i < c->r; i++) {
+            printf("%d ", c->a[i]);
+        }
+        printf("\n");
+    }
+    free_combi(c);
+}
+
+#endif
diff --git a/compile b/compile
new file mode 100755 (executable)
index 0000000..a85b723
--- /dev/null
+++ b/compile
@@ -0,0 +1,347 @@
+#! /bin/sh
+# Wrapper for compilers which do not understand '-c -o'.
+
+scriptversion=2012-10-14.11; # UTC
+
+# Copyright (C) 1999-2014 Free Software Foundation, Inc.
+# Written by Tom Tromey <tromey@cygnus.com>.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# This file is maintained in Automake, please report
+# bugs to <bug-automake@gnu.org> or send patches to
+# <automake-patches@gnu.org>.
+
+nl='
+'
+
+# We need space, tab and new line, in precisely that order.  Quoting is
+# there to prevent tools from complaining about whitespace usage.
+IFS=" ""       $nl"
+
+file_conv=
+
+# func_file_conv build_file lazy
+# Convert a $build file to $host form and store it in $file
+# Currently only supports Windows hosts. If the determined conversion
+# type is listed in (the comma separated) LAZY, no conversion will
+# take place.
+func_file_conv ()
+{
+  file=$1
+  case $file in
+    / | /[!/]*) # absolute file, and not a UNC file
+      if test -z "$file_conv"; then
+       # lazily determine how to convert abs files
+       case `uname -s` in
+         MINGW*)
+           file_conv=mingw
+           ;;
+         CYGWIN*)
+           file_conv=cygwin
+           ;;
+         *)
+           file_conv=wine
+           ;;
+       esac
+      fi
+      case $file_conv/,$2, in
+       *,$file_conv,*)
+         ;;
+       mingw/*)
+         file=`cmd //C echo "$file " | sed -e 's/"\(.*\) " *$/\1/'`
+         ;;
+       cygwin/*)
+         file=`cygpath -m "$file" || echo "$file"`
+         ;;
+       wine/*)
+         file=`winepath -w "$file" || echo "$file"`
+         ;;
+      esac
+      ;;
+  esac
+}
+
+# func_cl_dashL linkdir
+# Make cl look for libraries in LINKDIR
+func_cl_dashL ()
+{
+  func_file_conv "$1"
+  if test -z "$lib_path"; then
+    lib_path=$file
+  else
+    lib_path="$lib_path;$file"
+  fi
+  linker_opts="$linker_opts -LIBPATH:$file"
+}
+
+# func_cl_dashl library
+# Do a library search-path lookup for cl
+func_cl_dashl ()
+{
+  lib=$1
+  found=no
+  save_IFS=$IFS
+  IFS=';'
+  for dir in $lib_path $LIB
+  do
+    IFS=$save_IFS
+    if $shared && test -f "$dir/$lib.dll.lib"; then
+      found=yes
+      lib=$dir/$lib.dll.lib
+      break
+    fi
+    if test -f "$dir/$lib.lib"; then
+      found=yes
+      lib=$dir/$lib.lib
+      break
+    fi
+    if test -f "$dir/lib$lib.a"; then
+      found=yes
+      lib=$dir/lib$lib.a
+      break
+    fi
+  done
+  IFS=$save_IFS
+
+  if test "$found" != yes; then
+    lib=$lib.lib
+  fi
+}
+
+# func_cl_wrapper cl arg...
+# Adjust compile command to suit cl
+func_cl_wrapper ()
+{
+  # Assume a capable shell
+  lib_path=
+  shared=:
+  linker_opts=
+  for arg
+  do
+    if test -n "$eat"; then
+      eat=
+    else
+      case $1 in
+       -o)
+         # configure might choose to run compile as 'compile cc -o foo foo.c'.
+         eat=1
+         case $2 in
+           *.o | *.[oO][bB][jJ])
+             func_file_conv "$2"
+             set x "$@" -Fo"$file"
+             shift
+             ;;
+           *)
+             func_file_conv "$2"
+             set x "$@" -Fe"$file"
+             shift
+             ;;
+         esac
+         ;;
+       -I)
+         eat=1
+         func_file_conv "$2" mingw
+         set x "$@" -I"$file"
+         shift
+         ;;
+       -I*)
+         func_file_conv "${1#-I}" mingw
+         set x "$@" -I"$file"
+         shift
+         ;;
+       -l)
+         eat=1
+         func_cl_dashl "$2"
+         set x "$@" "$lib"
+         shift
+         ;;
+       -l*)
+         func_cl_dashl "${1#-l}"
+         set x "$@" "$lib"
+         shift
+         ;;
+       -L)
+         eat=1
+         func_cl_dashL "$2"
+         ;;
+       -L*)
+         func_cl_dashL "${1#-L}"
+         ;;
+       -static)
+         shared=false
+         ;;
+       -Wl,*)
+         arg=${1#-Wl,}
+         save_ifs="$IFS"; IFS=','
+         for flag in $arg; do
+           IFS="$save_ifs"
+           linker_opts="$linker_opts $flag"
+         done
+         IFS="$save_ifs"
+         ;;
+       -Xlinker)
+         eat=1
+         linker_opts="$linker_opts $2"
+         ;;
+       -*)
+         set x "$@" "$1"
+         shift
+         ;;
+       *.cc | *.CC | *.cxx | *.CXX | *.[cC]++)
+         func_file_conv "$1"
+         set x "$@" -Tp"$file"
+         shift
+         ;;
+       *.c | *.cpp | *.CPP | *.lib | *.LIB | *.Lib | *.OBJ | *.obj | *.[oO])
+         func_file_conv "$1" mingw
+         set x "$@" "$file"
+         shift
+         ;;
+       *)
+         set x "$@" "$1"
+         shift
+         ;;
+      esac
+    fi
+    shift
+  done
+  if test -n "$linker_opts"; then
+    linker_opts="-link$linker_opts"
+  fi
+  exec "$@" $linker_opts
+  exit 1
+}
+
+eat=
+
+case $1 in
+  '')
+     echo "$0: No command.  Try '$0 --help' for more information." 1>&2
+     exit 1;
+     ;;
+  -h | --h*)
+    cat <<\EOF
+Usage: compile [--help] [--version] PROGRAM [ARGS]
+
+Wrapper for compilers which do not understand '-c -o'.
+Remove '-o dest.o' from ARGS, run PROGRAM with the remaining
+arguments, and rename the output as expected.
+
+If you are trying to build a whole package this is not the
+right script to run: please start by reading the file 'INSTALL'.
+
+Report bugs to <bug-automake@gnu.org>.
+EOF
+    exit $?
+    ;;
+  -v | --v*)
+    echo "compile $scriptversion"
+    exit $?
+    ;;
+  cl | *[/\\]cl | cl.exe | *[/\\]cl.exe )
+    func_cl_wrapper "$@"      # Doesn't return...
+    ;;
+esac
+
+ofile=
+cfile=
+
+for arg
+do
+  if test -n "$eat"; then
+    eat=
+  else
+    case $1 in
+      -o)
+       # configure might choose to run compile as 'compile cc -o foo foo.c'.
+       # So we strip '-o arg' only if arg is an object.
+       eat=1
+       case $2 in
+         *.o | *.obj)
+           ofile=$2
+           ;;
+         *)
+           set x "$@" -o "$2"
+           shift
+           ;;
+       esac
+       ;;
+      *.c)
+       cfile=$1
+       set x "$@" "$1"
+       shift
+       ;;
+      *)
+       set x "$@" "$1"
+       shift
+       ;;
+    esac
+  fi
+  shift
+done
+
+if test -z "$ofile" || test -z "$cfile"; then
+  # If no '-o' option was seen then we might have been invoked from a
+  # pattern rule where we don't need one.  That is ok -- this is a
+  # normal compilation that the losing compiler can handle.  If no
+  # '.c' file was seen then we are probably linking.  That is also
+  # ok.
+  exec "$@"
+fi
+
+# Name of file we expect compiler to create.
+cofile=`echo "$cfile" | sed 's|^.*[\\/]||; s|^[a-zA-Z]:||; s/\.c$/.o/'`
+
+# Create the lock directory.
+# Note: use '[/\\:.-]' here to ensure that we don't use the same name
+# that we are using for the .o file.  Also, base the name on the expected
+# object file name, since that is what matters with a parallel build.
+lockdir=`echo "$cofile" | sed -e 's|[/\\:.-]|_|g'`.d
+while true; do
+  if mkdir "$lockdir" >/dev/null 2>&1; then
+    break
+  fi
+  sleep 1
+done
+# FIXME: race condition here if user kills between mkdir and trap.
+trap "rmdir '$lockdir'; exit 1" 1 2 15
+
+# Run the compile.
+"$@"
+ret=$?
+
+if test -f "$cofile"; then
+  test "$cofile" = "$ofile" || mv "$cofile" "$ofile"
+elif test -f "${cofile}bj"; then
+  test "${cofile}bj" = "$ofile" || mv "${cofile}bj" "$ofile"
+fi
+
+rmdir "$lockdir"
+exit $ret
+
+# Local Variables:
+# mode: shell-script
+# sh-indentation: 2
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC"
+# time-stamp-end: "; # UTC"
+# End:
diff --git a/configure b/configure
new file mode 100755 (executable)
index 0000000..ff361f3
--- /dev/null
+++ b/configure
@@ -0,0 +1,5751 @@
+#! /bin/sh
+# Guess values for system-dependent variables and create Makefiles.
+# Generated by GNU Autoconf 2.69 for puzzles 20161228.7cae89f.
+#
+# Report bugs to <anakin@pobox.com>.
+#
+#
+# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc.
+#
+#
+# This configure script is free software; the Free Software Foundation
+# gives unlimited permission to copy, distribute and modify it.
+## -------------------- ##
+## M4sh Initialization. ##
+## -------------------- ##
+
+# Be more Bourne compatible
+DUALCASE=1; export DUALCASE # for MKS sh
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then :
+  emulate sh
+  NULLCMD=:
+  # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '${1+"$@"}'='"$@"'
+  setopt NO_GLOB_SUBST
+else
+  case `(set -o) 2>/dev/null` in #(
+  *posix*) :
+    set -o posix ;; #(
+  *) :
+     ;;
+esac
+fi
+
+
+as_nl='
+'
+export as_nl
+# Printing a long string crashes Solaris 7 /usr/bin/printf.
+as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo
+# Prefer a ksh shell builtin over an external printf program on Solaris,
+# but without wasting forks for bash or zsh.
+if test -z "$BASH_VERSION$ZSH_VERSION" \
+    && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then
+  as_echo='print -r --'
+  as_echo_n='print -rn --'
+elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then
+  as_echo='printf %s\n'
+  as_echo_n='printf %s'
+else
+  if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then
+    as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"'
+    as_echo_n='/usr/ucb/echo -n'
+  else
+    as_echo_body='eval expr "X$1" : "X\\(.*\\)"'
+    as_echo_n_body='eval
+      arg=$1;
+      case $arg in #(
+      *"$as_nl"*)
+       expr "X$arg" : "X\\(.*\\)$as_nl";
+       arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;;
+      esac;
+      expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl"
+    '
+    export as_echo_n_body
+    as_echo_n='sh -c $as_echo_n_body as_echo'
+  fi
+  export as_echo_body
+  as_echo='sh -c $as_echo_body as_echo'
+fi
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+  PATH_SEPARATOR=:
+  (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && {
+    (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 ||
+      PATH_SEPARATOR=';'
+  }
+fi
+
+
+# IFS
+# We need space, tab and new line, in precisely that order.  Quoting is
+# there to prevent editors from complaining about space-tab.
+# (If _AS_PATH_WALK were called with IFS unset, it would disable word
+# splitting by setting IFS to empty value.)
+IFS=" ""       $as_nl"
+
+# Find who we are.  Look in the path if we contain no directory separator.
+as_myself=
+case $0 in #((
+  *[\\/]* ) as_myself=$0 ;;
+  *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+  done
+IFS=$as_save_IFS
+
+     ;;
+esac
+# We did not find ourselves, most probably we were run as `sh COMMAND'
+# in which case we are not to be found in the path.
+if test "x$as_myself" = x; then
+  as_myself=$0
+fi
+if test ! -f "$as_myself"; then
+  $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2
+  exit 1
+fi
+
+# Unset variables that we do not need and which cause bugs (e.g. in
+# pre-3.0 UWIN ksh).  But do not cause bugs in bash 2.01; the "|| exit 1"
+# suppresses any "Segmentation fault" message there.  '((' could
+# trigger a bug in pdksh 5.2.14.
+for as_var in BASH_ENV ENV MAIL MAILPATH
+do eval test x\${$as_var+set} = xset \
+  && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || :
+done
+PS1='$ '
+PS2='> '
+PS4='+ '
+
+# NLS nuisances.
+LC_ALL=C
+export LC_ALL
+LANGUAGE=C
+export LANGUAGE
+
+# CDPATH.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+# Use a proper internal environment variable to ensure we don't fall
+  # into an infinite loop, continuously re-executing ourselves.
+  if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then
+    _as_can_reexec=no; export _as_can_reexec;
+    # We cannot yet assume a decent shell, so we have to provide a
+# neutralization value for shells without unset; and this also
+# works around shells that cannot unset nonexistent variables.
+# Preserve -v and -x to the replacement shell.
+BASH_ENV=/dev/null
+ENV=/dev/null
+(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV
+case $- in # ((((
+  *v*x* | *x*v* ) as_opts=-vx ;;
+  *v* ) as_opts=-v ;;
+  *x* ) as_opts=-x ;;
+  * ) as_opts= ;;
+esac
+exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"}
+# Admittedly, this is quite paranoid, since all the known shells bail
+# out after a failed `exec'.
+$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2
+as_fn_exit 255
+  fi
+  # We don't want this to propagate to other subprocesses.
+          { _as_can_reexec=; unset _as_can_reexec;}
+if test "x$CONFIG_SHELL" = x; then
+  as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then :
+  emulate sh
+  NULLCMD=:
+  # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '\${1+\"\$@\"}'='\"\$@\"'
+  setopt NO_GLOB_SUBST
+else
+  case \`(set -o) 2>/dev/null\` in #(
+  *posix*) :
+    set -o posix ;; #(
+  *) :
+     ;;
+esac
+fi
+"
+  as_required="as_fn_return () { (exit \$1); }
+as_fn_success () { as_fn_return 0; }
+as_fn_failure () { as_fn_return 1; }
+as_fn_ret_success () { return 0; }
+as_fn_ret_failure () { return 1; }
+
+exitcode=0
+as_fn_success || { exitcode=1; echo as_fn_success failed.; }
+as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; }
+as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; }
+as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; }
+if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then :
+
+else
+  exitcode=1; echo positional parameters were not saved.
+fi
+test x\$exitcode = x0 || exit 1
+test -x / || exit 1"
+  as_suggested="  as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO
+  as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO
+  eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" &&
+  test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1"
+  if (eval "$as_required") 2>/dev/null; then :
+  as_have_required=yes
+else
+  as_have_required=no
+fi
+  if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then :
+
+else
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+as_found=false
+for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  as_found=:
+  case $as_dir in #(
+        /*)
+          for as_base in sh bash ksh sh5; do
+            # Try only shells that exist, to save several forks.
+            as_shell=$as_dir/$as_base
+            if { test -f "$as_shell" || test -f "$as_shell.exe"; } &&
+                   { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then :
+  CONFIG_SHELL=$as_shell as_have_required=yes
+                  if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then :
+  break 2
+fi
+fi
+          done;;
+       esac
+  as_found=false
+done
+$as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } &&
+             { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then :
+  CONFIG_SHELL=$SHELL as_have_required=yes
+fi; }
+IFS=$as_save_IFS
+
+
+      if test "x$CONFIG_SHELL" != x; then :
+  export CONFIG_SHELL
+             # We cannot yet assume a decent shell, so we have to provide a
+# neutralization value for shells without unset; and this also
+# works around shells that cannot unset nonexistent variables.
+# Preserve -v and -x to the replacement shell.
+BASH_ENV=/dev/null
+ENV=/dev/null
+(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV
+case $- in # ((((
+  *v*x* | *x*v* ) as_opts=-vx ;;
+  *v* ) as_opts=-v ;;
+  *x* ) as_opts=-x ;;
+  * ) as_opts= ;;
+esac
+exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"}
+# Admittedly, this is quite paranoid, since all the known shells bail
+# out after a failed `exec'.
+$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2
+exit 255
+fi
+
+    if test x$as_have_required = xno; then :
+  $as_echo "$0: This script requires a shell more modern than all"
+  $as_echo "$0: the shells that I found on your system."
+  if test x${ZSH_VERSION+set} = xset ; then
+    $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should"
+    $as_echo "$0: be upgraded to zsh 4.3.4 or later."
+  else
+    $as_echo "$0: Please tell bug-autoconf@gnu.org and anakin@pobox.com
+$0: about your system, including any error possibly output
+$0: before this message. Then install a modern shell, or
+$0: manually run the script under such a shell if you do
+$0: have one."
+  fi
+  exit 1
+fi
+fi
+fi
+SHELL=${CONFIG_SHELL-/bin/sh}
+export SHELL
+# Unset more variables known to interfere with behavior of common tools.
+CLICOLOR_FORCE= GREP_OPTIONS=
+unset CLICOLOR_FORCE GREP_OPTIONS
+
+## --------------------- ##
+## M4sh Shell Functions. ##
+## --------------------- ##
+# as_fn_unset VAR
+# ---------------
+# Portably unset VAR.
+as_fn_unset ()
+{
+  { eval $1=; unset $1;}
+}
+as_unset=as_fn_unset
+
+# as_fn_set_status STATUS
+# -----------------------
+# Set $? to STATUS, without forking.
+as_fn_set_status ()
+{
+  return $1
+} # as_fn_set_status
+
+# as_fn_exit STATUS
+# -----------------
+# Exit the shell with STATUS, even in a "trap 0" or "set -e" context.
+as_fn_exit ()
+{
+  set +e
+  as_fn_set_status $1
+  exit $1
+} # as_fn_exit
+
+# as_fn_mkdir_p
+# -------------
+# Create "$as_dir" as a directory, including parents if necessary.
+as_fn_mkdir_p ()
+{
+
+  case $as_dir in #(
+  -*) as_dir=./$as_dir;;
+  esac
+  test -d "$as_dir" || eval $as_mkdir_p || {
+    as_dirs=
+    while :; do
+      case $as_dir in #(
+      *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'(
+      *) as_qdir=$as_dir;;
+      esac
+      as_dirs="'$as_qdir' $as_dirs"
+      as_dir=`$as_dirname -- "$as_dir" ||
+$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+        X"$as_dir" : 'X\(//\)[^/]' \| \
+        X"$as_dir" : 'X\(//\)$' \| \
+        X"$as_dir" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$as_dir" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)[^/].*/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\).*/{
+           s//\1/
+           q
+         }
+         s/.*/./; q'`
+      test -d "$as_dir" && break
+    done
+    test -z "$as_dirs" || eval "mkdir $as_dirs"
+  } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir"
+
+
+} # as_fn_mkdir_p
+
+# as_fn_executable_p FILE
+# -----------------------
+# Test if FILE is an executable regular file.
+as_fn_executable_p ()
+{
+  test -f "$1" && test -x "$1"
+} # as_fn_executable_p
+# as_fn_append VAR VALUE
+# ----------------------
+# Append the text in VALUE to the end of the definition contained in VAR. Take
+# advantage of any shell optimizations that allow amortized linear growth over
+# repeated appends, instead of the typical quadratic growth present in naive
+# implementations.
+if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then :
+  eval 'as_fn_append ()
+  {
+    eval $1+=\$2
+  }'
+else
+  as_fn_append ()
+  {
+    eval $1=\$$1\$2
+  }
+fi # as_fn_append
+
+# as_fn_arith ARG...
+# ------------------
+# Perform arithmetic evaluation on the ARGs, and store the result in the
+# global $as_val. Take advantage of shells that can avoid forks. The arguments
+# must be portable across $(()) and expr.
+if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then :
+  eval 'as_fn_arith ()
+  {
+    as_val=$(( $* ))
+  }'
+else
+  as_fn_arith ()
+  {
+    as_val=`expr "$@" || test $? -eq 1`
+  }
+fi # as_fn_arith
+
+
+# as_fn_error STATUS ERROR [LINENO LOG_FD]
+# ----------------------------------------
+# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are
+# provided, also output the error to LOG_FD, referencing LINENO. Then exit the
+# script with STATUS, using 1 if that was 0.
+as_fn_error ()
+{
+  as_status=$1; test $as_status -eq 0 && as_status=1
+  if test "$4"; then
+    as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+    $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
+  fi
+  $as_echo "$as_me: error: $2" >&2
+  as_fn_exit $as_status
+} # as_fn_error
+
+if expr a : '\(a\)' >/dev/null 2>&1 &&
+   test "X`expr 00001 : '.*\(...\)'`" = X001; then
+  as_expr=expr
+else
+  as_expr=false
+fi
+
+if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then
+  as_basename=basename
+else
+  as_basename=false
+fi
+
+if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then
+  as_dirname=dirname
+else
+  as_dirname=false
+fi
+
+as_me=`$as_basename -- "$0" ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+        X"$0" : 'X\(//\)$' \| \
+        X"$0" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X/"$0" |
+    sed '/^.*\/\([^/][^/]*\)\/*$/{
+           s//\1/
+           q
+         }
+         /^X\/\(\/\/\)$/{
+           s//\1/
+           q
+         }
+         /^X\/\(\/\).*/{
+           s//\1/
+           q
+         }
+         s/.*/./; q'`
+
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+
+  as_lineno_1=$LINENO as_lineno_1a=$LINENO
+  as_lineno_2=$LINENO as_lineno_2a=$LINENO
+  eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" &&
+  test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || {
+  # Blame Lee E. McMahon (1931-1989) for sed's syntax.  :-)
+  sed -n '
+    p
+    /[$]LINENO/=
+  ' <$as_myself |
+    sed '
+      s/[$]LINENO.*/&-/
+      t lineno
+      b
+      :lineno
+      N
+      :loop
+      s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/
+      t loop
+      s/-\n.*//
+    ' >$as_me.lineno &&
+  chmod +x "$as_me.lineno" ||
+    { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; }
+
+  # If we had to re-execute with $CONFIG_SHELL, we're ensured to have
+  # already done that, so ensure we don't try to do so again and fall
+  # in an infinite loop.  This has already happened in practice.
+  _as_can_reexec=no; export _as_can_reexec
+  # Don't try to exec as it changes $[0], causing all sort of problems
+  # (the dirname of $[0] is not the place where we might find the
+  # original and so on.  Autoconf is especially sensitive to this).
+  . "./$as_me.lineno"
+  # Exit status is that of the last command.
+  exit
+}
+
+ECHO_C= ECHO_N= ECHO_T=
+case `echo -n x` in #(((((
+-n*)
+  case `echo 'xy\c'` in
+  *c*) ECHO_T='        ';;     # ECHO_T is single tab character.
+  xy)  ECHO_C='\c';;
+  *)   echo `echo ksh88 bug on AIX 6.1` > /dev/null
+       ECHO_T='        ';;
+  esac;;
+*)
+  ECHO_N='-n';;
+esac
+
+rm -f conf$$ conf$$.exe conf$$.file
+if test -d conf$$.dir; then
+  rm -f conf$$.dir/conf$$.file
+else
+  rm -f conf$$.dir
+  mkdir conf$$.dir 2>/dev/null
+fi
+if (echo >conf$$.file) 2>/dev/null; then
+  if ln -s conf$$.file conf$$ 2>/dev/null; then
+    as_ln_s='ln -s'
+    # ... but there are two gotchas:
+    # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail.
+    # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable.
+    # In both cases, we have to default to `cp -pR'.
+    ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe ||
+      as_ln_s='cp -pR'
+  elif ln conf$$.file conf$$ 2>/dev/null; then
+    as_ln_s=ln
+  else
+    as_ln_s='cp -pR'
+  fi
+else
+  as_ln_s='cp -pR'
+fi
+rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file
+rmdir conf$$.dir 2>/dev/null
+
+if mkdir -p . 2>/dev/null; then
+  as_mkdir_p='mkdir -p "$as_dir"'
+else
+  test -d ./-p && rmdir ./-p
+  as_mkdir_p=false
+fi
+
+as_test_x='test -x'
+as_executable_p=as_fn_executable_p
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
+
+
+test -n "$DJDIR" || exec 7<&0 </dev/null
+exec 6>&1
+
+# Name of the host.
+# hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status,
+# so uname gets run too.
+ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q`
+
+#
+# Initializations.
+#
+ac_default_prefix=/usr/local
+ac_clean_files=
+ac_config_libobj_dir=.
+LIBOBJS=
+cross_compiling=no
+subdirs=
+MFLAGS=
+MAKEFLAGS=
+
+# Identity of this package.
+PACKAGE_NAME='puzzles'
+PACKAGE_TARNAME='puzzles'
+PACKAGE_VERSION='20161228.7cae89f'
+PACKAGE_STRING='puzzles 20161228.7cae89f'
+PACKAGE_BUGREPORT='anakin@pobox.com'
+PACKAGE_URL=''
+
+ac_unique_file="midend.c"
+ac_subst_vars='am__EXEEXT_FALSE
+am__EXEEXT_TRUE
+LTLIBOBJS
+LIBOBJS
+RANLIB
+PKG_CONFIG_LIBDIR
+PKG_CONFIG_PATH
+GTK_LIBS
+GTK_CFLAGS
+PKG_CONFIG
+am__fastdepCC_FALSE
+am__fastdepCC_TRUE
+CCDEPMODE
+am__nodep
+AMDEPBACKSLASH
+AMDEP_FALSE
+AMDEP_TRUE
+am__quote
+am__include
+DEPDIR
+OBJEXT
+EXEEXT
+ac_ct_CC
+CPPFLAGS
+LDFLAGS
+CFLAGS
+CC
+AM_BACKSLASH
+AM_DEFAULT_VERBOSITY
+AM_DEFAULT_V
+AM_V
+am__untar
+am__tar
+AMTAR
+am__leading_dot
+SET_MAKE
+AWK
+mkdir_p
+MKDIR_P
+INSTALL_STRIP_PROGRAM
+STRIP
+install_sh
+MAKEINFO
+AUTOHEADER
+AUTOMAKE
+AUTOCONF
+ACLOCAL
+VERSION
+PACKAGE
+CYGPATH_W
+am__isrc
+INSTALL_DATA
+INSTALL_SCRIPT
+INSTALL_PROGRAM
+target_alias
+host_alias
+build_alias
+LIBS
+ECHO_T
+ECHO_N
+ECHO_C
+DEFS
+mandir
+localedir
+libdir
+psdir
+pdfdir
+dvidir
+htmldir
+infodir
+docdir
+oldincludedir
+includedir
+runstatedir
+localstatedir
+sharedstatedir
+sysconfdir
+datadir
+datarootdir
+libexecdir
+sbindir
+bindir
+program_transform_name
+prefix
+exec_prefix
+PACKAGE_URL
+PACKAGE_BUGREPORT
+PACKAGE_STRING
+PACKAGE_VERSION
+PACKAGE_TARNAME
+PACKAGE_NAME
+PATH_SEPARATOR
+SHELL'
+ac_subst_files=''
+ac_user_opts='
+enable_option_checking
+enable_silent_rules
+enable_dependency_tracking
+with_gtk
+enable_gtktest
+'
+      ac_precious_vars='build_alias
+host_alias
+target_alias
+CC
+CFLAGS
+LDFLAGS
+LIBS
+CPPFLAGS
+PKG_CONFIG
+PKG_CONFIG_PATH
+PKG_CONFIG_LIBDIR'
+
+
+# Initialize some variables set by options.
+ac_init_help=
+ac_init_version=false
+ac_unrecognized_opts=
+ac_unrecognized_sep=
+# The variables have the same names as the options, with
+# dashes changed to underlines.
+cache_file=/dev/null
+exec_prefix=NONE
+no_create=
+no_recursion=
+prefix=NONE
+program_prefix=NONE
+program_suffix=NONE
+program_transform_name=s,x,x,
+silent=
+site=
+srcdir=
+verbose=
+x_includes=NONE
+x_libraries=NONE
+
+# Installation directory options.
+# These are left unexpanded so users can "make install exec_prefix=/foo"
+# and all the variables that are supposed to be based on exec_prefix
+# by default will actually change.
+# Use braces instead of parens because sh, perl, etc. also accept them.
+# (The list follows the same order as the GNU Coding Standards.)
+bindir='${exec_prefix}/bin'
+sbindir='${exec_prefix}/sbin'
+libexecdir='${exec_prefix}/libexec'
+datarootdir='${prefix}/share'
+datadir='${datarootdir}'
+sysconfdir='${prefix}/etc'
+sharedstatedir='${prefix}/com'
+localstatedir='${prefix}/var'
+runstatedir='${localstatedir}/run'
+includedir='${prefix}/include'
+oldincludedir='/usr/include'
+docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
+infodir='${datarootdir}/info'
+htmldir='${docdir}'
+dvidir='${docdir}'
+pdfdir='${docdir}'
+psdir='${docdir}'
+libdir='${exec_prefix}/lib'
+localedir='${datarootdir}/locale'
+mandir='${datarootdir}/man'
+
+ac_prev=
+ac_dashdash=
+for ac_option
+do
+  # If the previous option needs an argument, assign it.
+  if test -n "$ac_prev"; then
+    eval $ac_prev=\$ac_option
+    ac_prev=
+    continue
+  fi
+
+  case $ac_option in
+  *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;;
+  *=)   ac_optarg= ;;
+  *)    ac_optarg=yes ;;
+  esac
+
+  # Accept the important Cygnus configure options, so we can diagnose typos.
+
+  case $ac_dashdash$ac_option in
+  --)
+    ac_dashdash=yes ;;
+
+  -bindir | --bindir | --bindi | --bind | --bin | --bi)
+    ac_prev=bindir ;;
+  -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*)
+    bindir=$ac_optarg ;;
+
+  -build | --build | --buil | --bui | --bu)
+    ac_prev=build_alias ;;
+  -build=* | --build=* | --buil=* | --bui=* | --bu=*)
+    build_alias=$ac_optarg ;;
+
+  -cache-file | --cache-file | --cache-fil | --cache-fi \
+  | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c)
+    ac_prev=cache_file ;;
+  -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \
+  | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*)
+    cache_file=$ac_optarg ;;
+
+  --config-cache | -C)
+    cache_file=config.cache ;;
+
+  -datadir | --datadir | --datadi | --datad)
+    ac_prev=datadir ;;
+  -datadir=* | --datadir=* | --datadi=* | --datad=*)
+    datadir=$ac_optarg ;;
+
+  -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \
+  | --dataroo | --dataro | --datar)
+    ac_prev=datarootdir ;;
+  -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \
+  | --dataroot=* | --dataroo=* | --dataro=* | --datar=*)
+    datarootdir=$ac_optarg ;;
+
+  -disable-* | --disable-*)
+    ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+      as_fn_error $? "invalid feature name: $ac_useropt"
+    ac_useropt_orig=$ac_useropt
+    ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+    case $ac_user_opts in
+      *"
+"enable_$ac_useropt"
+"*) ;;
+      *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig"
+        ac_unrecognized_sep=', ';;
+    esac
+    eval enable_$ac_useropt=no ;;
+
+  -docdir | --docdir | --docdi | --doc | --do)
+    ac_prev=docdir ;;
+  -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*)
+    docdir=$ac_optarg ;;
+
+  -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv)
+    ac_prev=dvidir ;;
+  -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*)
+    dvidir=$ac_optarg ;;
+
+  -enable-* | --enable-*)
+    ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+      as_fn_error $? "invalid feature name: $ac_useropt"
+    ac_useropt_orig=$ac_useropt
+    ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+    case $ac_user_opts in
+      *"
+"enable_$ac_useropt"
+"*) ;;
+      *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig"
+        ac_unrecognized_sep=', ';;
+    esac
+    eval enable_$ac_useropt=\$ac_optarg ;;
+
+  -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \
+  | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \
+  | --exec | --exe | --ex)
+    ac_prev=exec_prefix ;;
+  -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \
+  | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \
+  | --exec=* | --exe=* | --ex=*)
+    exec_prefix=$ac_optarg ;;
+
+  -gas | --gas | --ga | --g)
+    # Obsolete; use --with-gas.
+    with_gas=yes ;;
+
+  -help | --help | --hel | --he | -h)
+    ac_init_help=long ;;
+  -help=r* | --help=r* | --hel=r* | --he=r* | -hr*)
+    ac_init_help=recursive ;;
+  -help=s* | --help=s* | --hel=s* | --he=s* | -hs*)
+    ac_init_help=short ;;
+
+  -host | --host | --hos | --ho)
+    ac_prev=host_alias ;;
+  -host=* | --host=* | --hos=* | --ho=*)
+    host_alias=$ac_optarg ;;
+
+  -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht)
+    ac_prev=htmldir ;;
+  -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \
+  | --ht=*)
+    htmldir=$ac_optarg ;;
+
+  -includedir | --includedir | --includedi | --included | --include \
+  | --includ | --inclu | --incl | --inc)
+    ac_prev=includedir ;;
+  -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \
+  | --includ=* | --inclu=* | --incl=* | --inc=*)
+    includedir=$ac_optarg ;;
+
+  -infodir | --infodir | --infodi | --infod | --info | --inf)
+    ac_prev=infodir ;;
+  -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*)
+    infodir=$ac_optarg ;;
+
+  -libdir | --libdir | --libdi | --libd)
+    ac_prev=libdir ;;
+  -libdir=* | --libdir=* | --libdi=* | --libd=*)
+    libdir=$ac_optarg ;;
+
+  -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \
+  | --libexe | --libex | --libe)
+    ac_prev=libexecdir ;;
+  -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \
+  | --libexe=* | --libex=* | --libe=*)
+    libexecdir=$ac_optarg ;;
+
+  -localedir | --localedir | --localedi | --localed | --locale)
+    ac_prev=localedir ;;
+  -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*)
+    localedir=$ac_optarg ;;
+
+  -localstatedir | --localstatedir | --localstatedi | --localstated \
+  | --localstate | --localstat | --localsta | --localst | --locals)
+    ac_prev=localstatedir ;;
+  -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \
+  | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*)
+    localstatedir=$ac_optarg ;;
+
+  -mandir | --mandir | --mandi | --mand | --man | --ma | --m)
+    ac_prev=mandir ;;
+  -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*)
+    mandir=$ac_optarg ;;
+
+  -nfp | --nfp | --nf)
+    # Obsolete; use --without-fp.
+    with_fp=no ;;
+
+  -no-create | --no-create | --no-creat | --no-crea | --no-cre \
+  | --no-cr | --no-c | -n)
+    no_create=yes ;;
+
+  -no-recursion | --no-recursion | --no-recursio | --no-recursi \
+  | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r)
+    no_recursion=yes ;;
+
+  -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \
+  | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \
+  | --oldin | --oldi | --old | --ol | --o)
+    ac_prev=oldincludedir ;;
+  -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \
+  | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \
+  | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*)
+    oldincludedir=$ac_optarg ;;
+
+  -prefix | --prefix | --prefi | --pref | --pre | --pr | --p)
+    ac_prev=prefix ;;
+  -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*)
+    prefix=$ac_optarg ;;
+
+  -program-prefix | --program-prefix | --program-prefi | --program-pref \
+  | --program-pre | --program-pr | --program-p)
+    ac_prev=program_prefix ;;
+  -program-prefix=* | --program-prefix=* | --program-prefi=* \
+  | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*)
+    program_prefix=$ac_optarg ;;
+
+  -program-suffix | --program-suffix | --program-suffi | --program-suff \
+  | --program-suf | --program-su | --program-s)
+    ac_prev=program_suffix ;;
+  -program-suffix=* | --program-suffix=* | --program-suffi=* \
+  | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*)
+    program_suffix=$ac_optarg ;;
+
+  -program-transform-name | --program-transform-name \
+  | --program-transform-nam | --program-transform-na \
+  | --program-transform-n | --program-transform- \
+  | --program-transform | --program-transfor \
+  | --program-transfo | --program-transf \
+  | --program-trans | --program-tran \
+  | --progr-tra | --program-tr | --program-t)
+    ac_prev=program_transform_name ;;
+  -program-transform-name=* | --program-transform-name=* \
+  | --program-transform-nam=* | --program-transform-na=* \
+  | --program-transform-n=* | --program-transform-=* \
+  | --program-transform=* | --program-transfor=* \
+  | --program-transfo=* | --program-transf=* \
+  | --program-trans=* | --program-tran=* \
+  | --progr-tra=* | --program-tr=* | --program-t=*)
+    program_transform_name=$ac_optarg ;;
+
+  -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd)
+    ac_prev=pdfdir ;;
+  -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*)
+    pdfdir=$ac_optarg ;;
+
+  -psdir | --psdir | --psdi | --psd | --ps)
+    ac_prev=psdir ;;
+  -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*)
+    psdir=$ac_optarg ;;
+
+  -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+  | -silent | --silent | --silen | --sile | --sil)
+    silent=yes ;;
+
+  -runstatedir | --runstatedir | --runstatedi | --runstated \
+  | --runstate | --runstat | --runsta | --runst | --runs \
+  | --run | --ru | --r)
+    ac_prev=runstatedir ;;
+  -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \
+  | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \
+  | --run=* | --ru=* | --r=*)
+    runstatedir=$ac_optarg ;;
+
+  -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
+    ac_prev=sbindir ;;
+  -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
+  | --sbi=* | --sb=*)
+    sbindir=$ac_optarg ;;
+
+  -sharedstatedir | --sharedstatedir | --sharedstatedi \
+  | --sharedstated | --sharedstate | --sharedstat | --sharedsta \
+  | --sharedst | --shareds | --shared | --share | --shar \
+  | --sha | --sh)
+    ac_prev=sharedstatedir ;;
+  -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \
+  | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \
+  | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \
+  | --sha=* | --sh=*)
+    sharedstatedir=$ac_optarg ;;
+
+  -site | --site | --sit)
+    ac_prev=site ;;
+  -site=* | --site=* | --sit=*)
+    site=$ac_optarg ;;
+
+  -srcdir | --srcdir | --srcdi | --srcd | --src | --sr)
+    ac_prev=srcdir ;;
+  -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*)
+    srcdir=$ac_optarg ;;
+
+  -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \
+  | --syscon | --sysco | --sysc | --sys | --sy)
+    ac_prev=sysconfdir ;;
+  -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \
+  | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*)
+    sysconfdir=$ac_optarg ;;
+
+  -target | --target | --targe | --targ | --tar | --ta | --t)
+    ac_prev=target_alias ;;
+  -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*)
+    target_alias=$ac_optarg ;;
+
+  -v | -verbose | --verbose | --verbos | --verbo | --verb)
+    verbose=yes ;;
+
+  -version | --version | --versio | --versi | --vers | -V)
+    ac_init_version=: ;;
+
+  -with-* | --with-*)
+    ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+      as_fn_error $? "invalid package name: $ac_useropt"
+    ac_useropt_orig=$ac_useropt
+    ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+    case $ac_user_opts in
+      *"
+"with_$ac_useropt"
+"*) ;;
+      *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig"
+        ac_unrecognized_sep=', ';;
+    esac
+    eval with_$ac_useropt=\$ac_optarg ;;
+
+  -without-* | --without-*)
+    ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+      as_fn_error $? "invalid package name: $ac_useropt"
+    ac_useropt_orig=$ac_useropt
+    ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+    case $ac_user_opts in
+      *"
+"with_$ac_useropt"
+"*) ;;
+      *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig"
+        ac_unrecognized_sep=', ';;
+    esac
+    eval with_$ac_useropt=no ;;
+
+  --x)
+    # Obsolete; use --with-x.
+    with_x=yes ;;
+
+  -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \
+  | --x-incl | --x-inc | --x-in | --x-i)
+    ac_prev=x_includes ;;
+  -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \
+  | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*)
+    x_includes=$ac_optarg ;;
+
+  -x-libraries | --x-libraries | --x-librarie | --x-librari \
+  | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l)
+    ac_prev=x_libraries ;;
+  -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \
+  | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*)
+    x_libraries=$ac_optarg ;;
+
+  -*) as_fn_error $? "unrecognized option: \`$ac_option'
+Try \`$0 --help' for more information"
+    ;;
+
+  *=*)
+    ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='`
+    # Reject names that are not valid shell variable names.
+    case $ac_envvar in #(
+      '' | [0-9]* | *[!_$as_cr_alnum]* )
+      as_fn_error $? "invalid variable name: \`$ac_envvar'" ;;
+    esac
+    eval $ac_envvar=\$ac_optarg
+    export $ac_envvar ;;
+
+  *)
+    # FIXME: should be removed in autoconf 3.0.
+    $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2
+    expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null &&
+      $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2
+    : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}"
+    ;;
+
+  esac
+done
+
+if test -n "$ac_prev"; then
+  ac_option=--`echo $ac_prev | sed 's/_/-/g'`
+  as_fn_error $? "missing argument to $ac_option"
+fi
+
+if test -n "$ac_unrecognized_opts"; then
+  case $enable_option_checking in
+    no) ;;
+    fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;;
+    *)     $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;;
+  esac
+fi
+
+# Check all directory arguments for consistency.
+for ac_var in  exec_prefix prefix bindir sbindir libexecdir datarootdir \
+               datadir sysconfdir sharedstatedir localstatedir includedir \
+               oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
+               libdir localedir mandir runstatedir
+do
+  eval ac_val=\$$ac_var
+  # Remove trailing slashes.
+  case $ac_val in
+    */ )
+      ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'`
+      eval $ac_var=\$ac_val;;
+  esac
+  # Be sure to have absolute directory names.
+  case $ac_val in
+    [\\/$]* | ?:[\\/]* )  continue;;
+    NONE | '' ) case $ac_var in *prefix ) continue;; esac;;
+  esac
+  as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val"
+done
+
+# There might be people who depend on the old broken behavior: `$host'
+# used to hold the argument of --host etc.
+# FIXME: To remove some day.
+build=$build_alias
+host=$host_alias
+target=$target_alias
+
+# FIXME: To remove some day.
+if test "x$host_alias" != x; then
+  if test "x$build_alias" = x; then
+    cross_compiling=maybe
+  elif test "x$build_alias" != "x$host_alias"; then
+    cross_compiling=yes
+  fi
+fi
+
+ac_tool_prefix=
+test -n "$host_alias" && ac_tool_prefix=$host_alias-
+
+test "$silent" = yes && exec 6>/dev/null
+
+
+ac_pwd=`pwd` && test -n "$ac_pwd" &&
+ac_ls_di=`ls -di .` &&
+ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` ||
+  as_fn_error $? "working directory cannot be determined"
+test "X$ac_ls_di" = "X$ac_pwd_ls_di" ||
+  as_fn_error $? "pwd does not report name of working directory"
+
+
+# Find the source files, if location was not specified.
+if test -z "$srcdir"; then
+  ac_srcdir_defaulted=yes
+  # Try the directory containing this script, then the parent directory.
+  ac_confdir=`$as_dirname -- "$as_myself" ||
+$as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+        X"$as_myself" : 'X\(//\)[^/]' \| \
+        X"$as_myself" : 'X\(//\)$' \| \
+        X"$as_myself" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$as_myself" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)[^/].*/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\).*/{
+           s//\1/
+           q
+         }
+         s/.*/./; q'`
+  srcdir=$ac_confdir
+  if test ! -r "$srcdir/$ac_unique_file"; then
+    srcdir=..
+  fi
+else
+  ac_srcdir_defaulted=no
+fi
+if test ! -r "$srcdir/$ac_unique_file"; then
+  test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .."
+  as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir"
+fi
+ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work"
+ac_abs_confdir=`(
+       cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg"
+       pwd)`
+# When building in place, set srcdir=.
+if test "$ac_abs_confdir" = "$ac_pwd"; then
+  srcdir=.
+fi
+# Remove unnecessary trailing slashes from srcdir.
+# Double slashes in file names in object file debugging info
+# mess up M-x gdb in Emacs.
+case $srcdir in
+*/) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;;
+esac
+for ac_var in $ac_precious_vars; do
+  eval ac_env_${ac_var}_set=\${${ac_var}+set}
+  eval ac_env_${ac_var}_value=\$${ac_var}
+  eval ac_cv_env_${ac_var}_set=\${${ac_var}+set}
+  eval ac_cv_env_${ac_var}_value=\$${ac_var}
+done
+
+#
+# Report the --help message.
+#
+if test "$ac_init_help" = "long"; then
+  # Omit some internal or obsolete options to make the list less imposing.
+  # This message is too long to be a string in the A/UX 3.1 sh.
+  cat <<_ACEOF
+\`configure' configures puzzles 20161228.7cae89f to adapt to many kinds of systems.
+
+Usage: $0 [OPTION]... [VAR=VALUE]...
+
+To assign environment variables (e.g., CC, CFLAGS...), specify them as
+VAR=VALUE.  See below for descriptions of some of the useful variables.
+
+Defaults for the options are specified in brackets.
+
+Configuration:
+  -h, --help              display this help and exit
+      --help=short        display options specific to this package
+      --help=recursive    display the short help of all the included packages
+  -V, --version           display version information and exit
+  -q, --quiet, --silent   do not print \`checking ...' messages
+      --cache-file=FILE   cache test results in FILE [disabled]
+  -C, --config-cache      alias for \`--cache-file=config.cache'
+  -n, --no-create         do not create output files
+      --srcdir=DIR        find the sources in DIR [configure dir or \`..']
+
+Installation directories:
+  --prefix=PREFIX         install architecture-independent files in PREFIX
+                          [$ac_default_prefix]
+  --exec-prefix=EPREFIX   install architecture-dependent files in EPREFIX
+                          [PREFIX]
+
+By default, \`make install' will install all the files in
+\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc.  You can specify
+an installation prefix other than \`$ac_default_prefix' using \`--prefix',
+for instance \`--prefix=\$HOME'.
+
+For better control, use the options below.
+
+Fine tuning of the installation directories:
+  --bindir=DIR            user executables [EPREFIX/bin]
+  --sbindir=DIR           system admin executables [EPREFIX/sbin]
+  --libexecdir=DIR        program executables [EPREFIX/libexec]
+  --sysconfdir=DIR        read-only single-machine data [PREFIX/etc]
+  --sharedstatedir=DIR    modifiable architecture-independent data [PREFIX/com]
+  --localstatedir=DIR     modifiable single-machine data [PREFIX/var]
+  --runstatedir=DIR       modifiable per-process data [LOCALSTATEDIR/run]
+  --libdir=DIR            object code libraries [EPREFIX/lib]
+  --includedir=DIR        C header files [PREFIX/include]
+  --oldincludedir=DIR     C header files for non-gcc [/usr/include]
+  --datarootdir=DIR       read-only arch.-independent data root [PREFIX/share]
+  --datadir=DIR           read-only architecture-independent data [DATAROOTDIR]
+  --infodir=DIR           info documentation [DATAROOTDIR/info]
+  --localedir=DIR         locale-dependent data [DATAROOTDIR/locale]
+  --mandir=DIR            man documentation [DATAROOTDIR/man]
+  --docdir=DIR            documentation root [DATAROOTDIR/doc/puzzles]
+  --htmldir=DIR           html documentation [DOCDIR]
+  --dvidir=DIR            dvi documentation [DOCDIR]
+  --pdfdir=DIR            pdf documentation [DOCDIR]
+  --psdir=DIR             ps documentation [DOCDIR]
+_ACEOF
+
+  cat <<\_ACEOF
+
+Program names:
+  --program-prefix=PREFIX            prepend PREFIX to installed program names
+  --program-suffix=SUFFIX            append SUFFIX to installed program names
+  --program-transform-name=PROGRAM   run sed PROGRAM on installed program names
+_ACEOF
+fi
+
+if test -n "$ac_init_help"; then
+  case $ac_init_help in
+     short | recursive ) echo "Configuration of puzzles 20161228.7cae89f:";;
+   esac
+  cat <<\_ACEOF
+
+Optional Features:
+  --disable-option-checking  ignore unrecognized --enable/--with options
+  --disable-FEATURE       do not include FEATURE (same as --enable-FEATURE=no)
+  --enable-FEATURE[=ARG]  include FEATURE [ARG=yes]
+  --enable-silent-rules   less verbose build output (undo: "make V=1")
+  --disable-silent-rules  verbose build output (undo: "make V=0")
+  --enable-dependency-tracking
+                          do not reject slow dependency extractors
+  --disable-dependency-tracking
+                          speeds up one-time build
+  --disable-gtktest       do not try to compile and run a test GTK+ program
+
+Optional Packages:
+  --with-PACKAGE[=ARG]    use PACKAGE [ARG=yes]
+  --without-PACKAGE       do not use PACKAGE (same as --with-PACKAGE=no)
+  --with-gtk=VER          specify GTK version to use (`2' or `3')
+
+Some influential environment variables:
+  CC          C compiler command
+  CFLAGS      C compiler flags
+  LDFLAGS     linker flags, e.g. -L<lib dir> if you have libraries in a
+              nonstandard directory <lib dir>
+  LIBS        libraries to pass to the linker, e.g. -l<library>
+  CPPFLAGS    (Objective) C/C++ preprocessor flags, e.g. -I<include dir> if
+              you have headers in a nonstandard directory <include dir>
+  PKG_CONFIG  path to pkg-config utility
+  PKG_CONFIG_PATH
+              directories to add to pkg-config's search path
+  PKG_CONFIG_LIBDIR
+              path overriding pkg-config's built-in search path
+
+Use these variables to override the choices made by `configure' or to help
+it to find libraries and programs with nonstandard names/locations.
+
+Report bugs to <anakin@pobox.com>.
+_ACEOF
+ac_status=$?
+fi
+
+if test "$ac_init_help" = "recursive"; then
+  # If there are subdirs, report their specific --help.
+  for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue
+    test -d "$ac_dir" ||
+      { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } ||
+      continue
+    ac_builddir=.
+
+case "$ac_dir" in
+.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;;
+*)
+  ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'`
+  # A ".." for each directory in $ac_dir_suffix.
+  ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'`
+  case $ac_top_builddir_sub in
+  "") ac_top_builddir_sub=. ac_top_build_prefix= ;;
+  *)  ac_top_build_prefix=$ac_top_builddir_sub/ ;;
+  esac ;;
+esac
+ac_abs_top_builddir=$ac_pwd
+ac_abs_builddir=$ac_pwd$ac_dir_suffix
+# for backward compatibility:
+ac_top_builddir=$ac_top_build_prefix
+
+case $srcdir in
+  .)  # We are building in place.
+    ac_srcdir=.
+    ac_top_srcdir=$ac_top_builddir_sub
+    ac_abs_top_srcdir=$ac_pwd ;;
+  [\\/]* | ?:[\\/]* )  # Absolute name.
+    ac_srcdir=$srcdir$ac_dir_suffix;
+    ac_top_srcdir=$srcdir
+    ac_abs_top_srcdir=$srcdir ;;
+  *) # Relative name.
+    ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix
+    ac_top_srcdir=$ac_top_build_prefix$srcdir
+    ac_abs_top_srcdir=$ac_pwd/$srcdir ;;
+esac
+ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix
+
+    cd "$ac_dir" || { ac_status=$?; continue; }
+    # Check for guested configure.
+    if test -f "$ac_srcdir/configure.gnu"; then
+      echo &&
+      $SHELL "$ac_srcdir/configure.gnu" --help=recursive
+    elif test -f "$ac_srcdir/configure"; then
+      echo &&
+      $SHELL "$ac_srcdir/configure" --help=recursive
+    else
+      $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2
+    fi || ac_status=$?
+    cd "$ac_pwd" || { ac_status=$?; break; }
+  done
+fi
+
+test -n "$ac_init_help" && exit $ac_status
+if $ac_init_version; then
+  cat <<\_ACEOF
+puzzles configure 20161228.7cae89f
+generated by GNU Autoconf 2.69
+
+Copyright (C) 2012 Free Software Foundation, Inc.
+This configure script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it.
+_ACEOF
+  exit
+fi
+
+## ------------------------ ##
+## Autoconf initialization. ##
+## ------------------------ ##
+
+# ac_fn_c_try_compile LINENO
+# --------------------------
+# Try to compile conftest.$ac_ext, and return whether this succeeded.
+ac_fn_c_try_compile ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  rm -f conftest.$ac_objext
+  if { { ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_compile") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    grep -v '^ *+' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+    mv -f conftest.er1 conftest.err
+  fi
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } && {
+        test -z "$ac_c_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then :
+  ac_retval=0
+else
+  $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_retval=1
+fi
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+  as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_compile
+
+# ac_fn_c_try_run LINENO
+# ----------------------
+# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes
+# that executables *can* be run.
+ac_fn_c_try_run ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  if { { ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } && { ac_try='./conftest$ac_exeext'
+  { { case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_try") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; }; then :
+  ac_retval=0
+else
+  $as_echo "$as_me: program exited with status $ac_status" >&5
+       $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_retval=$ac_status
+fi
+  rm -rf conftest.dSYM conftest_ipa8_conftest.oo
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+  as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_run
+
+# ac_fn_c_try_link LINENO
+# -----------------------
+# Try to link conftest.$ac_ext, and return whether this succeeded.
+ac_fn_c_try_link ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  rm -f conftest.$ac_objext conftest$ac_exeext
+  if { { ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    grep -v '^ *+' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+    mv -f conftest.er1 conftest.err
+  fi
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } && {
+        test -z "$ac_c_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest$ac_exeext && {
+        test "$cross_compiling" = yes ||
+        test -x conftest$ac_exeext
+       }; then :
+  ac_retval=0
+else
+  $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_retval=1
+fi
+  # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information
+  # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would
+  # interfere with the next link command; also delete a directory that is
+  # left behind by Apple's compiler.  We do this before executing the actions.
+  rm -rf conftest.dSYM conftest_ipa8_conftest.oo
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+  as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_link
+cat >config.log <<_ACEOF
+This file contains any messages produced by compilers while
+running configure, to aid debugging if configure makes a mistake.
+
+It was created by puzzles $as_me 20161228.7cae89f, which was
+generated by GNU Autoconf 2.69.  Invocation command line was
+
+  $ $0 $@
+
+_ACEOF
+exec 5>>config.log
+{
+cat <<_ASUNAME
+## --------- ##
+## Platform. ##
+## --------- ##
+
+hostname = `(hostname || uname -n) 2>/dev/null | sed 1q`
+uname -m = `(uname -m) 2>/dev/null || echo unknown`
+uname -r = `(uname -r) 2>/dev/null || echo unknown`
+uname -s = `(uname -s) 2>/dev/null || echo unknown`
+uname -v = `(uname -v) 2>/dev/null || echo unknown`
+
+/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown`
+/bin/uname -X     = `(/bin/uname -X) 2>/dev/null     || echo unknown`
+
+/bin/arch              = `(/bin/arch) 2>/dev/null              || echo unknown`
+/usr/bin/arch -k       = `(/usr/bin/arch -k) 2>/dev/null       || echo unknown`
+/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown`
+/usr/bin/hostinfo      = `(/usr/bin/hostinfo) 2>/dev/null      || echo unknown`
+/bin/machine           = `(/bin/machine) 2>/dev/null           || echo unknown`
+/usr/bin/oslevel       = `(/usr/bin/oslevel) 2>/dev/null       || echo unknown`
+/bin/universe          = `(/bin/universe) 2>/dev/null          || echo unknown`
+
+_ASUNAME
+
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    $as_echo "PATH: $as_dir"
+  done
+IFS=$as_save_IFS
+
+} >&5
+
+cat >&5 <<_ACEOF
+
+
+## ----------- ##
+## Core tests. ##
+## ----------- ##
+
+_ACEOF
+
+
+# Keep a trace of the command line.
+# Strip out --no-create and --no-recursion so they do not pile up.
+# Strip out --silent because we don't want to record it for future runs.
+# Also quote any args containing shell meta-characters.
+# Make two passes to allow for proper duplicate-argument suppression.
+ac_configure_args=
+ac_configure_args0=
+ac_configure_args1=
+ac_must_keep_next=false
+for ac_pass in 1 2
+do
+  for ac_arg
+  do
+    case $ac_arg in
+    -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;;
+    -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+    | -silent | --silent | --silen | --sile | --sil)
+      continue ;;
+    *\'*)
+      ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;;
+    esac
+    case $ac_pass in
+    1) as_fn_append ac_configure_args0 " '$ac_arg'" ;;
+    2)
+      as_fn_append ac_configure_args1 " '$ac_arg'"
+      if test $ac_must_keep_next = true; then
+       ac_must_keep_next=false # Got value, back to normal.
+      else
+       case $ac_arg in
+         *=* | --config-cache | -C | -disable-* | --disable-* \
+         | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \
+         | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \
+         | -with-* | --with-* | -without-* | --without-* | --x)
+           case "$ac_configure_args0 " in
+             "$ac_configure_args1"*" '$ac_arg' "* ) continue ;;
+           esac
+           ;;
+         -* ) ac_must_keep_next=true ;;
+       esac
+      fi
+      as_fn_append ac_configure_args " '$ac_arg'"
+      ;;
+    esac
+  done
+done
+{ ac_configure_args0=; unset ac_configure_args0;}
+{ ac_configure_args1=; unset ac_configure_args1;}
+
+# When interrupted or exit'd, cleanup temporary files, and complete
+# config.log.  We remove comments because anyway the quotes in there
+# would cause problems or look ugly.
+# WARNING: Use '\'' to represent an apostrophe within the trap.
+# WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug.
+trap 'exit_status=$?
+  # Save into config.log some information that might help in debugging.
+  {
+    echo
+
+    $as_echo "## ---------------- ##
+## Cache variables. ##
+## ---------------- ##"
+    echo
+    # The following way of writing the cache mishandles newlines in values,
+(
+  for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do
+    eval ac_val=\$$ac_var
+    case $ac_val in #(
+    *${as_nl}*)
+      case $ac_var in #(
+      *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5
+$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
+      esac
+      case $ac_var in #(
+      _ | IFS | as_nl) ;; #(
+      BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #(
+      *) { eval $ac_var=; unset $ac_var;} ;;
+      esac ;;
+    esac
+  done
+  (set) 2>&1 |
+    case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #(
+    *${as_nl}ac_space=\ *)
+      sed -n \
+       "s/'\''/'\''\\\\'\'''\''/g;
+         s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p"
+      ;; #(
+    *)
+      sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p"
+      ;;
+    esac |
+    sort
+)
+    echo
+
+    $as_echo "## ----------------- ##
+## Output variables. ##
+## ----------------- ##"
+    echo
+    for ac_var in $ac_subst_vars
+    do
+      eval ac_val=\$$ac_var
+      case $ac_val in
+      *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;;
+      esac
+      $as_echo "$ac_var='\''$ac_val'\''"
+    done | sort
+    echo
+
+    if test -n "$ac_subst_files"; then
+      $as_echo "## ------------------- ##
+## File substitutions. ##
+## ------------------- ##"
+      echo
+      for ac_var in $ac_subst_files
+      do
+       eval ac_val=\$$ac_var
+       case $ac_val in
+       *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;;
+       esac
+       $as_echo "$ac_var='\''$ac_val'\''"
+      done | sort
+      echo
+    fi
+
+    if test -s confdefs.h; then
+      $as_echo "## ----------- ##
+## confdefs.h. ##
+## ----------- ##"
+      echo
+      cat confdefs.h
+      echo
+    fi
+    test "$ac_signal" != 0 &&
+      $as_echo "$as_me: caught signal $ac_signal"
+    $as_echo "$as_me: exit $exit_status"
+  } >&5
+  rm -f core *.core core.conftest.* &&
+    rm -f -r conftest* confdefs* conf$$* $ac_clean_files &&
+    exit $exit_status
+' 0
+for ac_signal in 1 2 13 15; do
+  trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal
+done
+ac_signal=0
+
+# confdefs.h avoids OS command line length limits that DEFS can exceed.
+rm -f -r conftest* confdefs.h
+
+$as_echo "/* confdefs.h */" > confdefs.h
+
+# Predefined preprocessor variables.
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_NAME "$PACKAGE_NAME"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_TARNAME "$PACKAGE_TARNAME"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_VERSION "$PACKAGE_VERSION"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_STRING "$PACKAGE_STRING"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_URL "$PACKAGE_URL"
+_ACEOF
+
+
+# Let the site file select an alternate cache file if it wants to.
+# Prefer an explicitly selected file to automatically selected ones.
+ac_site_file1=NONE
+ac_site_file2=NONE
+if test -n "$CONFIG_SITE"; then
+  # We do not want a PATH search for config.site.
+  case $CONFIG_SITE in #((
+    -*)  ac_site_file1=./$CONFIG_SITE;;
+    */*) ac_site_file1=$CONFIG_SITE;;
+    *)   ac_site_file1=./$CONFIG_SITE;;
+  esac
+elif test "x$prefix" != xNONE; then
+  ac_site_file1=$prefix/share/config.site
+  ac_site_file2=$prefix/etc/config.site
+else
+  ac_site_file1=$ac_default_prefix/share/config.site
+  ac_site_file2=$ac_default_prefix/etc/config.site
+fi
+for ac_site_file in "$ac_site_file1" "$ac_site_file2"
+do
+  test "x$ac_site_file" = xNONE && continue
+  if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then
+    { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5
+$as_echo "$as_me: loading site script $ac_site_file" >&6;}
+    sed 's/^/| /' "$ac_site_file" >&5
+    . "$ac_site_file" \
+      || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "failed to load site script $ac_site_file
+See \`config.log' for more details" "$LINENO" 5; }
+  fi
+done
+
+if test -r "$cache_file"; then
+  # Some versions of bash will fail to source /dev/null (special files
+  # actually), so we avoid doing that.  DJGPP emulates it as a regular file.
+  if test /dev/null != "$cache_file" && test -f "$cache_file"; then
+    { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5
+$as_echo "$as_me: loading cache $cache_file" >&6;}
+    case $cache_file in
+      [\\/]* | ?:[\\/]* ) . "$cache_file";;
+      *)                      . "./$cache_file";;
+    esac
+  fi
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5
+$as_echo "$as_me: creating cache $cache_file" >&6;}
+  >$cache_file
+fi
+
+# Check that the precious variables saved in the cache have kept the same
+# value.
+ac_cache_corrupted=false
+for ac_var in $ac_precious_vars; do
+  eval ac_old_set=\$ac_cv_env_${ac_var}_set
+  eval ac_new_set=\$ac_env_${ac_var}_set
+  eval ac_old_val=\$ac_cv_env_${ac_var}_value
+  eval ac_new_val=\$ac_env_${ac_var}_value
+  case $ac_old_set,$ac_new_set in
+    set,)
+      { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5
+$as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;}
+      ac_cache_corrupted=: ;;
+    ,set)
+      { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5
+$as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;}
+      ac_cache_corrupted=: ;;
+    ,);;
+    *)
+      if test "x$ac_old_val" != "x$ac_new_val"; then
+       # differences in whitespace do not lead to failure.
+       ac_old_val_w=`echo x $ac_old_val`
+       ac_new_val_w=`echo x $ac_new_val`
+       if test "$ac_old_val_w" != "$ac_new_val_w"; then
+         { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5
+$as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;}
+         ac_cache_corrupted=:
+       else
+         { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5
+$as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;}
+         eval $ac_var=\$ac_old_val
+       fi
+       { $as_echo "$as_me:${as_lineno-$LINENO}:   former value:  \`$ac_old_val'" >&5
+$as_echo "$as_me:   former value:  \`$ac_old_val'" >&2;}
+       { $as_echo "$as_me:${as_lineno-$LINENO}:   current value: \`$ac_new_val'" >&5
+$as_echo "$as_me:   current value: \`$ac_new_val'" >&2;}
+      fi;;
+  esac
+  # Pass precious variables to config.status.
+  if test "$ac_new_set" = set; then
+    case $ac_new_val in
+    *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;;
+    *) ac_arg=$ac_var=$ac_new_val ;;
+    esac
+    case " $ac_configure_args " in
+      *" '$ac_arg' "*) ;; # Avoid dups.  Use of quotes ensures accuracy.
+      *) as_fn_append ac_configure_args " '$ac_arg'" ;;
+    esac
+  fi
+done
+if $ac_cache_corrupted; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+  { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5
+$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;}
+  as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5
+fi
+## -------------------- ##
+## Main body of script. ##
+## -------------------- ##
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+am__api_version='1.15'
+
+ac_aux_dir=
+for ac_dir in "$srcdir" "$srcdir/.." "$srcdir/../.."; do
+  if test -f "$ac_dir/install-sh"; then
+    ac_aux_dir=$ac_dir
+    ac_install_sh="$ac_aux_dir/install-sh -c"
+    break
+  elif test -f "$ac_dir/install.sh"; then
+    ac_aux_dir=$ac_dir
+    ac_install_sh="$ac_aux_dir/install.sh -c"
+    break
+  elif test -f "$ac_dir/shtool"; then
+    ac_aux_dir=$ac_dir
+    ac_install_sh="$ac_aux_dir/shtool install -c"
+    break
+  fi
+done
+if test -z "$ac_aux_dir"; then
+  as_fn_error $? "cannot find install-sh, install.sh, or shtool in \"$srcdir\" \"$srcdir/..\" \"$srcdir/../..\"" "$LINENO" 5
+fi
+
+# These three variables are undocumented and unsupported,
+# and are intended to be withdrawn in a future Autoconf release.
+# They can cause serious problems if a builder's source tree is in a directory
+# whose full name contains unusual characters.
+ac_config_guess="$SHELL $ac_aux_dir/config.guess"  # Please don't use this var.
+ac_config_sub="$SHELL $ac_aux_dir/config.sub"  # Please don't use this var.
+ac_configure="$SHELL $ac_aux_dir/configure"  # Please don't use this var.
+
+
+# Find a good install program.  We prefer a C program (faster),
+# so one script is as good as another.  But avoid the broken or
+# incompatible versions:
+# SysV /etc/install, /usr/sbin/install
+# SunOS /usr/etc/install
+# IRIX /sbin/install
+# AIX /bin/install
+# AmigaOS /C/install, which installs bootblocks on floppy discs
+# AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag
+# AFS /usr/afsws/bin/install, which mishandles nonexistent args
+# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff"
+# OS/2's system install, which has a completely different semantic
+# ./install, which can be erroneously created by make from ./install.sh.
+# Reject install programs that cannot install multiple files.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5
+$as_echo_n "checking for a BSD-compatible install... " >&6; }
+if test -z "$INSTALL"; then
+if ${ac_cv_path_install+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    # Account for people who put trailing slashes in PATH elements.
+case $as_dir/ in #((
+  ./ | .// | /[cC]/* | \
+  /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \
+  ?:[\\/]os2[\\/]install[\\/]* | ?:[\\/]OS2[\\/]INSTALL[\\/]* | \
+  /usr/ucb/* ) ;;
+  *)
+    # OSF1 and SCO ODT 3.0 have their own names for install.
+    # Don't use installbsd from OSF since it installs stuff as root
+    # by default.
+    for ac_prog in ginstall scoinst install; do
+      for ac_exec_ext in '' $ac_executable_extensions; do
+       if as_fn_executable_p "$as_dir/$ac_prog$ac_exec_ext"; then
+         if test $ac_prog = install &&
+           grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then
+           # AIX install.  It has an incompatible calling convention.
+           :
+         elif test $ac_prog = install &&
+           grep pwplus "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then
+           # program-specific install script used by HP pwplus--don't use.
+           :
+         else
+           rm -rf conftest.one conftest.two conftest.dir
+           echo one > conftest.one
+           echo two > conftest.two
+           mkdir conftest.dir
+           if "$as_dir/$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir" &&
+             test -s conftest.one && test -s conftest.two &&
+             test -s conftest.dir/conftest.one &&
+             test -s conftest.dir/conftest.two
+           then
+             ac_cv_path_install="$as_dir/$ac_prog$ac_exec_ext -c"
+             break 3
+           fi
+         fi
+       fi
+      done
+    done
+    ;;
+esac
+
+  done
+IFS=$as_save_IFS
+
+rm -rf conftest.one conftest.two conftest.dir
+
+fi
+  if test "${ac_cv_path_install+set}" = set; then
+    INSTALL=$ac_cv_path_install
+  else
+    # As a last resort, use the slow shell script.  Don't cache a
+    # value for INSTALL within a source directory, because that will
+    # break other packages using the cache if that directory is
+    # removed, or if the value is a relative name.
+    INSTALL=$ac_install_sh
+  fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5
+$as_echo "$INSTALL" >&6; }
+
+# Use test -z because SunOS4 sh mishandles braces in ${var-val}.
+# It thinks the first close brace ends the variable substitution.
+test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}'
+
+test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}'
+
+test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644'
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether build environment is sane" >&5
+$as_echo_n "checking whether build environment is sane... " >&6; }
+# Reject unsafe characters in $srcdir or the absolute working directory
+# name.  Accept space and tab only in the latter.
+am_lf='
+'
+case `pwd` in
+  *[\\\"\#\$\&\'\`$am_lf]*)
+    as_fn_error $? "unsafe absolute working directory name" "$LINENO" 5;;
+esac
+case $srcdir in
+  *[\\\"\#\$\&\'\`$am_lf\ \    ]*)
+    as_fn_error $? "unsafe srcdir value: '$srcdir'" "$LINENO" 5;;
+esac
+
+# Do 'set' in a subshell so we don't clobber the current shell's
+# arguments.  Must try -L first in case configure is actually a
+# symlink; some systems play weird games with the mod time of symlinks
+# (eg FreeBSD returns the mod time of the symlink's containing
+# directory).
+if (
+   am_has_slept=no
+   for am_try in 1 2; do
+     echo "timestamp, slept: $am_has_slept" > conftest.file
+     set X `ls -Lt "$srcdir/configure" conftest.file 2> /dev/null`
+     if test "$*" = "X"; then
+       # -L didn't work.
+       set X `ls -t "$srcdir/configure" conftest.file`
+     fi
+     if test "$*" != "X $srcdir/configure conftest.file" \
+       && test "$*" != "X conftest.file $srcdir/configure"; then
+
+       # If neither matched, then we have a broken ls.  This can happen
+       # if, for instance, CONFIG_SHELL is bash and it inherits a
+       # broken ls alias from the environment.  This has actually
+       # happened.  Such a system could not be considered "sane".
+       as_fn_error $? "ls -t appears to fail.  Make sure there is not a broken
+  alias in your environment" "$LINENO" 5
+     fi
+     if test "$2" = conftest.file || test $am_try -eq 2; then
+       break
+     fi
+     # Just in case.
+     sleep 1
+     am_has_slept=yes
+   done
+   test "$2" = conftest.file
+   )
+then
+   # Ok.
+   :
+else
+   as_fn_error $? "newly created file is older than distributed files!
+Check your system clock" "$LINENO" 5
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+# If we didn't sleep, we still need to ensure time stamps of config.status and
+# generated files are strictly newer.
+am_sleep_pid=
+if grep 'slept: no' conftest.file >/dev/null 2>&1; then
+  ( sleep 1 ) &
+  am_sleep_pid=$!
+fi
+
+rm -f conftest.file
+
+test "$program_prefix" != NONE &&
+  program_transform_name="s&^&$program_prefix&;$program_transform_name"
+# Use a double $ so make ignores it.
+test "$program_suffix" != NONE &&
+  program_transform_name="s&\$&$program_suffix&;$program_transform_name"
+# Double any \ or $.
+# By default was `s,x,x', remove it if useless.
+ac_script='s/[\\$]/&&/g;s/;s,x,x,$//'
+program_transform_name=`$as_echo "$program_transform_name" | sed "$ac_script"`
+
+# Expand $ac_aux_dir to an absolute path.
+am_aux_dir=`cd "$ac_aux_dir" && pwd`
+
+if test x"${MISSING+set}" != xset; then
+  case $am_aux_dir in
+  *\ * | *\    *)
+    MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;;
+  *)
+    MISSING="\${SHELL} $am_aux_dir/missing" ;;
+  esac
+fi
+# Use eval to expand $SHELL
+if eval "$MISSING --is-lightweight"; then
+  am_missing_run="$MISSING "
+else
+  am_missing_run=
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: 'missing' script is too old or missing" >&5
+$as_echo "$as_me: WARNING: 'missing' script is too old or missing" >&2;}
+fi
+
+if test x"${install_sh+set}" != xset; then
+  case $am_aux_dir in
+  *\ * | *\    *)
+    install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;;
+  *)
+    install_sh="\${SHELL} $am_aux_dir/install-sh"
+  esac
+fi
+
+# Installed binaries are usually stripped using 'strip' when the user
+# run "make install-strip".  However 'strip' might not be the right
+# tool to use in cross-compilation environments, therefore Automake
+# will honor the 'STRIP' environment variable to overrule this program.
+if test "$cross_compiling" != no; then
+  if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args.
+set dummy ${ac_tool_prefix}strip; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_STRIP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$STRIP"; then
+  ac_cv_prog_STRIP="$STRIP" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_STRIP="${ac_tool_prefix}strip"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+STRIP=$ac_cv_prog_STRIP
+if test -n "$STRIP"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $STRIP" >&5
+$as_echo "$STRIP" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_STRIP"; then
+  ac_ct_STRIP=$STRIP
+  # Extract the first word of "strip", so it can be a program name with args.
+set dummy strip; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_STRIP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_STRIP"; then
+  ac_cv_prog_ac_ct_STRIP="$ac_ct_STRIP" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_ac_ct_STRIP="strip"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_STRIP=$ac_cv_prog_ac_ct_STRIP
+if test -n "$ac_ct_STRIP"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_STRIP" >&5
+$as_echo "$ac_ct_STRIP" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_STRIP" = x; then
+    STRIP=":"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    STRIP=$ac_ct_STRIP
+  fi
+else
+  STRIP="$ac_cv_prog_STRIP"
+fi
+
+fi
+INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s"
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a thread-safe mkdir -p" >&5
+$as_echo_n "checking for a thread-safe mkdir -p... " >&6; }
+if test -z "$MKDIR_P"; then
+  if ${ac_cv_path_mkdir+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/opt/sfw/bin
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_prog in mkdir gmkdir; do
+        for ac_exec_ext in '' $ac_executable_extensions; do
+          as_fn_executable_p "$as_dir/$ac_prog$ac_exec_ext" || continue
+          case `"$as_dir/$ac_prog$ac_exec_ext" --version 2>&1` in #(
+            'mkdir (GNU coreutils) '* | \
+            'mkdir (coreutils) '* | \
+            'mkdir (fileutils) '4.1*)
+              ac_cv_path_mkdir=$as_dir/$ac_prog$ac_exec_ext
+              break 3;;
+          esac
+        done
+       done
+  done
+IFS=$as_save_IFS
+
+fi
+
+  test -d ./--version && rmdir ./--version
+  if test "${ac_cv_path_mkdir+set}" = set; then
+    MKDIR_P="$ac_cv_path_mkdir -p"
+  else
+    # As a last resort, use the slow shell script.  Don't cache a
+    # value for MKDIR_P within a source directory, because that will
+    # break other packages using the cache if that directory is
+    # removed, or if the value is a relative name.
+    MKDIR_P="$ac_install_sh -d"
+  fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $MKDIR_P" >&5
+$as_echo "$MKDIR_P" >&6; }
+
+for ac_prog in gawk mawk nawk awk
+do
+  # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_AWK+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$AWK"; then
+  ac_cv_prog_AWK="$AWK" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_AWK="$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+AWK=$ac_cv_prog_AWK
+if test -n "$AWK"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $AWK" >&5
+$as_echo "$AWK" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  test -n "$AWK" && break
+done
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5
+$as_echo_n "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; }
+set x ${MAKE-make}
+ac_make=`$as_echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'`
+if eval \${ac_cv_prog_make_${ac_make}_set+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat >conftest.make <<\_ACEOF
+SHELL = /bin/sh
+all:
+       @echo '@@@%%%=$(MAKE)=@@@%%%'
+_ACEOF
+# GNU make sometimes prints "make[1]: Entering ...", which would confuse us.
+case `${MAKE-make} -f conftest.make 2>/dev/null` in
+  *@@@%%%=?*=@@@%%%*)
+    eval ac_cv_prog_make_${ac_make}_set=yes;;
+  *)
+    eval ac_cv_prog_make_${ac_make}_set=no;;
+esac
+rm -f conftest.make
+fi
+if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+  SET_MAKE=
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+  SET_MAKE="MAKE=${MAKE-make}"
+fi
+
+rm -rf .tst 2>/dev/null
+mkdir .tst 2>/dev/null
+if test -d .tst; then
+  am__leading_dot=.
+else
+  am__leading_dot=_
+fi
+rmdir .tst 2>/dev/null
+
+# Check whether --enable-silent-rules was given.
+if test "${enable_silent_rules+set}" = set; then :
+  enableval=$enable_silent_rules;
+fi
+
+case $enable_silent_rules in # (((
+  yes) AM_DEFAULT_VERBOSITY=0;;
+   no) AM_DEFAULT_VERBOSITY=1;;
+    *) AM_DEFAULT_VERBOSITY=1;;
+esac
+am_make=${MAKE-make}
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $am_make supports nested variables" >&5
+$as_echo_n "checking whether $am_make supports nested variables... " >&6; }
+if ${am_cv_make_support_nested_variables+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if $as_echo 'TRUE=$(BAR$(V))
+BAR0=false
+BAR1=true
+V=1
+am__doit:
+       @$(TRUE)
+.PHONY: am__doit' | $am_make -f - >/dev/null 2>&1; then
+  am_cv_make_support_nested_variables=yes
+else
+  am_cv_make_support_nested_variables=no
+fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_make_support_nested_variables" >&5
+$as_echo "$am_cv_make_support_nested_variables" >&6; }
+if test $am_cv_make_support_nested_variables = yes; then
+    AM_V='$(V)'
+  AM_DEFAULT_V='$(AM_DEFAULT_VERBOSITY)'
+else
+  AM_V=$AM_DEFAULT_VERBOSITY
+  AM_DEFAULT_V=$AM_DEFAULT_VERBOSITY
+fi
+AM_BACKSLASH='\'
+
+if test "`cd $srcdir && pwd`" != "`pwd`"; then
+  # Use -I$(srcdir) only when $(srcdir) != ., so that make's output
+  # is not polluted with repeated "-I."
+  am__isrc=' -I$(srcdir)'
+  # test to see if srcdir already configured
+  if test -f $srcdir/config.status; then
+    as_fn_error $? "source directory already configured; run \"make distclean\" there first" "$LINENO" 5
+  fi
+fi
+
+# test whether we have cygpath
+if test -z "$CYGPATH_W"; then
+  if (cygpath --version) >/dev/null 2>/dev/null; then
+    CYGPATH_W='cygpath -w'
+  else
+    CYGPATH_W=echo
+  fi
+fi
+
+
+# Define the identity of the package.
+ PACKAGE='puzzles'
+ VERSION='20161228.7cae89f'
+
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE "$PACKAGE"
+_ACEOF
+
+
+cat >>confdefs.h <<_ACEOF
+#define VERSION "$VERSION"
+_ACEOF
+
+# Some tools Automake needs.
+
+ACLOCAL=${ACLOCAL-"${am_missing_run}aclocal-${am__api_version}"}
+
+
+AUTOCONF=${AUTOCONF-"${am_missing_run}autoconf"}
+
+
+AUTOMAKE=${AUTOMAKE-"${am_missing_run}automake-${am__api_version}"}
+
+
+AUTOHEADER=${AUTOHEADER-"${am_missing_run}autoheader"}
+
+
+MAKEINFO=${MAKEINFO-"${am_missing_run}makeinfo"}
+
+# For better backward compatibility.  To be removed once Automake 1.9.x
+# dies out for good.  For more background, see:
+# <http://lists.gnu.org/archive/html/automake/2012-07/msg00001.html>
+# <http://lists.gnu.org/archive/html/automake/2012-07/msg00014.html>
+mkdir_p='$(MKDIR_P)'
+
+# We need awk for the "check" target (and possibly the TAP driver).  The
+# system "awk" is bad on some platforms.
+# Always define AMTAR for backward compatibility.  Yes, it's still used
+# in the wild :-(  We should find a proper way to deprecate it ...
+AMTAR='$${TAR-tar}'
+
+
+# We'll loop over all known methods to create a tar archive until one works.
+_am_tools='gnutar  pax cpio none'
+
+am__tar='$${TAR-tar} chof - "$$tardir"' am__untar='$${TAR-tar} xf -'
+
+
+
+
+
+
+# POSIX will say in a future version that running "rm -f" with no argument
+# is OK; and we want to be able to make that assumption in our Makefile
+# recipes.  So use an aggressive probe to check that the usage we want is
+# actually supported "in the wild" to an acceptable degree.
+# See automake bug#10828.
+# To make any issue more visible, cause the running configure to be aborted
+# by default if the 'rm' program in use doesn't match our expectations; the
+# user can still override this though.
+if rm -f && rm -fr && rm -rf; then : OK; else
+  cat >&2 <<'END'
+Oops!
+
+Your 'rm' program seems unable to run without file operands specified
+on the command line, even when the '-f' option is present.  This is contrary
+to the behaviour of most rm programs out there, and not conforming with
+the upcoming POSIX standard: <http://austingroupbugs.net/view.php?id=542>
+
+Please tell bug-automake@gnu.org about your system, including the value
+of your $PATH and any error possibly output before this message.  This
+can help us improve future automake versions.
+
+END
+  if test x"$ACCEPT_INFERIOR_RM_PROGRAM" = x"yes"; then
+    echo 'Configuration will proceed anyway, since you have set the' >&2
+    echo 'ACCEPT_INFERIOR_RM_PROGRAM variable to "yes"' >&2
+    echo >&2
+  else
+    cat >&2 <<'END'
+Aborting the configuration process, to ensure you take notice of the issue.
+
+You can download and install GNU coreutils to get an 'rm' implementation
+that behaves properly: <http://www.gnu.org/software/coreutils/>.
+
+If you want to complete the configuration process using your problematic
+'rm' anyway, export the environment variable ACCEPT_INFERIOR_RM_PROGRAM
+to "yes", and re-run configure.
+
+END
+    as_fn_error $? "Your 'rm' program is bad, sorry." "$LINENO" 5
+  fi
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args.
+set dummy ${ac_tool_prefix}gcc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_CC="${ac_tool_prefix}gcc"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_CC"; then
+  ac_ct_CC=$CC
+  # Extract the first word of "gcc", so it can be a program name with args.
+set dummy gcc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_CC"; then
+  ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_ac_ct_CC="gcc"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
+$as_echo "$ac_ct_CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_CC" = x; then
+    CC=""
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    CC=$ac_ct_CC
+  fi
+else
+  CC="$ac_cv_prog_CC"
+fi
+
+if test -z "$CC"; then
+          if test -n "$ac_tool_prefix"; then
+    # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args.
+set dummy ${ac_tool_prefix}cc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_CC="${ac_tool_prefix}cc"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  fi
+fi
+if test -z "$CC"; then
+  # Extract the first word of "cc", so it can be a program name with args.
+set dummy cc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+  ac_prog_rejected=no
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then
+       ac_prog_rejected=yes
+       continue
+     fi
+    ac_cv_prog_CC="cc"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+if test $ac_prog_rejected = yes; then
+  # We found a bogon in the path, so make sure we never use it.
+  set dummy $ac_cv_prog_CC
+  shift
+  if test $# != 0; then
+    # We chose a different compiler from the bogus one.
+    # However, it has the same basename, so the bogon will be chosen
+    # first if we set CC to just the basename; use the full file name.
+    shift
+    ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@"
+  fi
+fi
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$CC"; then
+  if test -n "$ac_tool_prefix"; then
+  for ac_prog in cl.exe
+  do
+    # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
+set dummy $ac_tool_prefix$ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_CC="$ac_tool_prefix$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+    test -n "$CC" && break
+  done
+fi
+if test -z "$CC"; then
+  ac_ct_CC=$CC
+  for ac_prog in cl.exe
+do
+  # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_CC"; then
+  ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_ac_ct_CC="$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
+$as_echo "$ac_ct_CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  test -n "$ac_ct_CC" && break
+done
+
+  if test "x$ac_ct_CC" = x; then
+    CC=""
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    CC=$ac_ct_CC
+  fi
+fi
+
+fi
+
+
+test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "no acceptable C compiler found in \$PATH
+See \`config.log' for more details" "$LINENO" 5; }
+
+# Provide some information about the compiler.
+$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5
+set X $ac_compile
+ac_compiler=$2
+for ac_option in --version -v -V -qversion; do
+  { { ac_try="$ac_compiler $ac_option >&5"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_compiler $ac_option >&5") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    sed '10a\
+... rest of stderr output deleted ...
+         10q' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+  fi
+  rm -f conftest.er1 conftest.err
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }
+done
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+ac_clean_files_save=$ac_clean_files
+ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out"
+# Try to create an executable without -o first, disregard a.out.
+# It will help us diagnose broken compilers, and finding out an intuition
+# of exeext.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5
+$as_echo_n "checking whether the C compiler works... " >&6; }
+ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'`
+
+# The possible output files:
+ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*"
+
+ac_rmfiles=
+for ac_file in $ac_files
+do
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;;
+    * ) ac_rmfiles="$ac_rmfiles $ac_file";;
+  esac
+done
+rm -f $ac_rmfiles
+
+if { { ac_try="$ac_link_default"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link_default") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then :
+  # Autoconf-2.13 could set the ac_cv_exeext variable to `no'.
+# So ignore a value of `no', otherwise this would lead to `EXEEXT = no'
+# in a Makefile.  We should not override ac_cv_exeext if it was cached,
+# so that the user can short-circuit this test for compilers unknown to
+# Autoconf.
+for ac_file in $ac_files ''
+do
+  test -f "$ac_file" || continue
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj )
+       ;;
+    [ab].out )
+       # We found the default executable, but exeext='' is most
+       # certainly right.
+       break;;
+    *.* )
+       if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no;
+       then :; else
+          ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
+       fi
+       # We set ac_cv_exeext here because the later test for it is not
+       # safe: cross compilers may not add the suffix if given an `-o'
+       # argument, so we may need to know it at that point already.
+       # Even if this section looks crufty: it has the advantage of
+       # actually working.
+       break;;
+    * )
+       break;;
+  esac
+done
+test "$ac_cv_exeext" = no && ac_cv_exeext=
+
+else
+  ac_file=''
+fi
+if test -z "$ac_file"; then :
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+$as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error 77 "C compiler cannot create executables
+See \`config.log' for more details" "$LINENO" 5; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5
+$as_echo_n "checking for C compiler default output file name... " >&6; }
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5
+$as_echo "$ac_file" >&6; }
+ac_exeext=$ac_cv_exeext
+
+rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out
+ac_clean_files=$ac_clean_files_save
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5
+$as_echo_n "checking for suffix of executables... " >&6; }
+if { { ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then :
+  # If both `conftest.exe' and `conftest' are `present' (well, observable)
+# catch `conftest.exe'.  For instance with Cygwin, `ls conftest' will
+# work properly (i.e., refer to `conftest.exe'), while it won't with
+# `rm'.
+for ac_file in conftest.exe conftest conftest.*; do
+  test -f "$ac_file" || continue
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;;
+    *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
+         break;;
+    * ) break;;
+  esac
+done
+else
+  { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot compute suffix of executables: cannot compile and link
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+rm -f conftest conftest$ac_cv_exeext
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5
+$as_echo "$ac_cv_exeext" >&6; }
+
+rm -f conftest.$ac_ext
+EXEEXT=$ac_cv_exeext
+ac_exeext=$EXEEXT
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <stdio.h>
+int
+main ()
+{
+FILE *f = fopen ("conftest.out", "w");
+ return ferror (f) || fclose (f) != 0;
+
+  ;
+  return 0;
+}
+_ACEOF
+ac_clean_files="$ac_clean_files conftest.out"
+# Check that the compiler produces executables we can run.  If not, either
+# the compiler is broken, or we cross compile.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5
+$as_echo_n "checking whether we are cross compiling... " >&6; }
+if test "$cross_compiling" != yes; then
+  { { ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }
+  if { ac_try='./conftest$ac_cv_exeext'
+  { { case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_try") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; }; then
+    cross_compiling=no
+  else
+    if test "$cross_compiling" = maybe; then
+       cross_compiling=yes
+    else
+       { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot run C compiled programs.
+If you meant to cross compile, use \`--host'.
+See \`config.log' for more details" "$LINENO" 5; }
+    fi
+  fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5
+$as_echo "$cross_compiling" >&6; }
+
+rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out
+ac_clean_files=$ac_clean_files_save
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5
+$as_echo_n "checking for suffix of object files... " >&6; }
+if ${ac_cv_objext+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.o conftest.obj
+if { { ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_compile") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then :
+  for ac_file in conftest.o conftest.obj conftest.*; do
+  test -f "$ac_file" || continue;
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;;
+    *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'`
+       break;;
+  esac
+done
+else
+  $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot compute suffix of object files: cannot compile
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+rm -f conftest.$ac_cv_objext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5
+$as_echo "$ac_cv_objext" >&6; }
+OBJEXT=$ac_cv_objext
+ac_objext=$OBJEXT
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5
+$as_echo_n "checking whether we are using the GNU C compiler... " >&6; }
+if ${ac_cv_c_compiler_gnu+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+#ifndef __GNUC__
+       choke me
+#endif
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_compiler_gnu=yes
+else
+  ac_compiler_gnu=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_cv_c_compiler_gnu=$ac_compiler_gnu
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5
+$as_echo "$ac_cv_c_compiler_gnu" >&6; }
+if test $ac_compiler_gnu = yes; then
+  GCC=yes
+else
+  GCC=
+fi
+ac_test_CFLAGS=${CFLAGS+set}
+ac_save_CFLAGS=$CFLAGS
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5
+$as_echo_n "checking whether $CC accepts -g... " >&6; }
+if ${ac_cv_prog_cc_g+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_save_c_werror_flag=$ac_c_werror_flag
+   ac_c_werror_flag=yes
+   ac_cv_prog_cc_g=no
+   CFLAGS="-g"
+   cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_prog_cc_g=yes
+else
+  CFLAGS=""
+      cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+else
+  ac_c_werror_flag=$ac_save_c_werror_flag
+        CFLAGS="-g"
+        cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_prog_cc_g=yes
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+   ac_c_werror_flag=$ac_save_c_werror_flag
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5
+$as_echo "$ac_cv_prog_cc_g" >&6; }
+if test "$ac_test_CFLAGS" = set; then
+  CFLAGS=$ac_save_CFLAGS
+elif test $ac_cv_prog_cc_g = yes; then
+  if test "$GCC" = yes; then
+    CFLAGS="-g -O2"
+  else
+    CFLAGS="-g"
+  fi
+else
+  if test "$GCC" = yes; then
+    CFLAGS="-O2"
+  else
+    CFLAGS=
+  fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5
+$as_echo_n "checking for $CC option to accept ISO C89... " >&6; }
+if ${ac_cv_prog_cc_c89+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_cv_prog_cc_c89=no
+ac_save_CC=$CC
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <stdarg.h>
+#include <stdio.h>
+struct stat;
+/* Most of the following tests are stolen from RCS 5.7's src/conf.sh.  */
+struct buf { int x; };
+FILE * (*rcsopen) (struct buf *, struct stat *, int);
+static char *e (p, i)
+     char **p;
+     int i;
+{
+  return p[i];
+}
+static char *f (char * (*g) (char **, int), char **p, ...)
+{
+  char *s;
+  va_list v;
+  va_start (v,p);
+  s = g (p, va_arg (v,int));
+  va_end (v);
+  return s;
+}
+
+/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default.  It has
+   function prototypes and stuff, but not '\xHH' hex character constants.
+   These don't provoke an error unfortunately, instead are silently treated
+   as 'x'.  The following induces an error, until -std is added to get
+   proper ANSI mode.  Curiously '\x00'!='x' always comes out true, for an
+   array size at least.  It's necessary to write '\x00'==0 to get something
+   that's true only with -std.  */
+int osf4_cc_array ['\x00' == 0 ? 1 : -1];
+
+/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters
+   inside strings and character constants.  */
+#define FOO(x) 'x'
+int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1];
+
+int test (int i, double x);
+struct s1 {int (*f) (int a);};
+struct s2 {int (*f) (double a);};
+int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int);
+int argc;
+char **argv;
+int
+main ()
+{
+return f (e, argv, 0) != argv[0]  ||  f (e, argv, 1) != argv[1];
+  ;
+  return 0;
+}
+_ACEOF
+for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \
+       -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__"
+do
+  CC="$ac_save_CC $ac_arg"
+  if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_prog_cc_c89=$ac_arg
+fi
+rm -f core conftest.err conftest.$ac_objext
+  test "x$ac_cv_prog_cc_c89" != "xno" && break
+done
+rm -f conftest.$ac_ext
+CC=$ac_save_CC
+
+fi
+# AC_CACHE_VAL
+case "x$ac_cv_prog_cc_c89" in
+  x)
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
+$as_echo "none needed" >&6; } ;;
+  xno)
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
+$as_echo "unsupported" >&6; } ;;
+  *)
+    CC="$CC $ac_cv_prog_cc_c89"
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5
+$as_echo "$ac_cv_prog_cc_c89" >&6; } ;;
+esac
+if test "x$ac_cv_prog_cc_c89" != xno; then :
+
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC understands -c and -o together" >&5
+$as_echo_n "checking whether $CC understands -c and -o together... " >&6; }
+if ${am_cv_prog_cc_c_o+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+  # Make sure it works both with $CC and with simple cc.
+  # Following AC_PROG_CC_C_O, we do the test twice because some
+  # compilers refuse to overwrite an existing .o file with -o,
+  # though they will create one.
+  am_cv_prog_cc_c_o=yes
+  for am_i in 1 2; do
+    if { echo "$as_me:$LINENO: $CC -c conftest.$ac_ext -o conftest2.$ac_objext" >&5
+   ($CC -c conftest.$ac_ext -o conftest2.$ac_objext) >&5 2>&5
+   ac_status=$?
+   echo "$as_me:$LINENO: \$? = $ac_status" >&5
+   (exit $ac_status); } \
+         && test -f conftest2.$ac_objext; then
+      : OK
+    else
+      am_cv_prog_cc_c_o=no
+      break
+    fi
+  done
+  rm -f core conftest*
+  unset am_i
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_prog_cc_c_o" >&5
+$as_echo "$am_cv_prog_cc_c_o" >&6; }
+if test "$am_cv_prog_cc_c_o" != yes; then
+   # Losing compiler, so override with the script.
+   # FIXME: It is wrong to rewrite CC.
+   # But if we don't then we get into trouble of one sort or another.
+   # A longer-term fix would be to have automake use am__CC in this case,
+   # and then we could set am__CC="\$(top_srcdir)/compile \$(CC)"
+   CC="$am_aux_dir/compile $CC"
+fi
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+DEPDIR="${am__leading_dot}deps"
+
+ac_config_commands="$ac_config_commands depfiles"
+
+
+am_make=${MAKE-make}
+cat > confinc << 'END'
+am__doit:
+       @echo this is the am__doit target
+.PHONY: am__doit
+END
+# If we don't find an include directive, just comment out the code.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for style of include used by $am_make" >&5
+$as_echo_n "checking for style of include used by $am_make... " >&6; }
+am__include="#"
+am__quote=
+_am_result=none
+# First try GNU make style include.
+echo "include confinc" > confmf
+# Ignore all kinds of additional output from 'make'.
+case `$am_make -s -f confmf 2> /dev/null` in #(
+*the\ am__doit\ target*)
+  am__include=include
+  am__quote=
+  _am_result=GNU
+  ;;
+esac
+# Now try BSD make style include.
+if test "$am__include" = "#"; then
+   echo '.include "confinc"' > confmf
+   case `$am_make -s -f confmf 2> /dev/null` in #(
+   *the\ am__doit\ target*)
+     am__include=.include
+     am__quote="\""
+     _am_result=BSD
+     ;;
+   esac
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $_am_result" >&5
+$as_echo "$_am_result" >&6; }
+rm -f confinc confmf
+
+# Check whether --enable-dependency-tracking was given.
+if test "${enable_dependency_tracking+set}" = set; then :
+  enableval=$enable_dependency_tracking;
+fi
+
+if test "x$enable_dependency_tracking" != xno; then
+  am_depcomp="$ac_aux_dir/depcomp"
+  AMDEPBACKSLASH='\'
+  am__nodep='_no'
+fi
+ if test "x$enable_dependency_tracking" != xno; then
+  AMDEP_TRUE=
+  AMDEP_FALSE='#'
+else
+  AMDEP_TRUE='#'
+  AMDEP_FALSE=
+fi
+
+
+
+depcc="$CC"   am_compiler_list=
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking dependency style of $depcc" >&5
+$as_echo_n "checking dependency style of $depcc... " >&6; }
+if ${am_cv_CC_dependencies_compiler_type+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then
+  # We make a subdir and do the tests there.  Otherwise we can end up
+  # making bogus files that we don't know about and never remove.  For
+  # instance it was reported that on HP-UX the gcc test will end up
+  # making a dummy file named 'D' -- because '-MD' means "put the output
+  # in D".
+  rm -rf conftest.dir
+  mkdir conftest.dir
+  # Copy depcomp to subdir because otherwise we won't find it if we're
+  # using a relative directory.
+  cp "$am_depcomp" conftest.dir
+  cd conftest.dir
+  # We will build objects and dependencies in a subdirectory because
+  # it helps to detect inapplicable dependency modes.  For instance
+  # both Tru64's cc and ICC support -MD to output dependencies as a
+  # side effect of compilation, but ICC will put the dependencies in
+  # the current directory while Tru64 will put them in the object
+  # directory.
+  mkdir sub
+
+  am_cv_CC_dependencies_compiler_type=none
+  if test "$am_compiler_list" = ""; then
+     am_compiler_list=`sed -n 's/^#*\([a-zA-Z0-9]*\))$/\1/p' < ./depcomp`
+  fi
+  am__universal=false
+  case " $depcc " in #(
+     *\ -arch\ *\ -arch\ *) am__universal=true ;;
+     esac
+
+  for depmode in $am_compiler_list; do
+    # Setup a source with many dependencies, because some compilers
+    # like to wrap large dependency lists on column 80 (with \), and
+    # we should not choose a depcomp mode which is confused by this.
+    #
+    # We need to recreate these files for each test, as the compiler may
+    # overwrite some of them when testing with obscure command lines.
+    # This happens at least with the AIX C compiler.
+    : > sub/conftest.c
+    for i in 1 2 3 4 5 6; do
+      echo '#include "conftst'$i'.h"' >> sub/conftest.c
+      # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with
+      # Solaris 10 /bin/sh.
+      echo '/* dummy */' > sub/conftst$i.h
+    done
+    echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf
+
+    # We check with '-c' and '-o' for the sake of the "dashmstdout"
+    # mode.  It turns out that the SunPro C++ compiler does not properly
+    # handle '-M -o', and we need to detect this.  Also, some Intel
+    # versions had trouble with output in subdirs.
+    am__obj=sub/conftest.${OBJEXT-o}
+    am__minus_obj="-o $am__obj"
+    case $depmode in
+    gcc)
+      # This depmode causes a compiler race in universal mode.
+      test "$am__universal" = false || continue
+      ;;
+    nosideeffect)
+      # After this tag, mechanisms are not by side-effect, so they'll
+      # only be used when explicitly requested.
+      if test "x$enable_dependency_tracking" = xyes; then
+       continue
+      else
+       break
+      fi
+      ;;
+    msvc7 | msvc7msys | msvisualcpp | msvcmsys)
+      # This compiler won't grok '-c -o', but also, the minuso test has
+      # not run yet.  These depmodes are late enough in the game, and
+      # so weak that their functioning should not be impacted.
+      am__obj=conftest.${OBJEXT-o}
+      am__minus_obj=
+      ;;
+    none) break ;;
+    esac
+    if depmode=$depmode \
+       source=sub/conftest.c object=$am__obj \
+       depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \
+       $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \
+         >/dev/null 2>conftest.err &&
+       grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 &&
+       grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 &&
+       grep $am__obj sub/conftest.Po > /dev/null 2>&1 &&
+       ${MAKE-make} -s -f confmf > /dev/null 2>&1; then
+      # icc doesn't choke on unknown options, it will just issue warnings
+      # or remarks (even with -Werror).  So we grep stderr for any message
+      # that says an option was ignored or not supported.
+      # When given -MP, icc 7.0 and 7.1 complain thusly:
+      #   icc: Command line warning: ignoring option '-M'; no argument required
+      # The diagnosis changed in icc 8.0:
+      #   icc: Command line remark: option '-MP' not supported
+      if (grep 'ignoring option' conftest.err ||
+          grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else
+        am_cv_CC_dependencies_compiler_type=$depmode
+        break
+      fi
+    fi
+  done
+
+  cd ..
+  rm -rf conftest.dir
+else
+  am_cv_CC_dependencies_compiler_type=none
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_CC_dependencies_compiler_type" >&5
+$as_echo "$am_cv_CC_dependencies_compiler_type" >&6; }
+CCDEPMODE=depmode=$am_cv_CC_dependencies_compiler_type
+
+ if
+  test "x$enable_dependency_tracking" != xno \
+  && test "$am_cv_CC_dependencies_compiler_type" = gcc3; then
+  am__fastdepCC_TRUE=
+  am__fastdepCC_FALSE='#'
+else
+  am__fastdepCC_TRUE='#'
+  am__fastdepCC_FALSE=
+fi
+
+
+
+
+# Check whether --with-gtk was given.
+if test "${with_gtk+set}" = set; then :
+  withval=$with_gtk; gtk_version_desired="$withval"
+else
+  gtk_version_desired="any"
+fi
+
+
+case "$gtk_version_desired" in
+  2 | 3 | any) ;;
+  yes) gtk_version_desired="any" ;;
+  *) as_fn_error $? "Invalid GTK version specified" "$LINENO" 5
+esac
+
+gtk=none
+
+case "$gtk_version_desired:$gtk" in
+  3:none | any:none)
+
+
+
+# Check whether --enable-gtktest was given.
+if test "${enable_gtktest+set}" = set; then :
+  enableval=$enable_gtktest;
+else
+  enable_gtktest=yes
+fi
+
+  min_gtk_version=3.0.0
+
+  pkg_config_args="gtk+-3.0 >= $min_gtk_version"
+  for module in .
+  do
+      case "$module" in
+         gthread)
+             pkg_config_args="$pkg_config_args gthread-2.0"
+         ;;
+      esac
+  done
+
+  no_gtk=""
+
+  # Extract the first word of "pkg-config", so it can be a program name with args.
+set dummy pkg-config; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_PKG_CONFIG+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  case $PKG_CONFIG in
+  [\\/]* | ?:[\\/]*)
+  ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path.
+  ;;
+  *)
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_path_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+  test -z "$ac_cv_path_PKG_CONFIG" && ac_cv_path_PKG_CONFIG="no"
+  ;;
+esac
+fi
+PKG_CONFIG=$ac_cv_path_PKG_CONFIG
+if test -n "$PKG_CONFIG"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5
+$as_echo "$PKG_CONFIG" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+
+  if test x$PKG_CONFIG != xno ; then
+    if $PKG_CONFIG --atleast-pkgconfig-version 0.7 ; then
+      :
+    else
+      echo "*** pkg-config too old; version 0.7 or better required."
+      no_gtk=yes
+      PKG_CONFIG=no
+    fi
+  else
+    no_gtk=yes
+  fi
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for GTK+ - version >= $min_gtk_version" >&5
+$as_echo_n "checking for GTK+ - version >= $min_gtk_version... " >&6; }
+
+  if test x$PKG_CONFIG != xno ; then
+    ## don't try to run the test against uninstalled libtool libs
+    if $PKG_CONFIG --uninstalled $pkg_config_args; then
+         echo "Will use uninstalled version of GTK+ found in PKG_CONFIG_PATH"
+         enable_gtktest=no
+    fi
+
+    if $PKG_CONFIG $pkg_config_args; then
+         :
+    else
+         no_gtk=yes
+    fi
+  fi
+
+  if test x"$no_gtk" = x ; then
+    GTK_CFLAGS=`$PKG_CONFIG $pkg_config_args --cflags`
+    GTK_LIBS=`$PKG_CONFIG $pkg_config_args --libs`
+    gtk_config_major_version=`$PKG_CONFIG --modversion gtk+-3.0 | \
+           sed 's/\([0-9]*\).\([0-9]*\).\([0-9]*\)/\1/'`
+    gtk_config_minor_version=`$PKG_CONFIG --modversion gtk+-3.0 | \
+           sed 's/\([0-9]*\).\([0-9]*\).\([0-9]*\)/\2/'`
+    gtk_config_micro_version=`$PKG_CONFIG --modversion gtk+-3.0 | \
+           sed 's/\([0-9]*\).\([0-9]*\).\([0-9]*\)/\3/'`
+    if test "x$enable_gtktest" = "xyes" ; then
+      ac_save_CFLAGS="$CFLAGS"
+      ac_save_LIBS="$LIBS"
+      CFLAGS="$CFLAGS $GTK_CFLAGS"
+      LIBS="$GTK_LIBS $LIBS"
+      rm -f conf.gtktest
+      if test "$cross_compiling" = yes; then :
+  echo $ac_n "cross compiling; assumed OK... $ac_c"
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+#include <gtk/gtk.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+int
+main ()
+{
+  unsigned int major, minor, micro;
+
+  fclose (fopen ("conf.gtktest", "w"));
+
+  if (sscanf("$min_gtk_version", "%u.%u.%u", &major, &minor, &micro) != 3) {
+     printf("%s, bad version string\n", "$min_gtk_version");
+     exit(1);
+   }
+
+  if ((gtk_major_version != $gtk_config_major_version) ||
+      (gtk_minor_version != $gtk_config_minor_version) ||
+      (gtk_micro_version != $gtk_config_micro_version))
+    {
+      printf("\n*** 'pkg-config --modversion gtk+-3.0' returned %d.%d.%d, but GTK+ (%d.%d.%d)\n",
+             $gtk_config_major_version, $gtk_config_minor_version, $gtk_config_micro_version,
+             gtk_major_version, gtk_minor_version, gtk_micro_version);
+      printf ("*** was found! If pkg-config was correct, then it is best\n");
+      printf ("*** to remove the old version of GTK+. You may also be able to fix the error\n");
+      printf("*** by modifying your LD_LIBRARY_PATH enviroment variable, or by editing\n");
+      printf("*** /etc/ld.so.conf. Make sure you have run ldconfig if that is\n");
+      printf("*** required on your system.\n");
+      printf("*** If pkg-config was wrong, set the environment variable PKG_CONFIG_PATH\n");
+      printf("*** to point to the correct configuration files\n");
+    }
+  else if ((gtk_major_version != GTK_MAJOR_VERSION) ||
+          (gtk_minor_version != GTK_MINOR_VERSION) ||
+           (gtk_micro_version != GTK_MICRO_VERSION))
+    {
+      printf("*** GTK+ header files (version %d.%d.%d) do not match\n",
+            GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION);
+      printf("*** library (version %d.%d.%d)\n",
+            gtk_major_version, gtk_minor_version, gtk_micro_version);
+    }
+  else
+    {
+      if ((gtk_major_version > major) ||
+        ((gtk_major_version == major) && (gtk_minor_version > minor)) ||
+        ((gtk_major_version == major) && (gtk_minor_version == minor) && (gtk_micro_version >= micro)))
+      {
+        return 0;
+       }
+     else
+      {
+        printf("\n*** An old version of GTK+ (%u.%u.%u) was found.\n",
+               gtk_major_version, gtk_minor_version, gtk_micro_version);
+        printf("*** You need a version of GTK+ newer than %u.%u.%u. The latest version of\n",
+              major, minor, micro);
+        printf("*** GTK+ is always available from ftp://ftp.gtk.org.\n");
+        printf("***\n");
+        printf("*** If you have already installed a sufficiently new version, this error\n");
+        printf("*** probably means that the wrong copy of the pkg-config shell script is\n");
+        printf("*** being found. The easiest way to fix this is to remove the old version\n");
+        printf("*** of GTK+, but you can also set the PKG_CONFIG environment to point to the\n");
+        printf("*** correct copy of pkg-config. (In this case, you will have to\n");
+        printf("*** modify your LD_LIBRARY_PATH enviroment variable, or edit /etc/ld.so.conf\n");
+        printf("*** so that the correct libraries are found at run-time))\n");
+      }
+    }
+  return 1;
+}
+
+_ACEOF
+if ac_fn_c_try_run "$LINENO"; then :
+
+else
+  no_gtk=yes
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+  conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+
+       CFLAGS="$ac_save_CFLAGS"
+       LIBS="$ac_save_LIBS"
+     fi
+  fi
+  if test "x$no_gtk" = x ; then
+     { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes (version $gtk_config_major_version.$gtk_config_minor_version.$gtk_config_micro_version)" >&5
+$as_echo "yes (version $gtk_config_major_version.$gtk_config_minor_version.$gtk_config_micro_version)" >&6; }
+     gtk=3
+  else
+     { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+     if test "$PKG_CONFIG" = "no" ; then
+       echo "*** A new enough version of pkg-config was not found."
+       echo "*** See http://pkgconfig.sourceforge.net"
+     else
+       if test -f conf.gtktest ; then
+        :
+       else
+          echo "*** Could not run GTK+ test program, checking why..."
+         ac_save_CFLAGS="$CFLAGS"
+         ac_save_LIBS="$LIBS"
+          CFLAGS="$CFLAGS $GTK_CFLAGS"
+          LIBS="$LIBS $GTK_LIBS"
+          cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+#include <gtk/gtk.h>
+#include <stdio.h>
+
+int
+main ()
+{
+ return ((gtk_major_version) || (gtk_minor_version) || (gtk_micro_version));
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+   echo "*** The test program compiled, but did not run. This usually means"
+          echo "*** that the run-time linker is not finding GTK+ or finding the wrong"
+          echo "*** version of GTK+. If it is not finding GTK+, you'll need to set your"
+          echo "*** LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point"
+          echo "*** to the installed location  Also, make sure you have run ldconfig if that"
+          echo "*** is required on your system"
+         echo "***"
+          echo "*** If you have an old version installed, it is best to remove it, although"
+          echo "*** you may also be able to get things to work by modifying LD_LIBRARY_PATH"
+else
+   echo "*** The test program failed to compile or link. See the file config.log for the"
+          echo "*** exact error that occured. This usually means GTK+ is incorrectly installed."
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+          CFLAGS="$ac_save_CFLAGS"
+          LIBS="$ac_save_LIBS"
+       fi
+     fi
+     GTK_CFLAGS=""
+     GTK_LIBS=""
+     :
+  fi
+
+
+  rm -f conf.gtktest
+
+
+    ;;
+esac
+
+case "$gtk_version_desired:$gtk" in
+  2:none | any:none)
+
+
+
+
+
+
+
+
+if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then
+       if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}pkg-config", so it can be a program name with args.
+set dummy ${ac_tool_prefix}pkg-config; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_PKG_CONFIG+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  case $PKG_CONFIG in
+  [\\/]* | ?:[\\/]*)
+  ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path.
+  ;;
+  *)
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_path_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+  ;;
+esac
+fi
+PKG_CONFIG=$ac_cv_path_PKG_CONFIG
+if test -n "$PKG_CONFIG"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5
+$as_echo "$PKG_CONFIG" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_path_PKG_CONFIG"; then
+  ac_pt_PKG_CONFIG=$PKG_CONFIG
+  # Extract the first word of "pkg-config", so it can be a program name with args.
+set dummy pkg-config; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_ac_pt_PKG_CONFIG+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  case $ac_pt_PKG_CONFIG in
+  [\\/]* | ?:[\\/]*)
+  ac_cv_path_ac_pt_PKG_CONFIG="$ac_pt_PKG_CONFIG" # Let the user override the test with a path.
+  ;;
+  *)
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_path_ac_pt_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+  ;;
+esac
+fi
+ac_pt_PKG_CONFIG=$ac_cv_path_ac_pt_PKG_CONFIG
+if test -n "$ac_pt_PKG_CONFIG"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_pt_PKG_CONFIG" >&5
+$as_echo "$ac_pt_PKG_CONFIG" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_pt_PKG_CONFIG" = x; then
+    PKG_CONFIG=""
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    PKG_CONFIG=$ac_pt_PKG_CONFIG
+  fi
+else
+  PKG_CONFIG="$ac_cv_path_PKG_CONFIG"
+fi
+
+fi
+if test -n "$PKG_CONFIG"; then
+       _pkg_min_version=0.9.0
+       { $as_echo "$as_me:${as_lineno-$LINENO}: checking pkg-config is at least version $_pkg_min_version" >&5
+$as_echo_n "checking pkg-config is at least version $_pkg_min_version... " >&6; }
+       if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then
+               { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+       else
+               { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+               PKG_CONFIG=""
+       fi
+fi
+# Check whether --enable-gtktest was given.
+if test "${enable_gtktest+set}" = set; then :
+  enableval=$enable_gtktest;
+else
+  enable_gtktest=yes
+fi
+
+
+  pkg_config_args=gtk+-2.0
+  for module in .
+  do
+      case "$module" in
+         gthread)
+             pkg_config_args="$pkg_config_args gthread-2.0"
+         ;;
+      esac
+  done
+
+  no_gtk=""
+
+
+
+
+
+
+
+
+
+if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then
+       if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}pkg-config", so it can be a program name with args.
+set dummy ${ac_tool_prefix}pkg-config; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_PKG_CONFIG+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  case $PKG_CONFIG in
+  [\\/]* | ?:[\\/]*)
+  ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path.
+  ;;
+  *)
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_path_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+  ;;
+esac
+fi
+PKG_CONFIG=$ac_cv_path_PKG_CONFIG
+if test -n "$PKG_CONFIG"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5
+$as_echo "$PKG_CONFIG" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_path_PKG_CONFIG"; then
+  ac_pt_PKG_CONFIG=$PKG_CONFIG
+  # Extract the first word of "pkg-config", so it can be a program name with args.
+set dummy pkg-config; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_ac_pt_PKG_CONFIG+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  case $ac_pt_PKG_CONFIG in
+  [\\/]* | ?:[\\/]*)
+  ac_cv_path_ac_pt_PKG_CONFIG="$ac_pt_PKG_CONFIG" # Let the user override the test with a path.
+  ;;
+  *)
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_path_ac_pt_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+  ;;
+esac
+fi
+ac_pt_PKG_CONFIG=$ac_cv_path_ac_pt_PKG_CONFIG
+if test -n "$ac_pt_PKG_CONFIG"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_pt_PKG_CONFIG" >&5
+$as_echo "$ac_pt_PKG_CONFIG" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_pt_PKG_CONFIG" = x; then
+    PKG_CONFIG=""
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    PKG_CONFIG=$ac_pt_PKG_CONFIG
+  fi
+else
+  PKG_CONFIG="$ac_cv_path_PKG_CONFIG"
+fi
+
+fi
+if test -n "$PKG_CONFIG"; then
+       _pkg_min_version=0.7
+       { $as_echo "$as_me:${as_lineno-$LINENO}: checking pkg-config is at least version $_pkg_min_version" >&5
+$as_echo_n "checking pkg-config is at least version $_pkg_min_version... " >&6; }
+       if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then
+               { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+       else
+               { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+               PKG_CONFIG=""
+       fi
+fi
+
+  min_gtk_version=2.0.0
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for GTK+ - version >= $min_gtk_version" >&5
+$as_echo_n "checking for GTK+ - version >= $min_gtk_version... " >&6; }
+
+  if test x$PKG_CONFIG != xno ; then
+    ## don't try to run the test against uninstalled libtool libs
+    if $PKG_CONFIG --uninstalled $pkg_config_args; then
+         echo "Will use uninstalled version of GTK+ found in PKG_CONFIG_PATH"
+         enable_gtktest=no
+    fi
+
+    if $PKG_CONFIG --atleast-version $min_gtk_version $pkg_config_args; then
+         :
+    else
+         no_gtk=yes
+    fi
+  fi
+
+  if test x"$no_gtk" = x ; then
+    GTK_CFLAGS=`$PKG_CONFIG $pkg_config_args --cflags`
+    GTK_LIBS=`$PKG_CONFIG $pkg_config_args --libs`
+    gtk_config_major_version=`$PKG_CONFIG --modversion gtk+-2.0 | \
+           sed 's/\([0-9]*\).\([0-9]*\).\([0-9]*\)/\1/'`
+    gtk_config_minor_version=`$PKG_CONFIG --modversion gtk+-2.0 | \
+           sed 's/\([0-9]*\).\([0-9]*\).\([0-9]*\)/\2/'`
+    gtk_config_micro_version=`$PKG_CONFIG --modversion gtk+-2.0 | \
+           sed 's/\([0-9]*\).\([0-9]*\).\([0-9]*\)/\3/'`
+    if test "x$enable_gtktest" = "xyes" ; then
+      ac_save_CFLAGS="$CFLAGS"
+      ac_save_LIBS="$LIBS"
+      CFLAGS="$CFLAGS $GTK_CFLAGS"
+      LIBS="$GTK_LIBS $LIBS"
+      rm -f conf.gtktest
+      if test "$cross_compiling" = yes; then :
+  echo $ac_n "cross compiling; assumed OK... $ac_c"
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+#include <gtk/gtk.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+int
+main ()
+{
+  int major, minor, micro;
+  char *tmp_version;
+
+  fclose (fopen ("conf.gtktest", "w"));
+
+  /* HP/UX 9 (%@#!) writes to sscanf strings */
+  tmp_version = g_strdup("$min_gtk_version");
+  if (sscanf(tmp_version, "%d.%d.%d", &major, &minor, &micro) != 3) {
+     printf("%s, bad version string\n", "$min_gtk_version");
+     exit(1);
+   }
+
+  if ((gtk_major_version != $gtk_config_major_version) ||
+      (gtk_minor_version != $gtk_config_minor_version) ||
+      (gtk_micro_version != $gtk_config_micro_version))
+    {
+      printf("\n*** 'pkg-config --modversion gtk+-2.0' returned %d.%d.%d, but GTK+ (%d.%d.%d)\n",
+             $gtk_config_major_version, $gtk_config_minor_version, $gtk_config_micro_version,
+             gtk_major_version, gtk_minor_version, gtk_micro_version);
+      printf ("*** was found! If pkg-config was correct, then it is best\n");
+      printf ("*** to remove the old version of GTK+. You may also be able to fix the error\n");
+      printf("*** by modifying your LD_LIBRARY_PATH enviroment variable, or by editing\n");
+      printf("*** /etc/ld.so.conf. Make sure you have run ldconfig if that is\n");
+      printf("*** required on your system.\n");
+      printf("*** If pkg-config was wrong, set the environment variable PKG_CONFIG_PATH\n");
+      printf("*** to point to the correct configuration files\n");
+    }
+  else if ((gtk_major_version != GTK_MAJOR_VERSION) ||
+          (gtk_minor_version != GTK_MINOR_VERSION) ||
+           (gtk_micro_version != GTK_MICRO_VERSION))
+    {
+      printf("*** GTK+ header files (version %d.%d.%d) do not match\n",
+            GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION);
+      printf("*** library (version %d.%d.%d)\n",
+            gtk_major_version, gtk_minor_version, gtk_micro_version);
+    }
+  else
+    {
+      if ((gtk_major_version > major) ||
+        ((gtk_major_version == major) && (gtk_minor_version > minor)) ||
+        ((gtk_major_version == major) && (gtk_minor_version == minor) && (gtk_micro_version >= micro)))
+      {
+        return 0;
+       }
+     else
+      {
+        printf("\n*** An old version of GTK+ (%d.%d.%d) was found.\n",
+               gtk_major_version, gtk_minor_version, gtk_micro_version);
+        printf("*** You need a version of GTK+ newer than %d.%d.%d. The latest version of\n",
+              major, minor, micro);
+        printf("*** GTK+ is always available from ftp://ftp.gtk.org.\n");
+        printf("***\n");
+        printf("*** If you have already installed a sufficiently new version, this error\n");
+        printf("*** probably means that the wrong copy of the pkg-config shell script is\n");
+        printf("*** being found. The easiest way to fix this is to remove the old version\n");
+        printf("*** of GTK+, but you can also set the PKG_CONFIG environment to point to the\n");
+        printf("*** correct copy of pkg-config. (In this case, you will have to\n");
+        printf("*** modify your LD_LIBRARY_PATH enviroment variable, or edit /etc/ld.so.conf\n");
+        printf("*** so that the correct libraries are found at run-time))\n");
+      }
+    }
+  return 1;
+}
+
+_ACEOF
+if ac_fn_c_try_run "$LINENO"; then :
+
+else
+  no_gtk=yes
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+  conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+
+       CFLAGS="$ac_save_CFLAGS"
+       LIBS="$ac_save_LIBS"
+     fi
+  fi
+  if test "x$no_gtk" = x ; then
+     { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes (version $gtk_config_major_version.$gtk_config_minor_version.$gtk_config_micro_version)" >&5
+$as_echo "yes (version $gtk_config_major_version.$gtk_config_minor_version.$gtk_config_micro_version)" >&6; }
+     gtk=2
+  else
+     { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+     if test "$PKG_CONFIG" = "no" ; then
+       echo "*** A new enough version of pkg-config was not found."
+       echo "*** See http://pkgconfig.sourceforge.net"
+     else
+       if test -f conf.gtktest ; then
+        :
+       else
+          echo "*** Could not run GTK+ test program, checking why..."
+         ac_save_CFLAGS="$CFLAGS"
+         ac_save_LIBS="$LIBS"
+          CFLAGS="$CFLAGS $GTK_CFLAGS"
+          LIBS="$LIBS $GTK_LIBS"
+          cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+#include <gtk/gtk.h>
+#include <stdio.h>
+
+int
+main ()
+{
+ return ((gtk_major_version) || (gtk_minor_version) || (gtk_micro_version));
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+   echo "*** The test program compiled, but did not run. This usually means"
+          echo "*** that the run-time linker is not finding GTK+ or finding the wrong"
+          echo "*** version of GTK+. If it is not finding GTK+, you'll need to set your"
+          echo "*** LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point"
+          echo "*** to the installed location  Also, make sure you have run ldconfig if that"
+          echo "*** is required on your system"
+         echo "***"
+          echo "*** If you have an old version installed, it is best to remove it, although"
+          echo "*** you may also be able to get things to work by modifying LD_LIBRARY_PATH"
+else
+   echo "*** The test program failed to compile or link. See the file config.log for the"
+          echo "*** exact error that occured. This usually means GTK+ is incorrectly installed."
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+          CFLAGS="$ac_save_CFLAGS"
+          LIBS="$ac_save_LIBS"
+       fi
+     fi
+     GTK_CFLAGS=""
+     GTK_LIBS=""
+     :
+  fi
+
+
+  rm -f conf.gtktest
+
+
+    ;;
+esac
+
+if test "$gtk" = "none"; then
+   as_fn_error $? "cannot build without GTK 2 or GTK 3" "$LINENO" 5
+fi
+
+if test "x$GCC" = "xyes"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for usable gcc warning flags" >&5
+$as_echo_n "checking for usable gcc warning flags... " >&6; }
+  gccwarningflags=
+  for flag in -Wall -Werror -std=c89 -pedantic; do
+    ac_save_CFLAGS="$CFLAGS"
+    ac_save_LIBS="$LIBS"
+    CFLAGS="$CFLAGS$gccwarningflags $flag $GTK_CFLAGS"
+    LIBS="$GTK_LIBS $LIBS"
+    cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+        #include <stdio.h>
+        #include <assert.h>
+        #include <stdlib.h>
+        #include <time.h>
+        #include <stdarg.h>
+        #include <string.h>
+        #include <errno.h>
+        #include <math.h>
+
+        #include <sys/time.h>
+        #include <sys/resource.h>
+
+        #include <gtk/gtk.h>
+        #include <gdk/gdkkeysyms.h>
+
+        #include <gdk-pixbuf/gdk-pixbuf.h>
+
+        #include <gdk/gdkx.h>
+        #include <X11/Xlib.h>
+        #include <X11/Xutil.h>
+        #include <X11/Xatom.h>
+
+int
+main ()
+{
+
+        return 0;
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  gccwarningflags="$gccwarningflags $flag"
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+    CFLAGS="$ac_save_CFLAGS"
+    LIBS="$ac_save_LIBS"
+  done
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $gccwarningflags" >&5
+$as_echo "$gccwarningflags" >&6; }
+  CFLAGS="$CFLAGS$gccwarningflags"
+fi
+
+if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}ranlib", so it can be a program name with args.
+set dummy ${ac_tool_prefix}ranlib; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_RANLIB+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$RANLIB"; then
+  ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_RANLIB="${ac_tool_prefix}ranlib"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+RANLIB=$ac_cv_prog_RANLIB
+if test -n "$RANLIB"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RANLIB" >&5
+$as_echo "$RANLIB" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_RANLIB"; then
+  ac_ct_RANLIB=$RANLIB
+  # Extract the first word of "ranlib", so it can be a program name with args.
+set dummy ranlib; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_RANLIB+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_RANLIB"; then
+  ac_cv_prog_ac_ct_RANLIB="$ac_ct_RANLIB" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_ac_ct_RANLIB="ranlib"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_RANLIB=$ac_cv_prog_ac_ct_RANLIB
+if test -n "$ac_ct_RANLIB"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_RANLIB" >&5
+$as_echo "$ac_ct_RANLIB" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_RANLIB" = x; then
+    RANLIB=":"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    RANLIB=$ac_ct_RANLIB
+  fi
+else
+  RANLIB="$ac_cv_prog_RANLIB"
+fi
+
+
+ac_config_files="$ac_config_files Makefile"
+
+cat >confcache <<\_ACEOF
+# This file is a shell script that caches the results of configure
+# tests run on this system so they can be shared between configure
+# scripts and configure runs, see configure's option --config-cache.
+# It is not useful on other systems.  If it contains results you don't
+# want to keep, you may remove or edit it.
+#
+# config.status only pays attention to the cache file if you give it
+# the --recheck option to rerun configure.
+#
+# `ac_cv_env_foo' variables (set or unset) will be overridden when
+# loading this file, other *unset* `ac_cv_foo' will be assigned the
+# following values.
+
+_ACEOF
+
+# The following way of writing the cache mishandles newlines in values,
+# but we know of no workaround that is simple, portable, and efficient.
+# So, we kill variables containing newlines.
+# Ultrix sh set writes to stderr and can't be redirected directly,
+# and sets the high bit in the cache file unless we assign to the vars.
+(
+  for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do
+    eval ac_val=\$$ac_var
+    case $ac_val in #(
+    *${as_nl}*)
+      case $ac_var in #(
+      *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5
+$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
+      esac
+      case $ac_var in #(
+      _ | IFS | as_nl) ;; #(
+      BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #(
+      *) { eval $ac_var=; unset $ac_var;} ;;
+      esac ;;
+    esac
+  done
+
+  (set) 2>&1 |
+    case $as_nl`(ac_space=' '; set) 2>&1` in #(
+    *${as_nl}ac_space=\ *)
+      # `set' does not quote correctly, so add quotes: double-quote
+      # substitution turns \\\\ into \\, and sed turns \\ into \.
+      sed -n \
+       "s/'/'\\\\''/g;
+         s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p"
+      ;; #(
+    *)
+      # `set' quotes correctly as required by POSIX, so do not add quotes.
+      sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p"
+      ;;
+    esac |
+    sort
+) |
+  sed '
+     /^ac_cv_env_/b end
+     t clear
+     :clear
+     s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/
+     t end
+     s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/
+     :end' >>confcache
+if diff "$cache_file" confcache >/dev/null 2>&1; then :; else
+  if test -w "$cache_file"; then
+    if test "x$cache_file" != "x/dev/null"; then
+      { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5
+$as_echo "$as_me: updating cache $cache_file" >&6;}
+      if test ! -f "$cache_file" || test -h "$cache_file"; then
+       cat confcache >"$cache_file"
+      else
+        case $cache_file in #(
+        */* | ?:*)
+         mv -f confcache "$cache_file"$$ &&
+         mv -f "$cache_file"$$ "$cache_file" ;; #(
+        *)
+         mv -f confcache "$cache_file" ;;
+       esac
+      fi
+    fi
+  else
+    { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5
+$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;}
+  fi
+fi
+rm -f confcache
+
+test "x$prefix" = xNONE && prefix=$ac_default_prefix
+# Let make expand exec_prefix.
+test "x$exec_prefix" = xNONE && exec_prefix='${prefix}'
+
+# Transform confdefs.h into DEFS.
+# Protect against shell expansion while executing Makefile rules.
+# Protect against Makefile macro expansion.
+#
+# If the first sed substitution is executed (which looks for macros that
+# take arguments), then branch to the quote section.  Otherwise,
+# look for a macro that doesn't take arguments.
+ac_script='
+:mline
+/\\$/{
+ N
+ s,\\\n,,
+ b mline
+}
+t clear
+:clear
+s/^[    ]*#[    ]*define[       ][      ]*\([^  (][^    (]*([^)]*)\)[   ]*\(.*\)/-D\1=\2/g
+t quote
+s/^[    ]*#[    ]*define[       ][      ]*\([^  ][^     ]*\)[   ]*\(.*\)/-D\1=\2/g
+t quote
+b any
+:quote
+s/[     `~#$^&*(){}\\|;'\''"<>?]/\\&/g
+s/\[/\\&/g
+s/\]/\\&/g
+s/\$/$$/g
+H
+:any
+${
+       g
+       s/^\n//
+       s/\n/ /g
+       p
+}
+'
+DEFS=`sed -n "$ac_script" confdefs.h`
+
+
+ac_libobjs=
+ac_ltlibobjs=
+U=
+for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue
+  # 1. Remove the extension, and $U if already installed.
+  ac_script='s/\$U\././;s/\.o$//;s/\.obj$//'
+  ac_i=`$as_echo "$ac_i" | sed "$ac_script"`
+  # 2. Prepend LIBOBJDIR.  When used with automake>=1.10 LIBOBJDIR
+  #    will be set to the directory where LIBOBJS objects are built.
+  as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext"
+  as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo'
+done
+LIBOBJS=$ac_libobjs
+
+LTLIBOBJS=$ac_ltlibobjs
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking that generated files are newer than configure" >&5
+$as_echo_n "checking that generated files are newer than configure... " >&6; }
+   if test -n "$am_sleep_pid"; then
+     # Hide warnings about reused PIDs.
+     wait $am_sleep_pid 2>/dev/null
+   fi
+   { $as_echo "$as_me:${as_lineno-$LINENO}: result: done" >&5
+$as_echo "done" >&6; }
+ if test -n "$EXEEXT"; then
+  am__EXEEXT_TRUE=
+  am__EXEEXT_FALSE='#'
+else
+  am__EXEEXT_TRUE='#'
+  am__EXEEXT_FALSE=
+fi
+
+if test -z "${AMDEP_TRUE}" && test -z "${AMDEP_FALSE}"; then
+  as_fn_error $? "conditional \"AMDEP\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+if test -z "${am__fastdepCC_TRUE}" && test -z "${am__fastdepCC_FALSE}"; then
+  as_fn_error $? "conditional \"am__fastdepCC\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
+
+: "${CONFIG_STATUS=./config.status}"
+ac_write_fail=0
+ac_clean_files_save=$ac_clean_files
+ac_clean_files="$ac_clean_files $CONFIG_STATUS"
+{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5
+$as_echo "$as_me: creating $CONFIG_STATUS" >&6;}
+as_write_fail=0
+cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1
+#! $SHELL
+# Generated by $as_me.
+# Run this file to recreate the current configuration.
+# Compiler output produced by configure, useful for debugging
+# configure, is in config.log if it exists.
+
+debug=false
+ac_cs_recheck=false
+ac_cs_silent=false
+
+SHELL=\${CONFIG_SHELL-$SHELL}
+export SHELL
+_ASEOF
+cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1
+## -------------------- ##
+## M4sh Initialization. ##
+## -------------------- ##
+
+# Be more Bourne compatible
+DUALCASE=1; export DUALCASE # for MKS sh
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then :
+  emulate sh
+  NULLCMD=:
+  # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '${1+"$@"}'='"$@"'
+  setopt NO_GLOB_SUBST
+else
+  case `(set -o) 2>/dev/null` in #(
+  *posix*) :
+    set -o posix ;; #(
+  *) :
+     ;;
+esac
+fi
+
+
+as_nl='
+'
+export as_nl
+# Printing a long string crashes Solaris 7 /usr/bin/printf.
+as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo
+# Prefer a ksh shell builtin over an external printf program on Solaris,
+# but without wasting forks for bash or zsh.
+if test -z "$BASH_VERSION$ZSH_VERSION" \
+    && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then
+  as_echo='print -r --'
+  as_echo_n='print -rn --'
+elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then
+  as_echo='printf %s\n'
+  as_echo_n='printf %s'
+else
+  if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then
+    as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"'
+    as_echo_n='/usr/ucb/echo -n'
+  else
+    as_echo_body='eval expr "X$1" : "X\\(.*\\)"'
+    as_echo_n_body='eval
+      arg=$1;
+      case $arg in #(
+      *"$as_nl"*)
+       expr "X$arg" : "X\\(.*\\)$as_nl";
+       arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;;
+      esac;
+      expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl"
+    '
+    export as_echo_n_body
+    as_echo_n='sh -c $as_echo_n_body as_echo'
+  fi
+  export as_echo_body
+  as_echo='sh -c $as_echo_body as_echo'
+fi
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+  PATH_SEPARATOR=:
+  (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && {
+    (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 ||
+      PATH_SEPARATOR=';'
+  }
+fi
+
+
+# IFS
+# We need space, tab and new line, in precisely that order.  Quoting is
+# there to prevent editors from complaining about space-tab.
+# (If _AS_PATH_WALK were called with IFS unset, it would disable word
+# splitting by setting IFS to empty value.)
+IFS=" ""       $as_nl"
+
+# Find who we are.  Look in the path if we contain no directory separator.
+as_myself=
+case $0 in #((
+  *[\\/]* ) as_myself=$0 ;;
+  *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+  done
+IFS=$as_save_IFS
+
+     ;;
+esac
+# We did not find ourselves, most probably we were run as `sh COMMAND'
+# in which case we are not to be found in the path.
+if test "x$as_myself" = x; then
+  as_myself=$0
+fi
+if test ! -f "$as_myself"; then
+  $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2
+  exit 1
+fi
+
+# Unset variables that we do not need and which cause bugs (e.g. in
+# pre-3.0 UWIN ksh).  But do not cause bugs in bash 2.01; the "|| exit 1"
+# suppresses any "Segmentation fault" message there.  '((' could
+# trigger a bug in pdksh 5.2.14.
+for as_var in BASH_ENV ENV MAIL MAILPATH
+do eval test x\${$as_var+set} = xset \
+  && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || :
+done
+PS1='$ '
+PS2='> '
+PS4='+ '
+
+# NLS nuisances.
+LC_ALL=C
+export LC_ALL
+LANGUAGE=C
+export LANGUAGE
+
+# CDPATH.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+
+# as_fn_error STATUS ERROR [LINENO LOG_FD]
+# ----------------------------------------
+# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are
+# provided, also output the error to LOG_FD, referencing LINENO. Then exit the
+# script with STATUS, using 1 if that was 0.
+as_fn_error ()
+{
+  as_status=$1; test $as_status -eq 0 && as_status=1
+  if test "$4"; then
+    as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+    $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
+  fi
+  $as_echo "$as_me: error: $2" >&2
+  as_fn_exit $as_status
+} # as_fn_error
+
+
+# as_fn_set_status STATUS
+# -----------------------
+# Set $? to STATUS, without forking.
+as_fn_set_status ()
+{
+  return $1
+} # as_fn_set_status
+
+# as_fn_exit STATUS
+# -----------------
+# Exit the shell with STATUS, even in a "trap 0" or "set -e" context.
+as_fn_exit ()
+{
+  set +e
+  as_fn_set_status $1
+  exit $1
+} # as_fn_exit
+
+# as_fn_unset VAR
+# ---------------
+# Portably unset VAR.
+as_fn_unset ()
+{
+  { eval $1=; unset $1;}
+}
+as_unset=as_fn_unset
+# as_fn_append VAR VALUE
+# ----------------------
+# Append the text in VALUE to the end of the definition contained in VAR. Take
+# advantage of any shell optimizations that allow amortized linear growth over
+# repeated appends, instead of the typical quadratic growth present in naive
+# implementations.
+if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then :
+  eval 'as_fn_append ()
+  {
+    eval $1+=\$2
+  }'
+else
+  as_fn_append ()
+  {
+    eval $1=\$$1\$2
+  }
+fi # as_fn_append
+
+# as_fn_arith ARG...
+# ------------------
+# Perform arithmetic evaluation on the ARGs, and store the result in the
+# global $as_val. Take advantage of shells that can avoid forks. The arguments
+# must be portable across $(()) and expr.
+if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then :
+  eval 'as_fn_arith ()
+  {
+    as_val=$(( $* ))
+  }'
+else
+  as_fn_arith ()
+  {
+    as_val=`expr "$@" || test $? -eq 1`
+  }
+fi # as_fn_arith
+
+
+if expr a : '\(a\)' >/dev/null 2>&1 &&
+   test "X`expr 00001 : '.*\(...\)'`" = X001; then
+  as_expr=expr
+else
+  as_expr=false
+fi
+
+if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then
+  as_basename=basename
+else
+  as_basename=false
+fi
+
+if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then
+  as_dirname=dirname
+else
+  as_dirname=false
+fi
+
+as_me=`$as_basename -- "$0" ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+        X"$0" : 'X\(//\)$' \| \
+        X"$0" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X/"$0" |
+    sed '/^.*\/\([^/][^/]*\)\/*$/{
+           s//\1/
+           q
+         }
+         /^X\/\(\/\/\)$/{
+           s//\1/
+           q
+         }
+         /^X\/\(\/\).*/{
+           s//\1/
+           q
+         }
+         s/.*/./; q'`
+
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+ECHO_C= ECHO_N= ECHO_T=
+case `echo -n x` in #(((((
+-n*)
+  case `echo 'xy\c'` in
+  *c*) ECHO_T='        ';;     # ECHO_T is single tab character.
+  xy)  ECHO_C='\c';;
+  *)   echo `echo ksh88 bug on AIX 6.1` > /dev/null
+       ECHO_T='        ';;
+  esac;;
+*)
+  ECHO_N='-n';;
+esac
+
+rm -f conf$$ conf$$.exe conf$$.file
+if test -d conf$$.dir; then
+  rm -f conf$$.dir/conf$$.file
+else
+  rm -f conf$$.dir
+  mkdir conf$$.dir 2>/dev/null
+fi
+if (echo >conf$$.file) 2>/dev/null; then
+  if ln -s conf$$.file conf$$ 2>/dev/null; then
+    as_ln_s='ln -s'
+    # ... but there are two gotchas:
+    # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail.
+    # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable.
+    # In both cases, we have to default to `cp -pR'.
+    ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe ||
+      as_ln_s='cp -pR'
+  elif ln conf$$.file conf$$ 2>/dev/null; then
+    as_ln_s=ln
+  else
+    as_ln_s='cp -pR'
+  fi
+else
+  as_ln_s='cp -pR'
+fi
+rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file
+rmdir conf$$.dir 2>/dev/null
+
+
+# as_fn_mkdir_p
+# -------------
+# Create "$as_dir" as a directory, including parents if necessary.
+as_fn_mkdir_p ()
+{
+
+  case $as_dir in #(
+  -*) as_dir=./$as_dir;;
+  esac
+  test -d "$as_dir" || eval $as_mkdir_p || {
+    as_dirs=
+    while :; do
+      case $as_dir in #(
+      *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'(
+      *) as_qdir=$as_dir;;
+      esac
+      as_dirs="'$as_qdir' $as_dirs"
+      as_dir=`$as_dirname -- "$as_dir" ||
+$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+        X"$as_dir" : 'X\(//\)[^/]' \| \
+        X"$as_dir" : 'X\(//\)$' \| \
+        X"$as_dir" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$as_dir" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)[^/].*/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\).*/{
+           s//\1/
+           q
+         }
+         s/.*/./; q'`
+      test -d "$as_dir" && break
+    done
+    test -z "$as_dirs" || eval "mkdir $as_dirs"
+  } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir"
+
+
+} # as_fn_mkdir_p
+if mkdir -p . 2>/dev/null; then
+  as_mkdir_p='mkdir -p "$as_dir"'
+else
+  test -d ./-p && rmdir ./-p
+  as_mkdir_p=false
+fi
+
+
+# as_fn_executable_p FILE
+# -----------------------
+# Test if FILE is an executable regular file.
+as_fn_executable_p ()
+{
+  test -f "$1" && test -x "$1"
+} # as_fn_executable_p
+as_test_x='test -x'
+as_executable_p=as_fn_executable_p
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
+
+
+exec 6>&1
+## ----------------------------------- ##
+## Main body of $CONFIG_STATUS script. ##
+## ----------------------------------- ##
+_ASEOF
+test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# Save the log message, to keep $0 and so on meaningful, and to
+# report actual input values of CONFIG_FILES etc. instead of their
+# values after options handling.
+ac_log="
+This file was extended by puzzles $as_me 20161228.7cae89f, which was
+generated by GNU Autoconf 2.69.  Invocation command line was
+
+  CONFIG_FILES    = $CONFIG_FILES
+  CONFIG_HEADERS  = $CONFIG_HEADERS
+  CONFIG_LINKS    = $CONFIG_LINKS
+  CONFIG_COMMANDS = $CONFIG_COMMANDS
+  $ $0 $@
+
+on `(hostname || uname -n) 2>/dev/null | sed 1q`
+"
+
+_ACEOF
+
+case $ac_config_files in *"
+"*) set x $ac_config_files; shift; ac_config_files=$*;;
+esac
+
+
+
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+# Files that config.status was made for.
+config_files="$ac_config_files"
+config_commands="$ac_config_commands"
+
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+ac_cs_usage="\
+\`$as_me' instantiates files and other configuration actions
+from templates according to the current configuration.  Unless the files
+and actions are specified as TAGs, all are instantiated by default.
+
+Usage: $0 [OPTION]... [TAG]...
+
+  -h, --help       print this help, then exit
+  -V, --version    print version number and configuration settings, then exit
+      --config     print configuration, then exit
+  -q, --quiet, --silent
+                   do not print progress messages
+  -d, --debug      don't remove temporary files
+      --recheck    update $as_me by reconfiguring in the same conditions
+      --file=FILE[:TEMPLATE]
+                   instantiate the configuration file FILE
+
+Configuration files:
+$config_files
+
+Configuration commands:
+$config_commands
+
+Report bugs to <anakin@pobox.com>."
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
+ac_cs_version="\\
+puzzles config.status 20161228.7cae89f
+configured by $0, generated by GNU Autoconf 2.69,
+  with options \\"\$ac_cs_config\\"
+
+Copyright (C) 2012 Free Software Foundation, Inc.
+This config.status script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it."
+
+ac_pwd='$ac_pwd'
+srcdir='$srcdir'
+INSTALL='$INSTALL'
+MKDIR_P='$MKDIR_P'
+AWK='$AWK'
+test -n "\$AWK" || AWK=awk
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# The default lists apply if the user does not specify any file.
+ac_need_defaults=:
+while test $# != 0
+do
+  case $1 in
+  --*=?*)
+    ac_option=`expr "X$1" : 'X\([^=]*\)='`
+    ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'`
+    ac_shift=:
+    ;;
+  --*=)
+    ac_option=`expr "X$1" : 'X\([^=]*\)='`
+    ac_optarg=
+    ac_shift=:
+    ;;
+  *)
+    ac_option=$1
+    ac_optarg=$2
+    ac_shift=shift
+    ;;
+  esac
+
+  case $ac_option in
+  # Handling of the options.
+  -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r)
+    ac_cs_recheck=: ;;
+  --version | --versio | --versi | --vers | --ver | --ve | --v | -V )
+    $as_echo "$ac_cs_version"; exit ;;
+  --config | --confi | --conf | --con | --co | --c )
+    $as_echo "$ac_cs_config"; exit ;;
+  --debug | --debu | --deb | --de | --d | -d )
+    debug=: ;;
+  --file | --fil | --fi | --f )
+    $ac_shift
+    case $ac_optarg in
+    *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;;
+    '') as_fn_error $? "missing file argument" ;;
+    esac
+    as_fn_append CONFIG_FILES " '$ac_optarg'"
+    ac_need_defaults=false;;
+  --he | --h |  --help | --hel | -h )
+    $as_echo "$ac_cs_usage"; exit ;;
+  -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+  | -silent | --silent | --silen | --sile | --sil | --si | --s)
+    ac_cs_silent=: ;;
+
+  # This is an error.
+  -*) as_fn_error $? "unrecognized option: \`$1'
+Try \`$0 --help' for more information." ;;
+
+  *) as_fn_append ac_config_targets " $1"
+     ac_need_defaults=false ;;
+
+  esac
+  shift
+done
+
+ac_configure_extra_args=
+
+if $ac_cs_silent; then
+  exec 6>/dev/null
+  ac_configure_extra_args="$ac_configure_extra_args --silent"
+fi
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+if \$ac_cs_recheck; then
+  set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion
+  shift
+  \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6
+  CONFIG_SHELL='$SHELL'
+  export CONFIG_SHELL
+  exec "\$@"
+fi
+
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+exec 5>>config.log
+{
+  echo
+  sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX
+## Running $as_me. ##
+_ASBOX
+  $as_echo "$ac_log"
+} >&5
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+#
+# INIT-COMMANDS
+#
+AMDEP_TRUE="$AMDEP_TRUE" ac_aux_dir="$ac_aux_dir"
+
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+
+# Handling of arguments.
+for ac_config_target in $ac_config_targets
+do
+  case $ac_config_target in
+    "depfiles") CONFIG_COMMANDS="$CONFIG_COMMANDS depfiles" ;;
+    "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;;
+
+  *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;;
+  esac
+done
+
+
+# If the user did not use the arguments to specify the items to instantiate,
+# then the envvar interface is used.  Set only those that are not.
+# We use the long form for the default assignment because of an extremely
+# bizarre bug on SunOS 4.1.3.
+if $ac_need_defaults; then
+  test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files
+  test "${CONFIG_COMMANDS+set}" = set || CONFIG_COMMANDS=$config_commands
+fi
+
+# Have a temporary directory for convenience.  Make it in the build tree
+# simply because there is no reason against having it here, and in addition,
+# creating and moving files from /tmp can sometimes cause problems.
+# Hook for its removal unless debugging.
+# Note that there is a small window in which the directory will not be cleaned:
+# after its creation but before its name has been assigned to `$tmp'.
+$debug ||
+{
+  tmp= ac_tmp=
+  trap 'exit_status=$?
+  : "${ac_tmp:=$tmp}"
+  { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status
+' 0
+  trap 'as_fn_exit 1' 1 2 13 15
+}
+# Create a (secure) tmp directory for tmp files.
+
+{
+  tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` &&
+  test -d "$tmp"
+}  ||
+{
+  tmp=./conf$$-$RANDOM
+  (umask 077 && mkdir "$tmp")
+} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5
+ac_tmp=$tmp
+
+# Set up the scripts for CONFIG_FILES section.
+# No need to generate them if there are no CONFIG_FILES.
+# This happens for instance with `./config.status config.h'.
+if test -n "$CONFIG_FILES"; then
+
+
+ac_cr=`echo X | tr X '\015'`
+# On cygwin, bash can eat \r inside `` if the user requested igncr.
+# But we know of no other shell where ac_cr would be empty at this
+# point, so we can use a bashism as a fallback.
+if test "x$ac_cr" = x; then
+  eval ac_cr=\$\'\\r\'
+fi
+ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' </dev/null 2>/dev/null`
+if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then
+  ac_cs_awk_cr='\\r'
+else
+  ac_cs_awk_cr=$ac_cr
+fi
+
+echo 'BEGIN {' >"$ac_tmp/subs1.awk" &&
+_ACEOF
+
+
+{
+  echo "cat >conf$$subs.awk <<_ACEOF" &&
+  echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' &&
+  echo "_ACEOF"
+} >conf$$subs.sh ||
+  as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'`
+ac_delim='%!_!# '
+for ac_last_try in false false false false false :; do
+  . ./conf$$subs.sh ||
+    as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+
+  ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X`
+  if test $ac_delim_n = $ac_delim_num; then
+    break
+  elif $ac_last_try; then
+    as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+  else
+    ac_delim="$ac_delim!$ac_delim _$ac_delim!! "
+  fi
+done
+rm -f conf$$subs.sh
+
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK &&
+_ACEOF
+sed -n '
+h
+s/^/S["/; s/!.*/"]=/
+p
+g
+s/^[^!]*!//
+:repl
+t repl
+s/'"$ac_delim"'$//
+t delim
+:nl
+h
+s/\(.\{148\}\)..*/\1/
+t more1
+s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/
+p
+n
+b repl
+:more1
+s/["\\]/\\&/g; s/^/"/; s/$/"\\/
+p
+g
+s/.\{148\}//
+t nl
+:delim
+h
+s/\(.\{148\}\)..*/\1/
+t more2
+s/["\\]/\\&/g; s/^/"/; s/$/"/
+p
+b
+:more2
+s/["\\]/\\&/g; s/^/"/; s/$/"\\/
+p
+g
+s/.\{148\}//
+t delim
+' <conf$$subs.awk | sed '
+/^[^""]/{
+  N
+  s/\n//
+}
+' >>$CONFIG_STATUS || ac_write_fail=1
+rm -f conf$$subs.awk
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+_ACAWK
+cat >>"\$ac_tmp/subs1.awk" <<_ACAWK &&
+  for (key in S) S_is_set[key] = 1
+  FS = "\a"
+
+}
+{
+  line = $ 0
+  nfields = split(line, field, "@")
+  substed = 0
+  len = length(field[1])
+  for (i = 2; i < nfields; i++) {
+    key = field[i]
+    keylen = length(key)
+    if (S_is_set[key]) {
+      value = S[key]
+      line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3)
+      len += length(value) + length(field[++i])
+      substed = 1
+    } else
+      len += 1 + keylen
+  }
+
+  print line
+}
+
+_ACAWK
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then
+  sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g"
+else
+  cat
+fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \
+  || as_fn_error $? "could not setup config files machinery" "$LINENO" 5
+_ACEOF
+
+# VPATH may cause trouble with some makes, so we remove sole $(srcdir),
+# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and
+# trailing colons and then remove the whole line if VPATH becomes empty
+# (actually we leave an empty line to preserve line numbers).
+if test "x$srcdir" = x.; then
+  ac_vpsub='/^[         ]*VPATH[        ]*=[    ]*/{
+h
+s///
+s/^/:/
+s/[     ]*$/:/
+s/:\$(srcdir):/:/g
+s/:\${srcdir}:/:/g
+s/:@srcdir@:/:/g
+s/^:*//
+s/:*$//
+x
+s/\(=[  ]*\).*/\1/
+G
+s/\n//
+s/^[^=]*=[      ]*$//
+}'
+fi
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+fi # test -n "$CONFIG_FILES"
+
+
+eval set X "  :F $CONFIG_FILES      :C $CONFIG_COMMANDS"
+shift
+for ac_tag
+do
+  case $ac_tag in
+  :[FHLC]) ac_mode=$ac_tag; continue;;
+  esac
+  case $ac_mode$ac_tag in
+  :[FHL]*:*);;
+  :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;;
+  :[FH]-) ac_tag=-:-;;
+  :[FH]*) ac_tag=$ac_tag:$ac_tag.in;;
+  esac
+  ac_save_IFS=$IFS
+  IFS=:
+  set x $ac_tag
+  IFS=$ac_save_IFS
+  shift
+  ac_file=$1
+  shift
+
+  case $ac_mode in
+  :L) ac_source=$1;;
+  :[FH])
+    ac_file_inputs=
+    for ac_f
+    do
+      case $ac_f in
+      -) ac_f="$ac_tmp/stdin";;
+      *) # Look for the file first in the build tree, then in the source tree
+        # (if the path is not absolute).  The absolute path cannot be DOS-style,
+        # because $ac_f cannot contain `:'.
+        test -f "$ac_f" ||
+          case $ac_f in
+          [\\/$]*) false;;
+          *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";;
+          esac ||
+          as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;;
+      esac
+      case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac
+      as_fn_append ac_file_inputs " '$ac_f'"
+    done
+
+    # Let's still pretend it is `configure' which instantiates (i.e., don't
+    # use $as_me), people would be surprised to read:
+    #    /* config.h.  Generated by config.status.  */
+    configure_input='Generated from '`
+         $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g'
+       `' by configure.'
+    if test x"$ac_file" != x-; then
+      configure_input="$ac_file.  $configure_input"
+      { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5
+$as_echo "$as_me: creating $ac_file" >&6;}
+    fi
+    # Neutralize special characters interpreted by sed in replacement strings.
+    case $configure_input in #(
+    *\&* | *\|* | *\\* )
+       ac_sed_conf_input=`$as_echo "$configure_input" |
+       sed 's/[\\\\&|]/\\\\&/g'`;; #(
+    *) ac_sed_conf_input=$configure_input;;
+    esac
+
+    case $ac_tag in
+    *:-:* | *:-) cat >"$ac_tmp/stdin" \
+      || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;;
+    esac
+    ;;
+  esac
+
+  ac_dir=`$as_dirname -- "$ac_file" ||
+$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+        X"$ac_file" : 'X\(//\)[^/]' \| \
+        X"$ac_file" : 'X\(//\)$' \| \
+        X"$ac_file" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$ac_file" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)[^/].*/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\).*/{
+           s//\1/
+           q
+         }
+         s/.*/./; q'`
+  as_dir="$ac_dir"; as_fn_mkdir_p
+  ac_builddir=.
+
+case "$ac_dir" in
+.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;;
+*)
+  ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'`
+  # A ".." for each directory in $ac_dir_suffix.
+  ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'`
+  case $ac_top_builddir_sub in
+  "") ac_top_builddir_sub=. ac_top_build_prefix= ;;
+  *)  ac_top_build_prefix=$ac_top_builddir_sub/ ;;
+  esac ;;
+esac
+ac_abs_top_builddir=$ac_pwd
+ac_abs_builddir=$ac_pwd$ac_dir_suffix
+# for backward compatibility:
+ac_top_builddir=$ac_top_build_prefix
+
+case $srcdir in
+  .)  # We are building in place.
+    ac_srcdir=.
+    ac_top_srcdir=$ac_top_builddir_sub
+    ac_abs_top_srcdir=$ac_pwd ;;
+  [\\/]* | ?:[\\/]* )  # Absolute name.
+    ac_srcdir=$srcdir$ac_dir_suffix;
+    ac_top_srcdir=$srcdir
+    ac_abs_top_srcdir=$srcdir ;;
+  *) # Relative name.
+    ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix
+    ac_top_srcdir=$ac_top_build_prefix$srcdir
+    ac_abs_top_srcdir=$ac_pwd/$srcdir ;;
+esac
+ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix
+
+
+  case $ac_mode in
+  :F)
+  #
+  # CONFIG_FILE
+  #
+
+  case $INSTALL in
+  [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;;
+  *) ac_INSTALL=$ac_top_build_prefix$INSTALL ;;
+  esac
+  ac_MKDIR_P=$MKDIR_P
+  case $MKDIR_P in
+  [\\/$]* | ?:[\\/]* ) ;;
+  */*) ac_MKDIR_P=$ac_top_build_prefix$MKDIR_P ;;
+  esac
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# If the template does not know about datarootdir, expand it.
+# FIXME: This hack should be removed a few years after 2.60.
+ac_datarootdir_hack=; ac_datarootdir_seen=
+ac_sed_dataroot='
+/datarootdir/ {
+  p
+  q
+}
+/@datadir@/p
+/@docdir@/p
+/@infodir@/p
+/@localedir@/p
+/@mandir@/p'
+case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in
+*datarootdir*) ac_datarootdir_seen=yes;;
+*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*)
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5
+$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;}
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+  ac_datarootdir_hack='
+  s&@datadir@&$datadir&g
+  s&@docdir@&$docdir&g
+  s&@infodir@&$infodir&g
+  s&@localedir@&$localedir&g
+  s&@mandir@&$mandir&g
+  s&\\\${datarootdir}&$datarootdir&g' ;;
+esac
+_ACEOF
+
+# Neutralize VPATH when `$srcdir' = `.'.
+# Shell code in configure.ac might set extrasub.
+# FIXME: do we really want to maintain this feature?
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+ac_sed_extra="$ac_vpsub
+$extrasub
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+:t
+/@[a-zA-Z_][a-zA-Z_0-9]*@/!b
+s|@configure_input@|$ac_sed_conf_input|;t t
+s&@top_builddir@&$ac_top_builddir_sub&;t t
+s&@top_build_prefix@&$ac_top_build_prefix&;t t
+s&@srcdir@&$ac_srcdir&;t t
+s&@abs_srcdir@&$ac_abs_srcdir&;t t
+s&@top_srcdir@&$ac_top_srcdir&;t t
+s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t
+s&@builddir@&$ac_builddir&;t t
+s&@abs_builddir@&$ac_abs_builddir&;t t
+s&@abs_top_builddir@&$ac_abs_top_builddir&;t t
+s&@INSTALL@&$ac_INSTALL&;t t
+s&@MKDIR_P@&$ac_MKDIR_P&;t t
+$ac_datarootdir_hack
+"
+eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \
+  >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+
+test -z "$ac_datarootdir_hack$ac_datarootdir_seen" &&
+  { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } &&
+  { ac_out=`sed -n '/^[         ]*datarootdir[  ]*:*=/p' \
+      "$ac_tmp/out"`; test -z "$ac_out"; } &&
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir'
+which seems to be undefined.  Please make sure it is defined" >&5
+$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir'
+which seems to be undefined.  Please make sure it is defined" >&2;}
+
+  rm -f "$ac_tmp/stdin"
+  case $ac_file in
+  -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";;
+  *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";;
+  esac \
+  || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+ ;;
+
+
+  :C)  { $as_echo "$as_me:${as_lineno-$LINENO}: executing $ac_file commands" >&5
+$as_echo "$as_me: executing $ac_file commands" >&6;}
+ ;;
+  esac
+
+
+  case $ac_file$ac_mode in
+    "depfiles":C) test x"$AMDEP_TRUE" != x"" || {
+  # Older Autoconf quotes --file arguments for eval, but not when files
+  # are listed without --file.  Let's play safe and only enable the eval
+  # if we detect the quoting.
+  case $CONFIG_FILES in
+  *\'*) eval set x "$CONFIG_FILES" ;;
+  *)   set x $CONFIG_FILES ;;
+  esac
+  shift
+  for mf
+  do
+    # Strip MF so we end up with the name of the file.
+    mf=`echo "$mf" | sed -e 's/:.*$//'`
+    # Check whether this is an Automake generated Makefile or not.
+    # We used to match only the files named 'Makefile.in', but
+    # some people rename them; so instead we look at the file content.
+    # Grep'ing the first line is not enough: some people post-process
+    # each Makefile.in and add a new line on top of each file to say so.
+    # Grep'ing the whole file is not good either: AIX grep has a line
+    # limit of 2048, but all sed's we know have understand at least 4000.
+    if sed -n 's,^#.*generated by automake.*,X,p' "$mf" | grep X >/dev/null 2>&1; then
+      dirpart=`$as_dirname -- "$mf" ||
+$as_expr X"$mf" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+        X"$mf" : 'X\(//\)[^/]' \| \
+        X"$mf" : 'X\(//\)$' \| \
+        X"$mf" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$mf" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)[^/].*/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\).*/{
+           s//\1/
+           q
+         }
+         s/.*/./; q'`
+    else
+      continue
+    fi
+    # Extract the definition of DEPDIR, am__include, and am__quote
+    # from the Makefile without running 'make'.
+    DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"`
+    test -z "$DEPDIR" && continue
+    am__include=`sed -n 's/^am__include = //p' < "$mf"`
+    test -z "$am__include" && continue
+    am__quote=`sed -n 's/^am__quote = //p' < "$mf"`
+    # Find all dependency output files, they are included files with
+    # $(DEPDIR) in their names.  We invoke sed twice because it is the
+    # simplest approach to changing $(DEPDIR) to its actual value in the
+    # expansion.
+    for file in `sed -n "
+      s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \
+        sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g'`; do
+      # Make sure the directory exists.
+      test -f "$dirpart/$file" && continue
+      fdir=`$as_dirname -- "$file" ||
+$as_expr X"$file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+        X"$file" : 'X\(//\)[^/]' \| \
+        X"$file" : 'X\(//\)$' \| \
+        X"$file" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$file" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)[^/].*/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\).*/{
+           s//\1/
+           q
+         }
+         s/.*/./; q'`
+      as_dir=$dirpart/$fdir; as_fn_mkdir_p
+      # echo "creating $dirpart/$file"
+      echo '# dummy' > "$dirpart/$file"
+    done
+  done
+}
+ ;;
+
+  esac
+done # for ac_tag
+
+
+as_fn_exit 0
+_ACEOF
+ac_clean_files=$ac_clean_files_save
+
+test $ac_write_fail = 0 ||
+  as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5
+
+
+# configure is writing to config.log, and then calls config.status.
+# config.status does its own redirection, appending to config.log.
+# Unfortunately, on DOS this fails, as config.log is still kept open
+# by configure, so config.status won't be able to write to it; its
+# output is simply discarded.  So we exec the FD to /dev/null,
+# effectively closing config.log, so it can be properly (re)opened and
+# appended to by config.status.  When coming back to configure, we
+# need to make the FD available again.
+if test "$no_create" != yes; then
+  ac_cs_success=:
+  ac_config_status_args=
+  test "$silent" = yes &&
+    ac_config_status_args="$ac_config_status_args --quiet"
+  exec 5>/dev/null
+  $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false
+  exec 5>>config.log
+  # Use ||, not &&, to avoid exiting from the if with $? = 1, which
+  # would make configure fail if this is the last instruction.
+  $ac_cs_success || as_fn_exit 1
+fi
+if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5
+$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;}
+fi
+
diff --git a/configure.ac b/configure.ac
new file mode 100644 (file)
index 0000000..d9b109b
--- /dev/null
@@ -0,0 +1,85 @@
+dnl Configure script for the Unix GTK build of puzzles.
+
+AC_INIT([puzzles], [20161228.7cae89f], [anakin@pobox.com])
+AC_CONFIG_SRCDIR([midend.c])
+AM_INIT_AUTOMAKE([foreign])
+AC_PROG_CC
+
+AC_ARG_WITH([gtk],
+  [AS_HELP_STRING([--with-gtk=VER],
+                  [specify GTK version to use (`2' or `3')])],
+  [gtk_version_desired="$withval"],
+  [gtk_version_desired="any"])
+
+case "$gtk_version_desired" in
+  2 | 3 | any) ;;
+  yes) gtk_version_desired="any" ;;
+  *) AC_ERROR([Invalid GTK version specified])
+esac
+
+gtk=none
+
+case "$gtk_version_desired:$gtk" in
+  3:none | any:none)
+    ifdef([AM_PATH_GTK_3_0],[
+    AM_PATH_GTK_3_0([3.0.0], [gtk=3], [])
+    ],[AC_WARNING([generating configure script without GTK 3 autodetection])])
+    ;;
+esac
+
+case "$gtk_version_desired:$gtk" in
+  2:none | any:none)
+    ifdef([AM_PATH_GTK_2_0],[
+    AM_PATH_GTK_2_0([2.0.0], [gtk=2], [])
+    ],[AC_WARNING([generating configure script without GTK 2 autodetection])])
+    ;;
+esac
+
+if test "$gtk" = "none"; then
+   AC_MSG_ERROR([cannot build without GTK 2 or GTK 3])
+fi
+
+if test "x$GCC" = "xyes"; then
+  AC_MSG_CHECKING([for usable gcc warning flags])
+  gccwarningflags=
+  for flag in -Wall -Werror -std=c89 -pedantic; do
+    ac_save_CFLAGS="$CFLAGS"
+    ac_save_LIBS="$LIBS"
+    CFLAGS="$CFLAGS$gccwarningflags $flag $GTK_CFLAGS"
+    LIBS="$GTK_LIBS $LIBS"
+    AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
+        #include <stdio.h>
+        #include <assert.h>
+        #include <stdlib.h>
+        #include <time.h>
+        #include <stdarg.h>
+        #include <string.h>
+        #include <errno.h>
+        #include <math.h>
+
+        #include <sys/time.h>
+        #include <sys/resource.h>
+
+        #include <gtk/gtk.h>
+        #include <gdk/gdkkeysyms.h>
+
+        #include <gdk-pixbuf/gdk-pixbuf.h>
+
+        #include <gdk/gdkx.h>
+        #include <X11/Xlib.h>
+        #include <X11/Xutil.h>
+        #include <X11/Xatom.h>
+    ],[
+        return 0;
+    ])], [gccwarningflags="$gccwarningflags $flag"], [])
+    CFLAGS="$ac_save_CFLAGS"
+    LIBS="$ac_save_LIBS"
+  done
+  AC_MSG_RESULT($gccwarningflags)
+  CFLAGS="$CFLAGS$gccwarningflags"
+fi
+
+AC_PROG_RANLIB
+AC_PROG_INSTALL
+AC_CONFIG_FILES([Makefile])
+AC_OUTPUT
diff --git a/cube.R b/cube.R
new file mode 100644 (file)
index 0000000..85b081e
--- /dev/null
+++ b/cube.R
@@ -0,0 +1,19 @@
+# -*- makefile -*-
+
+cube     : [X] GTK COMMON cube cube-icon|no-icon
+
+cube     : [G] WINDOWS COMMON cube cube.res|noicon.res
+
+ALL += cube[COMBINED]
+
+!begin am gtk
+GAMES += cube
+!end
+
+!begin >list.c
+    A(cube) \
+!end
+
+!begin >gamedesc.txt
+cube:cube.exe:Cube:Rolling cube puzzle:Pick up all the blue squares by rolling the cube over them.
+!end
diff --git a/cube.c b/cube.c
new file mode 100644 (file)
index 0000000..c22e299
--- /dev/null
+++ b/cube.c
@@ -0,0 +1,1773 @@
+/*
+ * cube.c: Cube game.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+
+#define MAXVERTICES 20
+#define MAXFACES 20
+#define MAXORDER 4
+struct solid {
+    int nvertices;
+    float vertices[MAXVERTICES * 3];   /* 3*npoints coordinates */
+    int order;
+    int nfaces;
+    int faces[MAXFACES * MAXORDER];    /* order*nfaces point indices */
+    float normals[MAXFACES * 3];       /* 3*npoints vector components */
+    float shear;                       /* isometric shear for nice drawing */
+    float border;                      /* border required around arena */
+};
+
+static const struct solid s_tetrahedron = {
+    4,
+    {
+        0.0F, -0.57735026919F, -0.20412414523F,
+        -0.5F, 0.28867513459F, -0.20412414523F,
+        0.0F, -0.0F, 0.6123724357F,
+        0.5F, 0.28867513459F, -0.20412414523F,
+    },
+    3, 4,
+    {
+        0,2,1, 3,1,2, 2,0,3, 1,3,0
+    },
+    {
+        -0.816496580928F, -0.471404520791F, 0.333333333334F,
+        0.0F, 0.942809041583F, 0.333333333333F,
+        0.816496580928F, -0.471404520791F, 0.333333333334F,
+        0.0F, 0.0F, -1.0F,
+    },
+    0.0F, 0.3F
+};
+
+static const struct solid s_cube = {
+    8,
+    {
+        -0.5F,-0.5F,-0.5F, -0.5F,-0.5F,+0.5F,
+       -0.5F,+0.5F,-0.5F, -0.5F,+0.5F,+0.5F,
+        +0.5F,-0.5F,-0.5F, +0.5F,-0.5F,+0.5F,
+       +0.5F,+0.5F,-0.5F, +0.5F,+0.5F,+0.5F,
+    },
+    4, 6,
+    {
+        0,1,3,2, 1,5,7,3, 5,4,6,7, 4,0,2,6, 0,4,5,1, 3,7,6,2
+    },
+    {
+        -1.0F,0.0F,0.0F, 0.0F,0.0F,+1.0F,
+       +1.0F,0.0F,0.0F, 0.0F,0.0F,-1.0F,
+       0.0F,-1.0F,0.0F, 0.0F,+1.0F,0.0F
+    },
+    0.3F, 0.5F
+};
+
+static const struct solid s_octahedron = {
+    6,
+    {
+        -0.5F, -0.28867513459472505F, 0.4082482904638664F,
+        0.5F, 0.28867513459472505F, -0.4082482904638664F,
+        -0.5F, 0.28867513459472505F, -0.4082482904638664F,
+        0.5F, -0.28867513459472505F, 0.4082482904638664F,
+        0.0F, -0.57735026918945009F, -0.4082482904638664F,
+        0.0F, 0.57735026918945009F, 0.4082482904638664F,
+    },
+    3, 8,
+    {
+        4,0,2, 0,5,2, 0,4,3, 5,0,3, 1,4,2, 5,1,2, 4,1,3, 1,5,3
+    },
+    {
+        -0.816496580928F, -0.471404520791F, -0.333333333334F,
+        -0.816496580928F, 0.471404520791F, 0.333333333334F,
+        0.0F, -0.942809041583F, 0.333333333333F,
+        0.0F, 0.0F, 1.0F,
+        0.0F, 0.0F, -1.0F,
+        0.0F, 0.942809041583F, -0.333333333333F,
+        0.816496580928F, -0.471404520791F, -0.333333333334F,
+        0.816496580928F, 0.471404520791F, 0.333333333334F,
+    },
+    0.0F, 0.5F
+};
+
+static const struct solid s_icosahedron = {
+    12,
+    {
+        0.0F, 0.57735026919F, 0.75576131408F,
+        0.0F, -0.93417235896F, 0.17841104489F,
+        0.0F, 0.93417235896F, -0.17841104489F,
+        0.0F, -0.57735026919F, -0.75576131408F,
+        -0.5F, -0.28867513459F, 0.75576131408F,
+        -0.5F, 0.28867513459F, -0.75576131408F,
+        0.5F, -0.28867513459F, 0.75576131408F,
+        0.5F, 0.28867513459F, -0.75576131408F,
+        -0.80901699437F, 0.46708617948F, 0.17841104489F,
+        0.80901699437F, 0.46708617948F, 0.17841104489F,
+        -0.80901699437F, -0.46708617948F, -0.17841104489F,
+        0.80901699437F, -0.46708617948F, -0.17841104489F,
+    },
+    3, 20,
+    {
+        8,0,2,  0,9,2,  1,10,3, 11,1,3,  0,4,6,
+        4,1,6,  5,2,7,  3,5,7,  4,8,10,  8,5,10,
+        9,6,11, 7,9,11,  0,8,4,  9,0,6,  10,1,4,
+        1,11,6, 8,2,5,  2,9,7,  3,10,5, 11,3,7,
+    },
+    {
+        -0.356822089773F, 0.87267799625F, 0.333333333333F,
+        0.356822089773F, 0.87267799625F, 0.333333333333F,
+        -0.356822089773F, -0.87267799625F, -0.333333333333F,
+        0.356822089773F, -0.87267799625F, -0.333333333333F,
+        -0.0F, 0.0F, 1.0F,
+        0.0F, -0.666666666667F, 0.745355992501F,
+        0.0F, 0.666666666667F, -0.745355992501F,
+        0.0F, 0.0F, -1.0F,
+        -0.934172358963F, -0.12732200375F, 0.333333333333F,
+        -0.934172358963F, 0.12732200375F, -0.333333333333F,
+        0.934172358963F, -0.12732200375F, 0.333333333333F,
+        0.934172358963F, 0.12732200375F, -0.333333333333F,
+        -0.57735026919F, 0.333333333334F, 0.745355992501F,
+        0.57735026919F, 0.333333333334F, 0.745355992501F,
+        -0.57735026919F, -0.745355992501F, 0.333333333334F,
+        0.57735026919F, -0.745355992501F, 0.333333333334F,
+        -0.57735026919F, 0.745355992501F, -0.333333333334F,
+        0.57735026919F, 0.745355992501F, -0.333333333334F,
+        -0.57735026919F, -0.333333333334F, -0.745355992501F,
+        0.57735026919F, -0.333333333334F, -0.745355992501F,
+    },
+    0.0F, 0.8F
+};
+
+enum {
+    TETRAHEDRON, CUBE, OCTAHEDRON, ICOSAHEDRON
+};
+static const struct solid *solids[] = {
+    &s_tetrahedron, &s_cube, &s_octahedron, &s_icosahedron
+};
+
+enum {
+    COL_BACKGROUND,
+    COL_BORDER,
+    COL_BLUE,
+    NCOLOURS
+};
+
+enum { LEFT, RIGHT, UP, DOWN, UP_LEFT, UP_RIGHT, DOWN_LEFT, DOWN_RIGHT };
+
+#define PREFERRED_GRID_SCALE 48
+#define GRID_SCALE (ds->gridscale)
+#define ROLLTIME 0.13F
+
+#define SQ(x) ( (x) * (x) )
+
+#define MATMUL(ra,m,a) do { \
+    float rx, ry, rz, xx = (a)[0], yy = (a)[1], zz = (a)[2], *mat = (m); \
+    rx = mat[0] * xx + mat[3] * yy + mat[6] * zz; \
+    ry = mat[1] * xx + mat[4] * yy + mat[7] * zz; \
+    rz = mat[2] * xx + mat[5] * yy + mat[8] * zz; \
+    (ra)[0] = rx; (ra)[1] = ry; (ra)[2] = rz; \
+} while (0)
+
+#define APPROXEQ(x,y) ( SQ(x-y) < 0.1 )
+
+struct grid_square {
+    float x, y;
+    int npoints;
+    float points[8];                   /* maximum */
+    int directions[8];                 /* bit masks showing point pairs */
+    int flip;
+    int tetra_class;
+};
+
+struct game_params {
+    int solid;
+    /*
+     * Grid dimensions. For a square grid these are width and
+     * height respectively; otherwise the grid is a hexagon, with
+     * the top side and the two lower diagonals having length d1
+     * and the remaining three sides having length d2 (so that
+     * d1==d2 gives a regular hexagon, and d2==0 gives a triangle).
+     */
+    int d1, d2;
+};
+
+typedef struct game_grid game_grid;
+struct game_grid {
+    int refcount;
+    struct grid_square *squares;
+    int nsquares;
+};
+
+#define SET_SQUARE(state, i, val) \
+    ((state)->bluemask[(i)/32] &= ~(1 << ((i)%32)), \
+     (state)->bluemask[(i)/32] |= ((!!val) << ((i)%32)))
+#define GET_SQUARE(state, i) \
+    (((state)->bluemask[(i)/32] >> ((i)%32)) & 1)
+
+struct game_state {
+    struct game_params params;
+    const struct solid *solid;
+    int *facecolours;
+    game_grid *grid;
+    unsigned long *bluemask;
+    int current;                       /* index of current grid square */
+    int sgkey[2];                      /* key-point indices into grid sq */
+    int dgkey[2];                      /* key-point indices into grid sq */
+    int spkey[2];                      /* key-point indices into polyhedron */
+    int dpkey[2];                      /* key-point indices into polyhedron */
+    int previous;
+    float angle;
+    int completed;
+    int movecount;
+};
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+
+    ret->solid = CUBE;
+    ret->d1 = 4;
+    ret->d2 = 4;
+
+    return ret;
+}
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params *ret = snew(game_params);
+    char *str;
+
+    switch (i) {
+      case 0:
+        str = "Cube";
+        ret->solid = CUBE;
+        ret->d1 = 4;
+        ret->d2 = 4;
+        break;
+      case 1:
+        str = "Tetrahedron";
+        ret->solid = TETRAHEDRON;
+        ret->d1 = 1;
+        ret->d2 = 2;
+        break;
+      case 2:
+        str = "Octahedron";
+        ret->solid = OCTAHEDRON;
+        ret->d1 = 2;
+        ret->d2 = 2;
+        break;
+      case 3:
+        str = "Icosahedron";
+        ret->solid = ICOSAHEDRON;
+        ret->d1 = 3;
+        ret->d2 = 3;
+        break;
+      default:
+        sfree(ret);
+        return FALSE;
+    }
+
+    *name = dupstr(str);
+    *params = ret;
+    return TRUE;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+static void decode_params(game_params *ret, char const *string)
+{
+    switch (*string) {
+      case 't': ret->solid = TETRAHEDRON; string++; break;
+      case 'c': ret->solid = CUBE;        string++; break;
+      case 'o': ret->solid = OCTAHEDRON;  string++; break;
+      case 'i': ret->solid = ICOSAHEDRON; string++; break;
+      default: break;
+    }
+    ret->d1 = ret->d2 = atoi(string);
+    while (*string && isdigit((unsigned char)*string)) string++;
+    if (*string == 'x') {
+        string++;
+        ret->d2 = atoi(string);
+    }
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char data[256];
+
+    assert(params->solid >= 0 && params->solid < 4);
+    sprintf(data, "%c%dx%d", "tcoi"[params->solid], params->d1, params->d2);
+
+    return dupstr(data);
+}
+typedef void (*egc_callback)(void *, struct grid_square *);
+
+static void enum_grid_squares(const game_params *params, egc_callback callback,
+                              void *ctx)
+{
+    const struct solid *solid = solids[params->solid];
+
+    if (solid->order == 4) {
+        int x, y;
+
+       for (y = 0; y < params->d2; y++)
+           for (x = 0; x < params->d1; x++) {
+                struct grid_square sq;
+
+                sq.x = (float)x;
+                sq.y = (float)y;
+                sq.points[0] = x - 0.5F;
+                sq.points[1] = y - 0.5F;
+                sq.points[2] = x - 0.5F;
+                sq.points[3] = y + 0.5F;
+                sq.points[4] = x + 0.5F;
+                sq.points[5] = y + 0.5F;
+                sq.points[6] = x + 0.5F;
+                sq.points[7] = y - 0.5F;
+                sq.npoints = 4;
+
+                sq.directions[LEFT]  = 0x03;   /* 0,1 */
+                sq.directions[RIGHT] = 0x0C;   /* 2,3 */
+                sq.directions[UP]    = 0x09;   /* 0,3 */
+                sq.directions[DOWN]  = 0x06;   /* 1,2 */
+                sq.directions[UP_LEFT] = 0;   /* no diagonals in a square */
+                sq.directions[UP_RIGHT] = 0;   /* no diagonals in a square */
+                sq.directions[DOWN_LEFT] = 0;   /* no diagonals in a square */
+                sq.directions[DOWN_RIGHT] = 0;   /* no diagonals in a square */
+
+                sq.flip = FALSE;
+
+                /*
+                 * This is supremely irrelevant, but just to avoid
+                 * having any uninitialised structure members...
+                 */
+                sq.tetra_class = 0;
+
+                callback(ctx, &sq);
+            }
+    } else {
+        int row, rowlen, other, i, firstix = -1;
+        float theight = (float)(sqrt(3) / 2.0);
+
+        for (row = 0; row < params->d1 + params->d2; row++) {
+            if (row < params->d2) {
+                other = +1;
+                rowlen = row + params->d1;
+            } else {
+                other = -1;
+                rowlen = 2*params->d2 + params->d1 - row;
+            }
+
+            /*
+             * There are `rowlen' down-pointing triangles.
+             */
+            for (i = 0; i < rowlen; i++) {
+                struct grid_square sq;
+                int ix;
+                float x, y;
+
+                ix = (2 * i - (rowlen-1));
+                x = ix * 0.5F;
+                y = theight * row;
+                sq.x = x;
+                sq.y = y + theight / 3;
+                sq.points[0] = x - 0.5F;
+                sq.points[1] = y;
+                sq.points[2] = x;
+                sq.points[3] = y + theight;
+                sq.points[4] = x + 0.5F;
+                sq.points[5] = y;
+                sq.npoints = 3;
+
+                sq.directions[LEFT]  = 0x03;   /* 0,1 */
+                sq.directions[RIGHT] = 0x06;   /* 1,2 */
+                sq.directions[UP]    = 0x05;   /* 0,2 */
+                sq.directions[DOWN]  = 0;      /* invalid move */
+
+                /*
+                 * Down-pointing triangle: both the up diagonals go
+                 * up, and the down ones go left and right.
+                 */
+                sq.directions[UP_LEFT] = sq.directions[UP_RIGHT] =
+                    sq.directions[UP];
+                sq.directions[DOWN_LEFT] = sq.directions[LEFT];
+                sq.directions[DOWN_RIGHT] = sq.directions[RIGHT];
+
+                sq.flip = TRUE;
+
+                if (firstix < 0)
+                    firstix = ix & 3;
+                ix -= firstix;
+                sq.tetra_class = ((row+(ix&1)) & 2) ^ (ix & 3);
+
+                callback(ctx, &sq);
+            }
+
+            /*
+             * There are `rowlen+other' up-pointing triangles.
+             */
+            for (i = 0; i < rowlen+other; i++) {
+                struct grid_square sq;
+                int ix;
+                float x, y;
+
+                ix = (2 * i - (rowlen+other-1));
+                x = ix * 0.5F;
+                y = theight * row;
+                sq.x = x;
+                sq.y = y + 2*theight / 3;
+                sq.points[0] = x + 0.5F;
+                sq.points[1] = y + theight;
+                sq.points[2] = x;
+                sq.points[3] = y;
+                sq.points[4] = x - 0.5F;
+                sq.points[5] = y + theight;
+                sq.npoints = 3;
+
+                sq.directions[LEFT]  = 0x06;   /* 1,2 */
+                sq.directions[RIGHT] = 0x03;   /* 0,1 */
+                sq.directions[DOWN]  = 0x05;   /* 0,2 */
+                sq.directions[UP]    = 0;      /* invalid move */
+
+                /*
+                 * Up-pointing triangle: both the down diagonals go
+                 * down, and the up ones go left and right.
+                 */
+                sq.directions[DOWN_LEFT] = sq.directions[DOWN_RIGHT] =
+                    sq.directions[DOWN];
+                sq.directions[UP_LEFT] = sq.directions[LEFT];
+                sq.directions[UP_RIGHT] = sq.directions[RIGHT];
+
+                sq.flip = FALSE;
+
+                if (firstix < 0)
+                    firstix = (ix - 1) & 3;
+                ix -= firstix;
+                sq.tetra_class = ((row+(ix&1)) & 2) ^ (ix & 3);
+
+                callback(ctx, &sq);
+            }
+        }
+    }
+}
+
+static int grid_area(int d1, int d2, int order)
+{
+    /*
+     * An NxM grid of squares has NM squares in it.
+     * 
+     * A grid of triangles with dimensions A and B has a total of
+     * A^2 + B^2 + 4AB triangles in it. (You can divide it up into
+     * a side-A triangle containing A^2 subtriangles, a side-B
+     * triangle containing B^2, and two congruent parallelograms,
+     * each with side lengths A and B, each therefore containing AB
+     * two-triangle rhombuses.)
+     */
+    if (order == 4)
+        return d1 * d2;
+    else
+        return d1*d1 + d2*d2 + 4*d1*d2;
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret = snewn(4, config_item);
+    char buf[80];
+
+    ret[0].name = "Type of solid";
+    ret[0].type = C_CHOICES;
+    ret[0].sval = ":Tetrahedron:Cube:Octahedron:Icosahedron";
+    ret[0].ival = params->solid;
+
+    ret[1].name = "Width / top";
+    ret[1].type = C_STRING;
+    sprintf(buf, "%d", params->d1);
+    ret[1].sval = dupstr(buf);
+    ret[1].ival = 0;
+
+    ret[2].name = "Height / bottom";
+    ret[2].type = C_STRING;
+    sprintf(buf, "%d", params->d2);
+    ret[2].sval = dupstr(buf);
+    ret[2].ival = 0;
+
+    ret[3].name = NULL;
+    ret[3].type = C_END;
+    ret[3].sval = NULL;
+    ret[3].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->solid = cfg[0].ival;
+    ret->d1 = atoi(cfg[1].sval);
+    ret->d2 = atoi(cfg[2].sval);
+
+    return ret;
+}
+
+static void count_grid_square_callback(void *ctx, struct grid_square *sq)
+{
+    int *classes = (int *)ctx;
+    int thisclass;
+
+    if (classes[4] == 4)
+       thisclass = sq->tetra_class;
+    else if (classes[4] == 2)
+       thisclass = sq->flip;
+    else
+       thisclass = 0;
+
+    classes[thisclass]++;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    int classes[5];
+    int i;
+
+    if (params->solid < 0 || params->solid >= lenof(solids))
+       return "Unrecognised solid type";
+
+    if (solids[params->solid]->order == 4) {
+       if (params->d1 <= 0 || params->d2 <= 0)
+           return "Both grid dimensions must be greater than zero";
+    } else {
+       if (params->d1 <= 0 && params->d2 <= 0)
+           return "At least one grid dimension must be greater than zero";
+    }
+
+    for (i = 0; i < 4; i++)
+       classes[i] = 0;
+    if (params->solid == TETRAHEDRON)
+       classes[4] = 4;
+    else if (params->solid == OCTAHEDRON)
+       classes[4] = 2;
+    else
+       classes[4] = 1;
+    enum_grid_squares(params, count_grid_square_callback, classes);
+
+    for (i = 0; i < classes[4]; i++)
+       if (classes[i] < solids[params->solid]->nfaces / classes[4])
+           return "Not enough grid space to place all blue faces";
+
+    if (grid_area(params->d1, params->d2, solids[params->solid]->order) <
+       solids[params->solid]->nfaces + 1)
+       return "Not enough space to place the solid on an empty square";
+
+    return NULL;
+}
+
+struct grid_data {
+    int *gridptrs[4];
+    int nsquares[4];
+    int nclasses;
+    int squareindex;
+};
+
+static void classify_grid_square_callback(void *ctx, struct grid_square *sq)
+{
+    struct grid_data *data = (struct grid_data *)ctx;
+    int thisclass;
+
+    if (data->nclasses == 4)
+       thisclass = sq->tetra_class;
+    else if (data->nclasses == 2)
+       thisclass = sq->flip;
+    else
+       thisclass = 0;
+
+    data->gridptrs[thisclass][data->nsquares[thisclass]++] =
+       data->squareindex++;
+}
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                          char **aux, int interactive)
+{
+    struct grid_data data;
+    int i, j, k, m, area, facesperclass;
+    int *flags;
+    char *desc, *p;
+
+    /*
+     * Enumerate the grid squares, dividing them into equivalence
+     * classes as appropriate. (For the tetrahedron, there is one
+     * equivalence class for each face; for the octahedron there
+     * are two classes; for the other two solids there's only one.)
+     */
+
+    area = grid_area(params->d1, params->d2, solids[params->solid]->order);
+    if (params->solid == TETRAHEDRON)
+       data.nclasses = 4;
+    else if (params->solid == OCTAHEDRON)
+       data.nclasses = 2;
+    else
+       data.nclasses = 1;
+    data.gridptrs[0] = snewn(data.nclasses * area, int);
+    for (i = 0; i < data.nclasses; i++) {
+       data.gridptrs[i] = data.gridptrs[0] + i * area;
+       data.nsquares[i] = 0;
+    }
+    data.squareindex = 0;
+    enum_grid_squares(params, classify_grid_square_callback, &data);
+
+    facesperclass = solids[params->solid]->nfaces / data.nclasses;
+
+    for (i = 0; i < data.nclasses; i++)
+       assert(data.nsquares[i] >= facesperclass);
+    assert(data.squareindex == area);
+
+    /*
+     * So now we know how many faces to allocate in each class. Get
+     * on with it.
+     */
+    flags = snewn(area, int);
+    for (i = 0; i < area; i++)
+       flags[i] = FALSE;
+
+    for (i = 0; i < data.nclasses; i++) {
+       for (j = 0; j < facesperclass; j++) {
+            int n = random_upto(rs, data.nsquares[i]);
+
+           assert(!flags[data.gridptrs[i][n]]);
+           flags[data.gridptrs[i][n]] = TRUE;
+
+           /*
+            * Move everything else up the array. I ought to use a
+            * better data structure for this, but for such small
+            * numbers it hardly seems worth the effort.
+            */
+           while (n < data.nsquares[i]-1) {
+               data.gridptrs[i][n] = data.gridptrs[i][n+1];
+               n++;
+           }
+           data.nsquares[i]--;
+       }
+    }
+
+    /*
+     * Now we know precisely which squares are blue. Encode this
+     * information in hex. While we're looping over this, collect
+     * the non-blue squares into a list in the now-unused gridptrs
+     * array.
+     */
+    desc = snewn(area / 4 + 40, char);
+    p = desc;
+    j = 0;
+    k = 8;
+    m = 0;
+    for (i = 0; i < area; i++) {
+       if (flags[i]) {
+           j |= k;
+       } else {
+           data.gridptrs[0][m++] = i;
+       }
+       k >>= 1;
+       if (!k) {
+           *p++ = "0123456789ABCDEF"[j];
+           k = 8;
+           j = 0;
+       }
+    }
+    if (k != 8)
+       *p++ = "0123456789ABCDEF"[j];
+
+    /*
+     * Choose a non-blue square for the polyhedron.
+     */
+    sprintf(p, ",%d", data.gridptrs[0][random_upto(rs, m)]);
+
+    sfree(data.gridptrs[0]);
+    sfree(flags);
+
+    return desc;
+}
+
+static void add_grid_square_callback(void *ctx, struct grid_square *sq)
+{
+    game_grid *grid = (game_grid *)ctx;
+
+    grid->squares[grid->nsquares++] = *sq;   /* structure copy */
+}
+
+static int lowest_face(const struct solid *solid)
+{
+    int i, j, best;
+    float zmin;
+
+    best = 0;
+    zmin = 0.0;
+    for (i = 0; i < solid->nfaces; i++) {
+        float z = 0;
+
+        for (j = 0; j < solid->order; j++) {
+            int f = solid->faces[i*solid->order + j];
+            z += solid->vertices[f*3+2];
+        }
+
+        if (i == 0 || zmin > z) {
+            zmin = z;
+            best = i;
+        }
+    }
+
+    return best;
+}
+
+static int align_poly(const struct solid *solid, struct grid_square *sq,
+                      int *pkey)
+{
+    float zmin;
+    int i, j;
+    int flip = (sq->flip ? -1 : +1);
+
+    /*
+     * First, find the lowest z-coordinate present in the solid.
+     */
+    zmin = 0.0;
+    for (i = 0; i < solid->nvertices; i++)
+        if (zmin > solid->vertices[i*3+2])
+            zmin = solid->vertices[i*3+2];
+
+    /*
+     * Now go round the grid square. For each point in the grid
+     * square, we're looking for a point of the polyhedron with the
+     * same x- and y-coordinates (relative to the square's centre),
+     * and z-coordinate equal to zmin (near enough).
+     */
+    for (j = 0; j < sq->npoints; j++) {
+        int matches, index;
+
+        matches = 0;
+        index = -1;
+
+        for (i = 0; i < solid->nvertices; i++) {
+            float dist = 0;
+
+            dist += SQ(solid->vertices[i*3+0] * flip - sq->points[j*2+0] + sq->x);
+            dist += SQ(solid->vertices[i*3+1] * flip - sq->points[j*2+1] + sq->y);
+            dist += SQ(solid->vertices[i*3+2] - zmin);
+
+            if (dist < 0.1) {
+                matches++;
+                index = i;
+            }
+        }
+
+        if (matches != 1 || index < 0)
+            return FALSE;
+        pkey[j] = index;
+    }
+
+    return TRUE;
+}
+
+static void flip_poly(struct solid *solid, int flip)
+{
+    int i;
+
+    if (flip) {
+        for (i = 0; i < solid->nvertices; i++) {
+            solid->vertices[i*3+0] *= -1;
+            solid->vertices[i*3+1] *= -1;
+        }
+        for (i = 0; i < solid->nfaces; i++) {
+            solid->normals[i*3+0] *= -1;
+            solid->normals[i*3+1] *= -1;
+        }
+    }
+}
+
+static struct solid *transform_poly(const struct solid *solid, int flip,
+                                    int key0, int key1, float angle)
+{
+    struct solid *ret = snew(struct solid);
+    float vx, vy, ax, ay;
+    float vmatrix[9], amatrix[9], vmatrix2[9];
+    int i;
+
+    *ret = *solid;                     /* structure copy */
+
+    flip_poly(ret, flip);
+
+    /*
+     * Now rotate the polyhedron through the given angle. We must
+     * rotate about the Z-axis to bring the two vertices key0 and
+     * key1 into horizontal alignment, then rotate about the
+     * X-axis, then rotate back again.
+     */
+    vx = ret->vertices[key1*3+0] - ret->vertices[key0*3+0];
+    vy = ret->vertices[key1*3+1] - ret->vertices[key0*3+1];
+    assert(APPROXEQ(vx*vx + vy*vy, 1.0));
+
+    vmatrix[0] =  vx; vmatrix[3] = vy; vmatrix[6] = 0;
+    vmatrix[1] = -vy; vmatrix[4] = vx; vmatrix[7] = 0;
+    vmatrix[2] =   0; vmatrix[5] =  0; vmatrix[8] = 1;
+
+    ax = (float)cos(angle);
+    ay = (float)sin(angle);
+
+    amatrix[0] = 1; amatrix[3] =   0; amatrix[6] =  0;
+    amatrix[1] = 0; amatrix[4] =  ax; amatrix[7] = ay;
+    amatrix[2] = 0; amatrix[5] = -ay; amatrix[8] = ax;
+
+    memcpy(vmatrix2, vmatrix, sizeof(vmatrix));
+    vmatrix2[1] = vy;
+    vmatrix2[3] = -vy;
+
+    for (i = 0; i < ret->nvertices; i++) {
+        MATMUL(ret->vertices + 3*i, vmatrix, ret->vertices + 3*i);
+        MATMUL(ret->vertices + 3*i, amatrix, ret->vertices + 3*i);
+        MATMUL(ret->vertices + 3*i, vmatrix2, ret->vertices + 3*i);
+    }
+    for (i = 0; i < ret->nfaces; i++) {
+        MATMUL(ret->normals + 3*i, vmatrix, ret->normals + 3*i);
+        MATMUL(ret->normals + 3*i, amatrix, ret->normals + 3*i);
+        MATMUL(ret->normals + 3*i, vmatrix2, ret->normals + 3*i);
+    }
+
+    return ret;
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    int area = grid_area(params->d1, params->d2, solids[params->solid]->order);
+    int i, j;
+
+    i = (area + 3) / 4;
+    for (j = 0; j < i; j++) {
+       int c = desc[j];
+       if (c >= '0' && c <= '9') continue;
+       if (c >= 'A' && c <= 'F') continue;
+       if (c >= 'a' && c <= 'f') continue;
+       return "Not enough hex digits at start of string";
+       /* NB if desc[j]=='\0' that will also be caught here, so we're safe */
+    }
+
+    if (desc[i] != ',')
+       return "Expected ',' after hex digits";
+
+    i++;
+    do {
+       if (desc[i] < '0' || desc[i] > '9')
+           return "Expected decimal integer after ','";
+       i++;
+    } while (desc[i]);
+
+    return NULL;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    game_grid *grid = snew(game_grid);
+    game_state *state = snew(game_state);
+    int area;
+
+    state->params = *params;           /* structure copy */
+    state->solid = solids[params->solid];
+
+    area = grid_area(params->d1, params->d2, state->solid->order);
+    grid->squares = snewn(area, struct grid_square);
+    grid->nsquares = 0;
+    enum_grid_squares(params, add_grid_square_callback, grid);
+    assert(grid->nsquares == area);
+    state->grid = grid;
+    grid->refcount = 1;
+
+    state->facecolours = snewn(state->solid->nfaces, int);
+    memset(state->facecolours, 0, state->solid->nfaces * sizeof(int));
+
+    state->bluemask = snewn((state->grid->nsquares + 31) / 32, unsigned long);
+    memset(state->bluemask, 0, (state->grid->nsquares + 31) / 32 *
+          sizeof(unsigned long));
+
+    /*
+     * Set up the blue squares and polyhedron position according to
+     * the game description.
+     */
+    {
+       const char *p = desc;
+       int i, j, v;
+
+       j = 8;
+       v = 0;
+       for (i = 0; i < state->grid->nsquares; i++) {
+           if (j == 8) {
+               v = *p++;
+               if (v >= '0' && v <= '9')
+                   v -= '0';
+               else if (v >= 'A' && v <= 'F')
+                   v -= 'A' - 10;
+               else if (v >= 'a' && v <= 'f')
+                   v -= 'a' - 10;
+               else
+                   break;
+           }
+           if (v & j)
+               SET_SQUARE(state, i, TRUE);
+           j >>= 1;
+           if (j == 0)
+               j = 8;
+       }
+
+       if (*p == ',')
+           p++;
+
+       state->current = atoi(p);
+       if (state->current < 0 || state->current >= state->grid->nsquares)
+           state->current = 0;        /* got to do _something_ */
+    }
+
+    /*
+     * Align the polyhedron with its grid square and determine
+     * initial key points.
+     */
+    {
+        int pkey[4];
+        int ret;
+
+        ret = align_poly(state->solid, &state->grid->squares[state->current], pkey);
+        assert(ret);
+
+        state->dpkey[0] = state->spkey[0] = pkey[0];
+        state->dpkey[1] = state->spkey[0] = pkey[1];
+        state->dgkey[0] = state->sgkey[0] = 0;
+        state->dgkey[1] = state->sgkey[0] = 1;
+    }
+
+    state->previous = state->current;
+    state->angle = 0.0;
+    state->completed = 0;
+    state->movecount = 0;
+
+    return state;
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    game_state *ret = snew(game_state);
+
+    ret->params = state->params;           /* structure copy */
+    ret->solid = state->solid;
+    ret->facecolours = snewn(ret->solid->nfaces, int);
+    memcpy(ret->facecolours, state->facecolours,
+           ret->solid->nfaces * sizeof(int));
+    ret->current = state->current;
+    ret->grid = state->grid;
+    ret->grid->refcount++;
+    ret->bluemask = snewn((ret->grid->nsquares + 31) / 32, unsigned long);
+    memcpy(ret->bluemask, state->bluemask, (ret->grid->nsquares + 31) / 32 *
+          sizeof(unsigned long));
+    ret->dpkey[0] = state->dpkey[0];
+    ret->dpkey[1] = state->dpkey[1];
+    ret->dgkey[0] = state->dgkey[0];
+    ret->dgkey[1] = state->dgkey[1];
+    ret->spkey[0] = state->spkey[0];
+    ret->spkey[1] = state->spkey[1];
+    ret->sgkey[0] = state->sgkey[0];
+    ret->sgkey[1] = state->sgkey[1];
+    ret->previous = state->previous;
+    ret->angle = state->angle;
+    ret->completed = state->completed;
+    ret->movecount = state->movecount;
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    if (--state->grid->refcount <= 0) {
+       sfree(state->grid->squares);
+       sfree(state->grid);
+    }
+    sfree(state->bluemask);
+    sfree(state->facecolours);
+    sfree(state);
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    return NULL;
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    return NULL;
+}
+
+static game_ui *new_ui(const game_state *state)
+{
+    return NULL;
+}
+
+static void free_ui(game_ui *ui)
+{
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+}
+
+struct game_drawstate {
+    float gridscale;
+    int ox, oy;                        /* pixel position of float origin */
+};
+
+/*
+ * Code shared between interpret_move() and execute_move().
+ */
+static int find_move_dest(const game_state *from, int direction,
+                         int *skey, int *dkey)
+{
+    int mask, dest, i, j;
+    float points[4];
+
+    /*
+     * Find the two points in the current grid square which
+     * correspond to this move.
+     */
+    mask = from->grid->squares[from->current].directions[direction];
+    if (mask == 0)
+        return -1;
+    for (i = j = 0; i < from->grid->squares[from->current].npoints; i++)
+        if (mask & (1 << i)) {
+            points[j*2] = from->grid->squares[from->current].points[i*2];
+            points[j*2+1] = from->grid->squares[from->current].points[i*2+1];
+            skey[j] = i;
+            j++;
+        }
+    assert(j == 2);
+
+    /*
+     * Now find the other grid square which shares those points.
+     * This is our move destination.
+     */
+    dest = -1;
+    for (i = 0; i < from->grid->nsquares; i++)
+        if (i != from->current) {
+            int match = 0;
+            float dist;
+
+            for (j = 0; j < from->grid->squares[i].npoints; j++) {
+                dist = (SQ(from->grid->squares[i].points[j*2] - points[0]) +
+                        SQ(from->grid->squares[i].points[j*2+1] - points[1]));
+                if (dist < 0.1)
+                    dkey[match++] = j;
+                dist = (SQ(from->grid->squares[i].points[j*2] - points[2]) +
+                        SQ(from->grid->squares[i].points[j*2+1] - points[3]));
+                if (dist < 0.1)
+                    dkey[match++] = j;
+            }
+
+            if (match == 2) {
+                dest = i;
+                break;
+            }
+        }
+
+    return dest;
+}
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    int direction, mask, i;
+    int skey[2], dkey[2];
+
+    button = button & (~MOD_MASK | MOD_NUM_KEYPAD);
+
+    /*
+     * Moves can be made with the cursor keys or numeric keypad, or
+     * alternatively you can left-click and the polyhedron will
+     * move in the general direction of the mouse pointer.
+     */
+    if (button == CURSOR_UP || button == (MOD_NUM_KEYPAD | '8'))
+        direction = UP;
+    else if (button == CURSOR_DOWN || button == (MOD_NUM_KEYPAD | '2'))
+        direction = DOWN;
+    else if (button == CURSOR_LEFT || button == (MOD_NUM_KEYPAD | '4'))
+        direction = LEFT;
+    else if (button == CURSOR_RIGHT || button == (MOD_NUM_KEYPAD | '6'))
+        direction = RIGHT;
+    else if (button == (MOD_NUM_KEYPAD | '7'))
+        direction = UP_LEFT;
+    else if (button == (MOD_NUM_KEYPAD | '1'))
+        direction = DOWN_LEFT;
+    else if (button == (MOD_NUM_KEYPAD | '9'))
+        direction = UP_RIGHT;
+    else if (button == (MOD_NUM_KEYPAD | '3'))
+        direction = DOWN_RIGHT;
+    else if (button == LEFT_BUTTON) {
+        /*
+         * Find the bearing of the click point from the current
+         * square's centre.
+         */
+        int cx, cy;
+        double angle;
+
+        cx = (int)(state->grid->squares[state->current].x * GRID_SCALE) + ds->ox;
+        cy = (int)(state->grid->squares[state->current].y * GRID_SCALE) + ds->oy;
+
+        if (x == cx && y == cy)
+            return NULL;               /* clicked in exact centre!  */
+        angle = atan2(y - cy, x - cx);
+
+        /*
+         * There are three possibilities.
+         * 
+         *  - This square is a square, so we choose between UP,
+         *    DOWN, LEFT and RIGHT by dividing the available angle
+         *    at the 45-degree points.
+         * 
+         *  - This square is an up-pointing triangle, so we choose
+         *    between DOWN, LEFT and RIGHT by dividing into
+         *    120-degree arcs.
+         * 
+         *  - This square is a down-pointing triangle, so we choose
+         *    between UP, LEFT and RIGHT in the inverse manner.
+         * 
+         * Don't forget that since our y-coordinates increase
+         * downwards, `angle' is measured _clockwise_ from the
+         * x-axis, not anticlockwise as most mathematicians would
+         * instinctively assume.
+         */
+        if (state->grid->squares[state->current].npoints == 4) {
+            /* Square. */
+            if (fabs(angle) > 3*PI/4)
+                direction = LEFT;
+            else if (fabs(angle) < PI/4)
+                direction = RIGHT;
+            else if (angle > 0)
+                direction = DOWN;
+            else
+                direction = UP;
+        } else if (state->grid->squares[state->current].directions[UP] == 0) {
+            /* Up-pointing triangle. */
+            if (angle < -PI/2 || angle > 5*PI/6)
+                direction = LEFT;
+            else if (angle > PI/6)
+                direction = DOWN;
+            else
+                direction = RIGHT;
+        } else {
+            /* Down-pointing triangle. */
+            assert(state->grid->squares[state->current].directions[DOWN] == 0);
+            if (angle > PI/2 || angle < -5*PI/6)
+                direction = LEFT;
+            else if (angle < -PI/6)
+                direction = UP;
+            else
+                direction = RIGHT;
+        }
+    } else
+        return NULL;
+
+    mask = state->grid->squares[state->current].directions[direction];
+    if (mask == 0)
+        return NULL;
+
+    /*
+     * Translate diagonal directions into orthogonal ones.
+     */
+    if (direction > DOWN) {
+       for (i = LEFT; i <= DOWN; i++)
+           if (state->grid->squares[state->current].directions[i] == mask) {
+               direction = i;
+               break;
+           }
+       assert(direction <= DOWN);
+    }
+
+    if (find_move_dest(state, direction, skey, dkey) < 0)
+       return NULL;
+
+    if (direction == LEFT)  return dupstr("L");
+    if (direction == RIGHT) return dupstr("R");
+    if (direction == UP)    return dupstr("U");
+    if (direction == DOWN)  return dupstr("D");
+
+    return NULL;                      /* should never happen */
+}
+
+static game_state *execute_move(const game_state *from, const char *move)
+{
+    game_state *ret;
+    float angle;
+    struct solid *poly;
+    int pkey[2];
+    int skey[2], dkey[2];
+    int i, j, dest;
+    int direction;
+
+    switch (*move) {
+      case 'L': direction = LEFT; break;
+      case 'R': direction = RIGHT; break;
+      case 'U': direction = UP; break;
+      case 'D': direction = DOWN; break;
+      default: return NULL;
+    }
+
+    dest = find_move_dest(from, direction, skey, dkey);
+    if (dest < 0)
+        return NULL;
+
+    ret = dup_game(from);
+    ret->current = dest;
+
+    /*
+     * So we know what grid square we're aiming for, and we also
+     * know the two key points (as indices in both the source and
+     * destination grid squares) which are invariant between source
+     * and destination.
+     * 
+     * Next we must roll the polyhedron on to that square. So we
+     * find the indices of the key points within the polyhedron's
+     * vertex array, then use those in a call to transform_poly,
+     * and align the result on the new grid square.
+     */
+    {
+        int all_pkey[4];
+        align_poly(from->solid, &from->grid->squares[from->current], all_pkey);
+        pkey[0] = all_pkey[skey[0]];
+        pkey[1] = all_pkey[skey[1]];
+        /*
+         * Now pkey[0] corresponds to skey[0] and dkey[0], and
+         * likewise [1].
+         */
+    }
+
+    /*
+     * Now find the angle through which to rotate the polyhedron.
+     * Do this by finding the two faces that share the two vertices
+     * we've found, and taking the dot product of their normals.
+     */
+    {
+        int f[2], nf = 0;
+        float dp;
+
+        for (i = 0; i < from->solid->nfaces; i++) {
+            int match = 0;
+            for (j = 0; j < from->solid->order; j++)
+                if (from->solid->faces[i*from->solid->order + j] == pkey[0] ||
+                    from->solid->faces[i*from->solid->order + j] == pkey[1])
+                    match++;
+            if (match == 2) {
+                assert(nf < 2);
+                f[nf++] = i;
+            }
+        }
+
+        assert(nf == 2);
+
+        dp = 0;
+        for (i = 0; i < 3; i++)
+            dp += (from->solid->normals[f[0]*3+i] *
+                   from->solid->normals[f[1]*3+i]);
+        angle = (float)acos(dp);
+    }
+
+    /*
+     * Now transform the polyhedron. We aren't entirely sure
+     * whether we need to rotate through angle or -angle, and the
+     * simplest way round this is to try both and see which one
+     * aligns successfully!
+     * 
+     * Unfortunately, _both_ will align successfully if this is a
+     * cube, which won't tell us anything much. So for that
+     * particular case, I resort to gross hackery: I simply negate
+     * the angle before trying the alignment, depending on the
+     * direction. Which directions work which way is determined by
+     * pure trial and error. I said it was gross :-/
+     */
+    {
+        int all_pkey[4];
+        int success;
+
+        if (from->solid->order == 4 && direction == UP)
+            angle = -angle;            /* HACK */
+
+        poly = transform_poly(from->solid,
+                              from->grid->squares[from->current].flip,
+                              pkey[0], pkey[1], angle);
+        flip_poly(poly, from->grid->squares[ret->current].flip);
+        success = align_poly(poly, &from->grid->squares[ret->current], all_pkey);
+
+        if (!success) {
+            sfree(poly);
+            angle = -angle;
+            poly = transform_poly(from->solid,
+                                  from->grid->squares[from->current].flip,
+                                  pkey[0], pkey[1], angle);
+            flip_poly(poly, from->grid->squares[ret->current].flip);
+            success = align_poly(poly, &from->grid->squares[ret->current], all_pkey);
+        }
+
+        assert(success);
+    }
+
+    /*
+     * Now we have our rotated polyhedron, which we expect to be
+     * exactly congruent to the one we started with - but with the
+     * faces permuted. So we map that congruence and thereby figure
+     * out how to permute the faces as a result of the polyhedron
+     * having rolled.
+     */
+    {
+        int *newcolours = snewn(from->solid->nfaces, int);
+
+        for (i = 0; i < from->solid->nfaces; i++)
+            newcolours[i] = -1;
+
+        for (i = 0; i < from->solid->nfaces; i++) {
+            int nmatch = 0;
+
+            /*
+             * Now go through the transformed polyhedron's faces
+             * and figure out which one's normal is approximately
+             * equal to this one.
+             */
+            for (j = 0; j < poly->nfaces; j++) {
+                float dist;
+                int k;
+
+                dist = 0;
+
+                for (k = 0; k < 3; k++)
+                    dist += SQ(poly->normals[j*3+k] -
+                               from->solid->normals[i*3+k]);
+
+                if (APPROXEQ(dist, 0)) {
+                    nmatch++;
+                    newcolours[i] = ret->facecolours[j];
+                }
+            }
+
+            assert(nmatch == 1);
+        }
+
+        for (i = 0; i < from->solid->nfaces; i++)
+            assert(newcolours[i] != -1);
+
+        sfree(ret->facecolours);
+        ret->facecolours = newcolours;
+    }
+
+    ret->movecount++;
+
+    /*
+     * And finally, swap the colour between the bottom face of the
+     * polyhedron and the face we've just landed on.
+     * 
+     * We don't do this if the game is already complete, since we
+     * allow the user to roll the fully blue polyhedron around the
+     * grid as a feeble reward.
+     */
+    if (!ret->completed) {
+        i = lowest_face(from->solid);
+        j = ret->facecolours[i];
+        ret->facecolours[i] = GET_SQUARE(ret, ret->current);
+        SET_SQUARE(ret, ret->current, j);
+
+        /*
+         * Detect game completion.
+         */
+        j = 0;
+        for (i = 0; i < ret->solid->nfaces; i++)
+            if (ret->facecolours[i])
+                j++;
+        if (j == ret->solid->nfaces)
+            ret->completed = ret->movecount;
+    }
+
+    sfree(poly);
+
+    /*
+     * Align the normal polyhedron with its grid square, to get key
+     * points for non-animated display.
+     */
+    {
+        int pkey[4];
+        int success;
+
+        success = align_poly(ret->solid, &ret->grid->squares[ret->current], pkey);
+        assert(success);
+
+        ret->dpkey[0] = pkey[0];
+        ret->dpkey[1] = pkey[1];
+        ret->dgkey[0] = 0;
+        ret->dgkey[1] = 1;
+    }
+
+
+    ret->spkey[0] = pkey[0];
+    ret->spkey[1] = pkey[1];
+    ret->sgkey[0] = skey[0];
+    ret->sgkey[1] = skey[1];
+    ret->previous = from->current;
+    ret->angle = angle;
+
+    return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+struct bbox {
+    float l, r, u, d;
+};
+
+static void find_bbox_callback(void *ctx, struct grid_square *sq)
+{
+    struct bbox *bb = (struct bbox *)ctx;
+    int i;
+
+    for (i = 0; i < sq->npoints; i++) {
+        if (bb->l > sq->points[i*2]) bb->l = sq->points[i*2];
+        if (bb->r < sq->points[i*2]) bb->r = sq->points[i*2];
+        if (bb->u > sq->points[i*2+1]) bb->u = sq->points[i*2+1];
+        if (bb->d < sq->points[i*2+1]) bb->d = sq->points[i*2+1];
+    }
+}
+
+static struct bbox find_bbox(const game_params *params)
+{
+    struct bbox bb;
+
+    /*
+     * These should be hugely more than the real bounding box will
+     * be.
+     */
+    bb.l = 2.0F * (params->d1 + params->d2);
+    bb.r = -2.0F * (params->d1 + params->d2);
+    bb.u = 2.0F * (params->d1 + params->d2);
+    bb.d = -2.0F * (params->d1 + params->d2);
+    enum_grid_squares(params, find_bbox_callback, &bb);
+
+    return bb;
+}
+
+#define XSIZE(gs, bb, solid) \
+    ((int)(((bb).r - (bb).l + 2*(solid)->border) * gs))
+#define YSIZE(gs, bb, solid) \
+    ((int)(((bb).d - (bb).u + 2*(solid)->border) * gs))
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    struct bbox bb = find_bbox(params);
+
+    *x = XSIZE(tilesize, bb, solids[params->solid]);
+    *y = YSIZE(tilesize, bb, solids[params->solid]);
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    struct bbox bb = find_bbox(params);
+
+    ds->gridscale = (float)tilesize;
+    ds->ox = (int)(-(bb.l - solids[params->solid]->border) * ds->gridscale);
+    ds->oy = (int)(-(bb.u - solids[params->solid]->border) * ds->gridscale);
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+
+    frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
+
+    ret[COL_BORDER * 3 + 0] = 0.0;
+    ret[COL_BORDER * 3 + 1] = 0.0;
+    ret[COL_BORDER * 3 + 2] = 0.0;
+
+    ret[COL_BLUE * 3 + 0] = 0.0;
+    ret[COL_BLUE * 3 + 1] = 0.0;
+    ret[COL_BLUE * 3 + 2] = 1.0;
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    struct game_drawstate *ds = snew(struct game_drawstate);
+
+    ds->ox = ds->oy = 0;
+    ds->gridscale = 0.0F; /* not decided yet */
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds);
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int i, j;
+    struct bbox bb = find_bbox(&state->params);
+    struct solid *poly;
+    const int *pkey, *gkey;
+    float t[3];
+    float angle;
+    int square;
+
+    draw_rect(dr, 0, 0, XSIZE(GRID_SCALE, bb, state->solid),
+             YSIZE(GRID_SCALE, bb, state->solid), COL_BACKGROUND);
+
+    if (dir < 0) {
+        const game_state *t;
+
+        /*
+         * This is an Undo. So reverse the order of the states, and
+         * run the roll timer backwards.
+         */
+       assert(oldstate);
+
+        t = oldstate;
+        oldstate = state;
+        state = t;
+
+        animtime = ROLLTIME - animtime;
+    }
+
+    if (!oldstate) {
+        oldstate = state;
+        angle = 0.0;
+        square = state->current;
+        pkey = state->dpkey;
+        gkey = state->dgkey;
+    } else {
+        angle = state->angle * animtime / ROLLTIME;
+        square = state->previous;
+        pkey = state->spkey;
+        gkey = state->sgkey;
+    }
+    state = oldstate;
+
+    for (i = 0; i < state->grid->nsquares; i++) {
+        int coords[8];
+
+        for (j = 0; j < state->grid->squares[i].npoints; j++) {
+            coords[2*j] = ((int)(state->grid->squares[i].points[2*j] * GRID_SCALE)
+                          + ds->ox);
+            coords[2*j+1] = ((int)(state->grid->squares[i].points[2*j+1]*GRID_SCALE)
+                            + ds->oy);
+        }
+
+        draw_polygon(dr, coords, state->grid->squares[i].npoints,
+                     GET_SQUARE(state, i) ? COL_BLUE : COL_BACKGROUND,
+                    COL_BORDER);
+    }
+
+    /*
+     * Now compute and draw the polyhedron.
+     */
+    poly = transform_poly(state->solid, state->grid->squares[square].flip,
+                          pkey[0], pkey[1], angle);
+
+    /*
+     * Compute the translation required to align the two key points
+     * on the polyhedron with the same key points on the current
+     * face.
+     */
+    for (i = 0; i < 3; i++) {
+        float tc = 0.0;
+
+        for (j = 0; j < 2; j++) {
+            float grid_coord;
+
+            if (i < 2) {
+                grid_coord =
+                    state->grid->squares[square].points[gkey[j]*2+i];
+            } else {
+                grid_coord = 0.0;
+            }
+
+            tc += (grid_coord - poly->vertices[pkey[j]*3+i]);
+        }
+
+        t[i] = tc / 2;
+    }
+    for (i = 0; i < poly->nvertices; i++)
+        for (j = 0; j < 3; j++)
+            poly->vertices[i*3+j] += t[j];
+
+    /*
+     * Now actually draw each face.
+     */
+    for (i = 0; i < poly->nfaces; i++) {
+        float points[8];
+        int coords[8];
+
+        for (j = 0; j < poly->order; j++) {
+            int f = poly->faces[i*poly->order + j];
+            points[j*2] = (poly->vertices[f*3+0] -
+                           poly->vertices[f*3+2] * poly->shear);
+            points[j*2+1] = (poly->vertices[f*3+1] -
+                             poly->vertices[f*3+2] * poly->shear);
+        }
+
+        for (j = 0; j < poly->order; j++) {
+            coords[j*2] = (int)floor(points[j*2] * GRID_SCALE) + ds->ox;
+            coords[j*2+1] = (int)floor(points[j*2+1] * GRID_SCALE) + ds->oy;
+        }
+
+        /*
+         * Find out whether these points are in a clockwise or
+         * anticlockwise arrangement. If the latter, discard the
+         * face because it's facing away from the viewer.
+         *
+         * This would involve fiddly winding-number stuff for a
+         * general polygon, but for the simple parallelograms we'll
+         * be seeing here, all we have to do is check whether the
+         * corners turn right or left. So we'll take the vector
+         * from point 0 to point 1, turn it right 90 degrees,
+         * and check the sign of the dot product with that and the
+         * next vector (point 1 to point 2).
+         */
+        {
+            float v1x = points[2]-points[0];
+            float v1y = points[3]-points[1];
+            float v2x = points[4]-points[2];
+            float v2y = points[5]-points[3];
+            float dp = v1x * v2y - v1y * v2x;
+
+            if (dp <= 0)
+                continue;
+        }
+
+        draw_polygon(dr, coords, poly->order,
+                     state->facecolours[i] ? COL_BLUE : COL_BACKGROUND,
+                    COL_BORDER);
+    }
+    sfree(poly);
+
+    draw_update(dr, 0, 0, XSIZE(GRID_SCALE, bb, state->solid),
+               YSIZE(GRID_SCALE, bb, state->solid));
+
+    /*
+     * Update the status bar.
+     */
+    {
+       char statusbuf[256];
+
+       sprintf(statusbuf, "%sMoves: %d",
+               (state->completed ? "COMPLETED! " : ""),
+               (state->completed ? state->completed : state->movecount));
+
+       status_bar(dr, statusbuf);
+    }
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return ROLLTIME;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    return state->completed ? +1 : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+}
+
+#ifdef COMBINED
+#define thegame cube
+#endif
+
+const struct game thegame = {
+    "Cube", "games.cube", "cube",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    FALSE, solve_game,
+    FALSE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_GRID_SCALE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    FALSE, FALSE, game_print_size, game_print,
+    TRUE,                             /* wants_statusbar */
+    FALSE, game_timing_state,
+    0,                                /* flags */
+};
similarity index 100%
rename from README.Debian
rename to debian/README.Debian
similarity index 100%
rename from changelog
rename to debian/changelog
similarity index 100%
rename from compat
rename to debian/compat
similarity index 100%
rename from control
rename to debian/control
similarity index 100%
rename from copyright
rename to debian/copyright
similarity index 100%
rename from patches/series
rename to debian/patches/series
similarity index 100%
rename from po/de.po
rename to debian/po/de.po
similarity index 100%
rename from po/puzzles-doc.pot
rename to debian/po/puzzles-doc.pot
similarity index 100%
rename from rules
rename to debian/rules
similarity index 100%
rename from sgt-puzzles.dirs
rename to debian/sgt-puzzles.dirs
similarity index 100%
rename from sgt-puzzles.docs
rename to debian/sgt-puzzles.docs
similarity index 100%
rename from source/format
rename to debian/source/format
diff --git a/depcomp b/depcomp
new file mode 100755 (executable)
index 0000000..fc98710
--- /dev/null
+++ b/depcomp
@@ -0,0 +1,791 @@
+#! /bin/sh
+# depcomp - compile a program generating dependencies as side-effects
+
+scriptversion=2013-05-30.07; # UTC
+
+# Copyright (C) 1999-2014 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# Originally written by Alexandre Oliva <oliva@dcc.unicamp.br>.
+
+case $1 in
+  '')
+    echo "$0: No command.  Try '$0 --help' for more information." 1>&2
+    exit 1;
+    ;;
+  -h | --h*)
+    cat <<\EOF
+Usage: depcomp [--help] [--version] PROGRAM [ARGS]
+
+Run PROGRAMS ARGS to compile a file, generating dependencies
+as side-effects.
+
+Environment variables:
+  depmode     Dependency tracking mode.
+  source      Source file read by 'PROGRAMS ARGS'.
+  object      Object file output by 'PROGRAMS ARGS'.
+  DEPDIR      directory where to store dependencies.
+  depfile     Dependency file to output.
+  tmpdepfile  Temporary file to use when outputting dependencies.
+  libtool     Whether libtool is used (yes/no).
+
+Report bugs to <bug-automake@gnu.org>.
+EOF
+    exit $?
+    ;;
+  -v | --v*)
+    echo "depcomp $scriptversion"
+    exit $?
+    ;;
+esac
+
+# Get the directory component of the given path, and save it in the
+# global variables '$dir'.  Note that this directory component will
+# be either empty or ending with a '/' character.  This is deliberate.
+set_dir_from ()
+{
+  case $1 in
+    */*) dir=`echo "$1" | sed -e 's|/[^/]*$|/|'`;;
+      *) dir=;;
+  esac
+}
+
+# Get the suffix-stripped basename of the given path, and save it the
+# global variable '$base'.
+set_base_from ()
+{
+  base=`echo "$1" | sed -e 's|^.*/||' -e 's/\.[^.]*$//'`
+}
+
+# If no dependency file was actually created by the compiler invocation,
+# we still have to create a dummy depfile, to avoid errors with the
+# Makefile "include basename.Plo" scheme.
+make_dummy_depfile ()
+{
+  echo "#dummy" > "$depfile"
+}
+
+# Factor out some common post-processing of the generated depfile.
+# Requires the auxiliary global variable '$tmpdepfile' to be set.
+aix_post_process_depfile ()
+{
+  # If the compiler actually managed to produce a dependency file,
+  # post-process it.
+  if test -f "$tmpdepfile"; then
+    # Each line is of the form 'foo.o: dependency.h'.
+    # Do two passes, one to just change these to
+    #   $object: dependency.h
+    # and one to simply output
+    #   dependency.h:
+    # which is needed to avoid the deleted-header problem.
+    { sed -e "s,^.*\.[$lower]*:,$object:," < "$tmpdepfile"
+      sed -e "s,^.*\.[$lower]*:[$tab ]*,," -e 's,$,:,' < "$tmpdepfile"
+    } > "$depfile"
+    rm -f "$tmpdepfile"
+  else
+    make_dummy_depfile
+  fi
+}
+
+# A tabulation character.
+tab='  '
+# A newline character.
+nl='
+'
+# Character ranges might be problematic outside the C locale.
+# These definitions help.
+upper=ABCDEFGHIJKLMNOPQRSTUVWXYZ
+lower=abcdefghijklmnopqrstuvwxyz
+digits=0123456789
+alpha=${upper}${lower}
+
+if test -z "$depmode" || test -z "$source" || test -z "$object"; then
+  echo "depcomp: Variables source, object and depmode must be set" 1>&2
+  exit 1
+fi
+
+# Dependencies for sub/bar.o or sub/bar.obj go into sub/.deps/bar.Po.
+depfile=${depfile-`echo "$object" |
+  sed 's|[^\\/]*$|'${DEPDIR-.deps}'/&|;s|\.\([^.]*\)$|.P\1|;s|Pobj$|Po|'`}
+tmpdepfile=${tmpdepfile-`echo "$depfile" | sed 's/\.\([^.]*\)$/.T\1/'`}
+
+rm -f "$tmpdepfile"
+
+# Avoid interferences from the environment.
+gccflag= dashmflag=
+
+# Some modes work just like other modes, but use different flags.  We
+# parameterize here, but still list the modes in the big case below,
+# to make depend.m4 easier to write.  Note that we *cannot* use a case
+# here, because this file can only contain one case statement.
+if test "$depmode" = hp; then
+  # HP compiler uses -M and no extra arg.
+  gccflag=-M
+  depmode=gcc
+fi
+
+if test "$depmode" = dashXmstdout; then
+  # This is just like dashmstdout with a different argument.
+  dashmflag=-xM
+  depmode=dashmstdout
+fi
+
+cygpath_u="cygpath -u -f -"
+if test "$depmode" = msvcmsys; then
+  # This is just like msvisualcpp but w/o cygpath translation.
+  # Just convert the backslash-escaped backslashes to single forward
+  # slashes to satisfy depend.m4
+  cygpath_u='sed s,\\\\,/,g'
+  depmode=msvisualcpp
+fi
+
+if test "$depmode" = msvc7msys; then
+  # This is just like msvc7 but w/o cygpath translation.
+  # Just convert the backslash-escaped backslashes to single forward
+  # slashes to satisfy depend.m4
+  cygpath_u='sed s,\\\\,/,g'
+  depmode=msvc7
+fi
+
+if test "$depmode" = xlc; then
+  # IBM C/C++ Compilers xlc/xlC can output gcc-like dependency information.
+  gccflag=-qmakedep=gcc,-MF
+  depmode=gcc
+fi
+
+case "$depmode" in
+gcc3)
+## gcc 3 implements dependency tracking that does exactly what
+## we want.  Yay!  Note: for some reason libtool 1.4 doesn't like
+## it if -MD -MP comes after the -MF stuff.  Hmm.
+## Unfortunately, FreeBSD c89 acceptance of flags depends upon
+## the command line argument order; so add the flags where they
+## appear in depend2.am.  Note that the slowdown incurred here
+## affects only configure: in makefiles, %FASTDEP% shortcuts this.
+  for arg
+  do
+    case $arg in
+    -c) set fnord "$@" -MT "$object" -MD -MP -MF "$tmpdepfile" "$arg" ;;
+    *)  set fnord "$@" "$arg" ;;
+    esac
+    shift # fnord
+    shift # $arg
+  done
+  "$@"
+  stat=$?
+  if test $stat -ne 0; then
+    rm -f "$tmpdepfile"
+    exit $stat
+  fi
+  mv "$tmpdepfile" "$depfile"
+  ;;
+
+gcc)
+## Note that this doesn't just cater to obsosete pre-3.x GCC compilers.
+## but also to in-use compilers like IMB xlc/xlC and the HP C compiler.
+## (see the conditional assignment to $gccflag above).
+## There are various ways to get dependency output from gcc.  Here's
+## why we pick this rather obscure method:
+## - Don't want to use -MD because we'd like the dependencies to end
+##   up in a subdir.  Having to rename by hand is ugly.
+##   (We might end up doing this anyway to support other compilers.)
+## - The DEPENDENCIES_OUTPUT environment variable makes gcc act like
+##   -MM, not -M (despite what the docs say).  Also, it might not be
+##   supported by the other compilers which use the 'gcc' depmode.
+## - Using -M directly means running the compiler twice (even worse
+##   than renaming).
+  if test -z "$gccflag"; then
+    gccflag=-MD,
+  fi
+  "$@" -Wp,"$gccflag$tmpdepfile"
+  stat=$?
+  if test $stat -ne 0; then
+    rm -f "$tmpdepfile"
+    exit $stat
+  fi
+  rm -f "$depfile"
+  echo "$object : \\" > "$depfile"
+  # The second -e expression handles DOS-style file names with drive
+  # letters.
+  sed -e 's/^[^:]*: / /' \
+      -e 's/^['$alpha']:\/[^:]*: / /' < "$tmpdepfile" >> "$depfile"
+## This next piece of magic avoids the "deleted header file" problem.
+## The problem is that when a header file which appears in a .P file
+## is deleted, the dependency causes make to die (because there is
+## typically no way to rebuild the header).  We avoid this by adding
+## dummy dependencies for each header file.  Too bad gcc doesn't do
+## this for us directly.
+## Some versions of gcc put a space before the ':'.  On the theory
+## that the space means something, we add a space to the output as
+## well.  hp depmode also adds that space, but also prefixes the VPATH
+## to the object.  Take care to not repeat it in the output.
+## Some versions of the HPUX 10.20 sed can't process this invocation
+## correctly.  Breaking it into two sed invocations is a workaround.
+  tr ' ' "$nl" < "$tmpdepfile" \
+    | sed -e 's/^\\$//' -e '/^$/d' -e "s|.*$object$||" -e '/:$/d' \
+    | sed -e 's/$/ :/' >> "$depfile"
+  rm -f "$tmpdepfile"
+  ;;
+
+hp)
+  # This case exists only to let depend.m4 do its work.  It works by
+  # looking at the text of this script.  This case will never be run,
+  # since it is checked for above.
+  exit 1
+  ;;
+
+sgi)
+  if test "$libtool" = yes; then
+    "$@" "-Wp,-MDupdate,$tmpdepfile"
+  else
+    "$@" -MDupdate "$tmpdepfile"
+  fi
+  stat=$?
+  if test $stat -ne 0; then
+    rm -f "$tmpdepfile"
+    exit $stat
+  fi
+  rm -f "$depfile"
+
+  if test -f "$tmpdepfile"; then  # yes, the sourcefile depend on other files
+    echo "$object : \\" > "$depfile"
+    # Clip off the initial element (the dependent).  Don't try to be
+    # clever and replace this with sed code, as IRIX sed won't handle
+    # lines with more than a fixed number of characters (4096 in
+    # IRIX 6.2 sed, 8192 in IRIX 6.5).  We also remove comment lines;
+    # the IRIX cc adds comments like '#:fec' to the end of the
+    # dependency line.
+    tr ' ' "$nl" < "$tmpdepfile" \
+      | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' \
+      | tr "$nl" ' ' >> "$depfile"
+    echo >> "$depfile"
+    # The second pass generates a dummy entry for each header file.
+    tr ' ' "$nl" < "$tmpdepfile" \
+      | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' -e 's/$/:/' \
+      >> "$depfile"
+  else
+    make_dummy_depfile
+  fi
+  rm -f "$tmpdepfile"
+  ;;
+
+xlc)
+  # This case exists only to let depend.m4 do its work.  It works by
+  # looking at the text of this script.  This case will never be run,
+  # since it is checked for above.
+  exit 1
+  ;;
+
+aix)
+  # The C for AIX Compiler uses -M and outputs the dependencies
+  # in a .u file.  In older versions, this file always lives in the
+  # current directory.  Also, the AIX compiler puts '$object:' at the
+  # start of each line; $object doesn't have directory information.
+  # Version 6 uses the directory in both cases.
+  set_dir_from "$object"
+  set_base_from "$object"
+  if test "$libtool" = yes; then
+    tmpdepfile1=$dir$base.u
+    tmpdepfile2=$base.u
+    tmpdepfile3=$dir.libs/$base.u
+    "$@" -Wc,-M
+  else
+    tmpdepfile1=$dir$base.u
+    tmpdepfile2=$dir$base.u
+    tmpdepfile3=$dir$base.u
+    "$@" -M
+  fi
+  stat=$?
+  if test $stat -ne 0; then
+    rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3"
+    exit $stat
+  fi
+
+  for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3"
+  do
+    test -f "$tmpdepfile" && break
+  done
+  aix_post_process_depfile
+  ;;
+
+tcc)
+  # tcc (Tiny C Compiler) understand '-MD -MF file' since version 0.9.26
+  # FIXME: That version still under development at the moment of writing.
+  #        Make that this statement remains true also for stable, released
+  #        versions.
+  # It will wrap lines (doesn't matter whether long or short) with a
+  # trailing '\', as in:
+  #
+  #   foo.o : \
+  #    foo.c \
+  #    foo.h \
+  #
+  # It will put a trailing '\' even on the last line, and will use leading
+  # spaces rather than leading tabs (at least since its commit 0394caf7
+  # "Emit spaces for -MD").
+  "$@" -MD -MF "$tmpdepfile"
+  stat=$?
+  if test $stat -ne 0; then
+    rm -f "$tmpdepfile"
+    exit $stat
+  fi
+  rm -f "$depfile"
+  # Each non-empty line is of the form 'foo.o : \' or ' dep.h \'.
+  # We have to change lines of the first kind to '$object: \'.
+  sed -e "s|.*:|$object :|" < "$tmpdepfile" > "$depfile"
+  # And for each line of the second kind, we have to emit a 'dep.h:'
+  # dummy dependency, to avoid the deleted-header problem.
+  sed -n -e 's|^  *\(.*\) *\\$|\1:|p' < "$tmpdepfile" >> "$depfile"
+  rm -f "$tmpdepfile"
+  ;;
+
+## The order of this option in the case statement is important, since the
+## shell code in configure will try each of these formats in the order
+## listed in this file.  A plain '-MD' option would be understood by many
+## compilers, so we must ensure this comes after the gcc and icc options.
+pgcc)
+  # Portland's C compiler understands '-MD'.
+  # Will always output deps to 'file.d' where file is the root name of the
+  # source file under compilation, even if file resides in a subdirectory.
+  # The object file name does not affect the name of the '.d' file.
+  # pgcc 10.2 will output
+  #    foo.o: sub/foo.c sub/foo.h
+  # and will wrap long lines using '\' :
+  #    foo.o: sub/foo.c ... \
+  #     sub/foo.h ... \
+  #     ...
+  set_dir_from "$object"
+  # Use the source, not the object, to determine the base name, since
+  # that's sadly what pgcc will do too.
+  set_base_from "$source"
+  tmpdepfile=$base.d
+
+  # For projects that build the same source file twice into different object
+  # files, the pgcc approach of using the *source* file root name can cause
+  # problems in parallel builds.  Use a locking strategy to avoid stomping on
+  # the same $tmpdepfile.
+  lockdir=$base.d-lock
+  trap "
+    echo '$0: caught signal, cleaning up...' >&2
+    rmdir '$lockdir'
+    exit 1
+  " 1 2 13 15
+  numtries=100
+  i=$numtries
+  while test $i -gt 0; do
+    # mkdir is a portable test-and-set.
+    if mkdir "$lockdir" 2>/dev/null; then
+      # This process acquired the lock.
+      "$@" -MD
+      stat=$?
+      # Release the lock.
+      rmdir "$lockdir"
+      break
+    else
+      # If the lock is being held by a different process, wait
+      # until the winning process is done or we timeout.
+      while test -d "$lockdir" && test $i -gt 0; do
+        sleep 1
+        i=`expr $i - 1`
+      done
+    fi
+    i=`expr $i - 1`
+  done
+  trap - 1 2 13 15
+  if test $i -le 0; then
+    echo "$0: failed to acquire lock after $numtries attempts" >&2
+    echo "$0: check lockdir '$lockdir'" >&2
+    exit 1
+  fi
+
+  if test $stat -ne 0; then
+    rm -f "$tmpdepfile"
+    exit $stat
+  fi
+  rm -f "$depfile"
+  # Each line is of the form `foo.o: dependent.h',
+  # or `foo.o: dep1.h dep2.h \', or ` dep3.h dep4.h \'.
+  # Do two passes, one to just change these to
+  # `$object: dependent.h' and one to simply `dependent.h:'.
+  sed "s,^[^:]*:,$object :," < "$tmpdepfile" > "$depfile"
+  # Some versions of the HPUX 10.20 sed can't process this invocation
+  # correctly.  Breaking it into two sed invocations is a workaround.
+  sed 's,^[^:]*: \(.*\)$,\1,;s/^\\$//;/^$/d;/:$/d' < "$tmpdepfile" \
+    | sed -e 's/$/ :/' >> "$depfile"
+  rm -f "$tmpdepfile"
+  ;;
+
+hp2)
+  # The "hp" stanza above does not work with aCC (C++) and HP's ia64
+  # compilers, which have integrated preprocessors.  The correct option
+  # to use with these is +Maked; it writes dependencies to a file named
+  # 'foo.d', which lands next to the object file, wherever that
+  # happens to be.
+  # Much of this is similar to the tru64 case; see comments there.
+  set_dir_from  "$object"
+  set_base_from "$object"
+  if test "$libtool" = yes; then
+    tmpdepfile1=$dir$base.d
+    tmpdepfile2=$dir.libs/$base.d
+    "$@" -Wc,+Maked
+  else
+    tmpdepfile1=$dir$base.d
+    tmpdepfile2=$dir$base.d
+    "$@" +Maked
+  fi
+  stat=$?
+  if test $stat -ne 0; then
+     rm -f "$tmpdepfile1" "$tmpdepfile2"
+     exit $stat
+  fi
+
+  for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2"
+  do
+    test -f "$tmpdepfile" && break
+  done
+  if test -f "$tmpdepfile"; then
+    sed -e "s,^.*\.[$lower]*:,$object:," "$tmpdepfile" > "$depfile"
+    # Add 'dependent.h:' lines.
+    sed -ne '2,${
+               s/^ *//
+               s/ \\*$//
+               s/$/:/
+               p
+             }' "$tmpdepfile" >> "$depfile"
+  else
+    make_dummy_depfile
+  fi
+  rm -f "$tmpdepfile" "$tmpdepfile2"
+  ;;
+
+tru64)
+  # The Tru64 compiler uses -MD to generate dependencies as a side
+  # effect.  'cc -MD -o foo.o ...' puts the dependencies into 'foo.o.d'.
+  # At least on Alpha/Redhat 6.1, Compaq CCC V6.2-504 seems to put
+  # dependencies in 'foo.d' instead, so we check for that too.
+  # Subdirectories are respected.
+  set_dir_from  "$object"
+  set_base_from "$object"
+
+  if test "$libtool" = yes; then
+    # Libtool generates 2 separate objects for the 2 libraries.  These
+    # two compilations output dependencies in $dir.libs/$base.o.d and
+    # in $dir$base.o.d.  We have to check for both files, because
+    # one of the two compilations can be disabled.  We should prefer
+    # $dir$base.o.d over $dir.libs/$base.o.d because the latter is
+    # automatically cleaned when .libs/ is deleted, while ignoring
+    # the former would cause a distcleancheck panic.
+    tmpdepfile1=$dir$base.o.d          # libtool 1.5
+    tmpdepfile2=$dir.libs/$base.o.d    # Likewise.
+    tmpdepfile3=$dir.libs/$base.d      # Compaq CCC V6.2-504
+    "$@" -Wc,-MD
+  else
+    tmpdepfile1=$dir$base.d
+    tmpdepfile2=$dir$base.d
+    tmpdepfile3=$dir$base.d
+    "$@" -MD
+  fi
+
+  stat=$?
+  if test $stat -ne 0; then
+    rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3"
+    exit $stat
+  fi
+
+  for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3"
+  do
+    test -f "$tmpdepfile" && break
+  done
+  # Same post-processing that is required for AIX mode.
+  aix_post_process_depfile
+  ;;
+
+msvc7)
+  if test "$libtool" = yes; then
+    showIncludes=-Wc,-showIncludes
+  else
+    showIncludes=-showIncludes
+  fi
+  "$@" $showIncludes > "$tmpdepfile"
+  stat=$?
+  grep -v '^Note: including file: ' "$tmpdepfile"
+  if test $stat -ne 0; then
+    rm -f "$tmpdepfile"
+    exit $stat
+  fi
+  rm -f "$depfile"
+  echo "$object : \\" > "$depfile"
+  # The first sed program below extracts the file names and escapes
+  # backslashes for cygpath.  The second sed program outputs the file
+  # name when reading, but also accumulates all include files in the
+  # hold buffer in order to output them again at the end.  This only
+  # works with sed implementations that can handle large buffers.
+  sed < "$tmpdepfile" -n '
+/^Note: including file:  *\(.*\)/ {
+  s//\1/
+  s/\\/\\\\/g
+  p
+}' | $cygpath_u | sort -u | sed -n '
+s/ /\\ /g
+s/\(.*\)/'"$tab"'\1 \\/p
+s/.\(.*\) \\/\1:/
+H
+$ {
+  s/.*/'"$tab"'/
+  G
+  p
+}' >> "$depfile"
+  echo >> "$depfile" # make sure the fragment doesn't end with a backslash
+  rm -f "$tmpdepfile"
+  ;;
+
+msvc7msys)
+  # This case exists only to let depend.m4 do its work.  It works by
+  # looking at the text of this script.  This case will never be run,
+  # since it is checked for above.
+  exit 1
+  ;;
+
+#nosideeffect)
+  # This comment above is used by automake to tell side-effect
+  # dependency tracking mechanisms from slower ones.
+
+dashmstdout)
+  # Important note: in order to support this mode, a compiler *must*
+  # always write the preprocessed file to stdout, regardless of -o.
+  "$@" || exit $?
+
+  # Remove the call to Libtool.
+  if test "$libtool" = yes; then
+    while test "X$1" != 'X--mode=compile'; do
+      shift
+    done
+    shift
+  fi
+
+  # Remove '-o $object'.
+  IFS=" "
+  for arg
+  do
+    case $arg in
+    -o)
+      shift
+      ;;
+    $object)
+      shift
+      ;;
+    *)
+      set fnord "$@" "$arg"
+      shift # fnord
+      shift # $arg
+      ;;
+    esac
+  done
+
+  test -z "$dashmflag" && dashmflag=-M
+  # Require at least two characters before searching for ':'
+  # in the target name.  This is to cope with DOS-style filenames:
+  # a dependency such as 'c:/foo/bar' could be seen as target 'c' otherwise.
+  "$@" $dashmflag |
+    sed "s|^[$tab ]*[^:$tab ][^:][^:]*:[$tab ]*|$object: |" > "$tmpdepfile"
+  rm -f "$depfile"
+  cat < "$tmpdepfile" > "$depfile"
+  # Some versions of the HPUX 10.20 sed can't process this sed invocation
+  # correctly.  Breaking it into two sed invocations is a workaround.
+  tr ' ' "$nl" < "$tmpdepfile" \
+    | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \
+    | sed -e 's/$/ :/' >> "$depfile"
+  rm -f "$tmpdepfile"
+  ;;
+
+dashXmstdout)
+  # This case only exists to satisfy depend.m4.  It is never actually
+  # run, as this mode is specially recognized in the preamble.
+  exit 1
+  ;;
+
+makedepend)
+  "$@" || exit $?
+  # Remove any Libtool call
+  if test "$libtool" = yes; then
+    while test "X$1" != 'X--mode=compile'; do
+      shift
+    done
+    shift
+  fi
+  # X makedepend
+  shift
+  cleared=no eat=no
+  for arg
+  do
+    case $cleared in
+    no)
+      set ""; shift
+      cleared=yes ;;
+    esac
+    if test $eat = yes; then
+      eat=no
+      continue
+    fi
+    case "$arg" in
+    -D*|-I*)
+      set fnord "$@" "$arg"; shift ;;
+    # Strip any option that makedepend may not understand.  Remove
+    # the object too, otherwise makedepend will parse it as a source file.
+    -arch)
+      eat=yes ;;
+    -*|$object)
+      ;;
+    *)
+      set fnord "$@" "$arg"; shift ;;
+    esac
+  done
+  obj_suffix=`echo "$object" | sed 's/^.*\././'`
+  touch "$tmpdepfile"
+  ${MAKEDEPEND-makedepend} -o"$obj_suffix" -f"$tmpdepfile" "$@"
+  rm -f "$depfile"
+  # makedepend may prepend the VPATH from the source file name to the object.
+  # No need to regex-escape $object, excess matching of '.' is harmless.
+  sed "s|^.*\($object *:\)|\1|" "$tmpdepfile" > "$depfile"
+  # Some versions of the HPUX 10.20 sed can't process the last invocation
+  # correctly.  Breaking it into two sed invocations is a workaround.
+  sed '1,2d' "$tmpdepfile" \
+    | tr ' ' "$nl" \
+    | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \
+    | sed -e 's/$/ :/' >> "$depfile"
+  rm -f "$tmpdepfile" "$tmpdepfile".bak
+  ;;
+
+cpp)
+  # Important note: in order to support this mode, a compiler *must*
+  # always write the preprocessed file to stdout.
+  "$@" || exit $?
+
+  # Remove the call to Libtool.
+  if test "$libtool" = yes; then
+    while test "X$1" != 'X--mode=compile'; do
+      shift
+    done
+    shift
+  fi
+
+  # Remove '-o $object'.
+  IFS=" "
+  for arg
+  do
+    case $arg in
+    -o)
+      shift
+      ;;
+    $object)
+      shift
+      ;;
+    *)
+      set fnord "$@" "$arg"
+      shift # fnord
+      shift # $arg
+      ;;
+    esac
+  done
+
+  "$@" -E \
+    | sed -n -e '/^# [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \
+             -e '/^#line [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \
+    | sed '$ s: \\$::' > "$tmpdepfile"
+  rm -f "$depfile"
+  echo "$object : \\" > "$depfile"
+  cat < "$tmpdepfile" >> "$depfile"
+  sed < "$tmpdepfile" '/^$/d;s/^ //;s/ \\$//;s/$/ :/' >> "$depfile"
+  rm -f "$tmpdepfile"
+  ;;
+
+msvisualcpp)
+  # Important note: in order to support this mode, a compiler *must*
+  # always write the preprocessed file to stdout.
+  "$@" || exit $?
+
+  # Remove the call to Libtool.
+  if test "$libtool" = yes; then
+    while test "X$1" != 'X--mode=compile'; do
+      shift
+    done
+    shift
+  fi
+
+  IFS=" "
+  for arg
+  do
+    case "$arg" in
+    -o)
+      shift
+      ;;
+    $object)
+      shift
+      ;;
+    "-Gm"|"/Gm"|"-Gi"|"/Gi"|"-ZI"|"/ZI")
+        set fnord "$@"
+        shift
+        shift
+        ;;
+    *)
+        set fnord "$@" "$arg"
+        shift
+        shift
+        ;;
+    esac
+  done
+  "$@" -E 2>/dev/null |
+  sed -n '/^#line [0-9][0-9]* "\([^"]*\)"/ s::\1:p' | $cygpath_u | sort -u > "$tmpdepfile"
+  rm -f "$depfile"
+  echo "$object : \\" > "$depfile"
+  sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::'"$tab"'\1 \\:p' >> "$depfile"
+  echo "$tab" >> "$depfile"
+  sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::\1\::p' >> "$depfile"
+  rm -f "$tmpdepfile"
+  ;;
+
+msvcmsys)
+  # This case exists only to let depend.m4 do its work.  It works by
+  # looking at the text of this script.  This case will never be run,
+  # since it is checked for above.
+  exit 1
+  ;;
+
+none)
+  exec "$@"
+  ;;
+
+*)
+  echo "Unknown depmode $depmode" 1>&2
+  exit 1
+  ;;
+esac
+
+exit 0
+
+# Local Variables:
+# mode: shell-script
+# sh-indentation: 2
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC"
+# time-stamp-end: "; # UTC"
+# End:
diff --git a/devel.but b/devel.but
new file mode 100644 (file)
index 0000000..3ffb76b
--- /dev/null
+++ b/devel.but
@@ -0,0 +1,4779 @@
+\cfg{text-indent}{0}
+\cfg{text-width}{72}
+\cfg{text-title-align}{left}
+\cfg{text-chapter-align}{left}
+\cfg{text-chapter-numeric}{true}
+\cfg{text-chapter-suffix}{. }
+\cfg{text-chapter-underline}{-}
+\cfg{text-section-align}{0}{left}
+\cfg{text-section-numeric}{0}{true}
+\cfg{text-section-suffix}{0}{. }
+\cfg{text-section-underline}{0}{-}
+\cfg{text-section-align}{1}{left}
+\cfg{text-section-numeric}{1}{true}
+\cfg{text-section-suffix}{1}{. }
+\cfg{text-section-underline}{1}{-}
+\cfg{text-versionid}{0}
+
+\cfg{html-contents-filename}{index.html}
+\cfg{html-template-filename}{%k.html}
+\cfg{html-index-filename}{docindex.html}
+\cfg{html-leaf-level}{1}
+\cfg{html-contents-depth-0}{1}
+\cfg{html-contents-depth-1}{3}
+\cfg{html-leaf-contains-contents}{true}
+
+\define{dash} \u2013{-}
+
+\title Developer documentation for Simon Tatham's puzzle collection
+
+This is a guide to the internal structure of Simon Tatham's Portable
+Puzzle Collection (henceforth referred to simply as \q{Puzzles}),
+for use by anyone attempting to implement a new puzzle or port to a
+new platform.
+
+This guide is believed correct as of r6190. Hopefully it will be
+updated along with the code in future, but if not, I've at least
+left this version number in here so you can figure out what's
+changed by tracking commit comments from there onwards.
+
+\C{intro} Introduction
+
+The Puzzles code base is divided into four parts: a set of
+interchangeable front ends, a set of interchangeable back ends, a
+universal \q{middle end} which acts as a buffer between the two, and
+a bunch of miscellaneous utility functions. In the following
+sections I give some general discussion of each of these parts.
+
+\H{intro-frontend} Front end
+
+The front end is the non-portable part of the code: it's the bit
+that you replace completely when you port to a different platform.
+So it's responsible for all system calls, all GUI interaction, and
+anything else platform-specific.
+
+The current front ends in the main code base are for Windows, GTK
+and MacOS X; I also know of a third-party front end for PalmOS.
+
+The front end contains \cw{main()} or the local platform's
+equivalent. Top-level control over the application's execution flow
+belongs to the front end (it isn't, for example, a set of functions
+called by a universal \cw{main()} somewhere else).
+
+The front end has complete freedom to design the GUI for any given
+port of Puzzles. There is no centralised mechanism for maintaining
+the menu layout, for example. This has a cost in consistency (when I
+\e{do} want the same menu layout on more than one platform, I have
+to edit two pieces of code in parallel every time I make a change),
+but the advantage is that local GUI conventions can be conformed to
+and local constraints adapted to. For example, MacOS X has strict
+human interface guidelines which specify a different menu layout
+from the one I've used on Windows and GTK; there's nothing stopping
+the OS X front end from providing a menu layout consistent with
+those guidelines.
+
+Although the front end is mostly caller rather than the callee in
+its interactions with other parts of the code, it is required to
+implement a small API for other modules to call, mostly of drawing
+functions for games to use when drawing their graphics. The drawing
+API is documented in \k{drawing}; the other miscellaneous front end
+API functions are documented in \k{frontend-api}.
+
+\H{intro-backend} Back end
+
+A \q{back end}, in this collection, is synonymous with a \q{puzzle}.
+Each back end implements a different game.
+
+At the top level, a back end is simply a data structure, containing
+a few constants (flag words, preferred pixel size) and a large
+number of function pointers. Back ends are almost invariably callee
+rather than caller, which means there's a limitation on what a back
+end can do on its own initiative.
+
+The persistent state in a back end is divided into a number of data
+structures, which are used for different purposes and therefore
+likely to be switched around, changed without notice, and otherwise
+updated by the rest of the code. It is important when designing a
+back end to put the right pieces of data into the right structures,
+or standard midend-provided features (such as Undo) may fail to
+work.
+
+The functions and variables provided in the back end data structure
+are documented in \k{backend}.
+
+\H{intro-midend} Middle end
+
+Puzzles has a single and universal \q{middle end}. This code is
+common to all platforms and all games; it sits in between the front
+end and the back end and provides standard functionality everywhere.
+
+People adding new back ends or new front ends should generally not
+need to edit the middle end. On rare occasions there might be a
+change that can be made to the middle end to permit a new game to do
+something not currently anticipated by the middle end's present
+design; however, this is terribly easy to get wrong and should
+probably not be undertaken without consulting the primary maintainer
+(me). Patch submissions containing unannounced mid-end changes will
+be treated on their merits like any other patch; this is just a
+friendly warning that mid-end changes will need quite a lot of
+merits to make them acceptable.
+
+Functionality provided by the mid-end includes:
+
+\b Maintaining a list of game state structures and moving back and
+forth along that list to provide Undo and Redo.
+
+\b Handling timers (for move animations, flashes on completion, and
+in some cases actually timing the game).
+
+\b Handling the container format of game IDs: receiving them,
+picking them apart into parameters, description and/or random seed,
+and so on. The game back end need only handle the individual parts
+of a game ID (encoded parameters and encoded game description);
+everything else is handled centrally by the mid-end.
+
+\b Handling standard keystrokes and menu commands, such as \q{New
+Game}, \q{Restart Game} and \q{Quit}.
+
+\b Pre-processing mouse events so that the game back ends can rely
+on them arriving in a sensible order (no missing button-release
+events, no sudden changes of which button is currently pressed,
+etc).
+
+\b Handling the dialog boxes which ask the user for a game ID.
+
+\b Handling serialisation of entire games (for loading and saving a
+half-finished game to a disk file, or for handling application
+shutdown and restart on platforms such as PalmOS where state is
+expected to be saved).
+
+Thus, there's a lot of work done once by the mid-end so that
+individual back ends don't have to worry about it. All the back end
+has to do is cooperate in ensuring the mid-end can do its work
+properly.
+
+The API of functions provided by the mid-end to be called by the
+front end is documented in \k{midend}.
+
+\H{intro-utils} Miscellaneous utilities
+
+In addition to these three major structural components, the Puzzles
+code also contains a variety of utility modules usable by all of the
+above components. There is a set of functions to provide
+platform-independent random number generation; functions to make
+memory allocation easier; functions which implement a balanced tree
+structure to be used as necessary in complex algorithms; and a few
+other miscellaneous functions. All of these are documented in
+\k{utils}.
+
+\H{intro-structure} Structure of this guide
+
+There are a number of function call interfaces within Puzzles, and
+this guide will discuss each one in a chapter of its own. After
+that, \k{writing} discusses how to design new games, with some
+general design thoughts and tips.
+
+\C{backend} Interface to the back end
+
+This chapter gives a detailed discussion of the interface that each
+back end must implement.
+
+At the top level, each back end source file exports a single global
+symbol, which is a \c{const struct game} containing a large number
+of function pointers and a small amount of constant data. This
+structure is called by different names depending on what kind of
+platform the puzzle set is being compiled on:
+
+\b On platforms such as Windows and GTK, which build a separate
+binary for each puzzle, the game structure in every back end has the
+same name, \cq{thegame}; the front end refers directly to this name,
+so that compiling the same front end module against a different back
+end module builds a different puzzle.
+
+\b On platforms such as MacOS X and PalmOS, which build all the
+puzzles into a single monolithic binary, the game structure in each
+back end must have a different name, and there's a helper module
+\c{list.c} (constructed automatically by the same Perl script that
+builds the \cw{Makefile}s) which contains a complete list of those
+game structures.
+
+On the latter type of platform, source files may assume that the
+preprocessor symbol \c{COMBINED} has been defined. Thus, the usual
+code to declare the game structure looks something like this:
+
+\c #ifdef COMBINED
+\c #define thegame net    /* or whatever this game is called */
+\e                 iii    iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
+\c #endif
+\c 
+\c const struct game thegame = {
+\c     /* lots of structure initialisation in here */
+\e     iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
+\c };
+
+Game back ends must also internally define a number of data
+structures, for storing their various persistent state. This chapter
+will first discuss the nature and use of those structures, and then
+go on to give details of every element of the game structure.
+
+\H{backend-structs} Data structures
+
+Each game is required to define four separate data structures. This
+section discusses each one and suggests what sorts of things need to
+be put in it.
+
+\S{backend-game-params} \c{game_params}
+
+The \c{game_params} structure contains anything which affects the
+automatic generation of new puzzles. So if puzzle generation is
+parametrised in any way, those parameters need to be stored in
+\c{game_params}.
+
+Most puzzles currently in this collection are played on a grid of
+squares, meaning that the most obvious parameter is the grid size.
+Many puzzles have additional parameters; for example, Mines allows
+you to control the number of mines in the grid independently of its
+size, Net can be wrapping or non-wrapping, Solo has difficulty
+levels and symmetry settings, and so on.
+
+A simple rule for deciding whether a data item needs to go in
+\c{game_params} is: would the user expect to be able to control this
+data item from either the preset-game-types menu or the \q{Custom}
+game type configuration? If so, it's part of \c{game_params}.
+
+\c{game_params} structures are permitted to contain pointers to
+subsidiary data if they need to. The back end is required to provide
+functions to create and destroy \c{game_params}, and those functions
+can allocate and free additional memory if necessary. (It has not
+yet been necessary to do this in any puzzle so far, but the
+capability is there just in case.)
+
+\c{game_params} is also the only structure which the game's
+\cw{compute_size()} function may refer to; this means that any
+aspect of the game which affects the size of the window it needs to
+be drawn in must be stored in \c{game_params}. In particular, this
+imposes the fundamental limitation that random game generation may
+not have a random effect on the window size: game generation
+algorithms are constrained to work by starting from the grid size
+rather than generating it as an emergent phenomenon. (Although this
+is a restriction in theory, it has not yet seemed to be a problem.)
+
+\S{backend-game-state} \c{game_state}
+
+While the user is actually playing a puzzle, the \c{game_state}
+structure stores all the data corresponding to the current state of
+play.
+
+The mid-end keeps \c{game_state}s in a list, and adds to the list
+every time the player makes a move; the Undo and Redo functions step
+back and forth through that list.
+
+Therefore, a good means of deciding whether a data item needs to go
+in \c{game_state} is: would a player expect that data item to be
+restored on undo? If so, put it in \c{game_state}, and this will
+automatically happen without you having to lift a finger. If not
+\dash for example, the deaths counter in Mines is precisely
+something that does \e{not} want to be reset to its previous state
+on an undo \dash then you might have found a data item that needs to
+go in \c{game_ui} instead.
+
+During play, \c{game_state}s are often passed around without an
+accompanying \c{game_params} structure. Therefore, any information
+in \c{game_params} which is important during play (such as the grid
+size) must be duplicated within the \c{game_state}. One simple
+method of doing this is to have the \c{game_state} structure
+\e{contain} a \c{game_params} structure as one of its members,
+although this isn't obligatory if you prefer to do it another way.
+
+\S{backend-game-drawstate} \c{game_drawstate}
+
+\c{game_drawstate} carries persistent state relating to the current
+graphical contents of the puzzle window. The same \c{game_drawstate}
+is passed to every call to the game redraw function, so that it can
+remember what it has already drawn and what needs redrawing.
+
+A typical use for a \c{game_drawstate} is to have an array mirroring
+the array of grid squares in the \c{game_state}; then every time the
+redraw function was passed a \c{game_state}, it would loop over all
+the squares, and physically redraw any whose description in the
+\c{game_state} (i.e. what the square needs to look like when the
+redraw is completed) did not match its description in the
+\c{game_drawstate} (i.e. what the square currently looks like).
+
+\c{game_drawstate} is occasionally completely torn down and
+reconstructed by the mid-end, if the user somehow forces a full
+redraw. Therefore, no data should be stored in \c{game_drawstate}
+which is \e{not} related to the state of the puzzle window, because
+it might be unexpectedly destroyed.
+
+The back end provides functions to create and destroy
+\c{game_drawstate}, which means it can contain pointers to
+subsidiary allocated data if it needs to. A common thing to want to
+allocate in a \c{game_drawstate} is a \c{blitter}; see
+\k{drawing-blitter} for more on this subject.
+
+\S{backend-game-ui} \c{game_ui}
+
+\c{game_ui} contains whatever doesn't fit into the above three
+structures!
+
+A new \c{game_ui} is created when the user begins playing a new
+instance of a puzzle (i.e. during \q{New Game} or after entering a
+game ID etc). It persists until the user finishes playing that game
+and begins another one (or closes the window); in particular,
+\q{Restart Game} does \e{not} destroy the \c{game_ui}.
+
+\c{game_ui} is useful for implementing user-interface state which is
+not part of \c{game_state}. Common examples are keyboard control
+(you wouldn't want to have to separately Undo through every cursor
+motion) and mouse dragging. See \k{writing-keyboard-cursor} and
+\k{writing-howto-dragging}, respectively, for more details.
+
+Another use for \c{game_ui} is to store highly persistent data such
+as the Mines death counter. This is conceptually rather different:
+where the Net cursor position was \e{not important enough} to
+preserve for the player to restore by Undo, the Mines death counter
+is \e{too important} to permit the player to revert by Undo!
+
+A final use for \c{game_ui} is to pass information to the redraw
+function about recent changes to the game state. This is used in
+Mines, for example, to indicate whether a requested \q{flash} should
+be a white flash for victory or a red flash for defeat; see
+\k{writing-flash-types}.
+
+\H{backend-simple} Simple data in the back end
+
+In this section I begin to discuss each individual element in the
+back end structure. To begin with, here are some simple
+self-contained data elements.
+
+\S{backend-name} \c{name}
+
+\c const char *name;
+
+This is a simple ASCII string giving the name of the puzzle. This
+name will be used in window titles, in game selection menus on
+monolithic platforms, and anywhere else that the front end needs to
+know the name of a game.
+
+\S{backend-winhelp} \c{winhelp_topic}
+
+\c const char *winhelp_topic;
+
+This member is used on Windows only, to provide online help.
+Although the Windows front end provides a separate binary for each
+puzzle, it has a single monolithic help file; so when a user selects
+\q{Help} from the menu, the program needs to open the help file and
+jump to the chapter describing that particular puzzle.
+
+Therefore, each chapter in \c{puzzles.but} is labelled with a
+\e{help topic} name, similar to this:
+
+\c \cfg{winhelp-topic}{games.net}
+
+And then the corresponding game back end encodes the topic string
+(here \cq{games.net}) in the \c{winhelp_topic} element of the game
+structure.
+
+\H{backend-params} Handling game parameter sets
+
+In this section I present the various functions which handle the
+\c{game_params} structure.
+
+\S{backend-default-params} \cw{default_params()}
+
+\c game_params *(*default_params)(void);
+
+This function allocates a new \c{game_params} structure, fills it
+with the default values, and returns a pointer to it.
+
+\S{backend-fetch-preset} \cw{fetch_preset()}
+
+\c int (*fetch_preset)(int i, char **name, game_params **params);
+
+This function is used to populate the \q{Type} menu, which provides
+a list of conveniently accessible preset parameters for most games.
+
+The function is called with \c{i} equal to the index of the preset
+required (numbering from zero). It returns \cw{FALSE} if that preset
+does not exist (if \c{i} is less than zero or greater than the
+largest preset index). Otherwise, it sets \c{*params} to point at a
+newly allocated \c{game_params} structure containing the preset
+information, sets \c{*name} to point at a newly allocated C string
+containing the preset title (to go on the \q{Type} menu), and
+returns \cw{TRUE}.
+
+If the game does not wish to support any presets at all, this
+function is permitted to return \cw{FALSE} always.
+
+\S{backend-encode-params} \cw{encode_params()}
+
+\c char *(*encode_params)(const game_params *params, int full);
+
+The job of this function is to take a \c{game_params}, and encode it
+in a string form for use in game IDs. The return value must be a
+newly allocated C string, and \e{must} not contain a colon or a hash
+(since those characters are used to mark the end of the parameter
+section in a game ID).
+
+Ideally, it should also not contain any other potentially
+controversial punctuation; bear in mind when designing a string
+parameter format that it will probably be used on both Windows and
+Unix command lines under a variety of exciting shell quoting and
+metacharacter rules. Sticking entirely to alphanumerics is the
+safest thing; if you really need punctuation, you can probably get
+away with commas, periods or underscores without causing anybody any
+major inconvenience. If you venture far beyond that, you're likely
+to irritate \e{somebody}.
+
+(At the time of writing this, all existing games have purely
+alphanumeric string parameter formats. Usually these involve a
+letter denoting a parameter, followed optionally by a number giving
+the value of that parameter, with a few mandatory parts at the
+beginning such as numeric width and height separated by \cq{x}.)
+
+If the \c{full} parameter is \cw{TRUE}, this function should encode
+absolutely everything in the \c{game_params}, such that a subsequent
+call to \cw{decode_params()} (\k{backend-decode-params}) will yield
+an identical structure. If \c{full} is \cw{FALSE}, however, you
+should leave out anything which is not necessary to describe a
+\e{specific puzzle instance}, i.e. anything which only takes effect
+when a new puzzle is \e{generated}. For example, the Solo
+\c{game_params} includes a difficulty rating used when constructing
+new puzzles; but a Solo game ID need not explicitly include the
+difficulty, since to describe a puzzle once generated it's
+sufficient to give the grid dimensions and the location and contents
+of the clue squares. (Indeed, one might very easily type in a puzzle
+out of a newspaper without \e{knowing} what its difficulty level is
+in Solo's terminology.) Therefore, Solo's \cw{encode_params()} only
+encodes the difficulty level if \c{full} is set.
+
+\S{backend-decode-params} \cw{decode_params()}
+
+\c void (*decode_params)(game_params *params, char const *string);
+
+This function is the inverse of \cw{encode_params()}
+(\k{backend-encode-params}). It parses the supplied string and fills
+in the supplied \c{game_params} structure. Note that the structure
+will \e{already} have been allocated: this function is not expected
+to create a \e{new} \c{game_params}, but to modify an existing one.
+
+This function can receive a string which only encodes a subset of
+the parameters. The most obvious way in which this can happen is if
+the string was constructed by \cw{encode_params()} with its \c{full}
+parameter set to \cw{FALSE}; however, it could also happen if the
+user typed in a parameter set manually and missed something out. Be
+prepared to deal with a wide range of possibilities.
+
+When dealing with a parameter which is not specified in the input
+string, what to do requires a judgment call on the part of the
+programmer. Sometimes it makes sense to adjust other parameters to
+bring them into line with the new ones. In Mines, for example, you
+would probably not want to keep the same mine count if the user
+dropped the grid size and didn't specify one, since you might easily
+end up with more mines than would actually fit in the grid! On the
+other hand, sometimes it makes sense to leave the parameter alone: a
+Solo player might reasonably expect to be able to configure size and
+difficulty independently of one another.
+
+This function currently has no direct means of returning an error if
+the string cannot be parsed at all. However, the returned
+\c{game_params} is almost always subsequently passed to
+\cw{validate_params()} (\k{backend-validate-params}), so if you
+really want to signal parse errors, you could always have a \c{char
+*} in your parameters structure which stored an error message, and
+have \cw{validate_params()} return it if it is non-\cw{NULL}.
+
+\S{backend-free-params} \cw{free_params()}
+
+\c void (*free_params)(game_params *params);
+
+This function frees a \c{game_params} structure, and any subsidiary
+allocations contained within it.
+
+\S{backend-dup-params} \cw{dup_params()}
+
+\c game_params *(*dup_params)(const game_params *params);
+
+This function allocates a new \c{game_params} structure and
+initialises it with an exact copy of the information in the one
+provided as input. It returns a pointer to the new duplicate.
+
+\S{backend-can-configure} \c{can_configure}
+
+\c int can_configure;
+
+This boolean data element is set to \cw{TRUE} if the back end
+supports custom parameter configuration via a dialog box. If it is
+\cw{TRUE}, then the functions \cw{configure()} and
+\cw{custom_params()} are expected to work. See \k{backend-configure}
+and \k{backend-custom-params} for more details.
+
+\S{backend-configure} \cw{configure()}
+
+\c config_item *(*configure)(const game_params *params);
+
+This function is called when the user requests a dialog box for
+custom parameter configuration. It returns a newly allocated array
+of \cw{config_item} structures, describing the GUI elements required
+in the dialog box. The array should have one more element than the
+number of controls, since it is terminated with a \cw{C_END} marker
+(see below). Each array element describes the control together with
+its initial value; the front end will modify the value fields and
+return the updated array to \cw{custom_params()} (see
+\k{backend-custom-params}).
+
+The \cw{config_item} structure contains the following elements:
+
+\c char *name;
+\c int type;
+\c char *sval;
+\c int ival;
+
+\c{name} is an ASCII string giving the textual label for a GUI
+control. It is \e{not} expected to be dynamically allocated.
+
+\c{type} contains one of a small number of \c{enum} values defining
+what type of control is being described. The meaning of the \c{sval}
+and \c{ival} fields depends on the value in \c{type}. The valid
+values are:
+
+\dt \c{C_STRING}
+
+\dd Describes a text input box. (This is also used for numeric
+input. The back end does not bother informing the front end that the
+box is numeric rather than textual; some front ends do have the
+capacity to take this into account, but I decided it wasn't worth
+the extra complexity in the interface.) For this type, \c{ival} is
+unused, and \c{sval} contains a dynamically allocated string
+representing the contents of the input box.
+
+\dt \c{C_BOOLEAN}
+
+\dd Describes a simple checkbox. For this type, \c{sval} is unused,
+and \c{ival} is \cw{TRUE} or \cw{FALSE}.
+
+\dt \c{C_CHOICES}
+
+\dd Describes a drop-down list presenting one of a small number of
+fixed choices. For this type, \c{sval} contains a list of strings
+describing the choices; the very first character of \c{sval} is used
+as a delimiter when processing the rest (so that the strings
+\cq{:zero:one:two}, \cq{!zero!one!two} and \cq{xzeroxonextwo} all
+define a three-element list containing \cq{zero}, \cq{one} and
+\cq{two}). \c{ival} contains the index of the currently selected
+element, numbering from zero (so that in the above example, 0 would
+mean \cq{zero} and 2 would mean \cq{two}).
+
+\lcont{
+
+Note that for this control type, \c{sval} is \e{not} dynamically
+allocated, whereas it was for \c{C_STRING}.
+
+}
+
+\dt \c{C_END}
+
+\dd Marks the end of the array of \c{config_item}s. All other fields
+are unused.
+
+The array returned from this function is expected to have filled in
+the initial values of all the controls according to the input
+\c{game_params} structure.
+
+If the game's \c{can_configure} flag is set to \cw{FALSE}, this
+function is never called and need not do anything at all.
+
+\S{backend-custom-params} \cw{custom_params()}
+
+\c game_params *(*custom_params)(const config_item *cfg);
+
+This function is the counterpart to \cw{configure()}
+(\k{backend-configure}). It receives as input an array of
+\c{config_item}s which was originally created by \cw{configure()},
+but in which the control values have since been changed in
+accordance with user input. Its function is to read the new values
+out of the controls and return a newly allocated \c{game_params}
+structure representing the user's chosen parameter set.
+
+(The front end will have modified the controls' \e{values}, but
+there will still always be the same set of controls, in the same
+order, as provided by \cw{configure()}. It is not necessary to check
+the \c{name} and \c{type} fields, although you could use
+\cw{assert()} if you were feeling energetic.)
+
+This function is not expected to (and indeed \e{must not}) free the
+input \c{config_item} array. (If the parameters fail to validate,
+the dialog box will stay open.)
+
+If the game's \c{can_configure} flag is set to \cw{FALSE}, this
+function is never called and need not do anything at all.
+
+\S{backend-validate-params} \cw{validate_params()}
+
+\c char *(*validate_params)(const game_params *params, int full);
+
+This function takes a \c{game_params} structure as input, and checks
+that the parameters described in it fall within sensible limits. (At
+the very least, grid dimensions should almost certainly be strictly
+positive, for example.)
+
+Return value is \cw{NULL} if no problems were found, or
+alternatively a (non-dynamically-allocated) ASCII string describing
+the error in human-readable form.
+
+If the \c{full} parameter is set, full validation should be
+performed: any set of parameters which would not permit generation
+of a sensible puzzle should be faulted. If \c{full} is \e{not} set,
+the implication is that these parameters are not going to be used
+for \e{generating} a puzzle; so parameters which can't even sensibly
+\e{describe} a valid puzzle should still be faulted, but parameters
+which only affect puzzle generation should not be.
+
+(The \c{full} option makes a difference when parameter combinations
+are non-orthogonal. For example, Net has a boolean option
+controlling whether it enforces a unique solution; it turns out that
+it's impossible to generate a uniquely soluble puzzle with wrapping
+walls and width 2, so \cw{validate_params()} will complain if you
+ask for one. However, if the user had just been playing a unique
+wrapping puzzle of a more sensible width, and then pastes in a game
+ID acquired from somebody else which happens to describe a
+\e{non}-unique wrapping width-2 puzzle, then \cw{validate_params()}
+will be passed a \c{game_params} containing the width and wrapping
+settings from the new game ID and the uniqueness setting from the
+old one. This would be faulted, if it weren't for the fact that
+\c{full} is not set during this call, so Net ignores the
+inconsistency. The resulting \c{game_params} is never subsequently
+used to generate a puzzle; this is a promise made by the mid-end
+when it asks for a non-full validation.)
+
+\H{backend-descs} Handling game descriptions
+
+In this section I present the functions that deal with a textual
+description of a puzzle, i.e. the part that comes after the colon in
+a descriptive-format game ID.
+
+\S{backend-new-desc} \cw{new_desc()}
+
+\c char *(*new_desc)(const game_params *params, random_state *rs,
+\c                   char **aux, int interactive);
+
+This function is where all the really hard work gets done. This is
+the function whose job is to randomly generate a new puzzle,
+ensuring solubility and uniqueness as appropriate.
+
+As input it is given a \c{game_params} structure and a random state
+(see \k{utils-random} for the random number API). It must invent a
+puzzle instance, encode it in string form, and return a dynamically
+allocated C string containing that encoding.
+
+Additionally, it may return a second dynamically allocated string in
+\c{*aux}. (If it doesn't want to, then it can leave that parameter
+completely alone; it isn't required to set it to \cw{NULL}, although
+doing so is harmless.) That string, if present, will be passed to
+\cw{solve()} (\k{backend-solve}) later on; so if the puzzle is
+generated in such a way that a solution is known, then information
+about that solution can be saved in \c{*aux} for \cw{solve()} to
+use.
+
+The \c{interactive} parameter should be ignored by almost all
+puzzles. Its purpose is to distinguish between generating a puzzle
+within a GUI context for immediate play, and generating a puzzle in
+a command-line context for saving to be played later. The only
+puzzle that currently uses this distinction (and, I fervently hope,
+the only one which will \e{ever} need to use it) is Mines, which
+chooses a random first-click location when generating puzzles
+non-interactively, but which waits for the user to place the first
+click when interactive. If you think you have come up with another
+puzzle which needs to make use of this parameter, please think for
+at least ten minutes about whether there is \e{any} alternative!
+
+Note that game description strings are not required to contain an
+encoding of parameters such as grid size; a game description is
+never separated from the \c{game_params} it was generated with, so
+any information contained in that structure need not be encoded
+again in the game description.
+
+\S{backend-validate-desc} \cw{validate_desc()}
+
+\c char *(*validate_desc)(const game_params *params, const char *desc);
+
+This function is given a game description, and its job is to
+validate that it describes a puzzle which makes sense.
+
+To some extent it's up to the user exactly how far they take the
+phrase \q{makes sense}; there are no particularly strict rules about
+how hard the user is permitted to shoot themself in the foot when
+typing in a bogus game description by hand. (For example, Rectangles
+will not verify that the sum of all the numbers in the grid equals
+the grid's area. So a user could enter a puzzle which was provably
+not soluble, and the program wouldn't complain; there just wouldn't
+happen to be any sequence of moves which solved it.)
+
+The one non-negotiable criterion is that any game description which
+makes it through \cw{validate_desc()} \e{must not} subsequently
+cause a crash or an assertion failure when fed to \cw{new_game()}
+and thence to the rest of the back end.
+
+The return value is \cw{NULL} on success, or a
+non-dynamically-allocated C string containing an error message.
+
+\S{backend-new-game} \cw{new_game()}
+
+\c game_state *(*new_game)(midend *me, const game_params *params,
+\c                         const char *desc);
+
+This function takes a game description as input, together with its
+accompanying \c{game_params}, and constructs a \c{game_state}
+describing the initial state of the puzzle. It returns a newly
+allocated \c{game_state} structure.
+
+Almost all puzzles should ignore the \c{me} parameter. It is
+required by Mines, which needs it for later passing to
+\cw{midend_supersede_game_desc()} (see \k{backend-supersede}) once
+the user has placed the first click. I fervently hope that no other
+puzzle will be awkward enough to require it, so everybody else
+should ignore it. As with the \c{interactive} parameter in
+\cw{new_desc()} (\k{backend-new-desc}), if you think you have a
+reason to need this parameter, please try very hard to think of an
+alternative approach!
+
+\H{backend-states} Handling game states
+
+This section describes the functions which create and destroy
+\c{game_state} structures.
+
+(Well, except \cw{new_game()}, which is in \k{backend-new-game}
+instead of under here; but it deals with game descriptions \e{and}
+game states and it had to go in one section or the other.)
+
+\S{backend-dup-game} \cw{dup_game()}
+
+\c game_state *(*dup_game)(const game_state *state);
+
+This function allocates a new \c{game_state} structure and
+initialises it with an exact copy of the information in the one
+provided as input. It returns a pointer to the new duplicate.
+
+\S{backend-free-game} \cw{free_game()}
+
+\c void (*free_game)(game_state *state);
+
+This function frees a \c{game_state} structure, and any subsidiary
+allocations contained within it.
+
+\H{backend-ui} Handling \c{game_ui}
+
+\S{backend-new-ui} \cw{new_ui()}
+
+\c game_ui *(*new_ui)(const game_state *state);
+
+This function allocates and returns a new \c{game_ui} structure for
+playing a particular puzzle. It is passed a pointer to the initial
+\c{game_state}, in case it needs to refer to that when setting up
+the initial values for the new game.
+
+\S{backend-free-ui} \cw{free_ui()}
+
+\c void (*free_ui)(game_ui *ui);
+
+This function frees a \c{game_ui} structure, and any subsidiary
+allocations contained within it.
+
+\S{backend-encode-ui} \cw{encode_ui()}
+
+\c char *(*encode_ui)(const game_ui *ui);
+
+This function encodes any \e{important} data in a \c{game_ui}
+structure in string form. It is only called when saving a
+half-finished game to a file.
+
+It should be used sparingly. Almost all data in a \c{game_ui} is not
+important enough to save. The location of the keyboard-controlled
+cursor, for example, can be reset to a default position on reloading
+the game without impacting the user experience. If the user should
+somehow manage to save a game while a mouse drag was in progress,
+then discarding that mouse drag would be an outright \e{feature}.
+
+A typical thing that \e{would} be worth encoding in this function is
+the Mines death counter: it's in the \c{game_ui} rather than the
+\c{game_state} because it's too important to allow the user to
+revert it by using Undo, and therefore it's also too important to
+allow the user to revert it by saving and reloading. (Of course, the
+user could edit the save file by hand... But if the user is \e{that}
+determined to cheat, they could just as easily modify the game's
+source.)
+
+\S{backend-decode-ui} \cw{decode_ui()}
+
+\c void (*decode_ui)(game_ui *ui, const char *encoding);
+
+This function parses a string previously output by \cw{encode_ui()},
+and writes the decoded data back into the provided \c{game_ui}
+structure.
+
+\S{backend-changed-state} \cw{changed_state()}
+
+\c void (*changed_state)(game_ui *ui, const game_state *oldstate,
+\c                       const game_state *newstate);
+
+This function is called by the mid-end whenever the current game
+state changes, for any reason. Those reasons include:
+
+\b a fresh move being made by \cw{interpret_move()} and
+\cw{execute_move()}
+
+\b a solve operation being performed by \cw{solve()} and
+\cw{execute_move()}
+
+\b the user moving back and forth along the undo list by means of
+the Undo and Redo operations
+
+\b the user selecting Restart to go back to the initial game state.
+
+The job of \cw{changed_state()} is to update the \c{game_ui} for
+consistency with the new game state, if any update is necessary. For
+example, Same Game stores data about the currently selected tile
+group in its \c{game_ui}, and this data is intrinsically related to
+the game state it was derived from. So it's very likely to become
+invalid when the game state changes; thus, Same Game's
+\cw{changed_state()} function clears the current selection whenever
+it is called.
+
+When \cw{anim_length()} or \cw{flash_length()} are called, you can
+be sure that there has been a previous call to \cw{changed_state()}.
+So \cw{changed_state()} can set up data in the \c{game_ui} which will
+be read by \cw{anim_length()} and \cw{flash_length()}, and those
+functions will not have to worry about being called without the data
+having been initialised.
+
+\H{backend-moves} Making moves
+
+This section describes the functions which actually make moves in
+the game: that is, the functions which process user input and end up
+producing new \c{game_state}s.
+
+\S{backend-interpret-move} \cw{interpret_move()}
+
+\c char *(*interpret_move)(const game_state *state, game_ui *ui,
+\c                         const game_drawstate *ds,
+\c                         int x, int y, int button);
+
+This function receives user input and processes it. Its input
+parameters are the current \c{game_state}, the current \c{game_ui}
+and the current \c{game_drawstate}, plus details of the input event.
+\c{button} is either an ASCII value or a special code (listed below)
+indicating an arrow or function key or a mouse event; when
+\c{button} is a mouse event, \c{x} and \c{y} contain the pixel
+coordinates of the mouse pointer relative to the top left of the
+puzzle's drawing area.
+
+(The pointer to the \c{game_drawstate} is marked \c{const}, because
+\c{interpret_move} should not write to it. The normal use of that
+pointer will be to read the game's tile size parameter in order to
+divide mouse coordinates by it.)
+
+\cw{interpret_move()} may return in three different ways:
+
+\b Returning \cw{NULL} indicates that no action whatsoever occurred
+in response to the input event; the puzzle was not interested in it
+at all.
+
+\b Returning the empty string (\cw{""}) indicates that the input
+event has resulted in a change being made to the \c{game_ui} which
+will require a redraw of the game window, but that no actual
+\e{move} was made (i.e. no new \c{game_state} needs to be created).
+
+\b Returning anything else indicates that a move was made and that a
+new \c{game_state} must be created. However, instead of actually
+constructing a new \c{game_state} itself, this function is required
+to return a string description of the details of the move. This
+string will be passed to \cw{execute_move()}
+(\k{backend-execute-move}) to actually create the new
+\c{game_state}. (Encoding moves as strings in this way means that
+the mid-end can keep the strings as well as the game states, and the
+strings can be written to disk when saving the game and fed to
+\cw{execute_move()} again on reloading.)
+
+The return value from \cw{interpret_move()} is expected to be
+dynamically allocated if and only if it is not either \cw{NULL}
+\e{or} the empty string.
+
+After this function is called, the back end is permitted to rely on
+some subsequent operations happening in sequence:
+
+\b \cw{execute_move()} will be called to convert this move
+description into a new \c{game_state}
+
+\b \cw{changed_state()} will be called with the new \c{game_state}.
+
+This means that if \cw{interpret_move()} needs to do updates to the
+\c{game_ui} which are easier to perform by referring to the new
+\c{game_state}, it can safely leave them to be done in
+\cw{changed_state()} and not worry about them failing to happen.
+
+(Note, however, that \cw{execute_move()} may \e{also} be called in
+other circumstances. It is only \cw{interpret_move()} which can rely
+on a subsequent call to \cw{changed_state()}.)
+
+The special key codes supported by this function are:
+
+\dt \cw{LEFT_BUTTON}, \cw{MIDDLE_BUTTON}, \cw{RIGHT_BUTTON}
+
+\dd Indicate that one of the mouse buttons was pressed down.
+
+\dt \cw{LEFT_DRAG}, \cw{MIDDLE_DRAG}, \cw{RIGHT_DRAG}
+
+\dd Indicate that the mouse was moved while one of the mouse buttons
+was still down. The mid-end guarantees that when one of these events
+is received, it will always have been preceded by a button-down
+event (and possibly other drag events) for the same mouse button,
+and no event involving another mouse button will have appeared in
+between.
+
+\dt \cw{LEFT_RELEASE}, \cw{MIDDLE_RELEASE}, \cw{RIGHT_RELEASE}
+
+\dd Indicate that a mouse button was released.  The mid-end
+guarantees that when one of these events is received, it will always
+have been preceded by a button-down event (and possibly some drag
+events) for the same mouse button, and no event involving another
+mouse button will have appeared in between.
+
+\dt \cw{CURSOR_UP}, \cw{CURSOR_DOWN}, \cw{CURSOR_LEFT},
+\cw{CURSOR_RIGHT}
+
+\dd Indicate that an arrow key was pressed.
+
+\dt \cw{CURSOR_SELECT}
+
+\dd On platforms which have a prominent \q{select} button alongside
+their cursor keys, indicates that that button was pressed.
+
+In addition, there are some modifiers which can be bitwise-ORed into
+the \c{button} parameter:
+
+\dt \cw{MOD_CTRL}, \cw{MOD_SHFT}
+
+\dd These indicate that the Control or Shift key was pressed
+alongside the key. They only apply to the cursor keys, not to mouse
+buttons or anything else.
+
+\dt \cw{MOD_NUM_KEYPAD}
+
+\dd This applies to some ASCII values, and indicates that the key
+code was input via the numeric keypad rather than the main keyboard.
+Some puzzles may wish to treat this differently (for example, a
+puzzle might want to use the numeric keypad as an eight-way
+directional pad), whereas others might not (a game involving numeric
+input probably just wants to treat the numeric keypad as numbers).
+
+\dt \cw{MOD_MASK}
+
+\dd This mask is the bitwise OR of all the available modifiers; you
+can bitwise-AND with \cw{~MOD_MASK} to strip all the modifiers off
+any input value.
+
+\S{backend-execute-move} \cw{execute_move()}
+
+\c game_state *(*execute_move)(const game_state *state, char *move);
+
+This function takes an input \c{game_state} and a move string as
+output from \cw{interpret_move()}. It returns a newly allocated
+\c{game_state} which contains the result of applying the specified
+move to the input game state.
+
+This function may return \cw{NULL} if it cannot parse the move
+string (and this is definitely preferable to crashing or failing an
+assertion, since one way this can happen is if loading a corrupt
+save file). However, it must not return \cw{NULL} for any move
+string that really was output from \cw{interpret_move()}: this is
+punishable by assertion failure in the mid-end.
+
+\S{backend-can-solve} \c{can_solve}
+
+\c int can_solve;
+
+This boolean field is set to \cw{TRUE} if the game's \cw{solve()}
+function does something. If it's set to \cw{FALSE}, the game will
+not even offer the \q{Solve} menu option.
+
+\S{backend-solve} \cw{solve()}
+
+\c char *(*solve)(const game_state *orig, const game_state *curr,
+\c                const char *aux, char **error);
+
+This function is called when the user selects the \q{Solve} option
+from the menu.
+
+It is passed two input game states: \c{orig} is the game state from
+the very start of the puzzle, and \c{curr} is the current one.
+(Different games find one or other or both of these convenient.) It
+is also passed the \c{aux} string saved by \cw{new_desc()}
+(\k{backend-new-desc}), in case that encodes important information
+needed to provide the solution.
+
+If this function is unable to produce a solution (perhaps, for
+example, the game has no in-built solver so it can only solve
+puzzles it invented internally and has an \c{aux} string for) then
+it may return \cw{NULL}. If it does this, it must also set
+\c{*error} to an error message to be presented to the user (such as
+\q{Solution not known for this puzzle}); that error message is not
+expected to be dynamically allocated.
+
+If this function \e{does} produce a solution, it returns a move string
+suitable for feeding to \cw{execute_move()}
+(\k{backend-execute-move}). Like a (non-empty) string returned from
+\cw{interpret_move()}, the returned string should be dynamically
+allocated.
+
+\H{backend-drawing} Drawing the game graphics
+
+This section discusses the back end functions that deal with
+drawing.
+
+\S{backend-new-drawstate} \cw{new_drawstate()}
+
+\c game_drawstate *(*new_drawstate)(drawing *dr,
+\c                                  const game_state *state);
+
+This function allocates and returns a new \c{game_drawstate}
+structure for drawing a particular puzzle. It is passed a pointer to
+a \c{game_state}, in case it needs to refer to that when setting up
+any initial data.
+
+This function may not rely on the puzzle having been newly started;
+a new draw state can be constructed at any time if the front end
+requests a forced redraw. For games like Pattern, in which initial
+game states are much simpler than general ones, this might be
+important to keep in mind.
+
+The parameter \c{dr} is a drawing object (see \k{drawing}) which the
+function might need to use to allocate blitters. (However, this
+isn't recommended; it's usually more sensible to wait to allocate a
+blitter until \cw{set_size()} is called, because that way you can
+tailor it to the scale at which the puzzle is being drawn.)
+
+\S{backend-free-drawstate} \cw{free_drawstate()}
+
+\c void (*free_drawstate)(drawing *dr, game_drawstate *ds);
+
+This function frees a \c{game_drawstate} structure, and any
+subsidiary allocations contained within it.
+
+The parameter \c{dr} is a drawing object (see \k{drawing}), which
+might be required if you are freeing a blitter.
+
+\S{backend-preferred-tilesize} \c{preferred_tilesize}
+
+\c int preferred_tilesize;
+
+Each game is required to define a single integer parameter which
+expresses, in some sense, the scale at which it is drawn. This is
+described in the APIs as \cq{tilesize}, since most puzzles are on a
+square (or possibly triangular or hexagonal) grid and hence a
+sensible interpretation of this parameter is to define it as the
+size of one grid tile in pixels; however, there's no actual
+requirement that the \q{tile size} be proportional to the game
+window size. Window size is required to increase monotonically with
+\q{tile size}, however.
+
+The data element \c{preferred_tilesize} indicates the tile size
+which should be used in the absence of a good reason to do otherwise
+(such as the screen being too small, or the user explicitly
+requesting a resize if that ever gets implemented).
+
+\S{backend-compute-size} \cw{compute_size()}
+
+\c void (*compute_size)(const game_params *params, int tilesize,
+\c                      int *x, int *y);
+
+This function is passed a \c{game_params} structure and a tile size.
+It returns, in \c{*x} and \c{*y}, the size in pixels of the drawing
+area that would be required to render a puzzle with those parameters
+at that tile size.
+
+\S{backend-set-size} \cw{set_size()}
+
+\c void (*set_size)(drawing *dr, game_drawstate *ds,
+\c                  const game_params *params, int tilesize);
+
+This function is responsible for setting up a \c{game_drawstate} to
+draw at a given tile size. Typically this will simply involve
+copying the supplied \c{tilesize} parameter into a \c{tilesize}
+field inside the draw state; for some more complex games it might
+also involve setting up other dimension fields, or possibly
+allocating a blitter (see \k{drawing-blitter}).
+
+The parameter \c{dr} is a drawing object (see \k{drawing}), which is
+required if a blitter needs to be allocated.
+
+Back ends may assume (and may enforce by assertion) that this
+function will be called at most once for any \c{game_drawstate}. If
+a puzzle needs to be redrawn at a different size, the mid-end will
+create a fresh drawstate.
+
+\S{backend-colours} \cw{colours()}
+
+\c float *(*colours)(frontend *fe, int *ncolours);
+
+This function is responsible for telling the front end what colours
+the puzzle will need to draw itself.
+
+It returns the number of colours required in \c{*ncolours}, and the
+return value from the function itself is a dynamically allocated
+array of three times that many \c{float}s, containing the red, green
+and blue components of each colour respectively as numbers in the
+range [0,1].
+
+The second parameter passed to this function is a front end handle.
+The only things it is permitted to do with this handle are to call
+the front-end function called \cw{frontend_default_colour()} (see
+\k{frontend-default-colour}) or the utility function called
+\cw{game_mkhighlight()} (see \k{utils-game-mkhighlight}). (The
+latter is a wrapper on the former, so front end implementors only
+need to provide \cw{frontend_default_colour()}.) This allows
+\cw{colours()} to take local configuration into account when
+deciding on its own colour allocations. Most games use the front
+end's default colour as their background, apart from a few which
+depend on drawing relief highlights so they adjust the background
+colour if it's too light for highlights to show up against it.
+
+Note that the colours returned from this function are for
+\e{drawing}, not for printing. Printing has an entirely different
+colour allocation policy.
+
+\S{backend-anim-length} \cw{anim_length()}
+
+\c float (*anim_length)(const game_state *oldstate,
+\c                      const game_state *newstate,
+\c                      int dir, game_ui *ui);
+
+This function is called when a move is made, undone or redone. It is
+given the old and the new \c{game_state}, and its job is to decide
+whether the transition between the two needs to be animated or can
+be instant.
+
+\c{oldstate} is the state that was current until this call;
+\c{newstate} is the state that will be current after it. \c{dir}
+specifies the chronological order of those states: if it is
+positive, then the transition is the result of a move or a redo (and
+so \c{newstate} is the later of the two moves), whereas if it is
+negative then the transition is the result of an undo (so that
+\c{newstate} is the \e{earlier} move).
+
+If this function decides the transition should be animated, it
+returns the desired length of the animation in seconds. If not, it
+returns zero.
+
+State changes as a result of a Restart operation are never animated;
+the mid-end will handle them internally and never consult this
+function at all. State changes as a result of Solve operations are
+also not animated by default, although you can change this for a
+particular game by setting a flag in \c{flags} (\k{backend-flags}).
+
+The function is also passed a pointer to the local \c{game_ui}. It
+may refer to information in here to help with its decision (see
+\k{writing-conditional-anim} for an example of this), and/or it may
+\e{write} information about the nature of the animation which will
+be read later by \cw{redraw()}.
+
+When this function is called, it may rely on \cw{changed_state()}
+having been called previously, so if \cw{anim_length()} needs to
+refer to information in the \c{game_ui}, then \cw{changed_state()}
+is a reliable place to have set that information up.
+
+Move animations do not inhibit further input events. If the user
+continues playing before a move animation is complete, the animation
+will be abandoned and the display will jump straight to the final
+state.
+
+\S{backend-flash-length} \cw{flash_length()}
+
+\c float (*flash_length)(const game_state *oldstate,
+\c                       const game_state *newstate,
+\c                       int dir, game_ui *ui);
+
+This function is called when a move is completed. (\q{Completed}
+means that not only has the move been made, but any animation which
+accompanied it has finished.) It decides whether the transition from
+\c{oldstate} to \c{newstate} merits a \q{flash}.
+
+A flash is much like a move animation, but it is \e{not} interrupted
+by further user interface activity; it runs to completion in
+parallel with whatever else might be going on on the display. The
+only thing which will rush a flash to completion is another flash.
+
+The purpose of flashes is to indicate that the game has been
+completed. They were introduced as a separate concept from move
+animations because of Net: the habit of most Net players (and
+certainly me) is to rotate a tile into place and immediately lock
+it, then move on to another tile. When you make your last move, at
+the instant the final tile is rotated into place the screen starts
+to flash to indicate victory \dash but if you then press the lock
+button out of habit, then the move animation is cancelled, and the
+victory flash does not complete. (And if you \e{don't} press the
+lock button, the completed grid will look untidy because there will
+be one unlocked square.) Therefore, I introduced a specific concept
+of a \q{flash} which is separate from a move animation and can
+proceed in parallel with move animations and any other display
+activity, so that the victory flash in Net is not cancelled by that
+final locking move.
+
+The input parameters to \cw{flash_length()} are exactly the same as
+the ones to \cw{anim_length()}.
+
+Just like \cw{anim_length()}, when this function is called, it may
+rely on \cw{changed_state()} having been called previously, so if it
+needs to refer to information in the \c{game_ui} then
+\cw{changed_state()} is a reliable place to have set that
+information up.
+
+(Some games use flashes to indicate defeat as well as victory;
+Mines, for example, flashes in a different colour when you tread on
+a mine from the colour it uses when you complete the game. In order
+to achieve this, its \cw{flash_length()} function has to store a
+flag in the \c{game_ui} to indicate which flash type is required.)
+
+\S{backend-status} \cw{status()}
+
+\c int (*status)(const game_state *state);
+
+This function returns a status value indicating whether the current
+game is still in play, or has been won, or has been conclusively lost.
+The mid-end uses this to implement \cw{midend_status()}
+(\k{midend-status}).
+
+The return value should be +1 if the game has been successfully
+solved. If the game has been lost in a situation where further play is
+unlikely, the return value should be -1. If neither is true (so play
+is still ongoing), return zero.
+
+Front ends may wish to use a non-zero status as a cue to proactively
+offer the option of starting a new game. Therefore, back ends should
+not return -1 if the game has been \e{technically} lost but undoing
+and continuing is still a realistic possibility.
+
+(For instance, games with hidden information such as Guess or Mines
+might well return a non-zero status whenever they reveal the solution,
+whether or not the player guessed it correctly, on the grounds that a
+player would be unlikely to hide the solution and continue playing
+after the answer was spoiled. On the other hand, games where you can
+merely get into a dead end such as Same Game or Inertia might choose
+to return 0 in that situation, on the grounds that the player would
+quite likely press Undo and carry on playing.)
+
+\S{backend-redraw} \cw{redraw()}
+
+\c void (*redraw)(drawing *dr, game_drawstate *ds,
+\c                const game_state *oldstate,
+\c                const game_state *newstate,
+\c                int dir, const game_ui *ui,
+\c                float anim_time, float flash_time);
+
+This function is responsible for actually drawing the contents of
+the game window, and for redrawing every time the game state or the
+\c{game_ui} changes.
+
+The parameter \c{dr} is a drawing object which may be passed to the
+drawing API functions (see \k{drawing} for documentation of the
+drawing API). This function may not save \c{dr} and use it
+elsewhere; it must only use it for calling back to the drawing API
+functions within its own lifetime.
+
+\c{ds} is the local \c{game_drawstate}, of course, and \c{ui} is the
+local \c{game_ui}.
+
+\c{newstate} is the semantically-current game state, and is always
+non-\cw{NULL}. If \c{oldstate} is also non-\cw{NULL}, it means that
+a move has recently been made and the game is still in the process
+of displaying an animation linking the old and new states; in this
+situation, \c{anim_time} will give the length of time (in seconds)
+that the animation has already been running. If \c{oldstate} is
+\cw{NULL}, then \c{anim_time} is unused (and will hopefully be set
+to zero to avoid confusion).
+
+\c{flash_time}, if it is is non-zero, denotes that the game is in
+the middle of a flash, and gives the time since the start of the
+flash. See \k{backend-flash-length} for general discussion of
+flashes.
+
+The very first time this function is called for a new
+\c{game_drawstate}, it is expected to redraw the \e{entire} drawing
+area. Since this often involves drawing visual furniture which is
+never subsequently altered, it is often simplest to arrange this by
+having a special \q{first time} flag in the draw state, and
+resetting it after the first redraw.
+
+When this function (or any subfunction) calls the drawing API, it is
+expected to pass colour indices which were previously defined by the
+\cw{colours()} function.
+
+\H{backend-printing} Printing functions
+
+This section discusses the back end functions that deal with
+printing puzzles out on paper.
+
+\S{backend-can-print} \c{can_print}
+
+\c int can_print;
+
+This flag is set to \cw{TRUE} if the puzzle is capable of printing
+itself on paper. (This makes sense for some puzzles, such as Solo,
+which can be filled in with a pencil. Other puzzles, such as
+Twiddle, inherently involve moving things around and so would not
+make sense to print.)
+
+If this flag is \cw{FALSE}, then the functions \cw{print_size()}
+and \cw{print()} will never be called.
+
+\S{backend-can-print-in-colour} \c{can_print_in_colour}
+
+\c int can_print_in_colour;
+
+This flag is set to \cw{TRUE} if the puzzle is capable of printing
+itself differently when colour is available. For example, Map can
+actually print coloured regions in different \e{colours} rather than
+resorting to cross-hatching.
+
+If the \c{can_print} flag is \cw{FALSE}, then this flag will be
+ignored.
+
+\S{backend-print-size} \cw{print_size()}
+
+\c void (*print_size)(const game_params *params, float *x, float *y);
+
+This function is passed a \c{game_params} structure and a tile size.
+It returns, in \c{*x} and \c{*y}, the preferred size in
+\e{millimetres} of that puzzle if it were to be printed out on paper.
+
+If the \c{can_print} flag is \cw{FALSE}, this function will never be
+called.
+
+\S{backend-print} \cw{print()}
+
+\c void (*print)(drawing *dr, const game_state *state, int tilesize);
+
+This function is called when a puzzle is to be printed out on paper.
+It should use the drawing API functions (see \k{drawing}) to print
+itself.
+
+This function is separate from \cw{redraw()} because it is often
+very different:
+
+\b The printing function may not depend on pixel accuracy, since
+printer resolution is variable. Draw as if your canvas had infinite
+resolution.
+
+\b The printing function sometimes needs to display things in a
+completely different style. Net, for example, is very different as
+an on-screen puzzle and as a printed one.
+
+\b The printing function is often much simpler since it has no need
+to deal with repeated partial redraws.
+
+However, there's no reason the printing and redraw functions can't
+share some code if they want to.
+
+When this function (or any subfunction) calls the drawing API, the
+colour indices it passes should be colours which have been allocated
+by the \cw{print_*_colour()} functions within this execution of
+\cw{print()}. This is very different from the fixed small number of
+colours used in \cw{redraw()}, because printers do not have a
+limitation on the total number of colours that may be used. Some
+puzzles' printing functions might wish to allocate only one \q{ink}
+colour and use it for all drawing; others might wish to allocate
+\e{more} colours than are used on screen.
+
+One possible colour policy worth mentioning specifically is that a
+puzzle's printing function might want to allocate the \e{same}
+colour indices as are used by the redraw function, so that code
+shared between drawing and printing does not have to keep switching
+its colour indices. In order to do this, the simplest thing is to
+make use of the fact that colour indices returned from
+\cw{print_*_colour()} are guaranteed to be in increasing order from
+zero. So if you have declared an \c{enum} defining three colours
+\cw{COL_BACKGROUND}, \cw{COL_THIS} and \cw{COL_THAT}, you might then
+write
+
+\c int c;
+\c c = print_mono_colour(dr, 1); assert(c == COL_BACKGROUND);
+\c c = print_mono_colour(dr, 0); assert(c == COL_THIS);
+\c c = print_mono_colour(dr, 0); assert(c == COL_THAT);
+
+If the \c{can_print} flag is \cw{FALSE}, this function will never be
+called.
+
+\H{backend-misc} Miscellaneous
+
+\S{backend-can-format-as-text-ever} \c{can_format_as_text_ever}
+
+\c int can_format_as_text_ever;
+
+This boolean field is \cw{TRUE} if the game supports formatting a
+game state as ASCII text (typically ASCII art) for copying to the
+clipboard and pasting into other applications. If it is \cw{FALSE},
+front ends will not offer the \q{Copy} command at all.
+
+If this field is \cw{TRUE}, the game does not necessarily have to
+support text formatting for \e{all} games: e.g. a game which can be
+played on a square grid or a triangular one might only support copy
+and paste for the former, because triangular grids in ASCII art are
+just too difficult.
+
+If this field is \cw{FALSE}, the functions
+\cw{can_format_as_text_now()} (\k{backend-can-format-as-text-now})
+and \cw{text_format()} (\k{backend-text-format}) are never called.
+
+\S{backend-can-format-as-text-now} \c{can_format_as_text_now()}
+
+\c int (*can_format_as_text_now)(const game_params *params);
+
+This function is passed a \c{game_params} and returns a boolean,
+which is \cw{TRUE} if the game can support ASCII text output for
+this particular game type. If it returns \cw{FALSE}, front ends will
+grey out or otherwise disable the \q{Copy} command.
+
+Games may enable and disable the copy-and-paste function for
+different game \e{parameters}, but are currently constrained to
+return the same answer from this function for all game \e{states}
+sharing the same parameters. In other words, the \q{Copy} function
+may enable or disable itself when the player changes game preset,
+but will never change during play of a single game or when another
+game of exactly the same type is generated.
+
+This function should not take into account aspects of the game
+parameters which are not encoded by \cw{encode_params()}
+(\k{backend-encode-params}) when the \c{full} parameter is set to
+\cw{FALSE}. Such parameters will not necessarily match up between a
+call to this function and a subsequent call to \cw{text_format()}
+itself. (For instance, game \e{difficulty} should not affect whether
+the game can be copied to the clipboard. Only the actual visible
+\e{shape} of the game can affect that.)
+
+\S{backend-text-format} \cw{text_format()}
+
+\c char *(*text_format)(const game_state *state);
+
+This function is passed a \c{game_state}, and returns a newly
+allocated C string containing an ASCII representation of that game
+state. It is used to implement the \q{Copy} operation in many front
+ends.
+
+This function will only ever be called if the back end field
+\c{can_format_as_text_ever} (\k{backend-can-format-as-text-ever}) is
+\cw{TRUE} \e{and} the function \cw{can_format_as_text_now()}
+(\k{backend-can-format-as-text-now}) has returned \cw{TRUE} for the
+currently selected game parameters.
+
+The returned string may contain line endings (and will probably want
+to), using the normal C internal \cq{\\n} convention. For
+consistency between puzzles, all multi-line textual puzzle
+representations should \e{end} with a newline as well as containing
+them internally. (There are currently no puzzles which have a
+one-line ASCII representation, so there's no precedent yet for
+whether that should come with a newline or not.)
+
+\S{backend-wants-statusbar} \cw{wants_statusbar}
+
+\c int wants_statusbar;
+
+This boolean field is set to \cw{TRUE} if the puzzle has a use for a
+textual status line (to display score, completion status, currently
+active tiles, etc).
+
+\S{backend-is-timed} \c{is_timed}
+
+\c int is_timed;
+
+This boolean field is \cw{TRUE} if the puzzle is time-critical. If
+so, the mid-end will maintain a game timer while the user plays.
+
+If this field is \cw{FALSE}, then \cw{timing_state()} will never be
+called and need not do anything.
+
+\S{backend-timing-state} \cw{timing_state()}
+
+\c int (*timing_state)(const game_state *state, game_ui *ui);
+
+This function is passed the current \c{game_state} and the local
+\c{game_ui}; it returns \cw{TRUE} if the game timer should currently
+be running.
+
+A typical use for the \c{game_ui} in this function is to note when
+the game was first completed (by setting a flag in
+\cw{changed_state()} \dash see \k{backend-changed-state}), and
+freeze the timer thereafter so that the user can undo back through
+their solution process without altering their time.
+
+\S{backend-flags} \c{flags}
+
+\c int flags;
+
+This field contains miscellaneous per-backend flags. It consists of
+the bitwise OR of some combination of the following:
+
+\dt \cw{BUTTON_BEATS(x,y)}
+
+\dd Given any \cw{x} and \cw{y} from the set \{\cw{LEFT_BUTTON},
+\cw{MIDDLE_BUTTON}, \cw{RIGHT_BUTTON}\}, this macro evaluates to a
+bit flag which indicates that when buttons \cw{x} and \cw{y} are
+both pressed simultaneously, the mid-end should consider \cw{x} to
+have priority. (In the absence of any such flags, the mid-end will
+always consider the most recently pressed button to have priority.)
+
+\dt \cw{SOLVE_ANIMATES}
+
+\dd This flag indicates that moves generated by \cw{solve()}
+(\k{backend-solve}) are candidates for animation just like any other
+move. For most games, solve moves should not be animated, so the
+mid-end doesn't even bother calling \cw{anim_length()}
+(\k{backend-anim-length}), thus saving some special-case code in
+each game. On the rare occasion that animated solve moves are
+actually required, you can set this flag.
+
+\dt \cw{REQUIRE_RBUTTON}
+
+\dd This flag indicates that the puzzle cannot be usefully played
+without the use of mouse buttons other than the left one. On some
+PDA platforms, this flag is used by the front end to enable
+right-button emulation through an appropriate gesture. Note that a
+puzzle is not required to set this just because it \e{uses} the
+right button, but only if its use of the right button is critical to
+playing the game. (Slant, for example, uses the right button to
+cycle through the three square states in the opposite order from the
+left button, and hence can manage fine without it.)
+
+\dt \cw{REQUIRE_NUMPAD}
+
+\dd This flag indicates that the puzzle cannot be usefully played
+without the use of number-key input. On some PDA platforms it causes
+an emulated number pad to appear on the screen. Similarly to
+\cw{REQUIRE_RBUTTON}, a puzzle need not specify this simply if its
+use of the number keys is not critical.
+
+\H{backend-initiative} Things a back end may do on its own initiative
+
+This section describes a couple of things that a back end may choose
+to do by calling functions elsewhere in the program, which would not
+otherwise be obvious.
+
+\S{backend-newrs} Create a random state
+
+If a back end needs random numbers at some point during normal play,
+it can create a fresh \c{random_state} by first calling
+\c{get_random_seed} (\k{frontend-get-random-seed}) and then passing
+the returned seed data to \cw{random_new()}.
+
+This is likely not to be what you want. If a puzzle needs randomness
+in the middle of play, it's likely to be more sensible to store some
+sort of random state within the \c{game_state}, so that the random
+numbers are tied to the particular game state and hence the player
+can't simply keep undoing their move until they get numbers they
+like better.
+
+This facility is currently used only in Net, to implement the
+\q{jumble} command, which sets every unlocked tile to a new random
+orientation. This randomness \e{is} a reasonable use of the feature,
+because it's non-adversarial \dash there's no advantage to the user
+in getting different random numbers.
+
+\S{backend-supersede} Supersede its own game description
+
+In response to a move, a back end is (reluctantly) permitted to call
+\cw{midend_supersede_game_desc()}:
+
+\c void midend_supersede_game_desc(midend *me,
+\c                                 char *desc, char *privdesc);
+
+When the user selects \q{New Game}, the mid-end calls
+\cw{new_desc()} (\k{backend-new-desc}) to get a new game
+description, and (as well as using that to generate an initial game
+state) stores it for the save file and for telling to the user. The
+function above overwrites that game description, and also splits it
+in two. \c{desc} becomes the new game description which is provided
+to the user on request, and is also the one used to construct a new
+initial game state if the user selects \q{Restart}. \c{privdesc} is
+a \q{private} game description, used to reconstruct the game's
+initial state when reloading.
+
+The distinction between the two, as well as the need for this
+function at all, comes from Mines. Mines begins with a blank grid
+and no idea of where the mines actually are; \cw{new_desc()} does
+almost no work in interactive mode, and simply returns a string
+encoding the \c{random_state}. When the user first clicks to open a
+tile, \e{then} Mines generates the mine positions, in such a way
+that the game is soluble from that starting point. Then it uses this
+function to supersede the random-state game description with a
+proper one. But it needs two: one containing the initial click
+location (because that's what you want to happen if you restart the
+game, and also what you want to send to a friend so that they play
+\e{the same game} as you), and one without the initial click
+location (because when you save and reload the game, you expect to
+see the same blank initial state as you had before saving).
+
+I should stress again that this function is a horrid hack. Nobody
+should use it if they're not Mines; if you think you need to use it,
+think again repeatedly in the hope of finding a better way to do
+whatever it was you needed to do.
+
+\C{drawing} The drawing API
+
+The back end function \cw{redraw()} (\k{backend-redraw}) is required
+to draw the puzzle's graphics on the window's drawing area, or on
+paper if the puzzle is printable. To do this portably, it is
+provided with a drawing API allowing it to talk directly to the
+front end. In this chapter I document that API, both for the benefit
+of back end authors trying to use it and for front end authors
+trying to implement it.
+
+The drawing API as seen by the back end is a collection of global
+functions, each of which takes a pointer to a \c{drawing} structure
+(a \q{drawing object}). These objects are supplied as parameters to
+the back end's \cw{redraw()} and \cw{print()} functions.
+
+In fact these global functions are not implemented directly by the
+front end; instead, they are implemented centrally in \c{drawing.c}
+and form a small piece of middleware. The drawing API as supplied by
+the front end is a structure containing a set of function pointers,
+plus a \cq{void *} handle which is passed to each of those
+functions. This enables a single front end to switch between
+multiple implementations of the drawing API if necessary. For
+example, the Windows API supplies a printing mechanism integrated
+into the same GDI which deals with drawing in windows, and therefore
+the same API implementation can handle both drawing and printing;
+but on Unix, the most common way for applications to print is by
+producing PostScript output directly, and although it would be
+\e{possible} to write a single (say) \cw{draw_rect()} function which
+checked a global flag to decide whether to do GTK drawing operations
+or output PostScript to a file, it's much nicer to have two separate
+functions and switch between them as appropriate.
+
+When drawing, the puzzle window is indexed by pixel coordinates,
+with the top left pixel defined as \cw{(0,0)} and the bottom right
+pixel \cw{(w-1,h-1)}, where \c{w} and \c{h} are the width and height
+values returned by the back end function \cw{compute_size()}
+(\k{backend-compute-size}).
+
+When printing, the puzzle's print area is indexed in exactly the
+same way (with an arbitrary tile size provided by the printing
+module \c{printing.c}), to facilitate sharing of code between the
+drawing and printing routines. However, when printing, puzzles may
+no longer assume that the coordinate unit has any relationship to a
+pixel; the printer's actual resolution might very well not even be
+known at print time, so the coordinate unit might be smaller or
+larger than a pixel. Puzzles' print functions should restrict
+themselves to drawing geometric shapes rather than fiddly pixel
+manipulation.
+
+\e{Puzzles' redraw functions may assume that the surface they draw
+on is persistent}. It is the responsibility of every front end to
+preserve the puzzle's window contents in the face of GUI window
+expose issues and similar. It is not permissible to request that the
+back end redraw any part of a window that it has already drawn,
+unless something has actually changed as a result of making moves in
+the puzzle.
+
+Most front ends accomplish this by having the drawing routines draw
+on a stored bitmap rather than directly on the window, and copying
+the bitmap to the window every time a part of the window needs to be
+redrawn. Therefore, it is vitally important that whenever the back
+end does any drawing it informs the front end of which parts of the
+window it has accessed, and hence which parts need repainting. This
+is done by calling \cw{draw_update()} (\k{drawing-draw-update}).
+
+Persistence of old drawing is convenient. However, a puzzle should
+be very careful about how it updates its drawing area. The problem
+is that some front ends do anti-aliased drawing: rather than simply
+choosing between leaving each pixel untouched or painting it a
+specified colour, an antialiased drawing function will \e{blend} the
+original and new colours in pixels at a figure's boundary according
+to the proportion of the pixel occupied by the figure (probably
+modified by some heuristic fudge factors). All of this produces a
+smoother appearance for curves and diagonal lines.
+
+An unfortunate effect of drawing an anti-aliased figure repeatedly
+is that the pixels around the figure's boundary come steadily more
+saturated with \q{ink} and the boundary appears to \q{spread out}.
+Worse, redrawing a figure in a different colour won't fully paint
+over the old boundary pixels, so the end result is a rather ugly
+smudge.
+
+A good strategy to avoid unpleasant anti-aliasing artifacts is to
+identify a number of rectangular areas which need to be redrawn,
+clear them to the background colour, and then redraw their contents
+from scratch, being careful all the while not to stray beyond the
+boundaries of the original rectangles. The \cw{clip()} function
+(\k{drawing-clip}) comes in very handy here. Games based on a square
+grid can often do this fairly easily. Other games may need to be
+somewhat more careful. For example, Loopy's redraw function first
+identifies portions of the display which need to be updated. Then,
+if the changes are fairly well localised, it clears and redraws a
+rectangle containing each changed area. Otherwise, it gives up and
+redraws the entire grid from scratch.
+
+It is possible to avoid clearing to background and redrawing from
+scratch if one is very careful about which drawing functions one
+uses: if a function is documented as not anti-aliasing under some
+circumstances, you can rely on each pixel in a drawing either being
+left entirely alone or being set to the requested colour, with no
+blending being performed.
+
+In the following sections I first discuss the drawing API as seen by
+the back end, and then the \e{almost} identical function-pointer
+form seen by the front end.
+
+\H{drawing-backend} Drawing API as seen by the back end
+
+This section documents the back-end drawing API, in the form of
+functions which take a \c{drawing} object as an argument.
+
+\S{drawing-draw-rect} \cw{draw_rect()}
+
+\c void draw_rect(drawing *dr, int x, int y, int w, int h,
+\c                int colour);
+
+Draws a filled rectangle in the puzzle window.
+
+\c{x} and \c{y} give the coordinates of the top left pixel of the
+rectangle. \c{w} and \c{h} give its width and height. Thus, the
+horizontal extent of the rectangle runs from \c{x} to \c{x+w-1}
+inclusive, and the vertical extent from \c{y} to \c{y+h-1}
+inclusive.
+
+\c{colour} is an integer index into the colours array returned by
+the back end function \cw{colours()} (\k{backend-colours}).
+
+There is no separate pixel-plotting function. If you want to plot a
+single pixel, the approved method is to use \cw{draw_rect()} with
+width and height set to 1.
+
+Unlike many of the other drawing functions, this function is
+guaranteed to be pixel-perfect: the rectangle will be sharply
+defined and not anti-aliased or anything like that.
+
+This function may be used for both drawing and printing.
+
+\S{drawing-draw-rect-outline} \cw{draw_rect_outline()}
+
+\c void draw_rect_outline(drawing *dr, int x, int y, int w, int h,
+\c                        int colour);
+
+Draws an outline rectangle in the puzzle window.
+
+\c{x} and \c{y} give the coordinates of the top left pixel of the
+rectangle. \c{w} and \c{h} give its width and height. Thus, the
+horizontal extent of the rectangle runs from \c{x} to \c{x+w-1}
+inclusive, and the vertical extent from \c{y} to \c{y+h-1}
+inclusive.
+
+\c{colour} is an integer index into the colours array returned by
+the back end function \cw{colours()} (\k{backend-colours}).
+
+From a back end perspective, this function may be considered to be
+part of the drawing API. However, front ends are not required to
+implement it, since it is actually implemented centrally (in
+\cw{misc.c}) as a wrapper on \cw{draw_polygon()}.
+
+This function may be used for both drawing and printing.
+
+\S{drawing-draw-line} \cw{draw_line()}
+
+\c void draw_line(drawing *dr, int x1, int y1, int x2, int y2,
+\c                int colour);
+
+Draws a straight line in the puzzle window.
+
+\c{x1} and \c{y1} give the coordinates of one end of the line.
+\c{x2} and \c{y2} give the coordinates of the other end. The line
+drawn includes both those points.
+
+\c{colour} is an integer index into the colours array returned by
+the back end function \cw{colours()} (\k{backend-colours}).
+
+Some platforms may perform anti-aliasing on this function.
+Therefore, do not assume that you can erase a line by drawing the
+same line over it in the background colour; anti-aliasing might lead
+to perceptible ghost artefacts around the vanished line. Horizontal
+and vertical lines, however, are pixel-perfect and not anti-aliased.
+
+This function may be used for both drawing and printing.
+
+\S{drawing-draw-polygon} \cw{draw_polygon()}
+
+\c void draw_polygon(drawing *dr, int *coords, int npoints,
+\c                   int fillcolour, int outlinecolour);
+
+Draws an outlined or filled polygon in the puzzle window.
+
+\c{coords} is an array of \cw{(2*npoints)} integers, containing the
+\c{x} and \c{y} coordinates of \c{npoints} vertices.
+
+\c{fillcolour} and \c{outlinecolour} are integer indices into the
+colours array returned by the back end function \cw{colours()}
+(\k{backend-colours}). \c{fillcolour} may also be \cw{-1} to
+indicate that the polygon should be outlined only.
+
+The polygon defined by the specified list of vertices is first
+filled in \c{fillcolour}, if specified, and then outlined in
+\c{outlinecolour}.
+
+\c{outlinecolour} may \e{not} be \cw{-1}; it must be a valid colour
+(and front ends are permitted to enforce this by assertion). This is
+because different platforms disagree on whether a filled polygon
+should include its boundary line or not, so drawing \e{only} a
+filled polygon would have non-portable effects. If you want your
+filled polygon not to have a visible outline, you must set
+\c{outlinecolour} to the same as \c{fillcolour}.
+
+Some platforms may perform anti-aliasing on this function.
+Therefore, do not assume that you can erase a polygon by drawing the
+same polygon over it in the background colour. Also, be prepared for
+the polygon to extend a pixel beyond its obvious bounding box as a
+result of this; if you really need it not to do this to avoid
+interfering with other delicate graphics, you should probably use
+\cw{clip()} (\k{drawing-clip}). You can rely on horizontal and
+vertical lines not being anti-aliased.
+
+This function may be used for both drawing and printing.
+
+\S{drawing-draw-circle} \cw{draw_circle()}
+
+\c void draw_circle(drawing *dr, int cx, int cy, int radius,
+\c                  int fillcolour, int outlinecolour);
+
+Draws an outlined or filled circle in the puzzle window.
+
+\c{cx} and \c{cy} give the coordinates of the centre of the circle.
+\c{radius} gives its radius. The total horizontal pixel extent of
+the circle is from \c{cx-radius+1} to \c{cx+radius-1} inclusive, and
+the vertical extent similarly around \c{cy}.
+
+\c{fillcolour} and \c{outlinecolour} are integer indices into the
+colours array returned by the back end function \cw{colours()}
+(\k{backend-colours}). \c{fillcolour} may also be \cw{-1} to
+indicate that the circle should be outlined only.
+
+The circle is first filled in \c{fillcolour}, if specified, and then
+outlined in \c{outlinecolour}.
+
+\c{outlinecolour} may \e{not} be \cw{-1}; it must be a valid colour
+(and front ends are permitted to enforce this by assertion). This is
+because different platforms disagree on whether a filled circle
+should include its boundary line or not, so drawing \e{only} a
+filled circle would have non-portable effects. If you want your
+filled circle not to have a visible outline, you must set
+\c{outlinecolour} to the same as \c{fillcolour}.
+
+Some platforms may perform anti-aliasing on this function.
+Therefore, do not assume that you can erase a circle by drawing the
+same circle over it in the background colour. Also, be prepared for
+the circle to extend a pixel beyond its obvious bounding box as a
+result of this; if you really need it not to do this to avoid
+interfering with other delicate graphics, you should probably use
+\cw{clip()} (\k{drawing-clip}).
+
+This function may be used for both drawing and printing.
+
+\S{drawing-draw-thick-line} \cw{draw_thick_line()}
+
+\c void draw_thick_line(drawing *dr, float thickness,
+\c                      float x1, float y1, float x2, float y2,
+\c                      int colour)
+
+Draws a line in the puzzle window, giving control over the line's
+thickness.
+
+\c{x1} and \c{y1} give the coordinates of one end of the line.
+\c{x2} and \c{y2} give the coordinates of the other end.
+\c{thickness} gives the thickness of the line, in pixels.
+
+Note that the coordinates and thickness are floating-point: the
+continuous coordinate system is in effect here. It's important to
+be able to address points with better-than-pixel precision in this
+case, because one can't otherwise properly express the endpoints of
+lines with both odd and even thicknesses.
+
+Some platforms may perform anti-aliasing on this function. The
+precise pixels affected by a thick-line drawing operation may vary
+between platforms, and no particular guarantees are provided.
+Indeed, even horizontal or vertical lines may be anti-aliased.
+
+This function may be used for both drawing and printing.
+
+\S{drawing-draw-text} \cw{draw_text()}
+
+\c void draw_text(drawing *dr, int x, int y, int fonttype,
+\c                int fontsize, int align, int colour, char *text);
+
+Draws text in the puzzle window.
+
+\c{x} and \c{y} give the coordinates of a point. The relation of
+this point to the location of the text is specified by \c{align},
+which is a bitwise OR of horizontal and vertical alignment flags:
+
+\dt \cw{ALIGN_VNORMAL}
+
+\dd Indicates that \c{y} is aligned with the baseline of the text.
+
+\dt \cw{ALIGN_VCENTRE}
+
+\dd Indicates that \c{y} is aligned with the vertical centre of the
+text. (In fact, it's aligned with the vertical centre of normal
+\e{capitalised} text: displaying two pieces of text with
+\cw{ALIGN_VCENTRE} at the same \cw{y}-coordinate will cause their
+baselines to be aligned with one another, even if one is an ascender
+and the other a descender.)
+
+\dt \cw{ALIGN_HLEFT}
+
+\dd Indicates that \c{x} is aligned with the left-hand end of the
+text.
+
+\dt \cw{ALIGN_HCENTRE}
+
+\dd Indicates that \c{x} is aligned with the horizontal centre of
+the text.
+
+\dt \cw{ALIGN_HRIGHT}
+
+\dd Indicates that \c{x} is aligned with the right-hand end of the
+text.
+
+\c{fonttype} is either \cw{FONT_FIXED} or \cw{FONT_VARIABLE}, for a
+monospaced or proportional font respectively. (No more detail than
+that may be specified; it would only lead to portability issues
+between different platforms.)
+
+\c{fontsize} is the desired size, in pixels, of the text. This size
+corresponds to the overall point size of the text, not to any
+internal dimension such as the cap-height.
+
+\c{colour} is an integer index into the colours array returned by
+the back end function \cw{colours()} (\k{backend-colours}).
+
+This function may be used for both drawing and printing.
+
+The character set used to encode the text passed to this function is
+specified \e{by the drawing object}, although it must be a superset
+of ASCII. If a puzzle wants to display text that is not contained in
+ASCII, it should use the \cw{text_fallback()} function
+(\k{drawing-text-fallback}) to query the drawing object for an
+appropriate representation of the characters it wants.
+
+\S{drawing-text-fallback} \cw{text_fallback()}
+
+\c char *text_fallback(drawing *dr, const char *const *strings,
+\c                     int nstrings);
+
+This function is used to request a translation of UTF-8 text into
+whatever character encoding is expected by the drawing object's
+implementation of \cw{draw_text()}.
+
+The input is a list of strings encoded in UTF-8: \cw{nstrings} gives
+the number of strings in the list, and \cw{strings[0]},
+\cw{strings[1]}, ..., \cw{strings[nstrings-1]} are the strings
+themselves.
+
+The returned string (which is dynamically allocated and must be
+freed when finished with) is derived from the first string in the
+list that the drawing object expects to be able to display reliably;
+it will consist of that string translated into the character set
+expected by \cw{draw_text()}.
+
+Drawing implementations are not required to handle anything outside
+ASCII, but are permitted to assume that \e{some} string will be
+successfully translated. So every call to this function must include
+a string somewhere in the list (presumably the last element) which
+consists of nothing but ASCII, to be used by any front end which
+cannot handle anything else.
+
+For example, if a puzzle wished to display a string including a
+multiplication sign (U+00D7 in Unicode, represented by the bytes C3
+97 in UTF-8), it might do something like this:
+
+\c static const char *const times_signs[] = { "\xC3\x97", "x" };
+\c char *times_sign = text_fallback(dr, times_signs, 2);
+\c sprintf(buffer, "%d%s%d", width, times_sign, height);
+\c draw_text(dr, x, y, font, size, align, colour, buffer);
+\c sfree(buffer);
+
+which would draw a string with a times sign in the middle on
+platforms that support it, and fall back to a simple ASCII \cq{x}
+where there was no alternative.
+
+\S{drawing-clip} \cw{clip()}
+
+\c void clip(drawing *dr, int x, int y, int w, int h);
+
+Establishes a clipping rectangle in the puzzle window.
+
+\c{x} and \c{y} give the coordinates of the top left pixel of the
+clipping rectangle. \c{w} and \c{h} give its width and height. Thus,
+the horizontal extent of the rectangle runs from \c{x} to \c{x+w-1}
+inclusive, and the vertical extent from \c{y} to \c{y+h-1}
+inclusive. (These are exactly the same semantics as
+\cw{draw_rect()}.)
+
+After this call, no drawing operation will affect anything outside
+the specified rectangle. The effect can be reversed by calling
+\cw{unclip()} (\k{drawing-unclip}). The clipping rectangle is
+pixel-perfect: pixels within the rectangle are affected as usual by
+drawing functions; pixels outside are completely untouched.
+
+Back ends should not assume that a clipping rectangle will be
+automatically cleared up by the front end if it's left lying around;
+that might work on current front ends, but shouldn't be relied upon.
+Always explicitly call \cw{unclip()}.
+
+This function may be used for both drawing and printing.
+
+\S{drawing-unclip} \cw{unclip()}
+
+\c void unclip(drawing *dr);
+
+Reverts the effect of a previous call to \cw{clip()}. After this
+call, all drawing operations will be able to affect the entire
+puzzle window again.
+
+This function may be used for both drawing and printing.
+
+\S{drawing-draw-update} \cw{draw_update()}
+
+\c void draw_update(drawing *dr, int x, int y, int w, int h);
+
+Informs the front end that a rectangular portion of the puzzle
+window has been drawn on and needs to be updated.
+
+\c{x} and \c{y} give the coordinates of the top left pixel of the
+update rectangle. \c{w} and \c{h} give its width and height. Thus,
+the horizontal extent of the rectangle runs from \c{x} to \c{x+w-1}
+inclusive, and the vertical extent from \c{y} to \c{y+h-1}
+inclusive. (These are exactly the same semantics as
+\cw{draw_rect()}.)
+
+The back end redraw function \e{must} call this function to report
+any changes it has made to the window. Otherwise, those changes may
+not become immediately visible, and may then appear at an
+unpredictable subsequent time such as the next time the window is
+covered and re-exposed.
+
+This function is only important when drawing. It may be called when
+printing as well, but doing so is not compulsory, and has no effect.
+(So if you have a shared piece of code between the drawing and
+printing routines, that code may safely call \cw{draw_update()}.)
+
+\S{drawing-status-bar} \cw{status_bar()}
+
+\c void status_bar(drawing *dr, char *text);
+
+Sets the text in the game's status bar to \c{text}. The text is copied
+from the supplied buffer, so the caller is free to deallocate or
+modify the buffer after use.
+
+(This function is not exactly a \e{drawing} function, but it shares
+with the drawing API the property that it may only be called from
+within the back end redraw function, so this is as good a place as
+any to document it.)
+
+The supplied text is filtered through the mid-end for optional
+rewriting before being passed on to the front end; the mid-end will
+prepend the current game time if the game is timed (and may in
+future perform other rewriting if it seems like a good idea).
+
+This function is for drawing only; it must never be called during
+printing.
+
+\S{drawing-blitter} Blitter functions
+
+This section describes a group of related functions which save and
+restore a section of the puzzle window. This is most commonly used
+to implement user interfaces involving dragging a puzzle element
+around the window: at the end of each call to \cw{redraw()}, if an
+object is currently being dragged, the back end saves the window
+contents under that location and then draws the dragged object, and
+at the start of the next \cw{redraw()} the first thing it does is to
+restore the background.
+
+The front end defines an opaque type called a \c{blitter}, which is
+capable of storing a rectangular area of a specified size.
+
+Blitter functions are for drawing only; they must never be called
+during printing.
+
+\S2{drawing-blitter-new} \cw{blitter_new()}
+
+\c blitter *blitter_new(drawing *dr, int w, int h);
+
+Creates a new blitter object which stores a rectangle of size \c{w}
+by \c{h} pixels. Returns a pointer to the blitter object.
+
+Blitter objects are best stored in the \c{game_drawstate}. A good
+time to create them is in the \cw{set_size()} function
+(\k{backend-set-size}), since it is at this point that you first
+know how big a rectangle they will need to save.
+
+\S2{drawing-blitter-free} \cw{blitter_free()}
+
+\c void blitter_free(drawing *dr, blitter *bl);
+
+Disposes of a blitter object. Best called in \cw{free_drawstate()}.
+(However, check that the blitter object is not \cw{NULL} before
+attempting to free it; it is possible that a draw state might be
+created and freed without ever having \cw{set_size()} called on it
+in between.)
+
+\S2{drawing-blitter-save} \cw{blitter_save()}
+
+\c void blitter_save(drawing *dr, blitter *bl, int x, int y);
+
+This is a true drawing API function, in that it may only be called
+from within the game redraw routine. It saves a rectangular portion
+of the puzzle window into the specified blitter object.
+
+\c{x} and \c{y} give the coordinates of the top left corner of the
+saved rectangle. The rectangle's width and height are the ones
+specified when the blitter object was created.
+
+This function is required to cope and do the right thing if \c{x}
+and \c{y} are out of range. (The right thing probably means saving
+whatever part of the blitter rectangle overlaps with the visible
+area of the puzzle window.)
+
+\S2{drawing-blitter-load} \cw{blitter_load()}
+
+\c void blitter_load(drawing *dr, blitter *bl, int x, int y);
+
+This is a true drawing API function, in that it may only be called
+from within the game redraw routine. It restores a rectangular
+portion of the puzzle window from the specified blitter object.
+
+\c{x} and \c{y} give the coordinates of the top left corner of the
+rectangle to be restored. The rectangle's width and height are the
+ones specified when the blitter object was created.
+
+Alternatively, you can specify both \c{x} and \c{y} as the special
+value \cw{BLITTER_FROMSAVED}, in which case the rectangle will be
+restored to exactly where it was saved from. (This is probably what
+you want to do almost all the time, if you're using blitters to
+implement draggable puzzle elements.)
+
+This function is required to cope and do the right thing if \c{x}
+and \c{y} (or the equivalent ones saved in the blitter) are out of
+range. (The right thing probably means restoring whatever part of
+the blitter rectangle overlaps with the visible area of the puzzle
+window.)
+
+If this function is called on a blitter which had previously been
+saved from a partially out-of-range rectangle, then the parts of the
+saved bitmap which were not visible at save time are undefined. If
+the blitter is restored to a different position so as to make those
+parts visible, the effect on the drawing area is undefined.
+
+\S{print-mono-colour} \cw{print_mono_colour()}
+
+\c int print_mono_colour(drawing *dr, int grey);
+
+This function allocates a colour index for a simple monochrome
+colour during printing.
+
+\c{grey} must be 0 or 1. If \c{grey} is 0, the colour returned is
+black; if \c{grey} is 1, the colour is white.
+
+\S{print-grey-colour} \cw{print_grey_colour()}
+
+\c int print_grey_colour(drawing *dr, float grey);
+
+This function allocates a colour index for a grey-scale colour
+during printing.
+
+\c{grey} may be any number between 0 (black) and 1 (white); for
+example, 0.5 indicates a medium grey.
+
+The chosen colour will be rendered to the limits of the printer's
+halftoning capability.
+
+\S{print-hatched-colour} \cw{print_hatched_colour()}
+
+\c int print_hatched_colour(drawing *dr, int hatch);
+
+This function allocates a colour index which does not represent a
+literal \e{colour}. Instead, regions shaded in this colour will be
+hatched with parallel lines. The \c{hatch} parameter defines what
+type of hatching should be used in place of this colour:
+
+\dt \cw{HATCH_SLASH}
+
+\dd This colour will be hatched by lines slanting to the right at 45
+degrees. 
+
+\dt \cw{HATCH_BACKSLASH}
+
+\dd This colour will be hatched by lines slanting to the left at 45
+degrees.
+
+\dt \cw{HATCH_HORIZ}
+
+\dd This colour will be hatched by horizontal lines.
+
+\dt \cw{HATCH_VERT}
+
+\dd This colour will be hatched by vertical lines.
+
+\dt \cw{HATCH_PLUS}
+
+\dd This colour will be hatched by criss-crossing horizontal and
+vertical lines.
+
+\dt \cw{HATCH_X}
+
+\dd This colour will be hatched by criss-crossing diagonal lines.
+
+Colours defined to use hatching may not be used for drawing lines or
+text; they may only be used for filling areas. That is, they may be
+used as the \c{fillcolour} parameter to \cw{draw_circle()} and
+\cw{draw_polygon()}, and as the colour parameter to
+\cw{draw_rect()}, but may not be used as the \c{outlinecolour}
+parameter to \cw{draw_circle()} or \cw{draw_polygon()}, or with
+\cw{draw_line()} or \cw{draw_text()}.
+
+\S{print-rgb-mono-colour} \cw{print_rgb_mono_colour()}
+
+\c int print_rgb_mono_colour(drawing *dr, float r, float g,
+\c                           float b, float grey);
+
+This function allocates a colour index for a fully specified RGB
+colour during printing.
+
+\c{r}, \c{g} and \c{b} may each be anywhere in the range from 0 to 1.
+
+If printing in black and white only, these values will be ignored,
+and either pure black or pure white will be used instead, according
+to the \q{grey} parameter. (The fallback colour is the same as the
+one which would be allocated by \cw{print_mono_colour(grey)}.)
+
+\S{print-rgb-grey-colour} \cw{print_rgb_grey_colour()}
+
+\c int print_rgb_grey_colour(drawing *dr, float r, float g,
+\c                           float b, float grey);
+
+This function allocates a colour index for a fully specified RGB
+colour during printing.
+
+\c{r}, \c{g} and \c{b} may each be anywhere in the range from 0 to 1.
+
+If printing in black and white only, these values will be ignored,
+and a shade of grey given by the \c{grey} parameter will be used
+instead. (The fallback colour is the same as the one which would be
+allocated by \cw{print_grey_colour(grey)}.)
+
+\S{print-rgb-hatched-colour} \cw{print_rgb_hatched_colour()}
+
+\c int print_rgb_hatched_colour(drawing *dr, float r, float g,
+\c                              float b, float hatched);
+
+This function allocates a colour index for a fully specified RGB
+colour during printing.
+
+\c{r}, \c{g} and \c{b} may each be anywhere in the range from 0 to 1.
+
+If printing in black and white only, these values will be ignored,
+and a form of cross-hatching given by the \c{hatch} parameter will
+be used instead; see \k{print-hatched-colour} for the possible
+values of this parameter. (The fallback colour is the same as the
+one which would be allocated by \cw{print_hatched_colour(hatch)}.)
+
+\S{print-line-width} \cw{print_line_width()}
+
+\c void print_line_width(drawing *dr, int width);
+
+This function is called to set the thickness of lines drawn during
+printing. It is meaningless in drawing: all lines drawn by
+\cw{draw_line()}, \cw{draw_circle} and \cw{draw_polygon()} are one
+pixel in thickness. However, in printing there is no clear
+definition of a pixel and so line widths must be explicitly
+specified.
+
+The line width is specified in the usual coordinate system. Note,
+however, that it is a hint only: the central printing system may
+choose to vary line thicknesses at user request or due to printer
+capabilities.
+
+\S{print-line-dotted} \cw{print_line_dotted()}
+
+\c void print_line_dotted(drawing *dr, int dotted);
+
+This function is called to toggle the drawing of dotted lines during
+printing. It is not supported during drawing.
+
+The parameter \cq{dotted} is a boolean; \cw{TRUE} means that future
+lines drawn by \cw{draw_line()}, \cw{draw_circle} and
+\cw{draw_polygon()} will be dotted, and \cw{FALSE} means that they
+will be solid.
+
+Some front ends may impose restrictions on the width of dotted
+lines. Asking for a dotted line via this front end will override any
+line width request if the front end requires it.
+
+\H{drawing-frontend} The drawing API as implemented by the front end
+
+This section describes the drawing API in the function-pointer form
+in which it is implemented by a front end.
+
+(It isn't only platform-specific front ends which implement this
+API; the platform-independent module \c{ps.c} also provides an
+implementation of it which outputs PostScript. Thus, any platform
+which wants to do PS printing can do so with minimum fuss.)
+
+The following entries all describe function pointer fields in a
+structure called \c{drawing_api}. Each of the functions takes a
+\cq{void *} context pointer, which it should internally cast back to
+a more useful type. Thus, a drawing \e{object} (\c{drawing *)}
+suitable for passing to the back end redraw or printing functions
+is constructed by passing a \c{drawing_api} and a \cq{void *} to the
+function \cw{drawing_new()} (see \k{drawing-new}).
+
+\S{drawingapi-draw-text} \cw{draw_text()}
+
+\c void (*draw_text)(void *handle, int x, int y, int fonttype,
+\c                   int fontsize, int align, int colour, char *text);
+
+This function behaves exactly like the back end \cw{draw_text()}
+function; see \k{drawing-draw-text}.
+
+\S{drawingapi-draw-rect} \cw{draw_rect()}
+
+\c void (*draw_rect)(void *handle, int x, int y, int w, int h,
+\c                   int colour);
+
+This function behaves exactly like the back end \cw{draw_rect()}
+function; see \k{drawing-draw-rect}.
+
+\S{drawingapi-draw-line} \cw{draw_line()}
+
+\c void (*draw_line)(void *handle, int x1, int y1, int x2, int y2,
+\c                   int colour);
+
+This function behaves exactly like the back end \cw{draw_line()}
+function; see \k{drawing-draw-line}.
+
+\S{drawingapi-draw-polygon} \cw{draw_polygon()}
+
+\c void (*draw_polygon)(void *handle, int *coords, int npoints,
+\c                      int fillcolour, int outlinecolour);
+
+This function behaves exactly like the back end \cw{draw_polygon()}
+function; see \k{drawing-draw-polygon}.
+
+\S{drawingapi-draw-circle} \cw{draw_circle()}
+
+\c void (*draw_circle)(void *handle, int cx, int cy, int radius,
+\c                     int fillcolour, int outlinecolour);
+
+This function behaves exactly like the back end \cw{draw_circle()}
+function; see \k{drawing-draw-circle}.
+
+\S{drawingapi-draw-thick-line} \cw{draw_thick_line()}
+
+\c void draw_thick_line(drawing *dr, float thickness,
+\c                      float x1, float y1, float x2, float y2,
+\c                      int colour)
+
+This function behaves exactly like the back end
+\cw{draw_thick_line()} function; see \k{drawing-draw-thick-line}.
+
+An implementation of this API which doesn't provide high-quality
+rendering of thick lines is permitted to define this function
+pointer to be \cw{NULL}. The middleware in \cw{drawing.c} will notice
+and provide a low-quality alternative using \cw{draw_polygon()}.
+
+\S{drawingapi-draw-update} \cw{draw_update()}
+
+\c void (*draw_update)(void *handle, int x, int y, int w, int h);
+
+This function behaves exactly like the back end \cw{draw_update()}
+function; see \k{drawing-draw-update}.
+
+An implementation of this API which only supports printing is
+permitted to define this function pointer to be \cw{NULL} rather
+than bothering to define an empty function. The middleware in
+\cw{drawing.c} will notice and avoid calling it.
+
+\S{drawingapi-clip} \cw{clip()}
+
+\c void (*clip)(void *handle, int x, int y, int w, int h);
+
+This function behaves exactly like the back end \cw{clip()}
+function; see \k{drawing-clip}.
+
+\S{drawingapi-unclip} \cw{unclip()}
+
+\c void (*unclip)(void *handle);
+
+This function behaves exactly like the back end \cw{unclip()}
+function; see \k{drawing-unclip}.
+
+\S{drawingapi-start-draw} \cw{start_draw()}
+
+\c void (*start_draw)(void *handle);
+
+This function is called at the start of drawing. It allows the front
+end to initialise any temporary data required to draw with, such as
+device contexts.
+
+Implementations of this API which do not provide drawing services
+may define this function pointer to be \cw{NULL}; it will never be
+called unless drawing is attempted.
+
+\S{drawingapi-end-draw} \cw{end_draw()}
+
+\c void (*end_draw)(void *handle);
+
+This function is called at the end of drawing. It allows the front
+end to do cleanup tasks such as deallocating device contexts and
+scheduling appropriate GUI redraw events.
+
+Implementations of this API which do not provide drawing services
+may define this function pointer to be \cw{NULL}; it will never be
+called unless drawing is attempted.
+
+\S{drawingapi-status-bar} \cw{status_bar()}
+
+\c void (*status_bar)(void *handle, char *text);
+
+This function behaves exactly like the back end \cw{status_bar()}
+function; see \k{drawing-status-bar}.
+
+Front ends implementing this function need not worry about it being
+called repeatedly with the same text; the middleware code in
+\cw{status_bar()} will take care of this.
+
+Implementations of this API which do not provide drawing services
+may define this function pointer to be \cw{NULL}; it will never be
+called unless drawing is attempted.
+
+\S{drawingapi-blitter-new} \cw{blitter_new()}
+
+\c blitter *(*blitter_new)(void *handle, int w, int h);
+
+This function behaves exactly like the back end \cw{blitter_new()}
+function; see \k{drawing-blitter-new}.
+
+Implementations of this API which do not provide drawing services
+may define this function pointer to be \cw{NULL}; it will never be
+called unless drawing is attempted.
+
+\S{drawingapi-blitter-free} \cw{blitter_free()}
+
+\c void (*blitter_free)(void *handle, blitter *bl);
+
+This function behaves exactly like the back end \cw{blitter_free()}
+function; see \k{drawing-blitter-free}.
+
+Implementations of this API which do not provide drawing services
+may define this function pointer to be \cw{NULL}; it will never be
+called unless drawing is attempted.
+
+\S{drawingapi-blitter-save} \cw{blitter_save()}
+
+\c void (*blitter_save)(void *handle, blitter *bl, int x, int y);
+
+This function behaves exactly like the back end \cw{blitter_save()}
+function; see \k{drawing-blitter-save}.
+
+Implementations of this API which do not provide drawing services
+may define this function pointer to be \cw{NULL}; it will never be
+called unless drawing is attempted.
+
+\S{drawingapi-blitter-load} \cw{blitter_load()}
+
+\c void (*blitter_load)(void *handle, blitter *bl, int x, int y);
+
+This function behaves exactly like the back end \cw{blitter_load()}
+function; see \k{drawing-blitter-load}.
+
+Implementations of this API which do not provide drawing services
+may define this function pointer to be \cw{NULL}; it will never be
+called unless drawing is attempted.
+
+\S{drawingapi-begin-doc} \cw{begin_doc()}
+
+\c void (*begin_doc)(void *handle, int pages);
+
+This function is called at the beginning of a printing run. It gives
+the front end an opportunity to initialise any required printing
+subsystem. It also provides the number of pages in advance.
+
+Implementations of this API which do not provide printing services
+may define this function pointer to be \cw{NULL}; it will never be
+called unless printing is attempted.
+
+\S{drawingapi-begin-page} \cw{begin_page()}
+
+\c void (*begin_page)(void *handle, int number);
+
+This function is called during printing, at the beginning of each
+page. It gives the page number (numbered from 1 rather than 0, so
+suitable for use in user-visible contexts).
+
+Implementations of this API which do not provide printing services
+may define this function pointer to be \cw{NULL}; it will never be
+called unless printing is attempted.
+
+\S{drawingapi-begin-puzzle} \cw{begin_puzzle()}
+
+\c void (*begin_puzzle)(void *handle, float xm, float xc,
+\c                      float ym, float yc, int pw, int ph, float wmm);
+
+This function is called during printing, just before printing a
+single puzzle on a page. It specifies the size and location of the
+puzzle on the page.
+
+\c{xm} and \c{xc} specify the horizontal position of the puzzle on
+the page, as a linear function of the page width. The front end is
+expected to multiply the page width by \c{xm}, add \c{xc} (measured
+in millimetres), and use the resulting x-coordinate as the left edge
+of the puzzle.
+
+Similarly, \c{ym} and \c{yc} specify the vertical position of the
+puzzle as a function of the page height: the page height times
+\c{ym}, plus \c{yc} millimetres, equals the desired distance from
+the top of the page to the top of the puzzle.
+
+(This unwieldy mechanism is required because not all printing
+systems can communicate the page size back to the software. The
+PostScript back end, for example, writes out PS which determines the
+page size at print time by means of calling \cq{clippath}, and
+centres the puzzles within that. Thus, exactly the same PS file
+works on A4 or on US Letter paper without needing local
+configuration, which simplifies matters.)
+
+\cw{pw} and \cw{ph} give the size of the puzzle in drawing API
+coordinates. The printing system will subsequently call the puzzle's
+own print function, which will in turn call drawing API functions in
+the expectation that an area \cw{pw} by \cw{ph} units is available
+to draw the puzzle on.
+
+Finally, \cw{wmm} gives the desired width of the puzzle in
+millimetres. (The aspect ratio is expected to be preserved, so if
+the desired puzzle height is also needed then it can be computed as
+\cw{wmm*ph/pw}.)
+
+Implementations of this API which do not provide printing services
+may define this function pointer to be \cw{NULL}; it will never be
+called unless printing is attempted.
+
+\S{drawingapi-end-puzzle} \cw{end_puzzle()}
+
+\c void (*end_puzzle)(void *handle);
+
+This function is called after the printing of a specific puzzle is
+complete.
+
+Implementations of this API which do not provide printing services
+may define this function pointer to be \cw{NULL}; it will never be
+called unless printing is attempted.
+
+\S{drawingapi-end-page} \cw{end_page()}
+
+\c void (*end_page)(void *handle, int number);
+
+This function is called after the printing of a page is finished.
+
+Implementations of this API which do not provide printing services
+may define this function pointer to be \cw{NULL}; it will never be
+called unless printing is attempted.
+
+\S{drawingapi-end-doc} \cw{end_doc()}
+
+\c void (*end_doc)(void *handle);
+
+This function is called after the printing of the entire document is
+finished. This is the moment to close files, send things to the
+print spooler, or whatever the local convention is.
+
+Implementations of this API which do not provide printing services
+may define this function pointer to be \cw{NULL}; it will never be
+called unless printing is attempted.
+
+\S{drawingapi-line-width} \cw{line_width()}
+
+\c void (*line_width)(void *handle, float width);
+
+This function is called to set the line thickness, during printing
+only. Note that the width is a \cw{float} here, where it was an
+\cw{int} as seen by the back end. This is because \cw{drawing.c} may
+have scaled it on the way past.
+
+However, the width is still specified in the same coordinate system
+as the rest of the drawing.
+
+Implementations of this API which do not provide printing services
+may define this function pointer to be \cw{NULL}; it will never be
+called unless printing is attempted.
+
+\S{drawingapi-text-fallback} \cw{text_fallback()}
+
+\c char *(*text_fallback)(void *handle, const char *const *strings,
+\c                        int nstrings);
+
+This function behaves exactly like the back end \cw{text_fallback()}
+function; see \k{drawing-text-fallback}.
+
+Implementations of this API which do not support any characters
+outside ASCII may define this function pointer to be \cw{NULL}, in
+which case the central code in \cw{drawing.c} will provide a default
+implementation.
+
+\H{drawingapi-frontend} The drawing API as called by the front end
+
+There are a small number of functions provided in \cw{drawing.c}
+which the front end needs to \e{call}, rather than helping to
+implement. They are described in this section.
+
+\S{drawing-new} \cw{drawing_new()}
+
+\c drawing *drawing_new(const drawing_api *api, midend *me,
+\c                      void *handle);
+
+This function creates a drawing object. It is passed a
+\c{drawing_api}, which is a structure containing nothing but
+function pointers; and also a \cq{void *} handle. The handle is
+passed back to each function pointer when it is called.
+
+The \c{midend} parameter is used for rewriting the status bar
+contents: \cw{status_bar()} (see \k{drawing-status-bar}) has to call
+a function in the mid-end which might rewrite the status bar text.
+If the drawing object is to be used only for printing, or if the
+game is known not to call \cw{status_bar()}, this parameter may be
+\cw{NULL}.
+
+\S{drawing-free} \cw{drawing_free()}
+
+\c void drawing_free(drawing *dr);
+
+This function frees a drawing object. Note that the \cq{void *}
+handle is not freed; if that needs cleaning up it must be done by
+the front end.
+
+\S{drawing-print-get-colour} \cw{print_get_colour()}
+
+\c void print_get_colour(drawing *dr, int colour, int printincolour,
+\c                       int *hatch, float *r, float *g, float *b)
+
+This function is called by the implementations of the drawing API
+functions when they are called in a printing context. It takes a
+colour index as input, and returns the description of the colour as
+requested by the back end.
+
+\c{printincolour} is \cw{TRUE} iff the implementation is printing in
+colour. This will alter the results returned if the colour in
+question was specified with a black-and-white fallback value.
+
+If the colour should be rendered by hatching, \c{*hatch} is filled
+with the type of hatching desired. See \k{print-grey-colour} for
+details of the values this integer can take.
+
+If the colour should be rendered as solid colour, \c{*hatch} is
+given a negative value, and \c{*r}, \c{*g} and \c{*b} are filled
+with the RGB values of the desired colour (if printing in colour),
+or all filled with the grey-scale value (if printing in black and
+white).
+
+\C{midend} The API provided by the mid-end
+
+This chapter documents the API provided by the mid-end to be called
+by the front end. You probably only need to read this if you are a
+front end implementor, i.e. you are porting Puzzles to a new
+platform. If you're only interested in writing new puzzles, you can
+safely skip this chapter.
+
+All the persistent state in the mid-end is encapsulated within a
+\c{midend} structure, to facilitate having multiple mid-ends in any
+port which supports multiple puzzle windows open simultaneously.
+Each \c{midend} is intended to handle the contents of a single
+puzzle window.
+
+\H{midend-new} \cw{midend_new()}
+
+\c midend *midend_new(frontend *fe, const game *ourgame,
+\c                    const drawing_api *drapi, void *drhandle)
+
+Allocates and returns a new mid-end structure.
+
+The \c{fe} argument is stored in the mid-end. It will be used when
+calling back to functions such as \cw{activate_timer()}
+(\k{frontend-activate-timer}), and will be passed on to the back end
+function \cw{colours()} (\k{backend-colours}).
+
+The parameters \c{drapi} and \c{drhandle} are passed to
+\cw{drawing_new()} (\k{drawing-new}) to construct a drawing object
+which will be passed to the back end function \cw{redraw()}
+(\k{backend-redraw}). Hence, all drawing-related function pointers
+defined in \c{drapi} can expect to be called with \c{drhandle} as
+their first argument.
+
+The \c{ourgame} argument points to a container structure describing
+a game back end. The mid-end thus created will only be capable of
+handling that one game. (So even in a monolithic front end
+containing all the games, this imposes the constraint that any
+individual puzzle window is tied to a single game. Unless, of
+course, you feel brave enough to change the mid-end for the window
+without closing the window...)
+
+\H{midend-free} \cw{midend_free()}
+
+\c void midend_free(midend *me);
+
+Frees a mid-end structure and all its associated data.
+
+\H{midend-tilesize} \cw{midend_tilesize()}
+
+\c int midend_tilesize(midend *me);
+
+Returns the \cq{tilesize} parameter being used to display the
+current puzzle (\k{backend-preferred-tilesize}).
+
+\H{midend-set-params} \cw{midend_set_params()}
+
+\c void midend_set_params(midend *me, game_params *params);
+
+Sets the current game parameters for a mid-end. Subsequent games
+generated by \cw{midend_new_game()} (\k{midend-new-game}) will use
+these parameters until further notice.
+
+The usual way in which the front end will have an actual
+\c{game_params} structure to pass to this function is if it had
+previously got it from \cw{midend_fetch_preset()}
+(\k{midend-fetch-preset}). Thus, this function is usually called in
+response to the user making a selection from the presets menu.
+
+\H{midend-get-params} \cw{midend_get_params()}
+
+\c game_params *midend_get_params(midend *me);
+
+Returns the current game parameters stored in this mid-end.
+
+The returned value is dynamically allocated, and should be freed
+when finished with by passing it to the game's own
+\cw{free_params()} function (see \k{backend-free-params}).
+
+\H{midend-size} \cw{midend_size()}
+
+\c void midend_size(midend *me, int *x, int *y, int user_size);
+
+Tells the mid-end to figure out its window size.
+
+On input, \c{*x} and \c{*y} should contain the maximum or requested
+size for the window. (Typically this will be the size of the screen
+that the window has to fit on, or similar.) The mid-end will
+repeatedly call the back end function \cw{compute_size()}
+(\k{backend-compute-size}), searching for a tile size that best
+satisfies the requirements. On exit, \c{*x} and \c{*y} will contain
+the size needed for the puzzle window's drawing area. (It is of
+course up to the front end to adjust this for any additional window
+furniture such as menu bars and window borders, if necessary. The
+status bar is also not included in this size.)
+
+Use \c{user_size} to indicate whether \c{*x} and \c{*y} are a
+requested size, or just a maximum size.
+
+If \c{user_size} is set to \cw{TRUE}, the mid-end will treat the
+input size as a request, and will pick a tile size which
+approximates it \e{as closely as possible}, going over the game's
+preferred tile size if necessary to achieve this. The mid-end will
+also use the resulting tile size as its preferred one until further
+notice, on the assumption that this size was explicitly requested
+by the user. Use this option if you want your front end to support
+dynamic resizing of the puzzle window with automatic scaling of the
+puzzle to fit.
+
+If \c{user_size} is set to \cw{FALSE}, then the game's tile size
+will never go over its preferred one, although it may go under in
+order to fit within the maximum bounds specified by \c{*x} and
+\c{*y}. This is the recommended approach when opening a new window
+at default size: the game will use its preferred size unless it has
+to use a smaller one to fit on the screen. If the tile size is
+shrunk for this reason, the change will not persist; if a smaller
+grid is subsequently chosen, the tile size will recover.
+
+The mid-end will try as hard as it can to return a size which is
+less than or equal to the input size, in both dimensions. In extreme
+circumstances it may fail (if even the lowest possible tile size
+gives window dimensions greater than the input), in which case it
+will return a size greater than the input size. Front ends should be
+prepared for this to happen (i.e. don't crash or fail an assertion),
+but may handle it in any way they see fit: by rejecting the game
+parameters which caused the problem, by opening a window larger than
+the screen regardless of inconvenience, by introducing scroll bars
+on the window, by drawing on a large bitmap and scaling it into a
+smaller window, or by any other means you can think of. It is likely
+that when the tile size is that small the game will be unplayable
+anyway, so don't put \e{too} much effort into handling it
+creatively.
+
+If your platform has no limit on window size (or if you're planning
+to use scroll bars for large puzzles), you can pass dimensions of
+\cw{INT_MAX} as input to this function. You should probably not do
+that \e{and} set the \c{user_size} flag, though!
+
+The midend relies on the frontend calling \cw{midend_new_game()}
+(\k{midend-new-game}) before calling \cw{midend_size()}.
+
+\H{midend-reset-tilesize} \cw{midend_reset_tilesize()}
+
+\c void midend_reset_tilesize(midend *me);
+
+This function resets the midend's preferred tile size to that of the
+standard puzzle.
+
+As discussed in \k{midend-size}, puzzle resizes are typically
+'sticky', in that once the user has dragged the puzzle to a different
+window size, the resulting tile size will be remembered and used when
+the puzzle configuration changes. If you \e{don't} want that, e.g. if
+you want to provide a command to explicitly reset the puzzle size back
+to its default, then you can call this just before calling
+\cw{midend_size()} (which, in turn, you would probably call with
+\c{user_size} set to \cw{FALSE}).
+
+\H{midend-new-game} \cw{midend_new_game()}
+
+\c void midend_new_game(midend *me);
+
+Causes the mid-end to begin a new game. Normally the game will be a
+new randomly generated puzzle. However, if you have previously
+called \cw{midend_game_id()} or \cw{midend_set_config()}, the game
+generated might be dictated by the results of those functions. (In
+particular, you \e{must} call \cw{midend_new_game()} after calling
+either of those functions, or else no immediate effect will be
+visible.)
+
+You will probably need to call \cw{midend_size()} after calling this
+function, because if the game parameters have been changed since the
+last new game then the window size might need to change. (If you
+know the parameters \e{haven't} changed, you don't need to do this.)
+
+This function will create a new \c{game_drawstate}, but does not
+actually perform a redraw (since you often need to call
+\cw{midend_size()} before the redraw can be done). So after calling
+this function and after calling \cw{midend_size()}, you should then
+call \cw{midend_redraw()}. (It is not necessary to call
+\cw{midend_force_redraw()}; that will discard the draw state and
+create a fresh one, which is unnecessary in this case since there's
+a fresh one already. It would work, but it's usually excessive.)
+
+\H{midend-restart-game} \cw{midend_restart_game()}
+
+\c void midend_restart_game(midend *me);
+
+This function causes the current game to be restarted. This is done
+by placing a new copy of the original game state on the end of the
+undo list (so that an accidental restart can be undone).
+
+This function automatically causes a redraw, i.e. the front end can
+expect its drawing API to be called from \e{within} a call to this
+function. Some back ends require that \cw{midend_size()}
+(\k{midend-size}) is called before \cw{midend_restart_game()}.
+
+\H{midend-force-redraw} \cw{midend_force_redraw()}
+
+\c void midend_force_redraw(midend *me);
+
+Forces a complete redraw of the puzzle window, by means of
+discarding the current \c{game_drawstate} and creating a new one
+from scratch before calling the game's \cw{redraw()} function.
+
+The front end can expect its drawing API to be called from within a
+call to this function. Some back ends require that \cw{midend_size()}
+(\k{midend-size}) is called before \cw{midend_force_redraw()}.
+
+\H{midend-redraw} \cw{midend_redraw()}
+
+\c void midend_redraw(midend *me);
+
+Causes a partial redraw of the puzzle window, by means of simply
+calling the game's \cw{redraw()} function. (That is, the only things
+redrawn will be things that have changed since the last redraw.)
+
+The front end can expect its drawing API to be called from within a
+call to this function. Some back ends require that \cw{midend_size()}
+(\k{midend-size}) is called before \cw{midend_redraw()}.
+
+\H{midend-process-key} \cw{midend_process_key()}
+
+\c int midend_process_key(midend *me, int x, int y, int button);
+
+The front end calls this function to report a mouse or keyboard
+event. The parameters \c{x}, \c{y} and \c{button} are almost
+identical to the ones passed to the back end function
+\cw{interpret_move()} (\k{backend-interpret-move}), except that the
+front end is \e{not} required to provide the guarantees about mouse
+event ordering. The mid-end will sort out multiple simultaneous
+button presses and changes of button; the front end's responsibility
+is simply to pass on the mouse events it receives as accurately as
+possible.
+
+(Some platforms may need to emulate absent mouse buttons by means of
+using a modifier key such as Shift with another mouse button. This
+tends to mean that if Shift is pressed or released in the middle of
+a mouse drag, the mid-end will suddenly stop receiving, say,
+\cw{LEFT_DRAG} events and start receiving \cw{RIGHT_DRAG}s, with no
+intervening button release or press events. This too is something
+which the mid-end will sort out for you; the front end has no
+obligation to maintain sanity in this area.)
+
+The front end \e{should}, however, always eventually send some kind
+of button release. On some platforms this requires special effort:
+Windows, for example, requires a call to the system API function
+\cw{SetCapture()} in order to ensure that your window receives a
+mouse-up event even if the pointer has left the window by the time
+the mouse button is released. On any platform that requires this
+sort of thing, the front end \e{is} responsible for doing it.
+
+Calling this function is very likely to result in calls back to the
+front end's drawing API and/or \cw{activate_timer()}
+(\k{frontend-activate-timer}).
+
+The return value from \cw{midend_process_key()} is non-zero, unless
+the effect of the keypress was to request termination of the
+program. A front end should shut down the puzzle in response to a
+zero return.
+
+\H{midend-colours} \cw{midend_colours()}
+
+\c float *midend_colours(midend *me, int *ncolours);
+
+Returns an array of the colours required by the game, in exactly the
+same format as that returned by the back end function \cw{colours()}
+(\k{backend-colours}). Front ends should call this function rather
+than calling the back end's version directly, since the mid-end adds
+standard customisation facilities. (At the time of writing, those
+customisation facilities are implemented hackily by means of
+environment variables, but it's not impossible that they may become
+more full and formal in future.)
+
+\H{midend-timer} \cw{midend_timer()}
+
+\c void midend_timer(midend *me, float tplus);
+
+If the mid-end has called \cw{activate_timer()}
+(\k{frontend-activate-timer}) to request regular callbacks for
+purposes of animation or timing, this is the function the front end
+should call on a regular basis. The argument \c{tplus} gives the
+time, in seconds, since the last time either this function was
+called or \cw{activate_timer()} was invoked.
+
+One of the major purposes of timing in the mid-end is to perform
+move animation. Therefore, calling this function is very likely to
+result in calls back to the front end's drawing API.
+
+\H{midend-num-presets} \cw{midend_num_presets()}
+
+\c int midend_num_presets(midend *me);
+
+Returns the number of game parameter presets supplied by this game.
+Front ends should use this function and \cw{midend_fetch_preset()}
+to configure their presets menu rather than calling the back end
+directly, since the mid-end adds standard customisation facilities.
+(At the time of writing, those customisation facilities are
+implemented hackily by means of environment variables, but it's not
+impossible that they may become more full and formal in future.)
+
+\H{midend-fetch-preset} \cw{midend_fetch_preset()}
+
+\c void midend_fetch_preset(midend *me, int n,
+\c                          char **name, game_params **params);
+
+Returns one of the preset game parameter structures for the game. On
+input \c{n} must be a non-negative integer and less than the value
+returned from \cw{midend_num_presets()}. On output, \c{*name} is set
+to an ASCII string suitable for entering in the game's presets menu,
+and \c{*params} is set to the corresponding \c{game_params}
+structure.
+
+Both of the two output values are dynamically allocated, but they
+are owned by the mid-end structure: the front end should not ever
+free them directly, because they will be freed automatically during
+\cw{midend_free()}.
+
+\H{midend-which-preset} \cw{midend_which_preset()}
+
+\c int midend_which_preset(midend *me);
+
+Returns the numeric index of the preset game parameter structure
+which matches the current game parameters, or a negative number if
+no preset matches. Front ends could use this to maintain a tick
+beside one of the items in the menu (or tick the \q{Custom} option
+if the return value is less than zero).
+
+\H{midend-wants-statusbar} \cw{midend_wants_statusbar()}
+
+\c int midend_wants_statusbar(midend *me);
+
+This function returns \cw{TRUE} if the puzzle has a use for a
+textual status line (to display score, completion status, currently
+active tiles, time, or anything else).
+
+Front ends should call this function rather than talking directly to
+the back end.
+
+\H{midend-get-config} \cw{midend_get_config()}
+
+\c config_item *midend_get_config(midend *me, int which,
+\c                                char **wintitle);
+
+Returns a dialog box description for user configuration.
+
+On input, \cw{which} should be set to one of three values, which
+select which of the various dialog box descriptions is returned:
+
+\dt \cw{CFG_SETTINGS}
+
+\dd Requests the GUI parameter configuration box generated by the
+puzzle itself. This should be used when the user selects \q{Custom}
+from the game types menu (or equivalent). The mid-end passes this
+request on to the back end function \cw{configure()}
+(\k{backend-configure}).
+
+\dt \cw{CFG_DESC}
+
+\dd Requests a box suitable for entering a descriptive game ID (and
+viewing the existing one). The mid-end generates this dialog box
+description itself. This should be used when the user selects
+\q{Specific} from the game menu (or equivalent).
+
+\dt \cw{CFG_SEED}
+
+\dd Requests a box suitable for entering a random-seed game ID (and
+viewing the existing one). The mid-end generates this dialog box
+description itself. This should be used when the user selects
+\q{Random Seed} from the game menu (or equivalent).
+
+The returned value is an array of \cw{config_item}s, exactly as
+described in \k{backend-configure}. Another returned value is an
+ASCII string giving a suitable title for the configuration window,
+in \c{*wintitle}.
+
+Both returned values are dynamically allocated and will need to be
+freed. The window title can be freed in the obvious way; the
+\cw{config_item} array is a slightly complex structure, so a utility
+function \cw{free_cfg()} is provided to free it for you. See
+\k{utils-free-cfg}.
+
+(Of course, you will probably not want to free the \cw{config_item}
+array until the dialog box is dismissed, because before then you
+will probably need to pass it to \cw{midend_set_config}.)
+
+\H{midend-set-config} \cw{midend_set_config()}
+
+\c char *midend_set_config(midend *me, int which,
+\c                         config_item *cfg);
+
+Passes the mid-end the results of a configuration dialog box.
+\c{which} should have the same value which it had when
+\cw{midend_get_config()} was called; \c{cfg} should be the array of
+\c{config_item}s returned from \cw{midend_get_config()}, modified to
+contain the results of the user's editing operations.
+
+This function returns \cw{NULL} on success, or otherwise (if the
+configuration data was in some way invalid) an ASCII string
+containing an error message suitable for showing to the user.
+
+If the function succeeds, it is likely that the game parameters will
+have been changed and it is certain that a new game will be
+requested. The front end should therefore call
+\cw{midend_new_game()}, and probably also re-think the window size
+using \cw{midend_size()} and eventually perform a refresh using
+\cw{midend_redraw()}.
+
+\H{midend-game-id} \cw{midend_game_id()}
+
+\c char *midend_game_id(midend *me, char *id);
+
+Passes the mid-end a string game ID (of any of the valid forms
+\cq{params}, \cq{params:description} or \cq{params#seed}) which the
+mid-end will process and use for the next generated game.
+
+This function returns \cw{NULL} on success, or otherwise (if the
+configuration data was in some way invalid) an ASCII string
+containing an error message (not dynamically allocated) suitable for
+showing to the user. In the event of an error, the mid-end's
+internal state will be left exactly as it was before the call.
+
+If the function succeeds, it is likely that the game parameters will
+have been changed and it is certain that a new game will be
+requested. The front end should therefore call
+\cw{midend_new_game()}, and probably also re-think the window size
+using \cw{midend_size()} and eventually case a refresh using
+\cw{midend_redraw()}.
+
+\H{midend-get-game-id} \cw{midend_get_game_id()}
+
+\c char *midend_get_game_id(midend *me)
+
+Returns a descriptive game ID (i.e. one in the form
+\cq{params:description}) describing the game currently active in the
+mid-end. The returned string is dynamically allocated.
+
+\H{midend-get-random-seed} \cw{midend_get_random_seed()}
+
+\c char *midend_get_random_seed(midend *me)
+
+Returns a random game ID (i.e. one in the form \cq{params#seedstring})
+describing the game currently active in the mid-end, if there is one.
+If the game was created by entering a description, no random seed will
+currently exist and this function will return \cw{NULL}.
+
+The returned string, if it is non-\cw{NULL}, is dynamically allocated.
+
+\H{midend-can-format-as-text-now} \cw{midend_can_format_as_text_now()}
+
+\c int midend_can_format_as_text_now(midend *me);
+
+Returns \cw{TRUE} if the game code is capable of formatting puzzles
+of the currently selected game type as ASCII.
+
+If this returns \cw{FALSE}, then \cw{midend_text_format()}
+(\k{midend-text-format}) will return \cw{NULL}.
+
+\H{midend-text-format} \cw{midend_text_format()}
+
+\c char *midend_text_format(midend *me);
+
+Formats the current game's current state as ASCII text suitable for
+copying to the clipboard. The returned string is dynamically
+allocated.
+
+If the game's \c{can_format_as_text_ever} flag is \cw{FALSE}, or if
+its \cw{can_format_as_text_now()} function returns \cw{FALSE}, then
+this function will return \cw{NULL}.
+
+If the returned string contains multiple lines (which is likely), it
+will use the normal C line ending convention (\cw{\\n} only). On
+platforms which use a different line ending convention for data in
+the clipboard, it is the front end's responsibility to perform the
+conversion.
+
+\H{midend-solve} \cw{midend_solve()}
+
+\c char *midend_solve(midend *me);
+
+Requests the mid-end to perform a Solve operation.
+
+On success, \cw{NULL} is returned. On failure, an error message (not
+dynamically allocated) is returned, suitable for showing to the
+user.
+
+The front end can expect its drawing API and/or
+\cw{activate_timer()} to be called from within a call to this
+function.  Some back ends require that \cw{midend_size()}
+(\k{midend-size}) is called before \cw{midend_solve()}.
+
+\H{midend-status} \cw{midend_status()}
+
+\c int midend_status(midend *me);
+
+This function returns +1 if the midend is currently displaying a game
+in a solved state, -1 if the game is in a permanently lost state, or 0
+otherwise. This function just calls the back end's \cw{status()}
+function. Front ends may wish to use this as a cue to proactively
+offer the option of starting a new game.
+
+(See \k{backend-status} for more detail about the back end's
+\cw{status()} function and discussion of what should count as which
+status code.)
+
+\H{midend-can-undo} \cw{midend_can_undo()}
+
+\c int midend_can_undo(midend *me);
+
+Returns \cw{TRUE} if the midend is currently in a state where the undo
+operation is meaningful (i.e. at least one position exists on the undo
+chain before the present one). Front ends may wish to use this to
+visually activate and deactivate an undo button.
+
+\H{midend-can-redo} \cw{midend_can_redo()}
+
+\c int midend_can_redo(midend *me);
+
+Returns \cw{TRUE} if the midend is currently in a state where the redo
+operation is meaningful (i.e. at least one position exists on the redo
+chain after the present one). Front ends may wish to use this to
+visually activate and deactivate a redo button.
+
+\H{midend-serialise} \cw{midend_serialise()}
+
+\c void midend_serialise(midend *me,
+\c                       void (*write)(void *ctx, void *buf, int len),
+\c                       void *wctx);
+
+Calling this function causes the mid-end to convert its entire
+internal state into a long ASCII text string, and to pass that
+string (piece by piece) to the supplied \c{write} function.
+
+Desktop implementations can use this function to save a game in any
+state (including half-finished) to a disk file, by supplying a
+\c{write} function which is a wrapper on \cw{fwrite()} (or local
+equivalent). Other implementations may find other uses for it, such
+as compressing the large and sprawling mid-end state into a
+manageable amount of memory when a palmtop application is suspended
+so that another one can run; in this case \cw{write} might want to
+write to a memory buffer rather than a file. There may be other uses
+for it as well.
+
+This function will call back to the supplied \c{write} function a
+number of times, with the first parameter (\c{ctx}) equal to
+\c{wctx}, and the other two parameters pointing at a piece of the
+output string.
+
+\H{midend-deserialise} \cw{midend_deserialise()}
+
+\c char *midend_deserialise(midend *me,
+\c                          int (*read)(void *ctx, void *buf, int len),
+\c                          void *rctx);
+
+This function is the counterpart to \cw{midend_serialise()}. It
+calls the supplied \cw{read} function repeatedly to read a quantity
+of data, and attempts to interpret that data as a serialised mid-end
+as output by \cw{midend_serialise()}.
+
+The \cw{read} function is called with the first parameter (\c{ctx})
+equal to \c{rctx}, and should attempt to read \c{len} bytes of data
+into the buffer pointed to by \c{buf}. It should return \cw{FALSE}
+on failure or \cw{TRUE} on success. It should not report success
+unless it has filled the entire buffer; on platforms which might be
+reading from a pipe or other blocking data source, \c{read} is
+responsible for looping until the whole buffer has been filled.
+
+If the de-serialisation operation is successful, the mid-end's
+internal data structures will be replaced by the results of the
+load, and \cw{NULL} will be returned. Otherwise, the mid-end's state
+will be completely unchanged and an error message (typically some
+variation on \q{save file is corrupt}) will be returned. As usual,
+the error message string is not dynamically allocated.
+
+If this function succeeds, it is likely that the game parameters
+will have been changed. The front end should therefore probably
+re-think the window size using \cw{midend_size()}, and probably
+cause a refresh using \cw{midend_redraw()}.
+
+Because each mid-end is tied to a specific game back end, this
+function will fail if you attempt to read in a save file generated by
+a different game from the one configured in this mid-end, even if your
+application is a monolithic one containing all the puzzles. See
+\k{identify-game} for a helper function which will allow you to
+identify a save file before you instantiate your mid-end in the first
+place.
+
+\H{identify-game} \cw{identify_game()}
+
+\c char *identify_game(char **name,
+\c                     int (*read)(void *ctx, void *buf, int len),
+\c                     void *rctx);
+
+This function examines a serialised midend stream, of the same kind
+used by \cw{midend_serialise()} and \cw{midend_deserialise()}, and
+returns the \cw{name} field of the game back end from which it was
+saved.
+
+You might want this if your front end was a monolithic one containing
+all the puzzles, and you wanted to be able to load an arbitrary save
+file and automatically switch to the right game. Probably your next
+step would be to iterate through \cw{gamelist} (\k{frontend-backend})
+looking for a game structure whose \cw{name} field matched the
+returned string, and give an error if you didn't find one.
+
+On success, the return value of this function is \cw{NULL}, and the
+game name string is written into \cw{*name}. The caller should free
+that string after using it.
+
+On failure, \cw{*name} is \cw{NULL}, and the return value is an error
+message (which does not need freeing at all).
+
+(This isn't strictly speaking a midend function, since it doesn't
+accept or return a pointer to a midend. You'd probably call it just
+\e{before} deciding what kind of midend you wanted to instantiate.)
+
+\H{midend-request-id-changes} \cw{midend_request_id_changes()}
+
+\c void midend_request_id_changes(midend *me,
+\c                                void (*notify)(void *), void *ctx);
+
+This function is called by the front end to request notification by
+the mid-end when the current game IDs (either descriptive or
+random-seed) change. This can occur as a result of keypresses ('n' for
+New Game, for example) or when a puzzle supersedes its game
+description (see \k{backend-supersede}). After this function is
+called, any change of the game ids will cause the mid-end to call
+\cw{notify(ctx)} after the change.
+
+This is for use by puzzles which want to present the game description
+to the user constantly (e.g. as an HTML hyperlink) instead of only
+showing it when the user explicitly requests it.
+
+This is a function I anticipate few front ends needing to implement,
+so I make it a callback rather than a static function in order to
+relieve most front ends of the need to provide an empty
+implementation.
+
+\H{frontend-backend} Direct reference to the back end structure by
+the front end
+
+Although \e{most} things the front end needs done should be done by
+calling the mid-end, there are a few situations in which the front
+end needs to refer directly to the game back end structure.
+
+The most obvious of these is
+
+\b passing the game back end as a parameter to \cw{midend_new()}.
+
+There are a few other back end features which are not wrapped by the
+mid-end because there didn't seem much point in doing so:
+
+\b fetching the \c{name} field to use in window titles and similar
+
+\b reading the \c{can_configure}, \c{can_solve} and
+\c{can_format_as_text_ever} fields to decide whether to add those
+items to the menu bar or equivalent
+
+\b reading the \c{winhelp_topic} field (Windows only)
+
+\b the GTK front end provides a \cq{--generate} command-line option
+which directly calls the back end to do most of its work. This is
+not really part of the main front end code, though, and I'm not sure
+it counts.
+
+In order to find the game back end structure, the front end does one
+of two things:
+
+\b If the particular front end is compiling a separate binary per
+game, then the back end structure is a global variable with the
+standard name \cq{thegame}:
+
+\lcont{
+
+\c extern const game thegame;
+
+}
+
+\b If the front end is compiled as a monolithic application
+containing all the puzzles together (in which case the preprocessor
+symbol \cw{COMBINED} must be defined when compiling most of the code
+base), then there will be two global variables defined:
+
+\lcont{
+
+\c extern const game *gamelist[];
+\c extern const int gamecount;
+
+\c{gamelist} will be an array of \c{gamecount} game structures,
+declared in the automatically constructed source module \c{list.c}.
+The application should search that array for the game it wants,
+probably by reaching into each game structure and looking at its
+\c{name} field.
+
+}
+
+\H{frontend-api} Mid-end to front-end calls
+
+This section describes the small number of functions which a front
+end must provide to be called by the mid-end or other standard
+utility modules.
+
+\H{frontend-get-random-seed} \cw{get_random_seed()}
+
+\c void get_random_seed(void **randseed, int *randseedsize);
+
+This function is called by a new mid-end, and also occasionally by
+game back ends. Its job is to return a piece of data suitable for
+using as a seed for initialisation of a new \c{random_state}.
+
+On exit, \c{*randseed} should be set to point at a newly allocated
+piece of memory containing some seed data, and \c{*randseedsize}
+should be set to the length of that data.
+
+A simple and entirely adequate implementation is to return a piece
+of data containing the current system time at the highest
+conveniently available resolution.
+
+\H{frontend-activate-timer} \cw{activate_timer()}
+
+\c void activate_timer(frontend *fe);
+
+This is called by the mid-end to request that the front end begin
+calling it back at regular intervals.
+
+The timeout interval is left up to the front end; the finer it is,
+the smoother move animations will be, but the more CPU time will be
+used. Current front ends use values around 20ms (i.e. 50Hz).
+
+After this function is called, the mid-end will expect to receive
+calls to \cw{midend_timer()} on a regular basis.
+
+\H{frontend-deactivate-timer} \cw{deactivate_timer()}
+
+\c void deactivate_timer(frontend *fe);
+
+This is called by the mid-end to request that the front end stop
+calling \cw{midend_timer()}.
+
+\H{frontend-fatal} \cw{fatal()}
+
+\c void fatal(char *fmt, ...);
+
+This is called by some utility functions if they encounter a
+genuinely fatal error such as running out of memory. It is a
+variadic function in the style of \cw{printf()}, and is expected to
+show the formatted error message to the user any way it can and then
+terminate the application. It must not return.
+
+\H{frontend-default-colour} \cw{frontend_default_colour()}
+
+\c void frontend_default_colour(frontend *fe, float *output);
+
+This function expects to be passed a pointer to an array of three
+\cw{float}s. It returns the platform's local preferred background
+colour in those three floats, as red, green and blue values (in that
+order) ranging from \cw{0.0} to \cw{1.0}.
+
+This function should only ever be called by the back end function
+\cw{colours()} (\k{backend-colours}). (Thus, it isn't a
+\e{midend}-to-frontend function as such, but there didn't seem to be
+anywhere else particularly good to put it. Sorry.)
+
+\C{utils} Utility APIs
+
+This chapter documents a variety of utility APIs provided for the
+general use of the rest of the Puzzles code.
+
+\H{utils-random} Random number generation
+
+Platforms' local random number generators vary widely in quality and
+seed size. Puzzles therefore supplies its own high-quality random
+number generator, with the additional advantage of giving the same
+results if fed the same seed data on different platforms. This
+allows game random seeds to be exchanged between different ports of
+Puzzles and still generate the same games.
+
+Unlike the ANSI C \cw{rand()} function, the Puzzles random number
+generator has an \e{explicit} state object called a
+\c{random_state}. One of these is managed by each mid-end, for
+example, and passed to the back end to generate a game with.
+
+\S{utils-random-init} \cw{random_new()}
+
+\c random_state *random_new(char *seed, int len);
+
+Allocates, initialises and returns a new \c{random_state}. The input
+data is used as the seed for the random number stream (i.e. using
+the same seed at a later time will generate the same stream).
+
+The seed data can be any data at all; there is no requirement to use
+printable ASCII, or NUL-terminated strings, or anything like that.
+
+\S{utils-random-copy} \cw{random_copy()}
+
+\c random_state *random_copy(random_state *tocopy);
+
+Allocates a new \c{random_state}, copies the contents of another
+\c{random_state} into it, and returns the new state.  If exactly the
+same sequence of functions is subseqently called on both the copy and
+the original, the results will be identical.  This may be useful for
+speculatively performing some operation using a given random state,
+and later replaying that operation precisely.
+
+\S{utils-random-free} \cw{random_free()}
+
+\c void random_free(random_state *state);
+
+Frees a \c{random_state}.
+
+\S{utils-random-bits} \cw{random_bits()}
+
+\c unsigned long random_bits(random_state *state, int bits);
+
+Returns a random number from 0 to \cw{2^bits-1} inclusive. \c{bits}
+should be between 1 and 32 inclusive.
+
+\S{utils-random-upto} \cw{random_upto()}
+
+\c unsigned long random_upto(random_state *state, unsigned long limit);
+
+Returns a random number from 0 to \cw{limit-1} inclusive.
+
+\S{utils-random-state-encode} \cw{random_state_encode()}
+
+\c char *random_state_encode(random_state *state);
+
+Encodes the entire contents of a \c{random_state} in printable
+ASCII. Returns a dynamically allocated string containing that
+encoding. This can subsequently be passed to
+\cw{random_state_decode()} to reconstruct the same \c{random_state}.
+
+\S{utils-random-state-decode} \cw{random_state_decode()}
+
+\c random_state *random_state_decode(char *input);
+
+Decodes a string generated by \cw{random_state_encode()} and
+reconstructs an equivalent \c{random_state} to the one encoded, i.e.
+it should produce the same stream of random numbers.
+
+This function has no error reporting; if you pass it an invalid
+string it will simply generate an arbitrary random state, which may
+turn out to be noticeably non-random.
+
+\S{utils-shuffle} \cw{shuffle()}
+
+\c void shuffle(void *array, int nelts, int eltsize, random_state *rs);
+
+Shuffles an array into a random order. The interface is much like
+ANSI C \cw{qsort()}, except that there's no need for a compare
+function.
+
+\c{array} is a pointer to the first element of the array. \c{nelts}
+is the number of elements in the array; \c{eltsize} is the size of a
+single element (typically measured using \c{sizeof}). \c{rs} is a
+\c{random_state} used to generate all the random numbers for the
+shuffling process.
+
+\H{utils-alloc} Memory allocation
+
+Puzzles has some central wrappers on the standard memory allocation
+functions, which provide compile-time type checking, and run-time
+error checking by means of quitting the application if it runs out
+of memory. This doesn't provide the best possible recovery from
+memory shortage, but on the other hand it greatly simplifies the
+rest of the code, because nothing else anywhere needs to worry about
+\cw{NULL} returns from allocation.
+
+\S{utils-snew} \cw{snew()}
+
+\c var = snew(type);
+\e iii        iiii
+
+This macro takes a single argument which is a \e{type name}. It
+allocates space for one object of that type. If allocation fails it
+will call \cw{fatal()} and not return; so if it does return, you can
+be confident that its return value is non-\cw{NULL}.
+
+The return value is cast to the specified type, so that the compiler
+will type-check it against the variable you assign it into. Thus,
+this ensures you don't accidentally allocate memory the size of the
+wrong type and assign it into a variable of the right one (or vice
+versa!).
+
+\S{utils-snewn} \cw{snewn()}
+
+\c var = snewn(n, type);
+\e iii         i  iiii
+
+This macro is the array form of \cw{snew()}. It takes two arguments;
+the first is a number, and the second is a type name. It allocates
+space for that many objects of that type, and returns a type-checked
+non-\cw{NULL} pointer just as \cw{snew()} does.
+
+\S{utils-sresize} \cw{sresize()}
+
+\c var = sresize(var, n, type);
+\e iii           iii  i  iiii
+
+This macro is a type-checked form of \cw{realloc()}. It takes three
+arguments: an input memory block, a new size in elements, and a
+type. It re-sizes the input memory block to a size sufficient to
+contain that many elements of that type. It returns a type-checked
+non-\cw{NULL} pointer, like \cw{snew()} and \cw{snewn()}.
+
+The input memory block can be \cw{NULL}, in which case this function
+will behave exactly like \cw{snewn()}. (In principle any
+ANSI-compliant \cw{realloc()} implementation ought to cope with
+this, but I've never quite trusted it to work everywhere.)
+
+\S{utils-sfree} \cw{sfree()}
+
+\c void sfree(void *p);
+
+This function is pretty much equivalent to \cw{free()}. It is
+provided with a dynamically allocated block, and frees it.
+
+The input memory block can be \cw{NULL}, in which case this function
+will do nothing. (In principle any ANSI-compliant \cw{free()}
+implementation ought to cope with this, but I've never quite trusted
+it to work everywhere.)
+
+\S{utils-dupstr} \cw{dupstr()}
+
+\c char *dupstr(const char *s);
+
+This function dynamically allocates a duplicate of a C string. Like
+the \cw{snew()} functions, it guarantees to return non-\cw{NULL} or
+not return at all.
+
+(Many platforms provide the function \cw{strdup()}. As well as
+guaranteeing never to return \cw{NULL}, my version has the advantage
+of being defined \e{everywhere}, rather than inconveniently not
+quite everywhere.)
+
+\S{utils-free-cfg} \cw{free_cfg()}
+
+\c void free_cfg(config_item *cfg);
+
+This function correctly frees an array of \c{config_item}s,
+including walking the array until it gets to the end and freeing
+precisely those \c{sval} fields which are expected to be dynamically
+allocated.
+
+(See \k{backend-configure} for details of the \c{config_item}
+structure.)
+
+\H{utils-tree234} Sorted and counted tree functions
+
+Many games require complex algorithms for generating random puzzles,
+and some require moderately complex algorithms even during play. A
+common requirement during these algorithms is for a means of
+maintaining sorted or unsorted lists of items, such that items can
+be removed and added conveniently.
+
+For general use, Puzzles provides the following set of functions
+which maintain 2-3-4 trees in memory. (A 2-3-4 tree is a balanced
+tree structure, with the property that all lookups, insertions,
+deletions, splits and joins can be done in \cw{O(log N)} time.)
+
+All these functions expect you to be storing a tree of \c{void *}
+pointers. You can put anything you like in those pointers.
+
+By the use of per-node element counts, these tree structures have
+the slightly unusual ability to look elements up by their numeric
+index within the list represented by the tree. This means that they
+can be used to store an unsorted list (in which case, every time you
+insert a new element, you must explicitly specify the position where
+you wish to insert it). They can also do numeric lookups in a sorted
+tree, which might be useful for (for example) tracking the median of
+a changing data set.
+
+As well as storing sorted lists, these functions can be used for
+storing \q{maps} (associative arrays), by defining each element of a
+tree to be a (key, value) pair.
+
+\S{utils-newtree234} \cw{newtree234()}
+
+\c tree234 *newtree234(cmpfn234 cmp);
+
+Creates a new empty tree, and returns a pointer to it.
+
+The parameter \c{cmp} determines the sorting criterion on the tree.
+Its prototype is
+
+\c typedef int (*cmpfn234)(void *, void *);
+
+If you want a sorted tree, you should provide a function matching
+this prototype, which returns like \cw{strcmp()} does (negative if
+the first argument is smaller than the second, positive if it is
+bigger, zero if they compare equal). In this case, the function
+\cw{addpos234()} will not be usable on your tree (because all
+insertions must respect the sorting order).
+
+If you want an unsorted tree, pass \cw{NULL}. In this case you will
+not be able to use either \cw{add234()} or \cw{del234()}, or any
+other function such as \cw{find234()} which depends on a sorting
+order. Your tree will become something more like an array, except
+that it will efficiently support insertion and deletion as well as
+lookups by numeric index.
+
+\S{utils-freetree234} \cw{freetree234()}
+
+\c void freetree234(tree234 *t);
+
+Frees a tree. This function will not free the \e{elements} of the
+tree (because they might not be dynamically allocated, or you might
+be storing the same set of elements in more than one tree); it will
+just free the tree structure itself. If you want to free all the
+elements of a tree, you should empty it before passing it to
+\cw{freetree234()}, by means of code along the lines of
+
+\c while ((element = delpos234(tree, 0)) != NULL)
+\c     sfree(element); /* or some more complicated free function */
+\e                     iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
+
+\S{utils-add234} \cw{add234()}
+
+\c void *add234(tree234 *t, void *e);
+
+Inserts a new element \c{e} into the tree \c{t}. This function
+expects the tree to be sorted; the new element is inserted according
+to the sort order.
+
+If an element comparing equal to \c{e} is already in the tree, then
+the insertion will fail, and the return value will be the existing
+element. Otherwise, the insertion succeeds, and \c{e} is returned.
+
+\S{utils-addpos234} \cw{addpos234()}
+
+\c void *addpos234(tree234 *t, void *e, int index);
+
+Inserts a new element into an unsorted tree. Since there is no
+sorting order to dictate where the new element goes, you must
+specify where you want it to go. Setting \c{index} to zero puts the
+new element right at the start of the list; setting \c{index} to the
+current number of elements in the tree puts the new element at the
+end.
+
+Return value is \c{e}, in line with \cw{add234()} (although this
+function cannot fail except by running out of memory, in which case
+it will bomb out and die rather than returning an error indication).
+
+\S{utils-index234} \cw{index234()}
+
+\c void *index234(tree234 *t, int index);
+
+Returns a pointer to the \c{index}th element of the tree, or
+\cw{NULL} if \c{index} is out of range. Elements of the tree are
+numbered from zero.
+
+\S{utils-find234} \cw{find234()}
+
+\c void *find234(tree234 *t, void *e, cmpfn234 cmp);
+
+Searches for an element comparing equal to \c{e} in a sorted tree.
+
+If \c{cmp} is \cw{NULL}, the tree's ordinary comparison function
+will be used to perform the search. However, sometimes you don't
+want that; suppose, for example, each of your elements is a big
+structure containing a \c{char *} name field, and you want to find
+the element with a given name. You \e{could} achieve this by
+constructing a fake element structure, setting its name field
+appropriately, and passing it to \cw{find234()}, but you might find
+it more convenient to pass \e{just} a name string to \cw{find234()},
+supplying an alternative comparison function which expects one of
+its arguments to be a bare name and the other to be a large
+structure containing a name field.
+
+Therefore, if \c{cmp} is not \cw{NULL}, then it will be used to
+compare \c{e} to elements of the tree. The first argument passed to
+\c{cmp} will always be \c{e}; the second will be an element of the
+tree.
+
+(See \k{utils-newtree234} for the definition of the \c{cmpfn234}
+function pointer type.)
+
+The returned value is the element found, or \cw{NULL} if the search
+is unsuccessful.
+
+\S{utils-findrel234} \cw{findrel234()}
+
+\c void *findrel234(tree234 *t, void *e, cmpfn234 cmp, int relation);
+
+This function is like \cw{find234()}, but has the additional ability
+to do a \e{relative} search. The additional parameter \c{relation}
+can be one of the following values:
+
+\dt \cw{REL234_EQ}
+
+\dd Find only an element that compares equal to \c{e}. This is
+exactly the behaviour of \cw{find234()}.
+
+\dt \cw{REL234_LT}
+
+\dd Find the greatest element that compares strictly less than
+\c{e}. \c{e} may be \cw{NULL}, in which case it finds the greatest
+element in the whole tree (which could also be done by
+\cw{index234(t, count234(t)-1)}).
+
+\dt \cw{REL234_LE}
+
+\dd Find the greatest element that compares less than or equal to
+\c{e}. (That is, find an element that compares equal to \c{e} if
+possible, but failing that settle for something just less than it.)
+
+\dt \cw{REL234_GT}
+
+\dd Find the smallest element that compares strictly greater than
+\c{e}. \c{e} may be \cw{NULL}, in which case it finds the smallest
+element in the whole tree (which could also be done by
+\cw{index234(t, 0)}).
+
+\dt \cw{REL234_GE}
+
+\dd Find the smallest element that compares greater than or equal to
+\c{e}. (That is, find an element that compares equal to \c{e} if
+possible, but failing that settle for something just bigger than
+it.)
+
+Return value, as before, is the element found or \cw{NULL} if no
+element satisfied the search criterion.
+
+\S{utils-findpos234} \cw{findpos234()}
+
+\c void *findpos234(tree234 *t, void *e, cmpfn234 cmp, int *index);
+
+This function is like \cw{find234()}, but has the additional feature
+of returning the index of the element found in the tree; that index
+is written to \c{*index} in the event of a successful search (a
+non-\cw{NULL} return value).
+
+\c{index} may be \cw{NULL}, in which case this function behaves
+exactly like \cw{find234()}.
+
+\S{utils-findrelpos234} \cw{findrelpos234()}
+
+\c void *findrelpos234(tree234 *t, void *e, cmpfn234 cmp, int relation,
+\c                     int *index);
+
+This function combines all the features of \cw{findrel234()} and
+\cw{findpos234()}.
+
+\S{utils-del234} \cw{del234()}
+
+\c void *del234(tree234 *t, void *e);
+
+Finds an element comparing equal to \c{e} in the tree, deletes it,
+and returns it.
+
+The input tree must be sorted.
+
+The element found might be \c{e} itself, or might merely compare
+equal to it.
+
+Return value is \cw{NULL} if no such element is found.
+
+\S{utils-delpos234} \cw{delpos234()}
+
+\c void *delpos234(tree234 *t, int index);
+
+Deletes the element at position \c{index} in the tree, and returns
+it.
+
+Return value is \cw{NULL} if the index is out of range.
+
+\S{utils-count234} \cw{count234()}
+
+\c int count234(tree234 *t);
+
+Returns the number of elements currently in the tree.
+
+\S{utils-splitpos234} \cw{splitpos234()}
+
+\c tree234 *splitpos234(tree234 *t, int index, int before);
+
+Splits the input tree into two pieces at a given position, and
+creates a new tree containing all the elements on one side of that
+position.
+
+If \c{before} is \cw{TRUE}, then all the items at or after position
+\c{index} are left in the input tree, and the items before that
+point are returned in the new tree. Otherwise, the reverse happens:
+all the items at or after \c{index} are moved into the new tree, and
+those before that point are left in the old one.
+
+If \c{index} is equal to 0 or to the number of elements in the input
+tree, then one of the two trees will end up empty (and this is not
+an error condition). If \c{index} is further out of range in either
+direction, the operation will fail completely and return \cw{NULL}.
+
+This operation completes in \cw{O(log N)} time, no matter how large
+the tree or how balanced or unbalanced the split.
+
+\S{utils-split234} \cw{split234()}
+
+\c tree234 *split234(tree234 *t, void *e, cmpfn234 cmp, int rel);
+
+Splits a sorted tree according to its sort order.
+
+\c{rel} can be any of the relation constants described in
+\k{utils-findrel234}, \e{except} for \cw{REL234_EQ}. All the
+elements having that relation to \c{e} will be transferred into the
+new tree; the rest will be left in the old one.
+
+The parameter \c{cmp} has the same semantics as it does in
+\cw{find234()}: if it is not \cw{NULL}, it will be used in place of
+the tree's own comparison function when comparing elements to \c{e},
+in such a way that \c{e} itself is always the first of its two
+operands.
+
+Again, this operation completes in \cw{O(log N)} time, no matter how
+large the tree or how balanced or unbalanced the split.
+
+\S{utils-join234} \cw{join234()}
+
+\c tree234 *join234(tree234 *t1, tree234 *t2);
+
+Joins two trees together by concatenating the lists they represent.
+All the elements of \c{t2} are moved into \c{t1}, in such a way that
+they appear \e{after} the elements of \c{t1}. The tree \c{t2} is
+freed; the return value is \c{t1}.
+
+If you apply this function to a sorted tree and it violates the sort
+order (i.e. the smallest element in \c{t2} is smaller than or equal
+to the largest element in \c{t1}), the operation will fail and
+return \cw{NULL}.
+
+This operation completes in \cw{O(log N)} time, no matter how large
+the trees being joined together.
+
+\S{utils-join234r} \cw{join234r()}
+
+\c tree234 *join234r(tree234 *t1, tree234 *t2);
+
+Joins two trees together in exactly the same way as \cw{join234()},
+but this time the combined tree is returned in \c{t2}, and \c{t1} is
+destroyed. The elements in \c{t1} still appear before those in
+\c{t2}.
+
+Again, this operation completes in \cw{O(log N)} time, no matter how
+large the trees being joined together.
+
+\S{utils-copytree234} \cw{copytree234()}
+
+\c tree234 *copytree234(tree234 *t, copyfn234 copyfn,
+\c                      void *copyfnstate);
+
+Makes a copy of an entire tree.
+
+If \c{copyfn} is \cw{NULL}, the tree will be copied but the elements
+will not be; i.e. the new tree will contain pointers to exactly the
+same physical elements as the old one.
+
+If you want to copy each actual element during the operation, you
+can instead pass a function in \c{copyfn} which makes a copy of each
+element. That function has the prototype
+
+\c typedef void *(*copyfn234)(void *state, void *element);
+
+and every time it is called, the \c{state} parameter will be set to
+the value you passed in as \c{copyfnstate}.
+
+\H{utils-misc} Miscellaneous utility functions and macros
+
+This section contains all the utility functions which didn't
+sensibly fit anywhere else.
+
+\S{utils-truefalse} \cw{TRUE} and \cw{FALSE}
+
+The main Puzzles header file defines the macros \cw{TRUE} and
+\cw{FALSE}, which are used throughout the code in place of 1 and 0
+(respectively) to indicate that the values are in a boolean context.
+For code base consistency, I'd prefer it if submissions of new code
+followed this convention as well.
+
+\S{utils-maxmin} \cw{max()} and \cw{min()}
+
+The main Puzzles header file defines the pretty standard macros
+\cw{max()} and \cw{min()}, each of which is given two arguments and
+returns the one which compares greater or less respectively.
+
+These macros may evaluate their arguments multiple times. Avoid side
+effects.
+
+\S{utils-pi} \cw{PI}
+
+The main Puzzles header file defines a macro \cw{PI} which expands
+to a floating-point constant representing pi.
+
+(I've never understood why ANSI's \cw{<math.h>} doesn't define this.
+It'd be so useful!)
+
+\S{utils-obfuscate-bitmap} \cw{obfuscate_bitmap()}
+
+\c void obfuscate_bitmap(unsigned char *bmp, int bits, int decode);
+
+This function obscures the contents of a piece of data, by
+cryptographic methods. It is useful for games of hidden information
+(such as Mines, Guess or Black Box), in which the game ID
+theoretically reveals all the information the player is supposed to
+be trying to guess. So in order that players should be able to send
+game IDs to one another without accidentally spoiling the resulting
+game by looking at them, these games obfuscate their game IDs using
+this function.
+
+Although the obfuscation function is cryptographic, it cannot
+properly be called encryption because it has no key. Therefore,
+anybody motivated enough can re-implement it, or hack it out of the
+Puzzles source, and strip the obfuscation off one of these game IDs
+to see what lies beneath. (Indeed, they could usually do it much
+more easily than that, by entering the game ID into their own copy
+of the puzzle and hitting Solve.) The aim is not to protect against
+a determined attacker; the aim is simply to protect people who
+wanted to play the game honestly from \e{accidentally} spoiling
+their own fun.
+
+The input argument \c{bmp} points at a piece of memory to be
+obfuscated. \c{bits} gives the length of the data. Note that that
+length is in \e{bits} rather than bytes: if you ask for obfuscation
+of a partial number of bytes, then you will get it. Bytes are
+considered to be used from the top down: thus, for example, setting
+\c{bits} to 10 will cover the whole of \cw{bmp[0]} and the \e{top
+two} bits of \cw{bmp[1]}. The remainder of a partially used byte is
+undefined (i.e. it may be corrupted by the function).
+
+The parameter \c{decode} is \cw{FALSE} for an encoding operation,
+and \cw{TRUE} for a decoding operation. Each is the inverse of the
+other. (There's no particular reason you shouldn't obfuscate by
+decoding and restore cleartext by encoding, if you really wanted to;
+it should still work.)
+
+The input bitmap is processed in place.
+
+\S{utils-bin2hex} \cw{bin2hex()}
+
+\c char *bin2hex(const unsigned char *in, int inlen);
+
+This function takes an input byte array and converts it into an
+ASCII string encoding those bytes in (lower-case) hex. It returns a
+dynamically allocated string containing that encoding.
+
+This function is useful for encoding the result of
+\cw{obfuscate_bitmap()} in printable ASCII for use in game IDs.
+
+\S{utils-hex2bin} \cw{hex2bin()}
+
+\c unsigned char *hex2bin(const char *in, int outlen);
+
+This function takes an ASCII string containing hex digits, and
+converts it back into a byte array of length \c{outlen}. If there
+aren't enough hex digits in the string, the contents of the
+resulting array will be undefined.
+
+This function is the inverse of \cw{bin2hex()}.
+
+\S{utils-game-mkhighlight} \cw{game_mkhighlight()}
+
+\c void game_mkhighlight(frontend *fe, float *ret,
+\c                       int background, int highlight, int lowlight);
+
+It's reasonably common for a puzzle game's graphics to use
+highlights and lowlights to indicate \q{raised} or \q{lowered}
+sections. Fifteen, Sixteen and Twiddle are good examples of this.
+
+Puzzles using this graphical style are running a risk if they just
+use whatever background colour is supplied to them by the front end,
+because that background colour might be too light to see any
+highlights on at all. (In particular, it's not unheard of for the
+front end to specify a default background colour of white.)
+
+Therefore, such puzzles can call this utility function from their
+\cw{colours()} routine (\k{backend-colours}). You pass it your front
+end handle, a pointer to the start of your return array, and three
+colour indices. It will:
+
+\b call \cw{frontend_default_colour()} (\k{frontend-default-colour})
+to fetch the front end's default background colour
+
+\b alter the brightness of that colour if it's unsuitable
+
+\b define brighter and darker variants of the colour to be used as
+highlights and lowlights
+
+\b write those results into the relevant positions in the \c{ret}
+array.
+
+Thus, \cw{ret[background*3]} to \cw{ret[background*3+2]} will be set
+to RGB values defining a sensible background colour, and similary
+\c{highlight} and \c{lowlight} will be set to sensible colours.
+
+\C{writing} How to write a new puzzle
+
+This chapter gives a guide to how to actually write a new puzzle:
+where to start, what to do first, how to solve common problems.
+
+The previous chapters have been largely composed of facts. This one
+is mostly advice.
+
+\H{writing-editorial} Choosing a puzzle
+
+Before you start writing a puzzle, you have to choose one. Your
+taste in puzzle games is up to you, of course; and, in fact, you're
+probably reading this guide because you've \e{already} thought of a
+game you want to write. But if you want to get it accepted into the
+official Puzzles distribution, then there's a criterion it has to
+meet.
+
+The current Puzzles editorial policy is that all games should be
+\e{fair}. A fair game is one which a player can only fail to
+complete through demonstrable lack of skill \dash that is, such that
+a better player in the same situation would have \e{known} to do
+something different.
+
+For a start, that means every game presented to the user must have
+\e{at least one solution}. Giving the unsuspecting user a puzzle
+which is actually impossible is not acceptable. (There is an
+exception: if the user has selected some non-default option which is
+clearly labelled as potentially unfair, \e{then} you're allowed to
+generate possibly insoluble puzzles, because the user isn't
+unsuspecting any more. Same Game and Mines both have options of this
+type.)
+
+Also, this actually \e{rules out} games such as Klondike, or the
+normal form of Mahjong Solitaire. Those games have the property that
+even if there is a solution (i.e. some sequence of moves which will
+get from the start state to the solved state), the player doesn't
+necessarily have enough information to \e{find} that solution. In
+both games, it is possible to reach a dead end because you had an
+arbitrary choice to make and made it the wrong way. This violates
+the fairness criterion, because a better player couldn't have known
+they needed to make the other choice.
+
+(GNOME has a variant on Mahjong Solitaire which makes it fair: there
+is a Shuffle operation which randomly permutes all the remaining
+tiles without changing their positions, which allows you to get out
+of a sticky situation. Using this operation adds a 60-second penalty
+to your solution time, so it's to the player's advantage to try to
+minimise the chance of having to use it. It's still possible to
+render the game uncompletable if you end up with only two tiles
+vertically stacked, but that's easy to foresee and avoid using a
+shuffle operation. This form of the game \e{is} fair. Implementing
+it in Puzzles would require an infrastructure change so that the
+back end could communicate time penalties to the mid-end, but that
+would be easy enough.)
+
+Providing a \e{unique} solution is a little more negotiable; it
+depends on the puzzle. Solo would have been of unacceptably low
+quality if it didn't always have a unique solution, whereas Twiddle
+inherently has multiple solutions by its very nature and it would
+have been meaningless to even \e{suggest} making it uniquely
+soluble. Somewhere in between, Flip could reasonably be made to have
+unique solutions (by enforcing a zero-dimension kernel in every
+generated matrix) but it doesn't seem like a serious quality problem
+that it doesn't.
+
+Of course, you don't \e{have} to care about all this. There's
+nothing stopping you implementing any puzzle you want to if you're
+happy to maintain your puzzle yourself, distribute it from your own
+web site, fork the Puzzles code completely, or anything like that.
+It's free software; you can do what you like with it. But any game
+that you want to be accepted into \e{my} Puzzles code base has to
+satisfy the fairness criterion, which means all randomly generated
+puzzles must have a solution (unless the user has deliberately
+chosen otherwise) and it must be possible \e{in theory} to find that
+solution without having to guess.
+
+\H{writing-gs} Getting started
+
+The simplest way to start writing a new puzzle is to copy
+\c{nullgame.c}. This is a template puzzle source file which does
+almost nothing, but which contains all the back end function
+prototypes and declares the back end data structure correctly. It is
+built every time the rest of Puzzles is built, to ensure that it
+doesn't get out of sync with the code and remains buildable.
+
+So start by copying \c{nullgame.c} into your new source file. Then
+you'll gradually add functionality until the very boring Null Game
+turns into your real game.
+
+Next you'll need to add your puzzle to the Makefiles, in order to
+compile it conveniently. \e{Do not edit the Makefiles}: they are
+created automatically by the script \c{mkfiles.pl}, from the file
+called \c{Recipe}. Edit \c{Recipe}, and then re-run \c{mkfiles.pl}.
+
+Also, don't forget to add your puzzle to \c{list.c}: if you don't,
+then it will still run fine on platforms which build each puzzle
+separately, but Mac OS X and other monolithic platforms will not
+include your new puzzle in their single binary.
+
+Once your source file is building, you can move on to the fun bit.
+
+\S{writing-generation} Puzzle generation
+
+Randomly generating instances of your puzzle is almost certain to be
+the most difficult part of the code, and also the task with the
+highest chance of turning out to be completely infeasible. Therefore
+I strongly recommend doing it \e{first}, so that if it all goes
+horribly wrong you haven't wasted any more time than you absolutely
+had to. What I usually do is to take an unmodified \c{nullgame.c},
+and start adding code to \cw{new_game_desc()} which tries to
+generate a puzzle instance and print it out using \cw{printf()}.
+Once that's working, \e{then} I start connecting it up to the return
+value of \cw{new_game_desc()}, populating other structures like
+\c{game_params}, and generally writing the rest of the source file.
+
+There are many ways to generate a puzzle which is known to be
+soluble. In this section I list all the methods I currently know of,
+in case any of them can be applied to your puzzle. (Not all of these
+methods will work, or in some cases even make sense, for all
+puzzles.)
+
+Some puzzles are mathematically tractable, meaning you can work out
+in advance which instances are soluble. Sixteen, for example, has a
+parity constraint in some settings which renders exactly half the
+game space unreachable, but it can be mathematically proved that any
+position not in that half \e{is} reachable. Therefore, Sixteen's
+grid generation simply consists of selecting at random from a well
+defined subset of the game space. Cube in its default state is even
+easier: \e{every} possible arrangement of the blue squares and the
+cube's starting position is soluble!
+
+Another option is to redefine what you mean by \q{soluble}. Black
+Box takes this approach. There are layouts of balls in the box which
+are completely indistinguishable from one another no matter how many
+beams you fire into the box from which angles, which would normally
+be grounds for declaring those layouts unfair; but fortunately,
+detecting that indistinguishability is computationally easy. So
+Black Box doesn't demand that your ball placements match its own; it
+merely demands that your ball placements be \e{indistinguishable}
+from the ones it was thinking of. If you have an ambiguous puzzle,
+then any of the possible answers is considered to be a solution.
+Having redefined the rules in that way, any puzzle is soluble again.
+
+Those are the simple techniques. If they don't work, you have to get
+cleverer.
+
+One way to generate a soluble puzzle is to start from the solved
+state and make inverse moves until you reach a starting state. Then
+you know there's a solution, because you can just list the inverse
+moves you made and make them in the opposite order to return to the
+solved state.
+
+This method can be simple and effective for puzzles where you get to
+decide what's a starting state and what's not. In Pegs, for example,
+the generator begins with one peg in the centre of the board and
+makes inverse moves until it gets bored; in this puzzle, valid
+inverse moves are easy to detect, and \e{any} state that's reachable
+from the solved state by inverse moves is a reasonable starting
+position. So Pegs just continues making inverse moves until the
+board satisfies some criteria about extent and density, and then
+stops and declares itself done.
+
+For other puzzles, it can be a lot more difficult. Same Game uses
+this strategy too, and it's lucky to get away with it at all: valid
+inverse moves aren't easy to find (because although it's easy to
+insert additional squares in a Same Game position, it's difficult to
+arrange that \e{after} the insertion they aren't adjacent to any
+other squares of the same colour), so you're constantly at risk of
+running out of options and having to backtrack or start again. Also,
+Same Game grids never start off half-empty, which means you can't
+just stop when you run out of moves \dash you have to find a way to
+fill the grid up \e{completely}.
+
+The other way to generate a puzzle that's soluble is to start from
+the other end, and actually write a \e{solver}. This tends to ensure
+that a puzzle has a \e{unique} solution over and above having a
+solution at all, so it's a good technique to apply to puzzles for
+which that's important.
+
+One theoretical drawback of generating soluble puzzles by using a
+solver is that your puzzles are restricted in difficulty to those
+which the solver can handle. (Most solvers are not fully general:
+many sets of puzzle rules are NP-complete or otherwise nasty, so
+most solvers can only handle a subset of the theoretically soluble
+puzzles.) It's been my experience in practice, however, that this
+usually isn't a problem; computers are good at very different things
+from humans, and what the computer thinks is nice and easy might
+still be pleasantly challenging for a human. For example, when
+solving Dominosa puzzles I frequently find myself using a variety of
+reasoning techniques that my solver doesn't know about; in
+principle, therefore, I should be able to solve the puzzle using
+only those techniques it \e{does} know about, but this would involve
+repeatedly searching the entire grid for the one simple deduction I
+can make. Computers are good at this sort of exhaustive search, but
+it's been my experience that human solvers prefer to do more complex
+deductions than to spend ages searching for simple ones. So in many
+cases I don't find my own playing experience to be limited by the
+restrictions on the solver.
+
+(This isn't \e{always} the case. Solo is a counter-example;
+generating Solo puzzles using a simple solver does lead to
+qualitatively easier puzzles. Therefore I had to make the Solo
+solver rather more advanced than most of them.)
+
+There are several different ways to apply a solver to the problem of
+generating a soluble puzzle. I list a few of them below.
+
+The simplest approach is brute force: randomly generate a puzzle,
+use the solver to see if it's soluble, and if not, throw it away and
+try again until you get lucky. This is often a viable technique if
+all else fails, but it tends not to scale well: for many puzzle
+types, the probability of finding a uniquely soluble instance
+decreases sharply as puzzle size goes up, so this technique might
+work reasonably fast for small puzzles but take (almost) forever at
+larger sizes. Still, if there's no other alternative it can be
+usable: Pattern and Dominosa both use this technique. (However,
+Dominosa has a means of tweaking the randomly generated grids to
+increase the \e{probability} of them being soluble, by ruling out
+one of the most common ambiguous cases. This improved generation
+speed by over a factor of 10 on the highest preset!)
+
+An approach which can be more scalable involves generating a grid
+and then tweaking it to make it soluble. This is the technique used
+by Mines and also by Net: first a random puzzle is generated, and
+then the solver is run to see how far it gets. Sometimes the solver
+will get stuck; when that happens, examine the area it's having
+trouble with, and make a small random change in that area to allow
+it to make more progress. Continue solving (possibly even without
+restarting the solver), tweaking as necessary, until the solver
+finishes. Then restart the solver from the beginning to ensure that
+the tweaks haven't caused new problems in the process of solving old
+ones (which can sometimes happen).
+
+This strategy works well in situations where the usual solver
+failure mode is to get stuck in an easily localised spot. Thus it
+works well for Net and Mines, whose most common failure mode tends
+to be that most of the grid is fine but there are a few widely
+separated ambiguous sections; but it would work less well for
+Dominosa, in which the way you get stuck is to have scoured the
+whole grid and not found anything you can deduce \e{anywhere}. Also,
+it relies on there being a low probability that tweaking the grid
+introduces a new problem at the same time as solving the old one;
+Mines and Net also have the property that most of their deductions
+are local, so that it's very unlikely for a tweak to affect
+something half way across the grid from the location where it was
+applied. In Dominosa, by contrast, a lot of deductions use
+information about half the grid (\q{out of all the sixes, only one
+is next to a three}, which can depend on the values of up to 32 of
+the 56 squares in the default setting!), so this tweaking strategy
+would be rather less likely to work well.
+
+A more specialised strategy is that used in Solo and Slant. These
+puzzles have the property that they derive their difficulty from not
+presenting all the available clues. (In Solo's case, if all the
+possible clues were provided then the puzzle would already be
+solved; in Slant it would still require user action to fill in the
+lines, but it would present no challenge at all). Therefore, a
+simple generation technique is to leave the decision of which clues
+to provide until the last minute. In other words, first generate a
+random \e{filled} grid with all possible clues present, and then
+gradually remove clues for as long as the solver reports that it's
+still soluble. Unlike the methods described above, this technique
+\e{cannot} fail \dash once you've got a filled grid, nothing can
+stop you from being able to convert it into a viable puzzle.
+However, it wouldn't even be meaningful to apply this technique to
+(say) Pattern, in which clues can never be left out, so the only way
+to affect the set of clues is by altering the solution.
+
+(Unfortunately, Solo is complicated by the need to provide puzzles
+at varying difficulty levels. It's easy enough to generate a puzzle
+of \e{at most} a given level of difficulty; you just have a solver
+with configurable intelligence, and you set it to a given level and
+apply the above technique, thus guaranteeing that the resulting grid
+is solvable by someone with at most that much intelligence. However,
+generating a puzzle of \e{at least} a given level of difficulty is
+rather harder; if you go for \e{at most} Intermediate level, you're
+likely to find that you've accidentally generated a Trivial grid a
+lot of the time, because removing just one number is sufficient to
+take the puzzle from Trivial straight to Ambiguous. In that
+situation Solo has no remaining options but to throw the puzzle away
+and start again.)
+
+A final strategy is to use the solver \e{during} puzzle
+construction: lay out a bit of the grid, run the solver to see what
+it allows you to deduce, and then lay out a bit more to allow the
+solver to make more progress. There are articles on the web that
+recommend constructing Sudoku puzzles by this method (which is
+completely the opposite way round to how Solo does it); for Sudoku
+it has the advantage that you get to specify your clue squares in
+advance (so you can have them make pretty patterns).
+
+Rectangles uses a strategy along these lines. First it generates a
+grid by placing the actual rectangles; then it has to decide where
+in each rectangle to place a number. It uses a solver to help it
+place the numbers in such a way as to ensure a unique solution. It
+does this by means of running a test solver, but it runs the solver
+\e{before} it's placed any of the numbers \dash which means the
+solver must be capable of coping with uncertainty about exactly
+where the numbers are! It runs the solver as far as it can until it
+gets stuck; then it narrows down the possible positions of a number
+in order to allow the solver to make more progress, and so on. Most
+of the time this process terminates with the grid fully solved, at
+which point any remaining number-placement decisions can be made at
+random from the options not so far ruled out. Note that unlike the
+Net/Mines tweaking strategy described above, this algorithm does not
+require a checking run after it completes: if it finishes
+successfully at all, then it has definitely produced a uniquely
+soluble puzzle.
+
+Most of the strategies described above are not 100% reliable. Each
+one has a failure rate: every so often it has to throw out the whole
+grid and generate a fresh one from scratch. (Solo's strategy would
+be the exception, if it weren't for the need to provide configurable
+difficulty levels.) Occasional failures are not a fundamental
+problem in this sort of work, however: it's just a question of
+dividing the grid generation time by the success rate (if it takes
+10ms to generate a candidate grid and 1/5 of them work, then it will
+take 50ms on average to generate a viable one), and seeing whether
+the expected time taken to \e{successfully} generate a puzzle is
+unacceptably slow. Dominosa's generator has a very low success rate
+(about 1 out of 20 candidate grids turn out to be usable, and if you
+think \e{that's} bad then go and look at the source code and find
+the comment showing what the figures were before the generation-time
+tweaks!), but the generator itself is very fast so this doesn't
+matter. Rectangles has a slower generator, but fails well under 50%
+of the time.
+
+So don't be discouraged if you have an algorithm that doesn't always
+work: if it \e{nearly} always works, that's probably good enough.
+The one place where reliability is important is that your algorithm
+must never produce false positives: it must not claim a puzzle is
+soluble when it isn't. It can produce false negatives (failing to
+notice that a puzzle is soluble), and it can fail to generate a
+puzzle at all, provided it doesn't do either so often as to become
+slow.
+
+One last piece of advice: for grid-based puzzles, when writing and
+testing your generation algorithm, it's almost always a good idea
+\e{not} to test it initially on a grid that's square (i.e.
+\cw{w==h}), because if the grid is square then you won't notice if
+you mistakenly write \c{h} instead of \c{w} (or vice versa)
+somewhere in the code. Use a rectangular grid for testing, and any
+size of grid will be likely to work after that.
+
+\S{writing-textformats} Designing textual description formats
+
+Another aspect of writing a puzzle which is worth putting some
+thought into is the design of the various text description formats:
+the format of the game parameter encoding, the game description
+encoding, and the move encoding.
+
+The first two of these should be reasonably intuitive for a user to
+type in; so provide some flexibility where possible. Suppose, for
+example, your parameter format consists of two numbers separated by
+an \c{x} to specify the grid dimensions (\c{10x10} or \c{20x15}),
+and then has some suffixes to specify other aspects of the game
+type. It's almost always a good idea in this situation to arrange
+that \cw{decode_params()} can handle the suffixes appearing in any
+order, even if \cw{encode_params()} only ever generates them in one
+order.
+
+These formats will also be expected to be reasonably stable: users
+will expect to be able to exchange game IDs with other users who
+aren't running exactly the same version of your game. So make them
+robust and stable: don't build too many assumptions into the game ID
+format which will have to be changed every time something subtle
+changes in the puzzle code.
+
+\H{writing-howto} Common how-to questions
+
+This section lists some common things people want to do when writing
+a puzzle, and describes how to achieve them within the Puzzles
+framework.
+
+\S{writing-howto-cursor} Drawing objects at only one position
+
+A common phenomenon is to have an object described in the
+\c{game_state} or the \c{game_ui} which can only be at one position.
+A cursor \dash probably specified in the \c{game_ui} \dash is a good
+example.
+
+In the \c{game_ui}, it would \e{obviously} be silly to have an array
+covering the whole game grid with a boolean flag stating whether the
+cursor was at each position. Doing that would waste space, would
+make it difficult to find the cursor in order to do anything with
+it, and would introduce the potential for synchronisation bugs in
+which you ended up with two cursors or none. The obviously sensible
+way to store a cursor in the \c{game_ui} is to have fields directly
+encoding the cursor's coordinates.
+
+However, it is a mistake to assume that the same logic applies to
+the \c{game_drawstate}. If you replicate the cursor position fields
+in the draw state, the redraw code will get very complicated. In the
+draw state, in fact, it \e{is} probably the right thing to have a
+cursor flag for every position in the grid. You probably have an
+array for the whole grid in the drawstate already (stating what is
+currently displayed in the window at each position); the sensible
+approach is to add a \q{cursor} flag to each element of that array.
+Then the main redraw loop will look something like this
+(pseudo-code):
+
+\c for (y = 0; y < h; y++) {
+\c     for (x = 0; x < w; x++) {
+\c         int value = state->symbol_at_position[y][x];
+\c         if (x == ui->cursor_x && y == ui->cursor_y)
+\c             value |= CURSOR;
+\c         if (ds->symbol_at_position[y][x] != value) {
+\c             symbol_drawing_subroutine(dr, ds, x, y, value);
+\c             ds->symbol_at_position[y][x] = value;
+\c         }
+\c     }
+\c }
+
+This loop is very simple, pretty hard to get wrong, and
+\e{automatically} deals both with erasing the previous cursor and
+drawing the new one, with no special case code required.
+
+This type of loop is generally a sensible way to write a redraw
+function, in fact. The best thing is to ensure that the information
+stored in the draw state for each position tells you \e{everything}
+about what was drawn there. A good way to ensure that is to pass
+precisely the same information, and \e{only} that information, to a
+subroutine that does the actual drawing; then you know there's no
+additional information which affects the drawing but which you don't
+notice changes in.
+
+\S{writing-keyboard-cursor} Implementing a keyboard-controlled cursor
+
+It is often useful to provide a keyboard control method in a
+basically mouse-controlled game. A keyboard-controlled cursor is
+best implemented by storing its location in the \c{game_ui} (since
+if it were in the \c{game_state} then the user would have to
+separately undo every cursor move operation). So the procedure would
+be:
+
+\b Put cursor position fields in the \c{game_ui}.
+
+\b \cw{interpret_move()} responds to arrow keys by modifying the
+cursor position fields and returning \cw{""}.
+
+\b \cw{interpret_move()} responds to some sort of fire button by
+actually performing a move based on the current cursor location.
+
+\b You might want an additional \c{game_ui} field stating whether
+the cursor is currently visible, and having it disappear when a
+mouse action occurs (so that it doesn't clutter the display when not
+actually in use).
+
+\b You might also want to automatically hide the cursor in
+\cw{changed_state()} when the current game state changes to one in
+which there is no move to make (which is the case in some types of
+completed game).
+
+\b \cw{redraw()} draws the cursor using the technique described in
+\k{writing-howto-cursor}.
+
+\S{writing-howto-dragging} Implementing draggable sprites
+
+Some games have a user interface which involves dragging some sort
+of game element around using the mouse. If you need to show a
+graphic moving smoothly over the top of other graphics, use a
+blitter (see \k{drawing-blitter} for the blitter API) to save the
+background underneath it. The typical scenario goes:
+
+\b Have a blitter field in the \c{game_drawstate}.
+
+\b Set the blitter field to \cw{NULL} in the game's
+\cw{new_drawstate()} function, since you don't yet know how big the
+piece of saved background needs to be.
+
+\b In the game's \cw{set_size()} function, once you know the size of
+the object you'll be dragging around the display and hence the
+required size of the blitter, actually allocate the blitter.
+
+\b In \cw{free_drawstate()}, free the blitter if it's not \cw{NULL}.
+
+\b In \cw{interpret_move()}, respond to mouse-down and mouse-drag
+events by updating some fields in the \cw{game_ui} which indicate
+that a drag is in progress.
+
+\b At the \e{very end} of \cw{redraw()}, after all other drawing has
+been done, draw the moving object if there is one. First save the
+background under the object in the blitter; then set a clip
+rectangle covering precisely the area you just saved (just in case
+anti-aliasing or some other error causes your drawing to go beyond
+the area you saved). Then draw the object, and call \cw{unclip()}.
+Finally, set a flag in the \cw{game_drawstate} that indicates that
+the blitter needs restoring.
+
+\b At the very start of \cw{redraw()}, before doing anything else at
+all, check the flag in the \cw{game_drawstate}, and if it says the
+blitter needs restoring then restore it. (Then clear the flag, so
+that this won't happen again in the next redraw if no moving object
+is drawn this time.)
+
+This way, you will be able to write the rest of the redraw function
+completely ignoring the dragged object, as if it were floating above
+your bitmap and being completely separate.
+
+\S{writing-ref-counting} Sharing large invariant data between all
+game states
+
+In some puzzles, there is a large amount of data which never changes
+between game states. The array of numbers in Dominosa is a good
+example.
+
+You \e{could} dynamically allocate a copy of that array in every
+\c{game_state}, and have \cw{dup_game()} make a fresh copy of it for
+every new \c{game_state}; but it would waste memory and time. A
+more efficient way is to use a reference-counted structure.
+
+\b Define a structure type containing the data in question, and also
+containing an integer reference count.
+
+\b Have a field in \c{game_state} which is a pointer to this
+structure.
+
+\b In \cw{new_game()}, when creating a fresh game state at the start
+of a new game, create an instance of this structure, initialise it
+with the invariant data, and set its reference count to 1.
+
+\b In \cw{dup_game()}, rather than making a copy of the structure
+for the new game state, simply set the new game state to point at
+the same copy of the structure, and increment its reference count.
+
+\b In \cw{free_game()}, decrement the reference count in the
+structure pointed to by the game state; if the count reaches zero,
+free the structure.
+
+This way, the invariant data will persist for only as long as it's
+genuinely needed; \e{as soon} as the last game state for a
+particular puzzle instance is freed, the invariant data for that
+puzzle will vanish as well. Reference counting is a very efficient
+form of garbage collection, when it works at all. (Which it does in
+this instance, of course, because there's no possibility of circular
+references.)
+
+\S{writing-flash-types} Implementing multiple types of flash
+
+In some games you need to flash in more than one different way.
+Mines, for example, flashes white when you win, and flashes red when
+you tread on a mine and die.
+
+The simple way to do this is:
+
+\b Have a field in the \c{game_ui} which describes the type of flash.
+
+\b In \cw{flash_length()}, examine the old and new game states to
+decide whether a flash is required and what type. Write the type of
+flash to the \c{game_ui} field whenever you return non-zero.
+
+\b In \cw{redraw()}, when you detect that \c{flash_time} is
+non-zero, examine the field in \c{game_ui} to decide which type of
+flash to draw.
+
+\cw{redraw()} will never be called with \c{flash_time} non-zero
+unless \cw{flash_length()} was first called to tell the mid-end that
+a flash was required; so whenever \cw{redraw()} notices that
+\c{flash_time} is non-zero, you can be sure that the field in
+\c{game_ui} is correctly set.
+
+\S{writing-move-anim} Animating game moves
+
+A number of puzzle types benefit from a quick animation of each move
+you make.
+
+For some games, such as Fifteen, this is particularly easy. Whenever
+\cw{redraw()} is called with \c{oldstate} non-\cw{NULL}, Fifteen
+simply compares the position of each tile in the two game states,
+and if the tile is not in the same place then it draws it some
+fraction of the way from its old position to its new position. This
+method copes automatically with undo.
+
+Other games are less obvious. In Sixteen, for example, you can't
+just draw each tile a fraction of the way from its old to its new
+position: if you did that, the end tile would zip very rapidly past
+all the others to get to the other end and that would look silly.
+(Worse, it would look inconsistent if the end tile was drawn on top
+going one way and on the bottom going the other way.)
+
+A useful trick here is to define a field or two in the game state
+that indicates what the last move was.
+
+\b Add a \q{last move} field to the \c{game_state} (or two or more
+fields if the move is complex enough to need them).
+
+\b \cw{new_game()} initialises this field to a null value for a new
+game state.
+
+\b \cw{execute_move()} sets up the field to reflect the move it just
+performed.
+
+\b \cw{redraw()} now needs to examine its \c{dir} parameter. If
+\c{dir} is positive, it determines the move being animated by
+looking at the last-move field in \c{newstate}; but if \c{dir} is
+negative, it has to look at the last-move field in \c{oldstate}, and
+invert whatever move it finds there.
+
+Note also that Sixteen needs to store the \e{direction} of the move,
+because you can't quite determine it by examining the row or column
+in question. You can in almost all cases, but when the row is
+precisely two squares long it doesn't work since a move in either
+direction looks the same. (You could argue that since moving a
+2-element row left and right has the same effect, it doesn't matter
+which one you animate; but in fact it's very disorienting to click
+the arrow left and find the row moving right, and almost as bad to
+undo a move to the right and find the game animating \e{another}
+move to the right.)
+
+\S{writing-conditional-anim} Animating drag operations
+
+In Untangle, moves are made by dragging a node from an old position
+to a new position. Therefore, at the time when the move is initially
+made, it should not be animated, because the node has already been
+dragged to the right place and doesn't need moving there. However,
+it's nice to animate the same move if it's later undone or redone.
+This requires a bit of fiddling.
+
+The obvious approach is to have a flag in the \c{game_ui} which
+inhibits move animation, and to set that flag in
+\cw{interpret_move()}. The question is, when would the flag be reset
+again? The obvious place to do so is \cw{changed_state()}, which
+will be called once per move. But it will be called \e{before}
+\cw{anim_length()}, so if it resets the flag then \cw{anim_length()}
+will never see the flag set at all.
+
+The solution is to have \e{two} flags in a queue.
+
+\b Define two flags in \c{game_ui}; let's call them \q{current} and
+\q{next}.
+
+\b Set both to \cw{FALSE} in \c{new_ui()}.
+
+\b When a drag operation completes in \cw{interpret_move()}, set the
+\q{next} flag to \cw{TRUE}.
+
+\b Every time \cw{changed_state()} is called, set the value of
+\q{current} to the value in \q{next}, and then set the value of
+\q{next} to \cw{FALSE}.
+
+\b That way, \q{current} will be \cw{TRUE} \e{after} a call to
+\cw{changed_state()} if and only if that call to
+\cw{changed_state()} was the result of a drag operation processed by
+\cw{interpret_move()}. Any other call to \cw{changed_state()}, due
+to an Undo or a Redo or a Restart or a Solve, will leave \q{current}
+\cw{FALSE}.
+
+\b So now \cw{anim_length()} can request a move animation if and
+only if the \q{current} flag is \e{not} set.
+
+\S{writing-cheating} Inhibiting the victory flash when Solve is used
+
+Many games flash when you complete them, as a visual congratulation
+for having got to the end of the puzzle. It often seems like a good
+idea to disable that flash when the puzzle is brought to a solved
+state by means of the Solve operation.
+
+This is easily done:
+
+\b Add a \q{cheated} flag to the \c{game_state}.
+
+\b Set this flag to \cw{FALSE} in \cw{new_game()}.
+
+\b Have \cw{solve()} return a move description string which clearly
+identifies the move as a solve operation.
+
+\b Have \cw{execute_move()} respond to that clear identification by
+setting the \q{cheated} flag in the returned \c{game_state}. The
+flag will then be propagated to all subsequent game states, even if
+the user continues fiddling with the game after it is solved.
+
+\b \cw{flash_length()} now returns non-zero if \c{oldstate} is not
+completed and \c{newstate} is, \e{and} neither state has the
+\q{cheated} flag set.
+
+\H{writing-testing} Things to test once your puzzle is written
+
+Puzzle implementations written in this framework are self-testing as
+far as I could make them.
+
+Textual game and move descriptions, for example, are generated and
+parsed as part of the normal process of play. Therefore, if you can
+make moves in the game \e{at all} you can be reasonably confident
+that the mid-end serialisation interface will function correctly and
+you will be able to save your game. (By contrast, if I'd stuck with
+a single \cw{make_move()} function performing the jobs of both
+\cw{interpret_move()} and \cw{execute_move()}, and had separate
+functions to encode and decode a game state in string form, then
+those functions would not be used during normal play; so they could
+have been completely broken, and you'd never know it until you tried
+to save the game \dash which would have meant you'd have to test
+game saving \e{extensively} and make sure to test every possible
+type of game state. As an added bonus, doing it the way I did leads
+to smaller save files.)
+
+There is one exception to this, which is the string encoding of the
+\c{game_ui}. Most games do not store anything permanent in the
+\c{game_ui}, and hence do not need to put anything in its encode and
+decode functions; but if there is anything in there, you do need to
+test game loading and saving to ensure those functions work
+properly.
+
+It's also worth testing undo and redo of all operations, to ensure
+that the redraw and the animations (if any) work properly. Failing
+to animate undo properly seems to be a common error.
+
+Other than that, just use your common sense.
+
+\versionid Simon Tatham's Portable Puzzle Collection, version 20161228.7cae89f
diff --git a/divvy.c b/divvy.c
new file mode 100644 (file)
index 0000000..517e3dd
--- /dev/null
+++ b/divvy.c
@@ -0,0 +1,781 @@
+/*
+ * Library code to divide up a rectangle into a number of equally
+ * sized ominoes, in a random fashion.
+ * 
+ * Could use this for generating solved grids of
+ * http://www.nikoli.co.jp/ja/puzzles/block_puzzle/
+ * or for generating the playfield for Jigsaw Sudoku.
+ */
+
+/*
+ * This code is restricted to simply connected solutions: that is,
+ * no single polyomino may completely surround another (not even
+ * with a corner visible to the outside world, in the sense that a
+ * 7-omino can `surround' a single square).
+ * 
+ * It's tempting to think that this is a natural consequence of
+ * all the ominoes being the same size - after all, a division of
+ * anything into 7-ominoes must necessarily have all of them
+ * simply connected, because if one was not then the 1-square
+ * space in the middle could not be part of any 7-omino - but in
+ * fact, for sufficiently large k, it is perfectly possible for a
+ * k-omino to completely surround another k-omino. A simple
+ * example is this one with two 25-ominoes:
+ * 
+ *   +--+--+--+--+--+--+--+
+ *   |                    |
+ *   +  +--+--+--+--+--+  +
+ *   |  |              |  |
+ *   +  +              +  +
+ *   |  |              |  |
+ *   +  +              +  +--+
+ *   |  |              |     |
+ *   +  +              +  +--+
+ *   |  |              |  |
+ *   +  +              +  +
+ *   |  |              |  |
+ *   +  +--+--+--+--+--+  +
+ *   |                    |
+ *   +--+--+--+--+--+--+--+
+ * 
+ * I claim the smallest k which can manage this is 23. More
+ * formally:
+ * 
+ *   If a k-omino P is completely surrounded by another k-omino Q,
+ *   such that every edge of P borders on Q, then k >= 23.
+ * 
+ * Proof:
+ * 
+ * It's relatively simple to find the largest _rectangle_ a
+ * k-omino can enclose. So I'll construct my proof in two parts:
+ * firstly, show that no 22-omino or smaller can enclose a
+ * rectangle as large as itself, and secondly, show that no
+ * polyomino can enclose a larger non-rectangle than a rectangle.
+ * 
+ * The first of those claims:
+ * 
+ * To surround an m x n rectangle, a polyomino must have 2m
+ * squares along the two m-sides of the rectangle, 2n squares
+ * along the two n-sides, and must fill in at least three of the
+ * corners in order to be connected. Thus, 2(m+n)+3 <= k. We wish
+ * to find the largest value of mn subject to that constraint, and
+ * it's clear that this is achieved when m and n are as close to
+ * equal as possible. (If they aren't, WLOG suppose m < n; then
+ * (m+1)(n-1) = mn + n - m - 1 >= mn, with equality only when
+ * m=n-1.)
+ * 
+ * So the area of the largest rectangle which can be enclosed by a
+ * k-omino is given by floor(k'/2) * ceil(k'/2), where k' =
+ * (k-3)/2. This is a monotonic function in k, so there will be a
+ * unique point at which it goes from being smaller than k to
+ * being larger than k. That point is between 22 (maximum area 20)
+ * and 23 (maximum area 25).
+ * 
+ * The second claim:
+ * 
+ * Suppose we have an inner polyomino P surrounded by an outer
+ * polyomino Q. I seek to show that if P is non-rectangular, then
+ * P is also non-maximal, in the sense that we can transform P and
+ * Q into a new pair of polyominoes in which P is larger and Q is
+ * at most the same size.
+ * 
+ * Consider walking along the boundary of P in a clockwise
+ * direction. (We may assume, of course, that there is only _one_
+ * boundary of P, i.e. P has no hole in the middle. If it does
+ * have a hole in the middle, it's _trivially_ non-maximal because
+ * we can just fill the hole in!) Our walk will take us along many
+ * edges between squares; sometimes we might turn left, and
+ * certainly sometimes we will turn right. Always there will be a
+ * square of P on our right, and a square of Q on our left.
+ * 
+ * The net angle through which we turn during the entire walk must
+ * add up to 360 degrees rightwards. So if there are no left
+ * turns, then we must turn right exactly four times, meaning we
+ * have described a rectangle. Hence, if P is _not_ rectangular,
+ * then there must have been a left turn at some point. A left
+ * turn must mean we walk along two edges of the same square of Q.
+ * 
+ * Thus, there is some square X in Q which is adjacent to two
+ * diagonally separated squares in P. Let us call those two
+ * squares N and E; let us refer to the other two neighbours of X
+ * as S and W; let us refer to the other mutual neighbour of S and
+ * W as D; and let us refer to the other mutual neighbour of S and
+ * E as Y. In other words, we have named seven squares, arranged
+ * thus:
+ * 
+ *     N
+ *   W X E
+ *   D S Y
+ * 
+ * where N and E are in P, and X is in Q.
+ * 
+ * Clearly at least one of W and S must be in Q (because otherwise
+ * X would not be connected to any other square in Q, and would
+ * hence have to be the whole of Q; and evidently if Q were a
+ * 1-omino it could not enclose _anything_). So we divide into
+ * cases:
+ * 
+ * If both W and S are in Q, then we take X out of Q and put it in
+ * P, which does not expose any edge of P. If this disconnects Q,
+ * then we can reconnect it by adding D to Q.
+ * 
+ * If only one of W and S is in Q, then wlog let it be W. If S is
+ * in _P_, then we have a particularly easy case: we can simply
+ * take X out of Q and add it to P, and this cannot disconnect X
+ * since X was a leaf square of Q.
+ * 
+ * Our remaining case is that W is in Q and S is in neither P nor
+ * Q. Again we take X out of Q and put it in P; we also add S to
+ * Q. This ensures we do not expose an edge of P, but we must now
+ * prove that S is adjacent to some other existing square of Q so
+ * that we haven't disconnected Q by adding it.
+ * 
+ * To do this, we recall that we walked along the edge XE, and
+ * then turned left to walk along XN. So just before doing all
+ * that, we must have reached the corner XSE, and we must have
+ * done it by walking along one of the three edges meeting at that
+ * corner which are _not_ XE. It can't have been SY, since S would
+ * then have been on our left and it isn't in Q; and it can't have
+ * been XS, since S would then have been on our right and it isn't
+ * in P. So it must have been YE, in which case Y was on our left,
+ * and hence is in Q.
+ * 
+ * So in all cases we have shown that we can take X out of Q and
+ * add it to P, and add at most one square to Q to restore the
+ * containment and connectedness properties. Hence, we can keep
+ * doing this until we run out of left turns and P becomes
+ * rectangular. []
+ * 
+ * ------------
+ * 
+ * Anyway, that entire proof was a bit of a sidetrack. The point
+ * is, although constructions of this type are possible for
+ * sufficiently large k, divvy_rectangle() will never generate
+ * them. This could be considered a weakness for some purposes, in
+ * the sense that we can't generate all possible divisions.
+ * However, there are many divisions which we are highly unlikely
+ * to generate anyway, so in practice it probably isn't _too_ bad.
+ * 
+ * If I wanted to fix this issue, I would have to make the rules
+ * more complicated for determining when a square can safely be
+ * _removed_ from a polyomino. Adding one becomes easier (a square
+ * may be added to a polyomino iff it is 4-adjacent to any square
+ * currently part of the polyomino, and the current test for loop
+ * formation may be dispensed with), but to determine which
+ * squares may be removed we must now resort to analysis of the
+ * overall structure of the polyomino rather than the simple local
+ * properties we can currently get away with measuring.
+ */
+
+/*
+ * Possible improvements which might cut the fail rate:
+ * 
+ *  - instead of picking one omino to extend in an iteration, try
+ *    them all in succession (in a randomised order)
+ * 
+ *  - (for real rigour) instead of bfsing over ominoes, bfs over
+ *    the space of possible _removed squares_. That way we aren't
+ *    limited to randomly choosing a single square to remove from
+ *    an omino and failing if that particular square doesn't
+ *    happen to work.
+ * 
+ * However, I don't currently think it's necessary to do either of
+ * these, because the failure rate is already low enough to be
+ * easily tolerable, under all circumstances I've been able to
+ * think of.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+
+#include "puzzles.h"
+
+/*
+ * Subroutine which implements a function used in computing both
+ * whether a square can safely be added to an omino, and whether
+ * it can safely be removed.
+ * 
+ * We enumerate the eight squares 8-adjacent to this one, in
+ * cyclic order. We go round that loop and count the number of
+ * times we find a square owned by the target omino next to one
+ * not owned by it. We then return success iff that count is 2.
+ * 
+ * When adding a square to an omino, this is precisely the
+ * criterion which tells us that adding the square won't leave a
+ * hole in the middle of the omino. (If it did, then things get
+ * more complicated; see above.)
+ * 
+ * When removing a square from an omino, the _same_ criterion
+ * tells us that removing the square won't disconnect the omino.
+ * (This only works _because_ we've ensured the omino is simply
+ * connected.)
+ */
+static int addremcommon(int w, int h, int x, int y, int *own, int val)
+{
+    int neighbours[8];
+    int dir, count;
+
+    for (dir = 0; dir < 8; dir++) {
+       int dx = ((dir & 3) == 2 ? 0 : dir > 2 && dir < 6 ? +1 : -1);
+       int dy = ((dir & 3) == 0 ? 0 : dir < 4 ? -1 : +1);
+       int sx = x+dx, sy = y+dy;
+
+       if (sx < 0 || sx >= w || sy < 0 || sy >= h)
+           neighbours[dir] = -1;      /* outside the grid */
+       else
+           neighbours[dir] = own[sy*w+sx];
+    }
+
+    /*
+     * To begin with, check 4-adjacency.
+     */
+    if (neighbours[0] != val && neighbours[2] != val &&
+       neighbours[4] != val && neighbours[6] != val)
+       return FALSE;
+
+    count = 0;
+
+    for (dir = 0; dir < 8; dir++) {
+       int next = (dir + 1) & 7;
+       int gotthis = (neighbours[dir] == val);
+       int gotnext = (neighbours[next] == val);
+
+       if (gotthis != gotnext)
+           count++;
+    }
+
+    return (count == 2);
+}
+
+/*
+ * w and h are the dimensions of the rectangle.
+ * 
+ * k is the size of the required ominoes. (So k must divide w*h,
+ * of course.)
+ * 
+ * The returned result is a w*h-sized dsf.
+ * 
+ * In both of the above suggested use cases, the user would
+ * probably want w==h==k, but that isn't a requirement.
+ */
+static int *divvy_internal(int w, int h, int k, random_state *rs)
+{
+    int *order, *queue, *tmp, *own, *sizes, *addable, *removable, *retdsf;
+    int wh = w*h;
+    int i, j, n, x, y, qhead, qtail;
+
+    n = wh / k;
+    assert(wh == k*n);
+
+    order = snewn(wh, int);
+    tmp = snewn(wh, int);
+    own = snewn(wh, int);
+    sizes = snewn(n, int);
+    queue = snewn(n, int);
+    addable = snewn(wh*4, int);
+    removable = snewn(wh, int);
+
+    /*
+     * Permute the grid squares into a random order, which will be
+     * used for iterating over the grid whenever we need to search
+     * for something. This prevents directional bias and arranges
+     * for the answer to be non-deterministic.
+     */
+    for (i = 0; i < wh; i++)
+       order[i] = i;
+    shuffle(order, wh, sizeof(*order), rs);
+
+    /*
+     * Begin by choosing a starting square at random for each
+     * omino.
+     */
+    for (i = 0; i < wh; i++) {
+       own[i] = -1;
+    }
+    for (i = 0; i < n; i++) {
+       own[order[i]] = i;
+       sizes[i] = 1;
+    }
+
+    /*
+     * Now repeatedly pick a random omino which isn't already at
+     * the target size, and find a way to expand it by one. This
+     * may involve stealing a square from another omino, in which
+     * case we then re-expand that omino, forming a chain of
+     * square-stealing which terminates in an as yet unclaimed
+     * square. Hence every successful iteration around this loop
+     * causes the number of unclaimed squares to drop by one, and
+     * so the process is bounded in duration.
+     */
+    while (1) {
+
+#ifdef DIVVY_DIAGNOSTICS
+       {
+           int x, y;
+           printf("Top of loop. Current grid:\n");
+           for (y = 0; y < h; y++) {
+               for (x = 0; x < w; x++)
+                   printf("%3d", own[y*w+x]);
+               printf("\n");
+           }
+       }
+#endif
+
+       /*
+        * Go over the grid and figure out which squares can
+        * safely be added to, or removed from, each omino. We
+        * don't take account of other ominoes in this process, so
+        * we will often end up knowing that a square can be
+        * poached from one omino by another.
+        * 
+        * For each square, there may be up to four ominoes to
+        * which it can be added (those to which it is
+        * 4-adjacent).
+        */
+       for (y = 0; y < h; y++) {
+           for (x = 0; x < w; x++) {
+               int yx = y*w+x;
+               int curr = own[yx];
+               int dir;
+
+               if (curr < 0) {
+                   removable[yx] = FALSE; /* can't remove if not owned! */
+               } else if (sizes[curr] == 1) {
+                   removable[yx] = TRUE; /* can always remove a singleton */
+               } else {
+                   /*
+                    * See if this square can be removed from its
+                    * omino without disconnecting it.
+                    */
+                   removable[yx] = addremcommon(w, h, x, y, own, curr);
+               }
+
+               for (dir = 0; dir < 4; dir++) {
+                   int dx = (dir == 0 ? -1 : dir == 1 ? +1 : 0);
+                   int dy = (dir == 2 ? -1 : dir == 3 ? +1 : 0);
+                   int sx = x + dx, sy = y + dy;
+                   int syx = sy*w+sx;
+
+                   addable[yx*4+dir] = -1;
+
+                   if (sx < 0 || sx >= w || sy < 0 || sy >= h)
+                       continue;      /* no omino here! */
+                   if (own[syx] < 0)
+                       continue;      /* also no omino here */
+                   if (own[syx] == own[yx])
+                       continue;      /* we already got one */
+                   if (!addremcommon(w, h, x, y, own, own[syx]))
+                       continue;      /* would non-simply connect the omino */
+                   
+                   addable[yx*4+dir] = own[syx];
+               }
+           }
+       }
+
+       for (i = j = 0; i < n; i++)
+           if (sizes[i] < k)
+               tmp[j++] = i;
+       if (j == 0)
+           break;                     /* all ominoes are complete! */
+       j = tmp[random_upto(rs, j)];
+#ifdef DIVVY_DIAGNOSTICS
+       printf("Trying to extend %d\n", j);
+#endif
+
+       /*
+        * So we're trying to expand omino j. We breadth-first
+        * search out from j across the space of ominoes.
+        * 
+        * For bfs purposes, we use two elements of tmp per omino:
+        * tmp[2*i+0] tells us which omino we got to i from, and
+        * tmp[2*i+1] numbers the grid square that omino stole
+        * from us.
+        * 
+        * This requires that wh (the size of tmp) is at least 2n,
+        * i.e. k is at least 2. There would have been nothing to
+        * stop a user calling this function with k=1, but if they
+        * did then we wouldn't have got to _here_ in the code -
+        * we would have noticed above that all ominoes were
+        * already at their target sizes, and terminated :-)
+        */
+       assert(wh >= 2*n);
+       for (i = 0; i < n; i++)
+           tmp[2*i] = tmp[2*i+1] = -1;
+       qhead = qtail = 0;
+       queue[qtail++] = j;
+       tmp[2*j] = tmp[2*j+1] = -2;    /* special value: `starting point' */
+
+       while (qhead < qtail) {
+           int tmpsq;
+
+           j = queue[qhead];
+
+           /*
+            * We wish to expand omino j. However, we might have
+            * got here by omino j having a square stolen from it,
+            * so first of all we must temporarily mark that
+            * square as not belonging to j, so that our adjacency
+            * calculations don't assume j _does_ belong to us.
+            */
+           tmpsq = tmp[2*j+1];
+           if (tmpsq >= 0) {
+               assert(own[tmpsq] == j);
+               own[tmpsq] = -3;
+           }
+
+           /*
+            * OK. Now begin by seeing if we can find any
+            * unclaimed square into which we can expand omino j.
+            * If we find one, the entire bfs terminates.
+            */
+           for (i = 0; i < wh; i++) {
+               int dir;
+
+               if (own[order[i]] != -1)
+                   continue;          /* this square is claimed */
+
+               /*
+                * Special case: if our current omino was size 1
+                * and then had a square stolen from it, it's now
+                * size zero, which means it's valid to `expand'
+                * it into _any_ unclaimed square.
+                */
+               if (sizes[j] == 1 && tmpsq >= 0)
+                   break;             /* got one */
+
+               /*
+                * Failing that, we must do the full test for
+                * addability.
+                */
+               for (dir = 0; dir < 4; dir++)
+                   if (addable[order[i]*4+dir] == j) {
+                       /*
+                        * We know this square is addable to this
+                        * omino with the grid in the state it had
+                        * at the top of the loop. However, we
+                        * must now check that it's _still_
+                        * addable to this omino when the omino is
+                        * missing a square. To do this it's only
+                        * necessary to re-check addremcommon.
+                        */
+                       if (!addremcommon(w, h, order[i]%w, order[i]/w,
+                                         own, j))
+                           continue;
+                       break;
+                   }
+               if (dir == 4)
+                   continue;          /* we can't add this square to j */
+
+               break;                 /* got one! */
+           }
+           if (i < wh) {
+               i = order[i];
+
+               /*
+                * Restore the temporarily removed square _before_
+                * we start shifting ownerships about.
+                */
+               if (tmpsq >= 0)
+                   own[tmpsq] = j;
+
+               /*
+                * We are done. We can add square i to omino j,
+                * and then backtrack along the trail in tmp
+                * moving squares between ominoes, ending up
+                * expanding our starting omino by one.
+                */
+#ifdef DIVVY_DIAGNOSTICS
+               printf("(%d,%d)", i%w, i/w);
+#endif
+               while (1) {
+                   own[i] = j;
+#ifdef DIVVY_DIAGNOSTICS
+                   printf(" -> %d", j);
+#endif
+                   if (tmp[2*j] == -2)
+                       break;
+                   i = tmp[2*j+1];
+                   j = tmp[2*j];
+#ifdef DIVVY_DIAGNOSTICS
+                   printf("; (%d,%d)", i%w, i/w);
+#endif
+               }
+#ifdef DIVVY_DIAGNOSTICS
+               printf("\n");
+#endif
+
+               /*
+                * Increment the size of the starting omino.
+                */
+               sizes[j]++;
+
+               /*
+                * Terminate the bfs loop.
+                */
+               break;
+           }
+
+           /*
+            * If we get here, we haven't been able to expand
+            * omino j into an unclaimed square. So now we begin
+            * to investigate expanding it into squares which are
+            * claimed by ominoes the bfs has not yet visited.
+            */
+           for (i = 0; i < wh; i++) {
+               int dir, nj;
+
+               nj = own[order[i]];
+               if (nj < 0 || tmp[2*nj] != -1)
+                   continue;          /* unclaimed, or owned by wrong omino */
+               if (!removable[order[i]])
+                   continue;          /* its omino won't let it go */
+
+               for (dir = 0; dir < 4; dir++)
+                   if (addable[order[i]*4+dir] == j) {
+                       /*
+                        * As above, re-check addremcommon.
+                        */
+                       if (!addremcommon(w, h, order[i]%w, order[i]/w,
+                                         own, j))
+                           continue;
+
+                       /*
+                        * We have found a square we can use to
+                        * expand omino j, at the expense of the
+                        * as-yet unvisited omino nj. So add this
+                        * to the bfs queue.
+                        */
+                       assert(qtail < n);
+                       queue[qtail++] = nj;
+                       tmp[2*nj] = j;
+                       tmp[2*nj+1] = order[i];
+
+                       /*
+                        * Now terminate the loop over dir, to
+                        * ensure we don't accidentally add the
+                        * same omino twice to the queue.
+                        */
+                       break;
+                   }
+           }
+
+           /*
+            * Restore the temporarily removed square.
+            */
+           if (tmpsq >= 0)
+               own[tmpsq] = j;
+
+           /*
+            * Advance the queue head.
+            */
+           qhead++;
+       }
+
+       if (qhead == qtail) {
+           /*
+            * We have finished the bfs and not found any way to
+            * expand omino j. Panic, and return failure.
+            * 
+            * FIXME: or should we loop over all ominoes before we
+            * give up?
+            */
+#ifdef DIVVY_DIAGNOSTICS
+           printf("FAIL!\n");
+#endif
+           retdsf = NULL;
+           goto cleanup;
+       }
+    }
+
+#ifdef DIVVY_DIAGNOSTICS
+    {
+       int x, y;
+       printf("SUCCESS! Final grid:\n");
+       for (y = 0; y < h; y++) {
+           for (x = 0; x < w; x++)
+               printf("%3d", own[y*w+x]);
+           printf("\n");
+       }
+    }
+#endif
+
+    /*
+     * Construct the output dsf.
+     */
+    for (i = 0; i < wh; i++) {
+       assert(own[i] >= 0 && own[i] < n);
+       tmp[own[i]] = i;
+    }
+    retdsf = snew_dsf(wh);
+    for (i = 0; i < wh; i++) {
+       dsf_merge(retdsf, i, tmp[own[i]]);
+    }
+
+    /*
+     * Construct the output dsf a different way, to verify that
+     * the ominoes really are k-ominoes and we haven't
+     * accidentally split one into two disconnected pieces.
+     */
+    dsf_init(tmp, wh);
+    for (y = 0; y < h; y++)
+       for (x = 0; x+1 < w; x++)
+           if (own[y*w+x] == own[y*w+(x+1)])
+               dsf_merge(tmp, y*w+x, y*w+(x+1));
+    for (x = 0; x < w; x++)
+       for (y = 0; y+1 < h; y++)
+           if (own[y*w+x] == own[(y+1)*w+x])
+               dsf_merge(tmp, y*w+x, (y+1)*w+x);
+    for (i = 0; i < wh; i++) {
+       j = dsf_canonify(retdsf, i);
+       assert(dsf_canonify(tmp, j) == dsf_canonify(tmp, i));
+    }
+
+    cleanup:
+
+    /*
+     * Free our temporary working space.
+     */
+    sfree(order);
+    sfree(tmp);
+    sfree(own);
+    sfree(sizes);
+    sfree(queue);
+    sfree(addable);
+    sfree(removable);
+
+    /*
+     * And we're done.
+     */
+    return retdsf;
+}
+
+#ifdef TESTMODE
+static int fail_counter = 0;
+#endif
+
+int *divvy_rectangle(int w, int h, int k, random_state *rs)
+{
+    int *ret;
+
+    do {
+       ret = divvy_internal(w, h, k, rs);
+
+#ifdef TESTMODE
+       if (!ret)
+           fail_counter++;
+#endif
+
+    } while (!ret);
+
+    return ret;
+}
+
+#ifdef TESTMODE
+
+/*
+ * gcc -g -O0 -DTESTMODE -I.. -o divvy divvy.c ../random.c ../malloc.c ../dsf.c ../misc.c ../nullfe.c
+ * 
+ * or to debug
+ * 
+ * gcc -g -O0 -DDIVVY_DIAGNOSTICS -DTESTMODE -I.. -o divvy divvy.c ../random.c ../malloc.c ../dsf.c ../misc.c ../nullfe.c
+ */
+
+int main(int argc, char **argv)
+{
+    int *dsf;
+    int i;
+    int w = 9, h = 4, k = 6, tries = 100;
+    random_state *rs;
+
+    rs = random_new("123456", 6);
+
+    if (argc > 1)
+       w = atoi(argv[1]);
+    if (argc > 2)
+       h = atoi(argv[2]);
+    if (argc > 3)
+       k = atoi(argv[3]);
+    if (argc > 4)
+       tries = atoi(argv[4]);
+
+    for (i = 0; i < tries; i++) {
+       int x, y;
+
+       dsf = divvy_rectangle(w, h, k, rs);
+       assert(dsf);
+
+       for (y = 0; y <= 2*h; y++) {
+           for (x = 0; x <= 2*w; x++) {
+               int miny = y/2 - 1, maxy = y/2;
+               int minx = x/2 - 1, maxx = x/2;
+               int classes[4], tx, ty;
+               for (ty = 0; ty < 2; ty++)
+                   for (tx = 0; tx < 2; tx++) {
+                       int cx = minx+tx, cy = miny+ty;
+                       if (cx < 0 || cx >= w || cy < 0 || cy >= h)
+                           classes[ty*2+tx] = -1;
+                       else
+                           classes[ty*2+tx] = dsf_canonify(dsf, cy*w+cx);
+                   }
+               switch (y%2 * 2 + x%2) {
+                 case 0:              /* corner */
+                   /*
+                    * Cases for the corner:
+                    *
+                    *  - if all four surrounding squares belong
+                    *    to the same omino, we print a space.
+                    *
+                    *  - if the top two are the same and the
+                    *    bottom two are the same, we print a
+                    *    horizontal line.
+                    *
+                    *  - if the left two are the same and the
+                    *    right two are the same, we print a
+                    *    vertical line.
+                    *
+                    *  - otherwise, we print a cross.
+                    */
+                   if (classes[0] == classes[1] &&
+                       classes[1] == classes[2] &&
+                       classes[2] == classes[3])
+                       printf(" ");
+                   else if (classes[0] == classes[1] &&
+                            classes[2] == classes[3])
+                       printf("-");
+                   else if (classes[0] == classes[2] &&
+                            classes[1] == classes[3])
+                       printf("|");
+                   else
+                       printf("+");
+                   break;
+                 case 1:              /* horiz edge */
+                   if (classes[1] == classes[3])
+                       printf("  ");
+                   else
+                       printf("--");
+                   break;
+                 case 2:              /* vert edge */
+                   if (classes[2] == classes[3])
+                       printf(" ");
+                   else
+                       printf("|");
+                   break;
+                 case 3:              /* square centre */
+                   printf("  ");
+                   break;
+               }
+           }
+           printf("\n");
+       }
+       printf("\n");
+       sfree(dsf);
+    }
+
+    printf("%d retries needed for %d successes\n", fail_counter, tries);
+
+    return 0;
+}
+
+#endif
diff --git a/dominosa.R b/dominosa.R
new file mode 100644 (file)
index 0000000..9921836
--- /dev/null
@@ -0,0 +1,21 @@
+# -*- makefile -*-
+
+DOMINOSA_EXTRA = laydomino
+
+dominosa : [X] GTK COMMON dominosa DOMINOSA_EXTRA dominosa-icon|no-icon
+
+dominosa : [G] WINDOWS COMMON dominosa DOMINOSA_EXTRA dominosa.res|noicon.res
+
+ALL += dominosa[COMBINED] DOMINOSA_EXTRA
+
+!begin am gtk
+GAMES += dominosa
+!end
+
+!begin >list.c
+    A(dominosa) \
+!end
+
+!begin >gamedesc.txt
+dominosa:dominosa.exe:Dominosa:Domino tiling puzzle:Tile the rectangle with a full set of dominoes.
+!end
diff --git a/dominosa.c b/dominosa.c
new file mode 100644 (file)
index 0000000..dc7c2c7
--- /dev/null
@@ -0,0 +1,1748 @@
+/*
+ * dominosa.c: Domino jigsaw puzzle. Aim to place one of every
+ * possible domino within a rectangle in such a way that the number
+ * on each square matches the provided clue.
+ */
+
+/*
+ * TODO:
+ * 
+ *  - improve solver so as to use more interesting forms of
+ *    deduction
+ *
+ *     * rule out a domino placement if it would divide an unfilled
+ *       region such that at least one resulting region had an odd
+ *       area
+ *        + use b.f.s. to determine the area of an unfilled region
+ *        + a square is unfilled iff it has at least two possible
+ *          placements, and two adjacent unfilled squares are part
+ *          of the same region iff the domino placement joining
+ *          them is possible
+ *
+ *     * perhaps set analysis
+ *        + look at all unclaimed squares containing a given number
+ *        + for each one, find the set of possible numbers that it
+ *          can connect to (i.e. each neighbouring tile such that
+ *          the placement between it and that neighbour has not yet
+ *          been ruled out)
+ *        + now proceed similarly to Solo set analysis: try to find
+ *          a subset of the squares such that the union of their
+ *          possible numbers is the same size as the subset. If so,
+ *          rule out those possible numbers for all other squares.
+ *           * important wrinkle: the double dominoes complicate
+ *             matters. Connecting a number to itself uses up _two_
+ *             of the unclaimed squares containing a number. Thus,
+ *             when finding the initial subset we must never
+ *             include two adjacent squares; and also, when ruling
+ *             things out after finding the subset, we must be
+ *             careful that we don't rule out precisely the domino
+ *             placement that was _included_ in our set!
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+
+/* nth triangular number */
+#define TRI(n) ( (n) * ((n) + 1) / 2 )
+/* number of dominoes for value n */
+#define DCOUNT(n) TRI((n)+1)
+/* map a pair of numbers to a unique domino index from 0 upwards. */
+#define DINDEX(n1,n2) ( TRI(max(n1,n2)) + min(n1,n2) )
+
+#define FLASH_TIME 0.13F
+
+enum {
+    COL_BACKGROUND,
+    COL_TEXT,
+    COL_DOMINO,
+    COL_DOMINOCLASH,
+    COL_DOMINOTEXT,
+    COL_EDGE,
+    COL_HIGHLIGHT_1,
+    COL_HIGHLIGHT_2,
+    NCOLOURS
+};
+
+struct game_params {
+    int n;
+    int unique;
+};
+
+struct game_numbers {
+    int refcount;
+    int *numbers;                      /* h x w */
+};
+
+#define EDGE_L 0x100
+#define EDGE_R 0x200
+#define EDGE_T 0x400
+#define EDGE_B 0x800
+
+struct game_state {
+    game_params params;
+    int w, h;
+    struct game_numbers *numbers;
+    int *grid;
+    unsigned short *edges;             /* h x w */
+    int completed, cheated;
+};
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+
+    ret->n = 6;
+    ret->unique = TRUE;
+
+    return ret;
+}
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params *ret;
+    int n;
+    char buf[80];
+
+    switch (i) {
+      case 0: n = 3; break;
+      case 1: n = 4; break;
+      case 2: n = 5; break;
+      case 3: n = 6; break;
+      case 4: n = 7; break;
+      case 5: n = 8; break;
+      case 6: n = 9; break;
+      default: return FALSE;
+    }
+
+    sprintf(buf, "Up to double-%d", n);
+    *name = dupstr(buf);
+
+    *params = ret = snew(game_params);
+    ret->n = n;
+    ret->unique = TRUE;
+
+    return TRUE;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+static void decode_params(game_params *params, char const *string)
+{
+    params->n = atoi(string);
+    while (*string && isdigit((unsigned char)*string)) string++;
+    if (*string == 'a')
+        params->unique = FALSE;
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char buf[80];
+    sprintf(buf, "%d", params->n);
+    if (full && !params->unique)
+        strcat(buf, "a");
+    return dupstr(buf);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+    char buf[80];
+
+    ret = snewn(3, config_item);
+
+    ret[0].name = "Maximum number on dominoes";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->n);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Ensure unique solution";
+    ret[1].type = C_BOOLEAN;
+    ret[1].sval = NULL;
+    ret[1].ival = params->unique;
+
+    ret[2].name = NULL;
+    ret[2].type = C_END;
+    ret[2].sval = NULL;
+    ret[2].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->n = atoi(cfg[0].sval);
+    ret->unique = cfg[1].ival;
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    if (params->n < 1)
+        return "Maximum face number must be at least one";
+    return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Solver.
+ */
+
+static int find_overlaps(int w, int h, int placement, int *set)
+{
+    int x, y, n;
+
+    n = 0;                             /* number of returned placements */
+
+    x = placement / 2;
+    y = x / w;
+    x %= w;
+
+    if (placement & 1) {
+        /*
+         * Horizontal domino, indexed by its left end.
+         */
+        if (x > 0)
+            set[n++] = placement-2;    /* horizontal domino to the left */
+        if (y > 0)
+            set[n++] = placement-2*w-1;/* vertical domino above left side */
+        if (y+1 < h)
+            set[n++] = placement-1;    /* vertical domino below left side */
+        if (x+2 < w)
+            set[n++] = placement+2;    /* horizontal domino to the right */
+        if (y > 0)
+            set[n++] = placement-2*w+2-1;/* vertical domino above right side */
+        if (y+1 < h)
+            set[n++] = placement+2-1;  /* vertical domino below right side */
+    } else {
+        /*
+         * Vertical domino, indexed by its top end.
+         */
+        if (y > 0)
+            set[n++] = placement-2*w;  /* vertical domino above */
+        if (x > 0)
+            set[n++] = placement-2+1;  /* horizontal domino left of top */
+        if (x+1 < w)
+            set[n++] = placement+1;    /* horizontal domino right of top */
+        if (y+2 < h)
+            set[n++] = placement+2*w;  /* vertical domino below */
+        if (x > 0)
+            set[n++] = placement-2+2*w+1;/* horizontal domino left of bottom */
+        if (x+1 < w)
+            set[n++] = placement+2*w+1;/* horizontal domino right of bottom */
+    }
+
+    return n;
+}
+
+/*
+ * Returns 0, 1 or 2 for number of solutions. 2 means `any number
+ * more than one', or more accurately `we were unable to prove
+ * there was only one'.
+ * 
+ * Outputs in a `placements' array, indexed the same way as the one
+ * within this function (see below); entries in there are <0 for a
+ * placement ruled out, 0 for an uncertain placement, and 1 for a
+ * definite one.
+ */
+static int solver(int w, int h, int n, int *grid, int *output)
+{
+    int wh = w*h, dc = DCOUNT(n);
+    int *placements, *heads;
+    int i, j, x, y, ret;
+
+    /*
+     * This array has one entry for every possible domino
+     * placement. Vertical placements are indexed by their top
+     * half, at (y*w+x)*2; horizontal placements are indexed by
+     * their left half at (y*w+x)*2+1.
+     * 
+     * This array is used to link domino placements together into
+     * linked lists, so that we can track all the possible
+     * placements of each different domino. It's also used as a
+     * quick means of looking up an individual placement to see
+     * whether we still think it's possible. Actual values stored
+     * in this array are -2 (placement not possible at all), -1
+     * (end of list), or the array index of the next item.
+     * 
+     * Oh, and -3 for `not even valid', used for array indices
+     * which don't even represent a plausible placement.
+     */
+    placements = snewn(2*wh, int);
+    for (i = 0; i < 2*wh; i++)
+        placements[i] = -3;            /* not even valid */
+
+    /*
+     * This array has one entry for every domino, and it is an
+     * index into `placements' denoting the head of the placement
+     * list for that domino.
+     */
+    heads = snewn(dc, int);
+    for (i = 0; i < dc; i++)
+        heads[i] = -1;
+
+    /*
+     * Set up the initial possibility lists by scanning the grid.
+     */
+    for (y = 0; y < h-1; y++)
+        for (x = 0; x < w; x++) {
+            int di = DINDEX(grid[y*w+x], grid[(y+1)*w+x]);
+            placements[(y*w+x)*2] = heads[di];
+            heads[di] = (y*w+x)*2;
+        }
+    for (y = 0; y < h; y++)
+        for (x = 0; x < w-1; x++) {
+            int di = DINDEX(grid[y*w+x], grid[y*w+(x+1)]);
+            placements[(y*w+x)*2+1] = heads[di];
+            heads[di] = (y*w+x)*2+1;
+        }
+
+#ifdef SOLVER_DIAGNOSTICS
+    printf("before solver:\n");
+    for (i = 0; i <= n; i++)
+        for (j = 0; j <= i; j++) {
+            int k, m;
+            m = 0;
+            printf("%2d [%d %d]:", DINDEX(i, j), i, j);
+            for (k = heads[DINDEX(i,j)]; k >= 0; k = placements[k])
+                printf(" %3d [%d,%d,%c]", k, k/2%w, k/2/w, k%2?'h':'v');
+            printf("\n");
+        }
+#endif
+
+    while (1) {
+        int done_something = FALSE;
+
+        /*
+         * For each domino, look at its possible placements, and
+         * for each placement consider the placements (of any
+         * domino) it overlaps. Any placement overlapped by all
+         * placements of this domino can be ruled out.
+         * 
+         * Each domino placement overlaps only six others, so we
+         * need not do serious set theory to work this out.
+         */
+        for (i = 0; i < dc; i++) {
+            int permset[6], permlen = 0, p;
+            
+
+            if (heads[i] == -1) {      /* no placement for this domino */
+                ret = 0;               /* therefore puzzle is impossible */
+                goto done;
+            }
+            for (j = heads[i]; j >= 0; j = placements[j]) {
+                assert(placements[j] != -2);
+
+                if (j == heads[i]) {
+                    permlen = find_overlaps(w, h, j, permset);
+                } else {
+                    int tempset[6], templen, m, n, k;
+
+                    templen = find_overlaps(w, h, j, tempset);
+
+                    /*
+                     * Pathetically primitive set intersection
+                     * algorithm, which I'm only getting away with
+                     * because I know my sets are bounded by a very
+                     * small size.
+                     */
+                    for (m = n = 0; m < permlen; m++) {
+                        for (k = 0; k < templen; k++)
+                            if (tempset[k] == permset[m])
+                                break;
+                        if (k < templen)
+                            permset[n++] = permset[m];
+                    }
+                    permlen = n;
+                }
+            }
+            for (p = 0; p < permlen; p++) {
+                j = permset[p];
+                if (placements[j] != -2) {
+                    int p1, p2, di;
+
+                    done_something = TRUE;
+
+                    /*
+                     * Rule out this placement. First find what
+                     * domino it is...
+                     */
+                    p1 = j / 2;
+                    p2 = (j & 1) ? p1 + 1 : p1 + w;
+                    di = DINDEX(grid[p1], grid[p2]);
+#ifdef SOLVER_DIAGNOSTICS
+                    printf("considering domino %d: ruling out placement %d"
+                           " for %d\n", i, j, di);
+#endif
+
+                    /*
+                     * ... then walk that domino's placement list,
+                     * removing this placement when we find it.
+                     */
+                    if (heads[di] == j)
+                        heads[di] = placements[j];
+                    else {
+                        int k = heads[di];
+                        while (placements[k] != -1 && placements[k] != j)
+                            k = placements[k];
+                        assert(placements[k] == j);
+                        placements[k] = placements[j];
+                    }
+                    placements[j] = -2;
+                }
+            }
+        }
+
+        /*
+         * For each square, look at the available placements
+         * involving that square. If all of them are for the same
+         * domino, then rule out any placements for that domino
+         * _not_ involving this square.
+         */
+        for (i = 0; i < wh; i++) {
+            int list[4], k, n, adi;
+
+            x = i % w;
+            y = i / w;
+
+            j = 0;
+            if (x > 0)
+                list[j++] = 2*(i-1)+1;
+            if (x+1 < w)
+                list[j++] = 2*i+1;
+            if (y > 0)
+                list[j++] = 2*(i-w);
+            if (y+1 < h)
+                list[j++] = 2*i;
+
+            for (n = k = 0; k < j; k++)
+                if (placements[list[k]] >= -1)
+                    list[n++] = list[k];
+
+            adi = -1;
+
+            for (j = 0; j < n; j++) {
+                int p1, p2, di;
+                k = list[j];
+
+                p1 = k / 2;
+                p2 = (k & 1) ? p1 + 1 : p1 + w;
+                di = DINDEX(grid[p1], grid[p2]);
+
+                if (adi == -1)
+                    adi = di;
+                if (adi != di)
+                    break;
+            }
+
+            if (j == n) {
+                int nn;
+
+                assert(adi >= 0);
+                /*
+                 * We've found something. All viable placements
+                 * involving this square are for domino `adi'. If
+                 * the current placement list for that domino is
+                 * longer than n, reduce it to precisely this
+                 * placement list and we've done something.
+                 */
+                nn = 0;
+                for (k = heads[adi]; k >= 0; k = placements[k])
+                    nn++;
+                if (nn > n) {
+                    done_something = TRUE;
+#ifdef SOLVER_DIAGNOSTICS
+                    printf("considering square %d,%d: reducing placements "
+                           "of domino %d\n", x, y, adi);
+#endif
+                    /*
+                     * Set all other placements on the list to
+                     * impossible.
+                     */
+                    k = heads[adi];
+                    while (k >= 0) {
+                        int tmp = placements[k];
+                        placements[k] = -2;
+                        k = tmp;
+                    }
+                    /*
+                     * Set up the new list.
+                     */
+                    heads[adi] = list[0];
+                    for (k = 0; k < n; k++)
+                        placements[list[k]] = (k+1 == n ? -1 : list[k+1]);
+                }
+            }
+        }
+
+        if (!done_something)
+            break;
+    }
+
+#ifdef SOLVER_DIAGNOSTICS
+    printf("after solver:\n");
+    for (i = 0; i <= n; i++)
+        for (j = 0; j <= i; j++) {
+            int k, m;
+            m = 0;
+            printf("%2d [%d %d]:", DINDEX(i, j), i, j);
+            for (k = heads[DINDEX(i,j)]; k >= 0; k = placements[k])
+                printf(" %3d [%d,%d,%c]", k, k/2%w, k/2/w, k%2?'h':'v');
+            printf("\n");
+        }
+#endif
+
+    ret = 1;
+    for (i = 0; i < wh*2; i++) {
+        if (placements[i] == -2) {
+            if (output)
+                output[i] = -1;        /* ruled out */
+        } else if (placements[i] != -3) {
+            int p1, p2, di;
+
+            p1 = i / 2;
+            p2 = (i & 1) ? p1 + 1 : p1 + w;
+            di = DINDEX(grid[p1], grid[p2]);
+
+            if (i == heads[di] && placements[i] == -1) {
+                if (output)
+                    output[i] = 1;     /* certain */
+            } else {
+                if (output)
+                    output[i] = 0;     /* uncertain */
+                ret = 2;
+            }
+        }
+    }
+
+    done:
+    /*
+     * Free working data.
+     */
+    sfree(placements);
+    sfree(heads);
+
+    return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * End of solver code.
+ */
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                          char **aux, int interactive)
+{
+    int n = params->n, w = n+2, h = n+1, wh = w*h;
+    int *grid, *grid2, *list;
+    int i, j, k, len;
+    char *ret;
+
+    /*
+     * Allocate space in which to lay the grid out.
+     */
+    grid = snewn(wh, int);
+    grid2 = snewn(wh, int);
+    list = snewn(2*wh, int);
+
+    /*
+     * I haven't been able to think of any particularly clever
+     * techniques for generating instances of Dominosa with a
+     * unique solution. Many of the deductions used in this puzzle
+     * are based on information involving half the grid at a time
+     * (`of all the 6s, exactly one is next to a 3'), so a strategy
+     * of partially solving the grid and then perturbing the place
+     * where the solver got stuck seems particularly likely to
+     * accidentally destroy the information which the solver had
+     * used in getting that far. (Contrast with, say, Mines, in
+     * which most deductions are local so this is an excellent
+     * strategy.)
+     *
+     * Therefore I resort to the basest of brute force methods:
+     * generate a random grid, see if it's solvable, throw it away
+     * and try again if not. My only concession to sophistication
+     * and cleverness is to at least _try_ not to generate obvious
+     * 2x2 ambiguous sections (see comment below in the domino-
+     * flipping section).
+     *
+     * During tests performed on 2005-07-15, I found that the brute
+     * force approach without that tweak had to throw away about 87
+     * grids on average (at the default n=6) before finding a
+     * unique one, or a staggering 379 at n=9; good job the
+     * generator and solver are fast! When I added the
+     * ambiguous-section avoidance, those numbers came down to 19
+     * and 26 respectively, which is a lot more sensible.
+     */
+
+    do {
+        domino_layout_prealloc(w, h, rs, grid, grid2, list);
+
+        /*
+         * Now we have a complete layout covering the whole
+         * rectangle with dominoes. So shuffle the actual domino
+         * values and fill the rectangle with numbers.
+         */
+        k = 0;
+        for (i = 0; i <= params->n; i++)
+            for (j = 0; j <= i; j++) {
+                list[k++] = i;
+                list[k++] = j;
+            }
+        shuffle(list, k/2, 2*sizeof(*list), rs);
+        j = 0;
+        for (i = 0; i < wh; i++)
+            if (grid[i] > i) {
+                /* Optionally flip the domino round. */
+                int flip = -1;
+
+                if (params->unique) {
+                    int t1, t2;
+                    /*
+                     * If we're after a unique solution, we can do
+                     * something here to improve the chances. If
+                     * we're placing a domino so that it forms a
+                     * 2x2 rectangle with one we've already placed,
+                     * and if that domino and this one share a
+                     * number, we can try not to put them so that
+                     * the identical numbers are diagonally
+                     * separated, because that automatically causes
+                     * non-uniqueness:
+                     * 
+                     * +---+      +-+-+
+                     * |2 3|      |2|3|
+                     * +---+  ->  | | |
+                     * |4 2|      |4|2|
+                     * +---+      +-+-+
+                     */
+                    t1 = i;
+                    t2 = grid[i];
+                    if (t2 == t1 + w) {  /* this domino is vertical */
+                        if (t1 % w > 0 &&/* and not on the left hand edge */
+                            grid[t1-1] == t2-1 &&/* alongside one to left */
+                            (grid2[t1-1] == list[j] ||   /* and has a number */
+                             grid2[t1-1] == list[j+1] ||   /* in common */
+                             grid2[t2-1] == list[j] ||
+                             grid2[t2-1] == list[j+1])) {
+                            if (grid2[t1-1] == list[j] ||
+                                grid2[t2-1] == list[j+1])
+                                flip = 0;
+                            else
+                                flip = 1;
+                        }
+                    } else {           /* this domino is horizontal */
+                        if (t1 / w > 0 &&/* and not on the top edge */
+                            grid[t1-w] == t2-w &&/* alongside one above */
+                            (grid2[t1-w] == list[j] ||   /* and has a number */
+                             grid2[t1-w] == list[j+1] ||   /* in common */
+                             grid2[t2-w] == list[j] ||
+                             grid2[t2-w] == list[j+1])) {
+                            if (grid2[t1-w] == list[j] ||
+                                grid2[t2-w] == list[j+1])
+                                flip = 0;
+                            else
+                                flip = 1;
+                        }
+                    }
+                }
+
+                if (flip < 0)
+                    flip = random_upto(rs, 2);
+
+                grid2[i] = list[j + flip];
+                grid2[grid[i]] = list[j + 1 - flip];
+                j += 2;
+            }
+        assert(j == k);
+    } while (params->unique && solver(w, h, n, grid2, NULL) > 1);
+
+#ifdef GENERATION_DIAGNOSTICS
+    for (j = 0; j < h; j++) {
+        for (i = 0; i < w; i++) {
+            putchar('0' + grid2[j*w+i]);
+        }
+        putchar('\n');
+    }
+    putchar('\n');
+#endif
+
+    /*
+     * Encode the resulting game state.
+     * 
+     * Our encoding is a string of digits. Any number greater than
+     * 9 is represented by a decimal integer within square
+     * brackets. We know there are n+2 of every number (it's paired
+     * with each number from 0 to n inclusive, and one of those is
+     * itself so that adds another occurrence), so we can work out
+     * the string length in advance.
+     */
+
+    /*
+     * To work out the total length of the decimal encodings of all
+     * the numbers from 0 to n inclusive:
+     *  - every number has a units digit; total is n+1.
+     *  - all numbers above 9 have a tens digit; total is max(n+1-10,0).
+     *  - all numbers above 99 have a hundreds digit; total is max(n+1-100,0).
+     *  - and so on.
+     */
+    len = n+1;
+    for (i = 10; i <= n; i *= 10)
+       len += max(n + 1 - i, 0);
+    /* Now add two square brackets for each number above 9. */
+    len += 2 * max(n + 1 - 10, 0);
+    /* And multiply by n+2 for the repeated occurrences of each number. */
+    len *= n+2;
+
+    /*
+     * Now actually encode the string.
+     */
+    ret = snewn(len+1, char);
+    j = 0;
+    for (i = 0; i < wh; i++) {
+        k = grid2[i];
+        if (k < 10)
+            ret[j++] = '0' + k;
+        else
+            j += sprintf(ret+j, "[%d]", k);
+        assert(j <= len);
+    }
+    assert(j == len);
+    ret[j] = '\0';
+
+    /*
+     * Encode the solved state as an aux_info.
+     */
+    {
+       char *auxinfo = snewn(wh+1, char);
+
+       for (i = 0; i < wh; i++) {
+           int v = grid[i];
+           auxinfo[i] = (v == i+1 ? 'L' : v == i-1 ? 'R' :
+                         v == i+w ? 'T' : v == i-w ? 'B' : '.');
+       }
+       auxinfo[wh] = '\0';
+
+       *aux = auxinfo;
+    }
+
+    sfree(list);
+    sfree(grid2);
+    sfree(grid);
+
+    return ret;
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    int n = params->n, w = n+2, h = n+1, wh = w*h;
+    int *occurrences;
+    int i, j;
+    char *ret;
+
+    ret = NULL;
+    occurrences = snewn(n+1, int);
+    for (i = 0; i <= n; i++)
+        occurrences[i] = 0;
+
+    for (i = 0; i < wh; i++) {
+        if (!*desc) {
+            ret = ret ? ret : "Game description is too short";
+        } else {
+            if (*desc >= '0' && *desc <= '9')
+                j = *desc++ - '0';
+            else if (*desc == '[') {
+                desc++;
+                j = atoi(desc);
+                while (*desc && isdigit((unsigned char)*desc)) desc++;
+                if (*desc != ']')
+                    ret = ret ? ret : "Missing ']' in game description";
+                else
+                    desc++;
+            } else {
+                j = -1;
+                ret = ret ? ret : "Invalid syntax in game description";
+            }
+            if (j < 0 || j > n)
+                ret = ret ? ret : "Number out of range in game description";
+            else
+                occurrences[j]++;
+        }
+    }
+
+    if (*desc)
+        ret = ret ? ret : "Game description is too long";
+
+    if (!ret) {
+        for (i = 0; i <= n; i++)
+            if (occurrences[i] != n+2)
+                ret = "Incorrect number balance in game description";
+    }
+
+    sfree(occurrences);
+
+    return ret;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    int n = params->n, w = n+2, h = n+1, wh = w*h;
+    game_state *state = snew(game_state);
+    int i, j;
+
+    state->params = *params;
+    state->w = w;
+    state->h = h;
+
+    state->grid = snewn(wh, int);
+    for (i = 0; i < wh; i++)
+        state->grid[i] = i;
+
+    state->edges = snewn(wh, unsigned short);
+    for (i = 0; i < wh; i++)
+        state->edges[i] = 0;
+
+    state->numbers = snew(struct game_numbers);
+    state->numbers->refcount = 1;
+    state->numbers->numbers = snewn(wh, int);
+
+    for (i = 0; i < wh; i++) {
+        assert(*desc);
+        if (*desc >= '0' && *desc <= '9')
+            j = *desc++ - '0';
+        else {
+            assert(*desc == '[');
+            desc++;
+            j = atoi(desc);
+            while (*desc && isdigit((unsigned char)*desc)) desc++;
+            assert(*desc == ']');
+            desc++;
+        }
+        assert(j >= 0 && j <= n);
+        state->numbers->numbers[i] = j;
+    }
+
+    state->completed = state->cheated = FALSE;
+
+    return state;
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    int n = state->params.n, w = n+2, h = n+1, wh = w*h;
+    game_state *ret = snew(game_state);
+
+    ret->params = state->params;
+    ret->w = state->w;
+    ret->h = state->h;
+    ret->grid = snewn(wh, int);
+    memcpy(ret->grid, state->grid, wh * sizeof(int));
+    ret->edges = snewn(wh, unsigned short);
+    memcpy(ret->edges, state->edges, wh * sizeof(unsigned short));
+    ret->numbers = state->numbers;
+    ret->numbers->refcount++;
+    ret->completed = state->completed;
+    ret->cheated = state->cheated;
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    sfree(state->grid);
+    sfree(state->edges);
+    if (--state->numbers->refcount <= 0) {
+        sfree(state->numbers->numbers);
+        sfree(state->numbers);
+    }
+    sfree(state);
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    int n = state->params.n, w = n+2, h = n+1, wh = w*h;
+    int *placements;
+    char *ret;
+    int retlen, retsize;
+    int i, v;
+    char buf[80];
+    int extra;
+
+    if (aux) {
+       retsize = 256;
+       ret = snewn(retsize, char);
+       retlen = sprintf(ret, "S");
+
+       for (i = 0; i < wh; i++) {
+           if (aux[i] == 'L')
+               extra = sprintf(buf, ";D%d,%d", i, i+1);
+           else if (aux[i] == 'T')
+               extra = sprintf(buf, ";D%d,%d", i, i+w);
+           else
+               continue;
+
+           if (retlen + extra + 1 >= retsize) {
+               retsize = retlen + extra + 256;
+               ret = sresize(ret, retsize, char);
+           }
+           strcpy(ret + retlen, buf);
+           retlen += extra;
+       }
+
+    } else {
+
+       placements = snewn(wh*2, int);
+       for (i = 0; i < wh*2; i++)
+           placements[i] = -3;
+       solver(w, h, n, state->numbers->numbers, placements);
+
+       /*
+        * First make a pass putting in edges for -1, then make a pass
+        * putting in dominoes for +1.
+        */
+       retsize = 256;
+       ret = snewn(retsize, char);
+       retlen = sprintf(ret, "S");
+
+       for (v = -1; v <= +1; v += 2)
+           for (i = 0; i < wh*2; i++)
+               if (placements[i] == v) {
+                   int p1 = i / 2;
+                   int p2 = (i & 1) ? p1+1 : p1+w;
+
+                   extra = sprintf(buf, ";%c%d,%d",
+                                   (int)(v==-1 ? 'E' : 'D'), p1, p2);
+
+                   if (retlen + extra + 1 >= retsize) {
+                       retsize = retlen + extra + 256;
+                       ret = sresize(ret, retsize, char);
+                   }
+                   strcpy(ret + retlen, buf);
+                   retlen += extra;
+               }
+
+       sfree(placements);
+    }
+
+    return ret;
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return params->n < 1000;
+}
+
+static void draw_domino(char *board, int start, char corner,
+                       int dshort, int nshort, char cshort,
+                       int dlong, int nlong, char clong)
+{
+    int go_short = nshort*dshort, go_long = nlong*dlong, i;
+
+    board[start] = corner;
+    board[start + go_short] = corner;
+    board[start + go_long] = corner;
+    board[start + go_short + go_long] = corner;
+
+    for (i = 1; i < nshort; ++i) {
+       int j = start + i*dshort, k = start + i*dshort + go_long;
+       if (board[j] != corner) board[j] = cshort;
+       if (board[k] != corner) board[k] = cshort;
+    }
+
+    for (i = 1; i < nlong; ++i) {
+       int j = start + i*dlong, k = start + i*dlong + go_short;
+       if (board[j] != corner) board[j] = clong;
+       if (board[k] != corner) board[k] = clong;
+    }
+}
+
+static char *game_text_format(const game_state *state)
+{
+    int w = state->w, h = state->h, r, c;
+    int cw = 4, ch = 2, gw = cw*w + 2, gh = ch * h + 1, len = gw * gh;
+    char *board = snewn(len + 1, char);
+
+    memset(board, ' ', len);
+
+    for (r = 0; r < h; ++r) {
+       for (c = 0; c < w; ++c) {
+           int cell = r*ch*gw + cw*c, center = cell + gw*ch/2 + cw/2;
+           int i = r*w + c, num = state->numbers->numbers[i];
+
+           if (num < 100) {
+               board[center] = '0' + num % 10;
+               if (num >= 10) board[center - 1] = '0' + num / 10;
+           } else {
+               board[center+1] = '0' + num % 10;
+               board[center] = '0' + num / 10 % 10;
+               board[center-1] = '0' + num / 100;
+           }
+
+           if (state->edges[i] & EDGE_L) board[center - cw/2] = '|';
+           if (state->edges[i] & EDGE_R) board[center + cw/2] = '|';
+           if (state->edges[i] & EDGE_T) board[center - gw] = '-';
+           if (state->edges[i] & EDGE_B) board[center + gw] = '-';
+
+           if (state->grid[i] == i) continue; /* no domino pairing */
+           if (state->grid[i] < i) continue; /* already done */
+           assert (state->grid[i] == i + 1 || state->grid[i] == i + w);
+           if (state->grid[i] == i + 1)
+               draw_domino(board, cell, '+', gw, ch, '|', +1, 2*cw, '-');
+           else if (state->grid[i] == i + w)
+               draw_domino(board, cell, '+', +1, cw, '-', gw, 2*ch, '|');
+       }
+       board[r*ch*gw + gw - 1] = '\n';
+       board[r*ch*gw + gw + gw - 1] = '\n';
+    }
+    board[len - 1] = '\n';
+    board[len] = '\0';
+    return board;
+}
+
+struct game_ui {
+    int cur_x, cur_y, cur_visible, highlight_1, highlight_2;
+};
+
+static game_ui *new_ui(const game_state *state)
+{
+    game_ui *ui = snew(game_ui);
+    ui->cur_x = ui->cur_y = 0;
+    ui->cur_visible = 0;
+    ui->highlight_1 = ui->highlight_2 = -1;
+    return ui;
+}
+
+static void free_ui(game_ui *ui)
+{
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+    if (!oldstate->completed && newstate->completed)
+        ui->cur_visible = 0;
+}
+
+#define PREFERRED_TILESIZE 32
+#define TILESIZE (ds->tilesize)
+#define BORDER (TILESIZE * 3 / 4)
+#define DOMINO_GUTTER (TILESIZE / 16)
+#define DOMINO_RADIUS (TILESIZE / 8)
+#define DOMINO_COFFSET (DOMINO_GUTTER + DOMINO_RADIUS)
+#define CURSOR_RADIUS (TILESIZE / 4)
+
+#define COORD(x) ( (x) * TILESIZE + BORDER )
+#define FROMCOORD(x) ( ((x) - BORDER + TILESIZE) / TILESIZE - 1 )
+
+struct game_drawstate {
+    int started;
+    int w, h, tilesize;
+    unsigned long *visible;
+};
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    int w = state->w, h = state->h;
+    char buf[80];
+
+    /*
+     * A left-click between two numbers toggles a domino covering
+     * them. A right-click toggles an edge.
+     */
+    if (button == LEFT_BUTTON || button == RIGHT_BUTTON) {
+        int tx = FROMCOORD(x), ty = FROMCOORD(y), t = ty*w+tx;
+        int dx, dy;
+        int d1, d2;
+
+        if (tx < 0 || tx >= w || ty < 0 || ty >= h)
+            return NULL;
+
+        /*
+         * Now we know which square the click was in, decide which
+         * edge of the square it was closest to.
+         */
+        dx = 2 * (x - COORD(tx)) - TILESIZE;
+        dy = 2 * (y - COORD(ty)) - TILESIZE;
+
+        if (abs(dx) > abs(dy) && dx < 0 && tx > 0)
+            d1 = t - 1, d2 = t;        /* clicked in right side of domino */
+        else if (abs(dx) > abs(dy) && dx > 0 && tx+1 < w)
+            d1 = t, d2 = t + 1;        /* clicked in left side of domino */
+        else if (abs(dy) > abs(dx) && dy < 0 && ty > 0)
+            d1 = t - w, d2 = t;        /* clicked in bottom half of domino */
+        else if (abs(dy) > abs(dx) && dy > 0 && ty+1 < h)
+            d1 = t, d2 = t + w;        /* clicked in top half of domino */
+        else
+            return NULL;
+
+        /*
+         * We can't mark an edge next to any domino.
+         */
+        if (button == RIGHT_BUTTON &&
+            (state->grid[d1] != d1 || state->grid[d2] != d2))
+            return NULL;
+
+        ui->cur_visible = 0;
+        sprintf(buf, "%c%d,%d", (int)(button == RIGHT_BUTTON ? 'E' : 'D'), d1, d2);
+        return dupstr(buf);
+    } else if (IS_CURSOR_MOVE(button)) {
+       ui->cur_visible = 1;
+
+        move_cursor(button, &ui->cur_x, &ui->cur_y, 2*w-1, 2*h-1, 0);
+
+       return "";
+    } else if (IS_CURSOR_SELECT(button)) {
+        int d1, d2;
+
+       if (!((ui->cur_x ^ ui->cur_y) & 1))
+           return NULL;               /* must have exactly one dimension odd */
+       d1 = (ui->cur_y / 2) * w + (ui->cur_x / 2);
+       d2 = ((ui->cur_y+1) / 2) * w + ((ui->cur_x+1) / 2);
+
+        /*
+         * We can't mark an edge next to any domino.
+         */
+        if (button == CURSOR_SELECT2 &&
+            (state->grid[d1] != d1 || state->grid[d2] != d2))
+            return NULL;
+
+        sprintf(buf, "%c%d,%d", (int)(button == CURSOR_SELECT2 ? 'E' : 'D'), d1, d2);
+        return dupstr(buf);
+    } else if (isdigit(button)) {
+        int n = state->params.n, num = button - '0';
+        if (num > n) {
+            return NULL;
+        } else if (ui->highlight_1 == num) {
+            ui->highlight_1 = -1;
+        } else if (ui->highlight_2 == num) {
+            ui->highlight_2 = -1;
+        } else if (ui->highlight_1 == -1) {
+            ui->highlight_1 = num;
+        } else if (ui->highlight_2 == -1) {
+            ui->highlight_2 = num;
+        } else {
+            return NULL;
+        }
+        return "";
+    }
+
+    return NULL;
+}
+
+static game_state *execute_move(const game_state *state, const char *move)
+{
+    int n = state->params.n, w = n+2, h = n+1, wh = w*h;
+    int d1, d2, d3, p;
+    game_state *ret = dup_game(state);
+
+    while (*move) {
+        if (move[0] == 'S') {
+            int i;
+
+            ret->cheated = TRUE;
+
+            /*
+             * Clear the existing edges and domino placements. We
+             * expect the S to be followed by other commands.
+             */
+            for (i = 0; i < wh; i++) {
+                ret->grid[i] = i;
+                ret->edges[i] = 0;
+            }
+            move++;
+        } else if (move[0] == 'D' &&
+                   sscanf(move+1, "%d,%d%n", &d1, &d2, &p) == 2 &&
+                   d1 >= 0 && d1 < wh && d2 >= 0 && d2 < wh && d1 < d2) {
+
+            /*
+             * Toggle domino presence between d1 and d2.
+             */
+            if (ret->grid[d1] == d2) {
+                assert(ret->grid[d2] == d1);
+                ret->grid[d1] = d1;
+                ret->grid[d2] = d2;
+            } else {
+                /*
+                 * Erase any dominoes that might overlap the new one.
+                 */
+                d3 = ret->grid[d1];
+                if (d3 != d1)
+                    ret->grid[d3] = d3;
+                d3 = ret->grid[d2];
+                if (d3 != d2)
+                    ret->grid[d3] = d3;
+                /*
+                 * Place the new one.
+                 */
+                ret->grid[d1] = d2;
+                ret->grid[d2] = d1;
+
+                /*
+                 * Destroy any edges lurking around it.
+                 */
+                if (ret->edges[d1] & EDGE_L) {
+                    assert(d1 - 1 >= 0);
+                    ret->edges[d1 - 1] &= ~EDGE_R;
+                }
+                if (ret->edges[d1] & EDGE_R) {
+                    assert(d1 + 1 < wh);
+                    ret->edges[d1 + 1] &= ~EDGE_L;
+                }
+                if (ret->edges[d1] & EDGE_T) {
+                    assert(d1 - w >= 0);
+                    ret->edges[d1 - w] &= ~EDGE_B;
+                }
+                if (ret->edges[d1] & EDGE_B) {
+                    assert(d1 + 1 < wh);
+                    ret->edges[d1 + w] &= ~EDGE_T;
+                }
+                ret->edges[d1] = 0;
+                if (ret->edges[d2] & EDGE_L) {
+                    assert(d2 - 1 >= 0);
+                    ret->edges[d2 - 1] &= ~EDGE_R;
+                }
+                if (ret->edges[d2] & EDGE_R) {
+                    assert(d2 + 1 < wh);
+                    ret->edges[d2 + 1] &= ~EDGE_L;
+                }
+                if (ret->edges[d2] & EDGE_T) {
+                    assert(d2 - w >= 0);
+                    ret->edges[d2 - w] &= ~EDGE_B;
+                }
+                if (ret->edges[d2] & EDGE_B) {
+                    assert(d2 + 1 < wh);
+                    ret->edges[d2 + w] &= ~EDGE_T;
+                }
+                ret->edges[d2] = 0;
+            }
+
+            move += p+1;
+        } else if (move[0] == 'E' &&
+                   sscanf(move+1, "%d,%d%n", &d1, &d2, &p) == 2 &&
+                   d1 >= 0 && d1 < wh && d2 >= 0 && d2 < wh && d1 < d2 &&
+                   ret->grid[d1] == d1 && ret->grid[d2] == d2) {
+
+            /*
+             * Toggle edge presence between d1 and d2.
+             */
+            if (d2 == d1 + 1) {
+                ret->edges[d1] ^= EDGE_R;
+                ret->edges[d2] ^= EDGE_L;
+            } else {
+                ret->edges[d1] ^= EDGE_B;
+                ret->edges[d2] ^= EDGE_T;
+            }
+
+            move += p+1;
+        } else {
+            free_game(ret);
+            return NULL;
+        }
+
+        if (*move) {
+            if (*move != ';') {
+                free_game(ret);
+                return NULL;
+            }
+            move++;
+        }
+    }
+
+    /*
+     * After modifying the grid, check completion.
+     */
+    if (!ret->completed) {
+        int i, ok = 0;
+        unsigned char *used = snewn(TRI(n+1), unsigned char);
+
+        memset(used, 0, TRI(n+1));
+        for (i = 0; i < wh; i++)
+            if (ret->grid[i] > i) {
+                int n1, n2, di;
+
+                n1 = ret->numbers->numbers[i];
+                n2 = ret->numbers->numbers[ret->grid[i]];
+
+                di = DINDEX(n1, n2);
+                assert(di >= 0 && di < TRI(n+1));
+
+                if (!used[di]) {
+                    used[di] = 1;
+                    ok++;
+                }
+            }
+
+        sfree(used);
+        if (ok == DCOUNT(n))
+            ret->completed = TRUE;
+    }
+
+    return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    int n = params->n, w = n+2, h = n+1;
+
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    struct { int tilesize; } ads, *ds = &ads;
+    ads.tilesize = tilesize;
+
+    *x = w * TILESIZE + 2*BORDER;
+    *y = h * TILESIZE + 2*BORDER;
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+
+    frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
+
+    ret[COL_TEXT * 3 + 0] = 0.0F;
+    ret[COL_TEXT * 3 + 1] = 0.0F;
+    ret[COL_TEXT * 3 + 2] = 0.0F;
+
+    ret[COL_DOMINO * 3 + 0] = 0.0F;
+    ret[COL_DOMINO * 3 + 1] = 0.0F;
+    ret[COL_DOMINO * 3 + 2] = 0.0F;
+
+    ret[COL_DOMINOCLASH * 3 + 0] = 0.5F;
+    ret[COL_DOMINOCLASH * 3 + 1] = 0.0F;
+    ret[COL_DOMINOCLASH * 3 + 2] = 0.0F;
+
+    ret[COL_DOMINOTEXT * 3 + 0] = 1.0F;
+    ret[COL_DOMINOTEXT * 3 + 1] = 1.0F;
+    ret[COL_DOMINOTEXT * 3 + 2] = 1.0F;
+
+    ret[COL_EDGE * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 2 / 3;
+    ret[COL_EDGE * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 2 / 3;
+    ret[COL_EDGE * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] * 2 / 3;
+
+    ret[COL_HIGHLIGHT_1 * 3 + 0] = 0.85;
+    ret[COL_HIGHLIGHT_1 * 3 + 1] = 0.20;
+    ret[COL_HIGHLIGHT_1 * 3 + 2] = 0.20;
+
+    ret[COL_HIGHLIGHT_2 * 3 + 0] = 0.30;
+    ret[COL_HIGHLIGHT_2 * 3 + 1] = 0.85;
+    ret[COL_HIGHLIGHT_2 * 3 + 2] = 0.20;
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    struct game_drawstate *ds = snew(struct game_drawstate);
+    int i;
+
+    ds->started = FALSE;
+    ds->w = state->w;
+    ds->h = state->h;
+    ds->visible = snewn(ds->w * ds->h, unsigned long);
+    ds->tilesize = 0;                  /* not decided yet */
+    for (i = 0; i < ds->w * ds->h; i++)
+        ds->visible[i] = 0xFFFF;
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds->visible);
+    sfree(ds);
+}
+
+enum {
+    TYPE_L,
+    TYPE_R,
+    TYPE_T,
+    TYPE_B,
+    TYPE_BLANK,
+    TYPE_MASK = 0x0F
+};
+
+/* These flags must be disjoint with:
+   * the above enum (TYPE_*)    [0x000 -- 0x00F]
+   * EDGE_*                     [0x100 -- 0xF00]
+ * and must fit into an unsigned long (32 bits).
+ */
+#define DF_HIGHLIGHT_1  0x10
+#define DF_HIGHLIGHT_2  0x20
+#define DF_FLASH        0x40
+#define DF_CLASH        0x80
+
+#define DF_CURSOR        0x01000
+#define DF_CURSOR_USEFUL 0x02000
+#define DF_CURSOR_XBASE  0x10000
+#define DF_CURSOR_XMASK  0x30000
+#define DF_CURSOR_YBASE  0x40000
+#define DF_CURSOR_YMASK  0xC0000
+
+#define CEDGE_OFF       (TILESIZE / 8)
+#define IS_EMPTY(s,x,y) ((s)->grid[(y)*(s)->w+(x)] == ((y)*(s)->w+(x)))
+
+static void draw_tile(drawing *dr, game_drawstate *ds, const game_state *state,
+                      int x, int y, int type, int highlight_1, int highlight_2)
+{
+    int w = state->w /*, h = state->h */;
+    int cx = COORD(x), cy = COORD(y);
+    int nc;
+    char str[80];
+    int flags;
+
+    clip(dr, cx, cy, TILESIZE, TILESIZE);
+    draw_rect(dr, cx, cy, TILESIZE, TILESIZE, COL_BACKGROUND);
+
+    flags = type &~ TYPE_MASK;
+    type &= TYPE_MASK;
+
+    if (type != TYPE_BLANK) {
+        int i, bg;
+
+        /*
+         * Draw one end of a domino. This is composed of:
+         * 
+         *  - two filled circles (rounded corners)
+         *  - two rectangles
+         *  - a slight shift in the number
+         */
+
+        if (flags & DF_CLASH)
+            bg = COL_DOMINOCLASH;
+        else
+            bg = COL_DOMINO;
+        nc = COL_DOMINOTEXT;
+
+        if (flags & DF_FLASH) {
+            int tmp = nc;
+            nc = bg;
+            bg = tmp;
+        }
+
+        if (type == TYPE_L || type == TYPE_T)
+            draw_circle(dr, cx+DOMINO_COFFSET, cy+DOMINO_COFFSET,
+                        DOMINO_RADIUS, bg, bg);
+        if (type == TYPE_R || type == TYPE_T)
+            draw_circle(dr, cx+TILESIZE-1-DOMINO_COFFSET, cy+DOMINO_COFFSET,
+                        DOMINO_RADIUS, bg, bg);
+        if (type == TYPE_L || type == TYPE_B)
+            draw_circle(dr, cx+DOMINO_COFFSET, cy+TILESIZE-1-DOMINO_COFFSET,
+                        DOMINO_RADIUS, bg, bg);
+        if (type == TYPE_R || type == TYPE_B)
+            draw_circle(dr, cx+TILESIZE-1-DOMINO_COFFSET,
+                        cy+TILESIZE-1-DOMINO_COFFSET,
+                        DOMINO_RADIUS, bg, bg);
+
+        for (i = 0; i < 2; i++) {
+            int x1, y1, x2, y2;
+
+            x1 = cx + (i ? DOMINO_GUTTER : DOMINO_COFFSET);
+            y1 = cy + (i ? DOMINO_COFFSET : DOMINO_GUTTER);
+            x2 = cx + TILESIZE-1 - (i ? DOMINO_GUTTER : DOMINO_COFFSET);
+            y2 = cy + TILESIZE-1 - (i ? DOMINO_COFFSET : DOMINO_GUTTER);
+            if (type == TYPE_L)
+                x2 = cx + TILESIZE + TILESIZE/16;
+            else if (type == TYPE_R)
+                x1 = cx - TILESIZE/16;
+            else if (type == TYPE_T)
+                y2 = cy + TILESIZE + TILESIZE/16;
+            else if (type == TYPE_B)
+                y1 = cy - TILESIZE/16;
+
+            draw_rect(dr, x1, y1, x2-x1+1, y2-y1+1, bg);
+        }
+    } else {
+        if (flags & EDGE_T)
+            draw_rect(dr, cx+DOMINO_GUTTER, cy,
+                      TILESIZE-2*DOMINO_GUTTER, 1, COL_EDGE);
+        if (flags & EDGE_B)
+            draw_rect(dr, cx+DOMINO_GUTTER, cy+TILESIZE-1,
+                      TILESIZE-2*DOMINO_GUTTER, 1, COL_EDGE);
+        if (flags & EDGE_L)
+            draw_rect(dr, cx, cy+DOMINO_GUTTER,
+                      1, TILESIZE-2*DOMINO_GUTTER, COL_EDGE);
+        if (flags & EDGE_R)
+            draw_rect(dr, cx+TILESIZE-1, cy+DOMINO_GUTTER,
+                      1, TILESIZE-2*DOMINO_GUTTER, COL_EDGE);
+        nc = COL_TEXT;
+    }
+
+    if (flags & DF_CURSOR) {
+       int curx = ((flags & DF_CURSOR_XMASK) / DF_CURSOR_XBASE) & 3;
+       int cury = ((flags & DF_CURSOR_YMASK) / DF_CURSOR_YBASE) & 3;
+       int ox = cx + curx*TILESIZE/2;
+       int oy = cy + cury*TILESIZE/2;
+
+       draw_rect_corners(dr, ox, oy, CURSOR_RADIUS, nc);
+        if (flags & DF_CURSOR_USEFUL)
+           draw_rect_corners(dr, ox, oy, CURSOR_RADIUS+1, nc);
+    }
+
+    if (flags & DF_HIGHLIGHT_1) {
+        nc = COL_HIGHLIGHT_1;
+    } else if (flags & DF_HIGHLIGHT_2) {
+        nc = COL_HIGHLIGHT_2;
+    }
+
+    sprintf(str, "%d", state->numbers->numbers[y*w+x]);
+    draw_text(dr, cx+TILESIZE/2, cy+TILESIZE/2, FONT_VARIABLE, TILESIZE/2,
+              ALIGN_HCENTRE | ALIGN_VCENTRE, nc, str);
+
+    draw_update(dr, cx, cy, TILESIZE, TILESIZE);
+    unclip(dr);
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int n = state->params.n, w = state->w, h = state->h, wh = w*h;
+    int x, y, i;
+    unsigned char *used;
+
+    if (!ds->started) {
+        int pw, ph;
+        game_compute_size(&state->params, TILESIZE, &pw, &ph);
+       draw_rect(dr, 0, 0, pw, ph, COL_BACKGROUND);
+       draw_update(dr, 0, 0, pw, ph);
+       ds->started = TRUE;
+    }
+
+    /*
+     * See how many dominoes of each type there are, so we can
+     * highlight clashes in red.
+     */
+    used = snewn(TRI(n+1), unsigned char);
+    memset(used, 0, TRI(n+1));
+    for (i = 0; i < wh; i++)
+        if (state->grid[i] > i) {
+            int n1, n2, di;
+
+            n1 = state->numbers->numbers[i];
+            n2 = state->numbers->numbers[state->grid[i]];
+
+            di = DINDEX(n1, n2);
+            assert(di >= 0 && di < TRI(n+1));
+
+            if (used[di] < 2)
+                used[di]++;
+        }
+
+    for (y = 0; y < h; y++)
+        for (x = 0; x < w; x++) {
+            int n = y*w+x;
+            int n1, n2, di;
+           unsigned long c;
+
+            if (state->grid[n] == n-1)
+                c = TYPE_R;
+            else if (state->grid[n] == n+1)
+                c = TYPE_L;
+            else if (state->grid[n] == n-w)
+                c = TYPE_B;
+            else if (state->grid[n] == n+w)
+                c = TYPE_T;
+            else
+                c = TYPE_BLANK;
+
+            n1 = state->numbers->numbers[n];
+            if (c != TYPE_BLANK) {
+                n2 = state->numbers->numbers[state->grid[n]];
+                di = DINDEX(n1, n2);
+                if (used[di] > 1)
+                    c |= DF_CLASH;         /* highlight a clash */
+            } else {
+                c |= state->edges[n];
+            }
+
+            if (n1 == ui->highlight_1)
+                c |= DF_HIGHLIGHT_1;
+            if (n1 == ui->highlight_2)
+                c |= DF_HIGHLIGHT_2;
+
+            if (flashtime != 0)
+                c |= DF_FLASH;             /* we're flashing */
+
+            if (ui->cur_visible) {
+               unsigned curx = (unsigned)(ui->cur_x - (2*x-1));
+               unsigned cury = (unsigned)(ui->cur_y - (2*y-1));
+               if (curx < 3 && cury < 3) {
+                   c |= (DF_CURSOR |
+                         (curx * DF_CURSOR_XBASE) |
+                         (cury * DF_CURSOR_YBASE));
+                    if ((ui->cur_x ^ ui->cur_y) & 1)
+                        c |= DF_CURSOR_USEFUL;
+                }
+            }
+
+           if (ds->visible[n] != c) {
+               draw_tile(dr, ds, state, x, y, c,
+                          ui->highlight_1, ui->highlight_2);
+                ds->visible[n] = c;
+           }
+       }
+
+    sfree(used);
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return 0.0F;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    if (!oldstate->completed && newstate->completed &&
+       !oldstate->cheated && !newstate->cheated)
+    {
+        ui->highlight_1 = ui->highlight_2 = -1;
+        return FLASH_TIME;
+    }
+    return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    return state->completed ? +1 : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+    int pw, ph;
+
+    /*
+     * I'll use 6mm squares by default.
+     */
+    game_compute_size(params, 600, &pw, &ph);
+    *x = pw / 100.0F;
+    *y = ph / 100.0F;
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+    int w = state->w, h = state->h;
+    int c, x, y;
+
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    game_drawstate ads, *ds = &ads;
+    game_set_size(dr, ds, NULL, tilesize);
+
+    c = print_mono_colour(dr, 1); assert(c == COL_BACKGROUND);
+    c = print_mono_colour(dr, 0); assert(c == COL_TEXT);
+    c = print_mono_colour(dr, 0); assert(c == COL_DOMINO);
+    c = print_mono_colour(dr, 0); assert(c == COL_DOMINOCLASH);
+    c = print_mono_colour(dr, 1); assert(c == COL_DOMINOTEXT);
+    c = print_mono_colour(dr, 0); assert(c == COL_EDGE);
+
+    for (y = 0; y < h; y++)
+        for (x = 0; x < w; x++) {
+            int n = y*w+x;
+           unsigned long c;
+
+            if (state->grid[n] == n-1)
+                c = TYPE_R;
+            else if (state->grid[n] == n+1)
+                c = TYPE_L;
+            else if (state->grid[n] == n-w)
+                c = TYPE_B;
+            else if (state->grid[n] == n+w)
+                c = TYPE_T;
+            else
+                c = TYPE_BLANK;
+
+           draw_tile(dr, ds, state, x, y, c, -1, -1);
+       }
+}
+
+#ifdef COMBINED
+#define thegame dominosa
+#endif
+
+const struct game thegame = {
+    "Dominosa", "games.dominosa", "dominosa",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    TRUE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILESIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    TRUE, FALSE, game_print_size, game_print,
+    FALSE,                            /* wants_statusbar */
+    FALSE, game_timing_state,
+    0,                                /* flags */
+};
+
+/* vim: set shiftwidth=4 :set textwidth=80: */
+
diff --git a/drawing.c b/drawing.c
new file mode 100644 (file)
index 0000000..7f4a6cf
--- /dev/null
+++ b/drawing.c
@@ -0,0 +1,351 @@
+/*
+ * drawing.c: Intermediary between the drawing interface as
+ * presented to the back end, and that implemented by the front
+ * end.
+ * 
+ * Mostly just looks up calls in a vtable and passes them through
+ * unchanged. However, on the printing side it tracks print colours
+ * so the front end API doesn't have to.
+ * 
+ * FIXME:
+ * 
+ *  - I'd _like_ to do automatic draw_updates, but it's a pain for
+ *    draw_text in particular. I'd have to invent a front end API
+ *    which retrieved the text bounds.
+ *     + that might allow me to do the alignment centrally as well?
+ *       * perhaps not, because PS can't return this information,
+ *         so there would have to be a special case for it.
+ *     + however, that at least doesn't stand in the way of using
+ *      the text bounds for draw_update, because PS doesn't need
+ *      draw_update since it's printing-only. Any _interactive_
+ *      drawing API couldn't get away with refusing to tell you
+ *      what parts of the screen a text draw had covered, because
+ *      you would inevitably need to erase it later on.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <math.h>
+
+#include "puzzles.h"
+
+struct print_colour {
+    int hatch;
+    int hatch_when;                   /* 0=never 1=only-in-b&w 2=always */
+    float r, g, b;
+    float grey;
+};
+
+struct drawing {
+    const drawing_api *api;
+    void *handle;
+    struct print_colour *colours;
+    int ncolours, coloursize;
+    float scale;
+    /* `me' is only used in status_bar(), so print-oriented instances of
+     * this may set it to NULL. */
+    midend *me;
+    char *laststatus;
+};
+
+drawing *drawing_new(const drawing_api *api, midend *me, void *handle)
+{
+    drawing *dr = snew(drawing);
+    dr->api = api;
+    dr->handle = handle;
+    dr->colours = NULL;
+    dr->ncolours = dr->coloursize = 0;
+    dr->scale = 1.0F;
+    dr->me = me;
+    dr->laststatus = NULL;
+    return dr;
+}
+
+void drawing_free(drawing *dr)
+{
+    sfree(dr->laststatus);
+    sfree(dr->colours);
+    sfree(dr);
+}
+
+void draw_text(drawing *dr, int x, int y, int fonttype, int fontsize,
+               int align, int colour, char *text)
+{
+    dr->api->draw_text(dr->handle, x, y, fonttype, fontsize, align,
+                      colour, text);
+}
+
+void draw_rect(drawing *dr, int x, int y, int w, int h, int colour)
+{
+    dr->api->draw_rect(dr->handle, x, y, w, h, colour);
+}
+
+void draw_line(drawing *dr, int x1, int y1, int x2, int y2, int colour)
+{
+    dr->api->draw_line(dr->handle, x1, y1, x2, y2, colour);
+}
+
+void draw_thick_line(drawing *dr, float thickness,
+                    float x1, float y1, float x2, float y2, int colour)
+{
+    if (dr->api->draw_thick_line) {
+       dr->api->draw_thick_line(dr->handle, thickness,
+                                x1, y1, x2, y2, colour);
+    } else {
+       /* We'll fake it up with a filled polygon.  The tweak to the
+        * thickness empirically compensates for rounding errors, because
+        * polygon rendering uses integer coordinates.
+        */
+       float len = sqrt((x2 - x1)*(x2 - x1) + (y2 - y1)*(y2 - y1));
+       float tvhatx = (x2 - x1)/len * (thickness/2 - 0.2);
+       float tvhaty = (y2 - y1)/len * (thickness/2 - 0.2);
+       int p[8];
+
+       p[0] = x1 - tvhaty;
+       p[1] = y1 + tvhatx;
+       p[2] = x2 - tvhaty;
+       p[3] = y2 + tvhatx;
+       p[4] = x2 + tvhaty;
+       p[5] = y2 - tvhatx;
+       p[6] = x1 + tvhaty;
+       p[7] = y1 - tvhatx;
+       dr->api->draw_polygon(dr->handle, p, 4, colour, colour);
+    }
+}
+
+void draw_polygon(drawing *dr, int *coords, int npoints,
+                  int fillcolour, int outlinecolour)
+{
+    dr->api->draw_polygon(dr->handle, coords, npoints, fillcolour,
+                         outlinecolour);
+}
+
+void draw_circle(drawing *dr, int cx, int cy, int radius,
+                 int fillcolour, int outlinecolour)
+{
+    dr->api->draw_circle(dr->handle, cx, cy, radius, fillcolour,
+                        outlinecolour);
+}
+
+void draw_update(drawing *dr, int x, int y, int w, int h)
+{
+    if (dr->api->draw_update)
+       dr->api->draw_update(dr->handle, x, y, w, h);
+}
+
+void clip(drawing *dr, int x, int y, int w, int h)
+{
+    dr->api->clip(dr->handle, x, y, w, h);
+}
+
+void unclip(drawing *dr)
+{
+    dr->api->unclip(dr->handle);
+}
+
+void start_draw(drawing *dr)
+{
+    dr->api->start_draw(dr->handle);
+}
+
+void end_draw(drawing *dr)
+{
+    dr->api->end_draw(dr->handle);
+}
+
+char *text_fallback(drawing *dr, const char *const *strings, int nstrings)
+{
+    int i;
+
+    /*
+     * If the drawing implementation provides one of these, use it.
+     */
+    if (dr && dr->api->text_fallback)
+       return dr->api->text_fallback(dr->handle, strings, nstrings);
+
+    /*
+     * Otherwise, do the simple thing and just pick the first string
+     * that fits in plain ASCII. It will then need no translation
+     * out of UTF-8.
+     */
+    for (i = 0; i < nstrings; i++) {
+       const char *p;
+
+       for (p = strings[i]; *p; p++)
+           if (*p & 0x80)
+               break;
+       if (!*p)
+           return dupstr(strings[i]);
+    }
+
+    /*
+     * The caller was responsible for making sure _some_ string in
+     * the list was in plain ASCII.
+     */
+    assert(!"Should never get here");
+    return NULL;                      /* placate optimiser */
+}
+
+void status_bar(drawing *dr, char *text)
+{
+    char *rewritten;
+
+    if (!dr->api->status_bar)
+       return;
+
+    assert(dr->me);
+
+    rewritten = midend_rewrite_statusbar(dr->me, text);
+    if (!dr->laststatus || strcmp(rewritten, dr->laststatus)) {
+       dr->api->status_bar(dr->handle, rewritten);
+       sfree(dr->laststatus);
+       dr->laststatus = rewritten;
+    } else {
+       sfree(rewritten);
+    }
+}
+
+blitter *blitter_new(drawing *dr, int w, int h)
+{
+    return dr->api->blitter_new(dr->handle, w, h);
+}
+
+void blitter_free(drawing *dr, blitter *bl)
+{
+    dr->api->blitter_free(dr->handle, bl);
+}
+
+void blitter_save(drawing *dr, blitter *bl, int x, int y)
+{
+    dr->api->blitter_save(dr->handle, bl, x, y);
+}
+
+void blitter_load(drawing *dr, blitter *bl, int x, int y)
+{
+    dr->api->blitter_load(dr->handle, bl, x, y);
+}
+
+void print_begin_doc(drawing *dr, int pages)
+{
+    dr->api->begin_doc(dr->handle, pages);
+}
+
+void print_begin_page(drawing *dr, int number)
+{
+    dr->api->begin_page(dr->handle, number);
+}
+
+void print_begin_puzzle(drawing *dr, float xm, float xc,
+                       float ym, float yc, int pw, int ph, float wmm,
+                       float scale)
+{
+    dr->scale = scale;
+    dr->ncolours = 0;
+    dr->api->begin_puzzle(dr->handle, xm, xc, ym, yc, pw, ph, wmm);
+}
+
+void print_end_puzzle(drawing *dr)
+{
+    dr->api->end_puzzle(dr->handle);
+    dr->scale = 1.0F;
+}
+
+void print_end_page(drawing *dr, int number)
+{
+    dr->api->end_page(dr->handle, number);
+}
+
+void print_end_doc(drawing *dr)
+{
+    dr->api->end_doc(dr->handle);
+}
+
+void print_get_colour(drawing *dr, int colour, int printing_in_colour,
+                     int *hatch, float *r, float *g, float *b)
+{
+    assert(colour >= 0 && colour < dr->ncolours);
+    if (dr->colours[colour].hatch_when == 2 ||
+       (dr->colours[colour].hatch_when == 1 && !printing_in_colour)) {
+       *hatch = dr->colours[colour].hatch;
+    } else {
+       *hatch = -1;
+       if (printing_in_colour) {
+           *r = dr->colours[colour].r;
+           *g = dr->colours[colour].g;
+           *b = dr->colours[colour].b;
+       } else {
+           *r = *g = *b = dr->colours[colour].grey;
+       }
+    }
+}
+
+static int print_generic_colour(drawing *dr, float r, float g, float b,
+                               float grey, int hatch, int hatch_when)
+{
+    if (dr->ncolours >= dr->coloursize) {
+       dr->coloursize = dr->ncolours + 16;
+       dr->colours = sresize(dr->colours, dr->coloursize,
+                             struct print_colour);
+    }
+    dr->colours[dr->ncolours].hatch = hatch;
+    dr->colours[dr->ncolours].hatch_when = hatch_when;
+    dr->colours[dr->ncolours].r = r;
+    dr->colours[dr->ncolours].g = g;
+    dr->colours[dr->ncolours].b = b;
+    dr->colours[dr->ncolours].grey = grey;
+    return dr->ncolours++;
+}
+
+int print_mono_colour(drawing *dr, int grey)
+{
+    return print_generic_colour(dr, grey, grey, grey, grey, -1, 0);
+}
+
+int print_grey_colour(drawing *dr, float grey)
+{
+    return print_generic_colour(dr, grey, grey, grey, grey, -1, 0);
+}
+
+int print_hatched_colour(drawing *dr, int hatch)
+{
+    return print_generic_colour(dr, 0, 0, 0, 0, hatch, 2);
+}
+
+int print_rgb_mono_colour(drawing *dr, float r, float g, float b, int grey)
+{
+    return print_generic_colour(dr, r, g, b, grey, -1, 0);
+}
+
+int print_rgb_grey_colour(drawing *dr, float r, float g, float b, float grey)
+{
+    return print_generic_colour(dr, r, g, b, grey, -1, 0);
+}
+
+int print_rgb_hatched_colour(drawing *dr, float r, float g, float b, int hatch)
+{
+    return print_generic_colour(dr, r, g, b, 0, hatch, 1);
+}
+
+void print_line_width(drawing *dr, int width)
+{
+    /*
+     * I don't think it's entirely sensible to have line widths be
+     * entirely relative to the puzzle size; there is a point
+     * beyond which lines are just _stupidly_ thick. On the other
+     * hand, absolute line widths aren't particularly nice either
+     * because they start to feel a bit feeble at really large
+     * scales.
+     * 
+     * My experimental answer is to scale line widths as the
+     * _square root_ of the main puzzle scale. Double the puzzle
+     * size, and the line width multiplies by 1.4.
+     */
+    dr->api->line_width(dr->handle, (float)sqrt(dr->scale) * width);
+}
+
+void print_line_dotted(drawing *dr, int dotted)
+{
+    dr->api->line_dotted(dr->handle, dotted);
+}
diff --git a/dsf.c b/dsf.c
new file mode 100644 (file)
index 0000000..aa22392
--- /dev/null
+++ b/dsf.c
@@ -0,0 +1,192 @@
+/*
+ * dsf.c: some functions to handle a disjoint set forest,
+ * which is a data structure useful in any solver which has to
+ * worry about avoiding closed loops.
+ */
+
+#include <assert.h>
+#include <string.h>
+
+#include "puzzles.h"
+
+/*void print_dsf(int *dsf, int size)
+{
+    int *printed_elements = snewn(size, int);
+    int *equal_elements = snewn(size, int);
+    int *inverse_elements = snewn(size, int);
+    int printed_count = 0, equal_count, inverse_count;
+    int i, n, inverse;
+
+    memset(printed_elements, -1, sizeof(int) * size);
+
+    while (1) {
+        equal_count = 0;
+        inverse_count = 0;
+        for (i = 0; i < size; ++i) {
+            if (!memchr(printed_elements, i, sizeof(int) * size)) 
+                break;
+        }
+        if (i == size)
+            goto done;
+
+        i = dsf_canonify(dsf, i);
+
+        for (n = 0; n < size; ++n) {
+            if (edsf_canonify(dsf, n, &inverse) == i) {
+               if (inverse)
+                   inverse_elements[inverse_count++] = n;
+               else
+                   equal_elements[equal_count++] = n;
+            }
+        }
+        
+        for (n = 0; n < equal_count; ++n) {
+            fprintf(stderr, "%d ", equal_elements[n]);
+            printed_elements[printed_count++] = equal_elements[n];
+        }
+        if (inverse_count) {
+            fprintf(stderr, "!= ");
+            for (n = 0; n < inverse_count; ++n) {
+                fprintf(stderr, "%d ", inverse_elements[n]);
+                printed_elements[printed_count++] = inverse_elements[n];
+            }
+        }
+        fprintf(stderr, "\n");
+    }
+done:
+
+    sfree(printed_elements);
+    sfree(equal_elements);
+    sfree(inverse_elements);
+}*/
+
+void dsf_init(int *dsf, int size)
+{
+    int i;
+
+    for (i = 0; i < size; i++) dsf[i] = 6;
+    /* Bottom bit of each element of this array stores whether that
+     * element is opposite to its parent, which starts off as
+     * false. Second bit of each element stores whether that element
+     * is the root of its tree or not.  If it's not the root, the
+     * remaining 30 bits are the parent, otherwise the remaining 30
+     * bits are the number of elements in the tree.  */
+}
+
+int *snew_dsf(int size)
+{
+    int *ret;
+    
+    ret = snewn(size, int);
+    dsf_init(ret, size);
+
+    /*print_dsf(ret, size); */
+
+    return ret;
+}
+
+int dsf_canonify(int *dsf, int index)
+{
+    return edsf_canonify(dsf, index, NULL);
+}
+
+void dsf_merge(int *dsf, int v1, int v2)
+{
+    edsf_merge(dsf, v1, v2, FALSE);
+}
+
+int dsf_size(int *dsf, int index) {
+    return dsf[dsf_canonify(dsf, index)] >> 2;
+}
+
+int edsf_canonify(int *dsf, int index, int *inverse_return)
+{
+    int start_index = index, canonical_index;
+    int inverse = 0;
+
+/*    fprintf(stderr, "dsf = %p\n", dsf); */
+/*    fprintf(stderr, "Canonify %2d\n", index); */
+
+    assert(index >= 0);
+
+    /* Find the index of the canonical element of the 'equivalence class' of
+     * which start_index is a member, and figure out whether start_index is the
+     * same as or inverse to that. */
+    while ((dsf[index] & 2) == 0) {
+        inverse ^= (dsf[index] & 1);
+       index = dsf[index] >> 2;
+/*        fprintf(stderr, "index = %2d, ", index); */
+/*        fprintf(stderr, "inverse = %d\n", inverse); */
+    }
+    canonical_index = index;
+    
+    if (inverse_return)
+        *inverse_return = inverse;
+    
+    /* Update every member of this 'equivalence class' to point directly at the
+     * canonical member. */
+    index = start_index;
+    while (index != canonical_index) {
+       int nextindex = dsf[index] >> 2;
+        int nextinverse = inverse ^ (dsf[index] & 1);
+       dsf[index] = (canonical_index << 2) | inverse;
+        inverse = nextinverse;
+       index = nextindex;
+    }
+
+    assert(inverse == 0);
+
+/*    fprintf(stderr, "Return %2d\n", index); */
+    
+    return index;
+}
+
+void edsf_merge(int *dsf, int v1, int v2, int inverse)
+{
+    int i1, i2;
+
+/*    fprintf(stderr, "dsf = %p\n", dsf); */
+/*    fprintf(stderr, "Merge [%2d,%2d], %d\n", v1, v2, inverse); */
+    
+    v1 = edsf_canonify(dsf, v1, &i1);
+    assert(dsf[v1] & 2);
+    inverse ^= i1;
+    v2 = edsf_canonify(dsf, v2, &i2);
+    assert(dsf[v2] & 2);
+    inverse ^= i2;
+
+/*    fprintf(stderr, "Doing [%2d,%2d], %d\n", v1, v2, inverse); */
+
+    if (v1 == v2)
+        assert(!inverse);
+    else {
+       assert(inverse == 0 || inverse == 1);
+       /*
+        * We always make the smaller of v1 and v2 the new canonical
+        * element. This ensures that the canonical element of any
+        * class in this structure is always the first element in
+        * it. 'Keen' depends critically on this property.
+        *
+        * (Jonas Koelker previously had this code choosing which
+        * way round to connect the trees by examining the sizes of
+        * the classes being merged, so that the root of the
+        * larger-sized class became the new root. This gives better
+        * asymptotic performance, but I've changed it to do it this
+        * way because I like having a deterministic canonical
+        * element.)
+        */
+       if (v1 > v2) {
+           int v3 = v1;
+           v1 = v2;
+           v2 = v3;
+       }
+       dsf[v1] += (dsf[v2] >> 2) << 2;
+       dsf[v2] = (v1 << 2) | !!inverse;
+    }
+    
+    v2 = edsf_canonify(dsf, v2, &i2);
+    assert(v2 == v1);
+    assert(i2 == inverse);
+
+/*    fprintf(stderr, "dsf[%2d] = %2d\n", v2, dsf[v2]); */
+}
diff --git a/emcc.c b/emcc.c
new file mode 100644 (file)
index 0000000..c1b1f7a
--- /dev/null
+++ b/emcc.c
@@ -0,0 +1,867 @@
+/*
+ * emcc.c: the C component of an Emscripten-based web/Javascript front
+ * end for Puzzles.
+ *
+ * The Javascript parts of this system live in emcclib.js and
+ * emccpre.js. It also depends on being run in the context of a web
+ * page containing an appropriate collection of bits and pieces (a
+ * canvas, some buttons and links etc), which is generated for each
+ * puzzle by the script html/jspage.pl.
+ */
+
+/*
+ * Further thoughts on possible enhancements:
+ *
+ *  - I think it might be feasible to have these JS puzzles permit
+ *    loading and saving games in disk files. Saving would be done by
+ *    constructing a data: URI encapsulating the save file, and then
+ *    telling the browser to visit that URI with the effect that it
+ *    would naturally pop up a 'where would you like to save this'
+ *    dialog box. Loading, more or less similarly, might be feasible
+ *    by using the DOM File API to ask the user to select a file and
+ *    permit us to see its contents.
+ *
+ *  - I should think about whether these webified puzzles can support
+ *    touchscreen-based tablet browsers (assuming there are any that
+ *    can cope with the reasonably modern JS and run it fast enough to
+ *    be worthwhile).
+ *
+ *  - think about making use of localStorage. It might be useful to
+ *    let the user save games into there as an alternative to disk
+ *    files - disk files are all very well for getting the save right
+ *    out of your browser to (e.g.) email to me as a bug report, but
+ *    for just resuming a game you were in the middle of, you'd
+ *    probably rather have a nice simple 'quick save' and 'quick load'
+ *    button pair. Also, that might be a useful place to store
+ *    preferences, if I ever get round to writing a preferences UI.
+ *
+ *  - some CSS to make the button bar and configuration dialogs a
+ *    little less ugly would probably not go amiss.
+ *
+ *  - this is a downright silly idea, but it does occur to me that if
+ *    I were to write a PDF output driver for the Puzzles printing
+ *    API, then I might be able to implement a sort of 'printing'
+ *    feature in this front end, using data: URIs again. (Ask the user
+ *    exactly what they want printed, then construct an appropriate
+ *    PDF and embed it in a gigantic data: URI. Then they can print
+ *    that using whatever they normally use to print PDFs!)
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "puzzles.h"
+
+/*
+ * Extern references to Javascript functions provided in emcclib.js.
+ */
+extern void js_debug(const char *);
+extern void js_error_box(const char *message);
+extern void js_remove_type_dropdown(void);
+extern void js_remove_solve_button(void);
+extern void js_add_preset(const char *name);
+extern int js_get_selected_preset(void);
+extern void js_select_preset(int n);
+extern void js_get_date_64(unsigned *p);
+extern void js_update_permalinks(const char *desc, const char *seed);
+extern void js_enable_undo_redo(int undo, int redo);
+extern void js_activate_timer();
+extern void js_deactivate_timer();
+extern void js_canvas_start_draw(void);
+extern void js_canvas_draw_update(int x, int y, int w, int h);
+extern void js_canvas_end_draw(void);
+extern void js_canvas_draw_rect(int x, int y, int w, int h,
+                                const char *colour);
+extern void js_canvas_clip_rect(int x, int y, int w, int h);
+extern void js_canvas_unclip(void);
+extern void js_canvas_draw_line(float x1, float y1, float x2, float y2,
+                                int width, const char *colour);
+extern void js_canvas_draw_poly(int *points, int npoints,
+                                const char *fillcolour,
+                                const char *outlinecolour);
+extern void js_canvas_draw_circle(int x, int y, int r,
+                                  const char *fillcolour,
+                                  const char *outlinecolour);
+extern int js_canvas_find_font_midpoint(int height, const char *fontptr);
+extern void js_canvas_draw_text(int x, int y, int halign,
+                                const char *colptr, const char *fontptr,
+                                const char *text);
+extern int js_canvas_new_blitter(int w, int h);
+extern void js_canvas_free_blitter(int id);
+extern void js_canvas_copy_to_blitter(int id, int x, int y, int w, int h);
+extern void js_canvas_copy_from_blitter(int id, int x, int y, int w, int h);
+extern void js_canvas_make_statusbar(void);
+extern void js_canvas_set_statusbar(const char *text);
+extern void js_canvas_set_size(int w, int h);
+
+extern void js_dialog_init(const char *title);
+extern void js_dialog_string(int i, const char *title, const char *initvalue);
+extern void js_dialog_choices(int i, const char *title, const char *choicelist,
+                              int initvalue);
+extern void js_dialog_boolean(int i, const char *title, int initvalue);
+extern void js_dialog_launch(void);
+extern void js_dialog_cleanup(void);
+extern void js_focus_canvas(void);
+
+/*
+ * Call JS to get the date, and use that to initialise our random
+ * number generator to invent the first game seed.
+ */
+void get_random_seed(void **randseed, int *randseedsize)
+{
+    unsigned *ret = snewn(2, unsigned);
+    js_get_date_64(ret);
+    *randseed = ret;
+    *randseedsize = 2*sizeof(unsigned);
+}
+
+/*
+ * Fatal error, called in cases of complete despair such as when
+ * malloc() has returned NULL.
+ */
+void fatal(char *fmt, ...)
+{
+    char buf[512];
+    va_list ap;
+
+    strcpy(buf, "puzzle fatal error: ");
+
+    va_start(ap, fmt);
+    vsnprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), fmt, ap);
+    va_end(ap);
+
+    js_error_box(buf);
+}
+
+void debug_printf(char *fmt, ...)
+{
+    char buf[512];
+    va_list ap;
+    va_start(ap, fmt);
+    vsnprintf(buf, sizeof(buf), fmt, ap);
+    va_end(ap);
+    js_debug(buf);
+}
+
+/*
+ * Helper function that makes it easy to test strings that might be
+ * NULL.
+ */
+int strnullcmp(const char *a, const char *b)
+{
+    if (a == NULL || b == NULL)
+        return a != NULL ? +1 : b != NULL ? -1 : 0;
+    return strcmp(a, b);
+}
+
+/*
+ * HTMLish names for the colours allocated by the puzzle.
+ */
+char **colour_strings;
+int ncolours;
+
+/*
+ * The global midend object.
+ */
+midend *me;
+
+/* ----------------------------------------------------------------------
+ * Timing functions.
+ */
+int timer_active = FALSE;
+void deactivate_timer(frontend *fe)
+{
+    js_deactivate_timer();
+    timer_active = FALSE;
+}
+void activate_timer(frontend *fe)
+{
+    if (!timer_active) {
+        js_activate_timer();
+        timer_active = TRUE;
+    }
+}
+void timer_callback(double tplus)
+{
+    if (timer_active)
+        midend_timer(me, tplus);
+}
+
+/* ----------------------------------------------------------------------
+ * Helper functions to resize the canvas, and variables to remember
+ * its size for other functions (e.g. trimming blitter rectangles).
+ */
+static int canvas_w, canvas_h;
+
+/* Called when we resize as a result of changing puzzle settings */
+static void resize(void)
+{
+    int w, h;
+    w = h = INT_MAX;
+    midend_size(me, &w, &h, FALSE);
+    js_canvas_set_size(w, h);
+    canvas_w = w;
+    canvas_h = h;
+}
+
+/* Called from JS when the user uses the resize handle */
+void resize_puzzle(int w, int h)
+{
+    midend_size(me, &w, &h, TRUE);
+    if (canvas_w != w || canvas_h != h) { 
+        js_canvas_set_size(w, h);
+        canvas_w = w;
+        canvas_h = h;
+        midend_force_redraw(me);
+    }
+}
+
+/* Called from JS when the user uses the restore button */
+void restore_puzzle_size(int w, int h)
+{
+    midend_reset_tilesize(me);
+    resize();
+    midend_force_redraw(me);
+}
+
+/*
+ * HTML doesn't give us a default frontend colour of its own, so we
+ * just make up a lightish grey ourselves.
+ */
+void frontend_default_colour(frontend *fe, float *output)
+{
+    output[0] = output[1] = output[2] = 0.9F;
+}
+
+/*
+ * Helper function called from all over the place to ensure the undo
+ * and redo buttons get properly enabled and disabled after every move
+ * or undo or new-game event.
+ */
+static void update_undo_redo(void)
+{
+    js_enable_undo_redo(midend_can_undo(me), midend_can_redo(me));
+}
+
+/*
+ * Mouse event handlers called from JS.
+ */
+void mousedown(int x, int y, int button)
+{
+    button = (button == 0 ? LEFT_BUTTON :
+              button == 1 ? MIDDLE_BUTTON : RIGHT_BUTTON);
+    midend_process_key(me, x, y, button);
+    update_undo_redo();
+}
+
+void mouseup(int x, int y, int button)
+{
+    button = (button == 0 ? LEFT_RELEASE :
+              button == 1 ? MIDDLE_RELEASE : RIGHT_RELEASE);
+    midend_process_key(me, x, y, button);
+    update_undo_redo();
+}
+
+void mousemove(int x, int y, int buttons)
+{
+    int button = (buttons & 2 ? MIDDLE_DRAG :
+                  buttons & 4 ? RIGHT_DRAG : LEFT_DRAG);
+    midend_process_key(me, x, y, button);
+    update_undo_redo();
+}
+
+/*
+ * Keyboard handler called from JS.
+ */
+void key(int keycode, int charcode, const char *key, const char *chr,
+         int shift, int ctrl)
+{
+    int keyevent = -1;
+
+    if (!strnullcmp(key, "Backspace") || !strnullcmp(key, "Del") ||
+        keycode == 8 || keycode == 46) {
+        keyevent = 127;                /* Backspace / Delete */
+    } else if (!strnullcmp(key, "Enter") || keycode == 13) {
+        keyevent = 13;             /* return */
+    } else if (!strnullcmp(key, "Left") || keycode == 37) {
+        keyevent = CURSOR_LEFT;
+    } else if (!strnullcmp(key, "Up") || keycode == 38) {
+        keyevent = CURSOR_UP;
+    } else if (!strnullcmp(key, "Right") || keycode == 39) {
+        keyevent = CURSOR_RIGHT;
+    } else if (!strnullcmp(key, "Down") || keycode == 40) {
+        keyevent = CURSOR_DOWN;
+    } else if (!strnullcmp(key, "End") || keycode == 35) {
+        /*
+         * We interpret Home, End, PgUp and PgDn as numeric keypad
+         * controls regardless of whether they're the ones on the
+         * numeric keypad (since we can't tell). The effect of
+         * this should only be that the non-numeric-pad versions
+         * of those keys generate directions in 8-way movement
+         * puzzles like Cube and Inertia.
+         */
+        keyevent = MOD_NUM_KEYPAD | '1';
+    } else if (!strnullcmp(key, "PageDown") || keycode==34) {
+        keyevent = MOD_NUM_KEYPAD | '3';
+    } else if (!strnullcmp(key, "Home") || keycode==36) {
+        keyevent = MOD_NUM_KEYPAD | '7';
+    } else if (!strnullcmp(key, "PageUp") || keycode==33) {
+        keyevent = MOD_NUM_KEYPAD | '9';
+    } else if (chr && chr[0] && !chr[1]) {
+        keyevent = chr[0] & 0xFF;
+    } else if (keycode >= 96 && keycode < 106) {
+        keyevent = MOD_NUM_KEYPAD | ('0' + keycode - 96);
+    } else if (keycode >= 65 && keycode <= 90) {
+        keyevent = keycode + (shift ? 0 : 32);
+    } else if (keycode >= 48 && keycode <= 57) {
+        keyevent = keycode;
+    } else if (keycode == 32) {        /* space / CURSOR_SELECT2 */
+        keyevent = keycode;
+    }
+
+    if (keyevent >= 0) {
+        if (shift && keyevent >= 0x100)
+            keyevent |= MOD_SHFT;
+
+        if (ctrl) {
+            if (keyevent >= 0x100)
+                keyevent |= MOD_CTRL;
+            else
+                keyevent &= 0x1F;
+        }
+
+        midend_process_key(me, 0, 0, keyevent);
+        update_undo_redo();
+    }
+}
+
+/*
+ * Helper function called from several places to update the permalinks
+ * whenever a new game is created.
+ */
+static void update_permalinks(void)
+{
+    char *desc, *seed;
+    desc = midend_get_game_id(me);
+    seed = midend_get_random_seed(me);
+    js_update_permalinks(desc, seed);
+    sfree(desc);
+    sfree(seed);
+}
+
+/*
+ * Callback from the midend when the game ids change, so we can update
+ * the permalinks.
+ */
+static void ids_changed(void *ignored)
+{
+    update_permalinks();
+}
+
+/* ----------------------------------------------------------------------
+ * Implementation of the drawing API by calling Javascript canvas
+ * drawing functions. (Well, half of it; the other half is on the JS
+ * side.)
+ */
+static void js_start_draw(void *handle)
+{
+    js_canvas_start_draw();
+}
+
+static void js_clip(void *handle, int x, int y, int w, int h)
+{
+    js_canvas_clip_rect(x, y, w, h);
+}
+
+static void js_unclip(void *handle)
+{
+    js_canvas_unclip();
+}
+
+static void js_draw_text(void *handle, int x, int y, int fonttype,
+                         int fontsize, int align, int colour, char *text)
+{
+    char fontstyle[80];
+    int halign;
+
+    sprintf(fontstyle, "%dpx %s", fontsize,
+            fonttype == FONT_FIXED ? "monospace" : "sans-serif");
+
+    if (align & ALIGN_VCENTRE)
+       y += js_canvas_find_font_midpoint(fontsize, fontstyle);
+
+    if (align & ALIGN_HCENTRE)
+       halign = 1;
+    else if (align & ALIGN_HRIGHT)
+        halign = 2;
+    else
+        halign = 0;
+
+    js_canvas_draw_text(x, y, halign, colour_strings[colour], fontstyle, text);
+}
+
+static void js_draw_rect(void *handle, int x, int y, int w, int h, int colour)
+{
+    js_canvas_draw_rect(x, y, w, h, colour_strings[colour]);
+}
+
+static void js_draw_line(void *handle, int x1, int y1, int x2, int y2,
+                         int colour)
+{
+    js_canvas_draw_line(x1, y1, x2, y2, 1, colour_strings[colour]);
+}
+
+static void js_draw_thick_line(void *handle, float thickness,
+                               float x1, float y1, float x2, float y2,
+                               int colour)
+{
+    js_canvas_draw_line(x1, y1, x2, y2, thickness, colour_strings[colour]);
+}
+
+static void js_draw_poly(void *handle, int *coords, int npoints,
+                         int fillcolour, int outlinecolour)
+{
+    js_canvas_draw_poly(coords, npoints,
+                        fillcolour >= 0 ? colour_strings[fillcolour] : NULL,
+                        colour_strings[outlinecolour]);
+}
+
+static void js_draw_circle(void *handle, int cx, int cy, int radius,
+                           int fillcolour, int outlinecolour)
+{
+    js_canvas_draw_circle(cx, cy, radius,
+                          fillcolour >= 0 ? colour_strings[fillcolour] : NULL,
+                          colour_strings[outlinecolour]);
+}
+
+struct blitter {
+    int id;                            /* allocated on the js side */
+    int w, h;                          /* easier to retain here */
+};
+
+static blitter *js_blitter_new(void *handle, int w, int h)
+{
+    blitter *bl = snew(blitter);
+    bl->w = w;
+    bl->h = h;
+    bl->id = js_canvas_new_blitter(w, h);
+    return bl;
+}
+
+static void js_blitter_free(void *handle, blitter *bl)
+{
+    js_canvas_free_blitter(bl->id);
+    sfree(bl);
+}
+
+static void trim_rect(int *x, int *y, int *w, int *h)
+{
+    int x0, x1, y0, y1;
+
+    /*
+     * Reduce the size of the copied rectangle to stop it going
+     * outside the bounds of the canvas.
+     */
+
+    /* Transform from x,y,w,h form into coordinates of all edges */
+    x0 = *x;
+    y0 = *y;
+    x1 = *x + *w;
+    y1 = *y + *h;
+
+    /* Clip each coordinate at both extremes of the canvas */
+    x0 = (x0 < 0 ? 0 : x0 > canvas_w ? canvas_w : x0);
+    x1 = (x1 < 0 ? 0 : x1 > canvas_w ? canvas_w : x1);
+    y0 = (y0 < 0 ? 0 : y0 > canvas_h ? canvas_h : y0);
+    y1 = (y1 < 0 ? 0 : y1 > canvas_h ? canvas_h : y1); 
+
+    /* Transform back into x,y,w,h to return */
+    *x = x0;
+    *y = y0;
+    *w = x1 - x0;
+    *h = y1 - y0;
+}
+
+static void js_blitter_save(void *handle, blitter *bl, int x, int y)
+{
+    int w = bl->w, h = bl->h;
+    trim_rect(&x, &y, &w, &h);
+    if (w > 0 && h > 0)
+        js_canvas_copy_to_blitter(bl->id, x, y, w, h);
+}
+
+static void js_blitter_load(void *handle, blitter *bl, int x, int y)
+{
+    int w = bl->w, h = bl->h;
+    trim_rect(&x, &y, &w, &h);
+    if (w > 0 && h > 0)
+        js_canvas_copy_from_blitter(bl->id, x, y, w, h);
+}
+
+static void js_draw_update(void *handle, int x, int y, int w, int h)
+{
+    trim_rect(&x, &y, &w, &h);
+    if (w > 0 && h > 0)
+        js_canvas_draw_update(x, y, w, h);
+}
+
+static void js_end_draw(void *handle)
+{
+    js_canvas_end_draw();
+}
+
+static void js_status_bar(void *handle, char *text)
+{
+    js_canvas_set_statusbar(text);
+}
+
+static char *js_text_fallback(void *handle, const char *const *strings,
+                              int nstrings)
+{
+    return dupstr(strings[0]); /* Emscripten has no trouble with UTF-8 */
+}
+
+const struct drawing_api js_drawing = {
+    js_draw_text,
+    js_draw_rect,
+    js_draw_line,
+    js_draw_poly,
+    js_draw_circle,
+    js_draw_update,
+    js_clip,
+    js_unclip,
+    js_start_draw,
+    js_end_draw,
+    js_status_bar,
+    js_blitter_new,
+    js_blitter_free,
+    js_blitter_save,
+    js_blitter_load,
+    NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */
+    NULL, NULL,                               /* line_width, line_dotted */
+    js_text_fallback,
+    js_draw_thick_line,
+};
+
+/* ----------------------------------------------------------------------
+ * Presets and game-configuration dialog support.
+ */
+static game_params **presets;
+static int npresets;
+int have_presets_dropdown;
+
+void select_appropriate_preset(void)
+{
+    if (have_presets_dropdown) {
+        int preset = midend_which_preset(me);
+        js_select_preset(preset < 0 ? -1 : preset);
+    }
+}
+
+static config_item *cfg = NULL;
+static int cfg_which;
+
+/*
+ * Set up a dialog box. This is pretty easy on the C side; most of the
+ * work is done in JS.
+ */
+static void cfg_start(int which)
+{
+    char *title;
+    int i;
+
+    cfg = midend_get_config(me, which, &title);
+    cfg_which = which;
+
+    js_dialog_init(title);
+    sfree(title);
+
+    for (i = 0; cfg[i].type != C_END; i++) {
+       switch (cfg[i].type) {
+         case C_STRING:
+            js_dialog_string(i, cfg[i].name, cfg[i].sval);
+           break;
+         case C_BOOLEAN:
+            js_dialog_boolean(i, cfg[i].name, cfg[i].ival);
+           break;
+         case C_CHOICES:
+            js_dialog_choices(i, cfg[i].name, cfg[i].sval, cfg[i].ival);
+           break;
+       }
+    }
+
+    js_dialog_launch();
+}
+
+/*
+ * Callbacks from JS when the OK button is clicked, to return the
+ * final state of each control.
+ */
+void dlg_return_sval(int index, const char *val)
+{
+    sfree(cfg[index].sval);
+    cfg[index].sval = dupstr(val);
+}
+void dlg_return_ival(int index, int val)
+{
+    cfg[index].ival = val;
+}
+
+/*
+ * Called when the user clicks OK or Cancel. use_results will be TRUE
+ * or FALSE respectively, in those cases. We terminate the dialog box,
+ * unless the user selected an invalid combination of parameters.
+ */
+static void cfg_end(int use_results)
+{
+    if (use_results) {
+        /*
+         * User hit OK.
+         */
+        char *err = midend_set_config(me, cfg_which, cfg);
+
+        if (err) {
+            /*
+             * The settings were unacceptable, so leave the config box
+             * open for the user to adjust them and try again.
+             */
+            js_error_box(err);
+        } else {
+            /*
+             * New settings are fine; start a new game and close the
+             * dialog.
+             */
+            select_appropriate_preset();
+            midend_new_game(me);
+            resize();
+            midend_redraw(me);
+            free_cfg(cfg);
+            js_dialog_cleanup();
+        }
+    } else {
+        /*
+         * User hit Cancel. Close the dialog, but also we must still
+         * reselect the right element of the dropdown list.
+         *
+         * (Because: imagine you have a preset selected, and then you
+         * select Custom from the list, but change your mind and hit
+         * Esc. The Custom option will now still be selected in the
+         * list, whereas obviously it should show the preset you still
+         * _actually_ have selected. Worse still, it'll be the visible
+         * rather than invisible Custom option - see the comment in
+         * js_add_preset in emcclib.js - so you won't even be able to
+         * select Custom without a faffy workaround.)
+         */
+        select_appropriate_preset();
+
+        free_cfg(cfg);
+        js_dialog_cleanup();
+    }
+}
+
+/* ----------------------------------------------------------------------
+ * Called from JS when a command is given to the puzzle by clicking a
+ * button or control of some sort.
+ */
+void command(int n)
+{
+    switch (n) {
+      case 0:                          /* specific game ID */
+        cfg_start(CFG_DESC);
+        break;
+      case 1:                          /* random game seed */
+        cfg_start(CFG_SEED);
+        break;
+      case 2:                          /* game parameter dropdown changed */
+        {
+            int i = js_get_selected_preset();
+            if (i < 0) {
+                /*
+                 * The user selected 'Custom', so launch the config
+                 * box.
+                 */
+                if (thegame.can_configure) /* (double-check just in case) */
+                    cfg_start(CFG_SETTINGS);
+            } else {
+                /*
+                 * The user selected a preset, so just switch straight
+                 * to that.
+                 */
+                assert(i < npresets);
+                midend_set_params(me, presets[i]);
+                midend_new_game(me);
+                resize();
+                midend_redraw(me);
+                update_undo_redo();
+                js_focus_canvas();
+                select_appropriate_preset(); /* sort out Custom/Customise */
+            }
+        }
+        break;
+      case 3:                          /* OK clicked in a config box */
+        cfg_end(TRUE);
+        update_undo_redo();
+        break;
+      case 4:                          /* Cancel clicked in a config box */
+        cfg_end(FALSE);
+        update_undo_redo();
+        break;
+      case 5:                          /* New Game */
+        midend_process_key(me, 0, 0, 'n');
+        update_undo_redo();
+        js_focus_canvas();
+        break;
+      case 6:                          /* Restart */
+        midend_restart_game(me);
+        update_undo_redo();
+        js_focus_canvas();
+        break;
+      case 7:                          /* Undo */
+        midend_process_key(me, 0, 0, 'u');
+        update_undo_redo();
+        js_focus_canvas();
+        break;
+      case 8:                          /* Redo */
+        midend_process_key(me, 0, 0, 'r');
+        update_undo_redo();
+        js_focus_canvas();
+        break;
+      case 9:                          /* Solve */
+        if (thegame.can_solve) {
+            char *msg = midend_solve(me);
+            if (msg)
+                js_error_box(msg);
+        }
+        update_undo_redo();
+        js_focus_canvas();
+        break;
+    }
+}
+
+/* ----------------------------------------------------------------------
+ * Setup function called at page load time. It's called main() because
+ * that's the most convenient thing in Emscripten, but it's not main()
+ * in the usual sense of bounding the program's entire execution.
+ * Instead, this function returns once the initial puzzle is set up
+ * and working, and everything thereafter happens by means of JS event
+ * handlers sending us callbacks.
+ */
+int main(int argc, char **argv)
+{
+    char *param_err;
+    float *colours;
+    int i;
+
+    /*
+     * Instantiate a midend.
+     */
+    me = midend_new(NULL, &thegame, &js_drawing, NULL);
+
+    /*
+     * Chuck in the HTML fragment ID if we have one (trimming the
+     * leading # off the front first). If that's invalid, we retain
+     * the error message and will display it at the end, after setting
+     * up a random puzzle as usual.
+     */
+    if (argc > 1 && argv[1][0] == '#' && argv[1][1] != '\0')
+        param_err = midend_game_id(me, argv[1] + 1);
+    else
+        param_err = NULL;
+
+    /*
+     * Create either a random game or the specified one, and set the
+     * canvas size appropriately.
+     */
+    midend_new_game(me);
+    resize();
+
+    /*
+     * Create a status bar, if needed.
+     */
+    if (midend_wants_statusbar(me))
+        js_canvas_make_statusbar();
+
+    /*
+     * Set up the game-type dropdown with presets and/or the Custom
+     * option.
+     */
+    npresets = midend_num_presets(me);
+    if (npresets == 0) {
+        /*
+         * This puzzle doesn't have selectable game types at all.
+         * Completely remove the drop-down list from the page.
+         */
+        js_remove_type_dropdown();
+        have_presets_dropdown = FALSE;
+    } else {
+        presets = snewn(npresets, game_params *);
+        for (i = 0; i < npresets; i++) {
+            char *name;
+            midend_fetch_preset(me, i, &name, &presets[i]);
+            js_add_preset(name);
+        }
+        if (thegame.can_configure)
+            js_add_preset(NULL);   /* the 'Custom' entry in the dropdown */
+
+        have_presets_dropdown = TRUE;
+
+        /*
+         * Now ensure the appropriate element of the presets menu
+         * starts off selected, in case it isn't the first one in the
+         * list (e.g. Slant).
+         */
+        select_appropriate_preset();
+    }
+
+    /*
+     * Remove the Solve button if the game doesn't support it.
+     */
+    if (!thegame.can_solve)
+        js_remove_solve_button();
+
+    /*
+     * Retrieve the game's colours, and convert them into #abcdef type
+     * hex ID strings.
+     */
+    colours = midend_colours(me, &ncolours);
+    colour_strings = snewn(ncolours, char *);
+    for (i = 0; i < ncolours; i++) {
+        char col[40];
+        sprintf(col, "#%02x%02x%02x",
+                (unsigned)(0.5 + 255 * colours[i*3+0]),
+                (unsigned)(0.5 + 255 * colours[i*3+1]),
+                (unsigned)(0.5 + 255 * colours[i*3+2]));
+        colour_strings[i] = dupstr(col);
+    }
+
+    /*
+     * Request notification when the game ids change (e.g. if the user
+     * presses 'n', and also when Mines supersedes its game
+     * description), so that we can proactively update the permalink.
+     */
+    midend_request_id_changes(me, ids_changed, NULL);
+
+    /*
+     * Draw the puzzle's initial state, and set up the permalinks and
+     * undo/redo greying out.
+     */
+    midend_redraw(me);
+    update_permalinks();
+    update_undo_redo();
+
+    /*
+     * If we were given an erroneous game ID in argv[1], now's the
+     * time to put up the error box about it, after we've fully set up
+     * a random puzzle. Then when the user clicks 'ok', we have a
+     * puzzle for them.
+     */
+    if (param_err)
+        js_error_box(param_err);
+
+    /*
+     * Done. Return to JS, and await callbacks!
+     */
+    return 0;
+}
diff --git a/fifteen.R b/fifteen.R
new file mode 100644 (file)
index 0000000..b2292ac
--- /dev/null
+++ b/fifteen.R
@@ -0,0 +1,22 @@
+# -*- makefile -*-
+
+fifteen  : [X] GTK COMMON fifteen fifteen-icon|no-icon
+
+fifteen  : [G] WINDOWS COMMON fifteen fifteen.res|noicon.res
+
+fifteensolver :    [U] fifteen[STANDALONE_SOLVER] STANDALONE
+fifteensolver :    [C] fifteen[STANDALONE_SOLVER] STANDALONE
+
+ALL += fifteen[COMBINED]
+
+!begin am gtk
+GAMES += fifteen
+!end
+
+!begin >list.c
+    A(fifteen) \
+!end
+
+!begin >gamedesc.txt
+fifteen:fifteen.exe:Fifteen:Sliding block puzzle:Slide the tiles around to arrange them into order.
+!end
diff --git a/fifteen.c b/fifteen.c
new file mode 100644 (file)
index 0000000..6482714
--- /dev/null
+++ b/fifteen.c
@@ -0,0 +1,1215 @@
+/*
+ * fifteen.c: standard 15-puzzle.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+
+#define PREFERRED_TILE_SIZE 48
+#define TILE_SIZE (ds->tilesize)
+#define BORDER    (TILE_SIZE / 2)
+#define HIGHLIGHT_WIDTH (TILE_SIZE / 20)
+#define COORD(x)  ( (x) * TILE_SIZE + BORDER )
+#define FROMCOORD(x)  ( ((x) - BORDER + TILE_SIZE) / TILE_SIZE - 1 )
+
+#define ANIM_TIME 0.13F
+#define FLASH_FRAME 0.13F
+
+#define X(state, i) ( (i) % (state)->w )
+#define Y(state, i) ( (i) / (state)->w )
+#define C(state, x, y) ( (y) * (state)->w + (x) )
+
+#define PARITY_P(params, gap) (((X((params), (gap)) - ((params)->w - 1)) ^ \
+                                (Y((params), (gap)) - ((params)->h - 1)) ^ \
+                                (((params)->w * (params)->h) + 1)) & 1)
+#define PARITY_S(state) PARITY_P((state), ((state)->gap_pos))
+
+enum {
+    COL_BACKGROUND,
+    COL_TEXT,
+    COL_HIGHLIGHT,
+    COL_LOWLIGHT,
+    NCOLOURS
+};
+
+struct game_params {
+    int w, h;
+};
+
+struct game_state {
+    int w, h, n;
+    int *tiles;
+    int gap_pos;
+    int completed;
+    int used_solve;                   /* used to suppress completion flash */
+    int movecount;
+};
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = ret->h = 4;
+
+    return ret;
+}
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    if (i == 0) {
+       *params = default_params();
+       *name = dupstr("4x4");
+       return TRUE;
+    }
+    return FALSE;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+static void decode_params(game_params *ret, char const *string)
+{
+    ret->w = ret->h = atoi(string);
+    while (*string && isdigit((unsigned char)*string)) string++;
+    if (*string == 'x') {
+        string++;
+        ret->h = atoi(string);
+    }
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char data[256];
+
+    sprintf(data, "%dx%d", params->w, params->h);
+
+    return dupstr(data);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+    char buf[80];
+
+    ret = snewn(3, config_item);
+
+    ret[0].name = "Width";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->w);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Height";
+    ret[1].type = C_STRING;
+    sprintf(buf, "%d", params->h);
+    ret[1].sval = dupstr(buf);
+    ret[1].ival = 0;
+
+    ret[2].name = NULL;
+    ret[2].type = C_END;
+    ret[2].sval = NULL;
+    ret[2].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = atoi(cfg[0].sval);
+    ret->h = atoi(cfg[1].sval);
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    if (params->w < 2 || params->h < 2)
+       return "Width and height must both be at least two";
+
+    return NULL;
+}
+
+static int perm_parity(int *perm, int n)
+{
+    int i, j, ret;
+
+    ret = 0;
+
+    for (i = 0; i < n-1; i++)
+        for (j = i+1; j < n; j++)
+            if (perm[i] > perm[j])
+                ret = !ret;
+
+    return ret;
+}
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                          char **aux, int interactive)
+{
+    int gap, n, i, x;
+    int x1, x2, p1, p2, parity;
+    int *tiles, *used;
+    char *ret;
+    int retlen;
+
+    n = params->w * params->h;
+
+    tiles = snewn(n, int);
+    used = snewn(n, int);
+
+    for (i = 0; i < n; i++) {
+        tiles[i] = -1;
+        used[i] = FALSE;
+    }
+
+    gap = random_upto(rs, n);
+    tiles[gap] = 0;
+    used[0] = TRUE;
+
+    /*
+     * Place everything else except the last two tiles.
+     */
+    for (x = 0, i = n-1; i > 2; i--) {
+        int k = random_upto(rs, i);
+        int j;
+
+        for (j = 0; j < n; j++)
+            if (!used[j] && (k-- == 0))
+                break;
+
+        assert(j < n && !used[j]);
+        used[j] = TRUE;
+
+        while (tiles[x] >= 0)
+            x++;
+        assert(x < n);
+        tiles[x] = j;
+    }
+
+    /*
+     * Find the last two locations, and the last two pieces.
+     */
+    while (tiles[x] >= 0)
+        x++;
+    assert(x < n);
+    x1 = x;
+    x++;
+    while (tiles[x] >= 0)
+        x++;
+    assert(x < n);
+    x2 = x;
+
+    for (i = 0; i < n; i++)
+        if (!used[i])
+            break;
+    p1 = i;
+    for (i = p1+1; i < n; i++)
+        if (!used[i])
+            break;
+    p2 = i;
+
+    /*
+     * Determine the required parity of the overall permutation.
+     * This is the XOR of:
+     * 
+     *         - The chessboard parity ((x^y)&1) of the gap square. The
+     *           bottom right counts as even.
+     * 
+     *  - The parity of n. (The target permutation is 1,...,n-1,0
+     *    rather than 0,...,n-1; this is a cyclic permutation of
+     *    the starting point and hence is odd iff n is even.)
+     */
+    parity = PARITY_P(params, gap);
+
+    /*
+     * Try the last two tiles one way round. If that fails, swap
+     * them.
+     */
+    tiles[x1] = p1;
+    tiles[x2] = p2;
+    if (perm_parity(tiles, n) != parity) {
+        tiles[x1] = p2;
+        tiles[x2] = p1;
+        assert(perm_parity(tiles, n) == parity);
+    }
+
+    /*
+     * Now construct the game description, by describing the tile
+     * array as a simple sequence of comma-separated integers.
+     */
+    ret = NULL;
+    retlen = 0;
+    for (i = 0; i < n; i++) {
+        char buf[80];
+        int k;
+
+        k = sprintf(buf, "%d,", tiles[i]);
+
+        ret = sresize(ret, retlen + k + 1, char);
+        strcpy(ret + retlen, buf);
+        retlen += k;
+    }
+    ret[retlen-1] = '\0';              /* delete last comma */
+
+    sfree(tiles);
+    sfree(used);
+
+    return ret;
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    const char *p;
+    char *err;
+    int i, area;
+    int *used;
+
+    area = params->w * params->h;
+    p = desc;
+    err = NULL;
+
+    used = snewn(area, int);
+    for (i = 0; i < area; i++)
+       used[i] = FALSE;
+
+    for (i = 0; i < area; i++) {
+       const char *q = p;
+       int n;
+
+       if (*p < '0' || *p > '9') {
+           err = "Not enough numbers in string";
+           goto leave;
+       }
+       while (*p >= '0' && *p <= '9')
+           p++;
+       if (i < area-1 && *p != ',') {
+           err = "Expected comma after number";
+           goto leave;
+       }
+       else if (i == area-1 && *p) {
+           err = "Excess junk at end of string";
+           goto leave;
+       }
+       n = atoi(q);
+       if (n < 0 || n >= area) {
+           err = "Number out of range";
+           goto leave;
+       }
+       if (used[n]) {
+           err = "Number used twice";
+           goto leave;
+       }
+       used[n] = TRUE;
+
+       if (*p) p++;                   /* eat comma */
+    }
+
+    leave:
+    sfree(used);
+    return err;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    game_state *state = snew(game_state);
+    int i;
+    const char *p;
+
+    state->w = params->w;
+    state->h = params->h;
+    state->n = params->w * params->h;
+    state->tiles = snewn(state->n, int);
+
+    state->gap_pos = 0;
+    p = desc;
+    i = 0;
+    for (i = 0; i < state->n; i++) {
+        assert(*p);
+        state->tiles[i] = atoi(p);
+        if (state->tiles[i] == 0)
+            state->gap_pos = i;
+        while (*p && *p != ',')
+            p++;
+        if (*p) p++;                   /* eat comma */
+    }
+    assert(!*p);
+    assert(state->tiles[state->gap_pos] == 0);
+
+    state->completed = state->movecount = 0;
+    state->used_solve = FALSE;
+
+    return state;
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    game_state *ret = snew(game_state);
+
+    ret->w = state->w;
+    ret->h = state->h;
+    ret->n = state->n;
+    ret->tiles = snewn(state->w * state->h, int);
+    memcpy(ret->tiles, state->tiles, state->w * state->h * sizeof(int));
+    ret->gap_pos = state->gap_pos;
+    ret->completed = state->completed;
+    ret->movecount = state->movecount;
+    ret->used_solve = state->used_solve;
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    sfree(state->tiles);
+    sfree(state);
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    return dupstr("S");
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    char *ret, *p, buf[80];
+    int x, y, col, maxlen;
+
+    /*
+     * First work out how many characters we need to display each
+     * number.
+     */
+    col = sprintf(buf, "%d", state->n-1);
+
+    /*
+     * Now we know the exact total size of the grid we're going to
+     * produce: it's got h rows, each containing w lots of col, w-1
+     * spaces and a trailing newline.
+     */
+    maxlen = state->h * state->w * (col+1);
+
+    ret = snewn(maxlen+1, char);
+    p = ret;
+
+    for (y = 0; y < state->h; y++) {
+       for (x = 0; x < state->w; x++) {
+           int v = state->tiles[state->w*y+x];
+           if (v == 0)
+               sprintf(buf, "%*s", col, "");
+           else
+               sprintf(buf, "%*d", col, v);
+           memcpy(p, buf, col);
+           p += col;
+           if (x+1 == state->w)
+               *p++ = '\n';
+           else
+               *p++ = ' ';
+       }
+    }
+
+    assert(p - ret == maxlen);
+    *p = '\0';
+    return ret;
+}
+
+static game_ui *new_ui(const game_state *state)
+{
+    return NULL;
+}
+
+static void free_ui(game_ui *ui)
+{
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+}
+
+struct game_drawstate {
+    int started;
+    int w, h, bgcolour;
+    int *tiles;
+    int tilesize;
+};
+
+static int flip_cursor(int button)
+{
+    switch (button) {
+    case CURSOR_UP: return CURSOR_DOWN;
+    case CURSOR_DOWN: return CURSOR_UP;
+    case CURSOR_LEFT: return CURSOR_RIGHT;
+    case CURSOR_RIGHT: return CURSOR_LEFT;
+    }
+    return 0;
+}
+
+static void next_move_3x2(int ax, int ay, int bx, int by,
+                          int gx, int gy, int *dx, int *dy)
+{
+    /* When w = 3 and h = 2 and the tile going in the top left corner
+     * is at (ax, ay) and the tile going in the bottom left corner is
+     * at (bx, by) and the blank tile is at (gx, gy), how do you move? */
+
+    /* Hard-coded shortest solutions.  Sorry. */
+    static const unsigned char move[120] = {
+        1,2,0,1,2,2,
+        2,0,0,2,0,0,
+        0,0,2,0,2,0,
+        0,0,0,2,0,2,
+        2,0,0,0,2,0,
+
+        0,3,0,1,1,1,
+        3,0,3,2,1,2,
+        2,1,1,0,1,0,
+        2,1,2,1,0,1,
+        1,2,0,2,1,2,
+
+        0,1,3,1,3,0,
+        1,3,1,3,0,3,
+        0,0,3,3,0,0,
+        0,0,0,1,2,1,
+        3,0,0,1,1,1,
+
+        3,1,1,1,3,0,
+        1,1,1,1,1,1,
+        1,3,1,1,3,0,
+        1,1,3,3,1,3,
+        1,3,0,0,0,0
+    };
+    static const struct { int dx, dy; } d[4] = {{+1,0},{-1,0},{0,+1},{0,-1}};
+
+    int ea = 3*ay + ax, eb = 3*by + bx, eg = 3*gy + gx, v;
+    if (eb > ea) --eb;
+    if (eg > ea) --eg;
+    if (eg > eb) --eg;
+    v = move[ea + eb*6 + eg*5*6];
+    *dx = d[v].dx;
+    *dy = d[v].dy;
+}
+
+static void next_move(int nx, int ny, int ox, int oy, int gx, int gy,
+                      int tx, int ty, int w, int *dx, int *dy)
+{
+    const int to_tile_x = (gx < nx ? +1 : -1);
+    const int to_goal_x = (gx < tx ? +1 : -1);
+    const int gap_x_on_goal_side = ((nx-tx) * (nx-gx) > 0);
+
+    assert (nx != tx || ny != ty); /* not already in place */
+    assert (nx != gx || ny != gy); /* not placing the gap */
+    assert (ty <= ny); /* because we're greedy (and flipping) */
+    assert (ty <= gy); /* because we're greedy (and flipping) */
+
+    /* TODO: define a termination function.  Idea: 0 if solved, or
+     * the number of moves to solve the next piece plus the number of
+     * further unsolved pieces times an upper bound on the number of
+     * moves required to solve any piece.  If such a function can be
+     * found, we have (termination && (termination => correctness)).
+     * The catch is our temporary disturbance of 2x3 corners. */
+
+    /* handles end-of-row, when 3 and 4 are in the top right 2x3 box */
+    if (tx == w - 2 &&
+        ny <= ty + 2 && (nx == tx || nx == tx + 1) &&
+        oy <= ty + 2 && (ox == tx || ox == tx + 1) &&
+        gy <= ty + 2 && (gx == tx || gx == tx + 1))
+    {
+        next_move_3x2(oy - ty, tx + 1 - ox,
+                      ny - ty, tx + 1 - nx,
+                      gy - ty, tx + 1 - gx, dy, dx);
+        *dx *= -1;
+        return;
+    }
+
+    if (tx == w - 1) {
+        if (ny <= ty + 2 && (nx == tx || nx == tx - 1) &&
+            gy <= ty + 2 && (gx == tx || gx == tx - 1)) {
+            next_move_3x2(ny - ty, tx - nx, 0, 1, gy - ty, tx - gx, dy, dx);
+            *dx *= -1;
+        } else if (gy == ty)
+            *dy = +1;
+        else if (nx != tx || ny != ty + 1) {
+            next_move((w - 1) - nx, ny, -1, -1, (w - 1) - gx, gy,
+                      0, ty + 1, -1, dx, dy);
+            *dx *= -1;
+        } else if (gx == nx)
+            *dy = -1;
+        else
+            *dx = +1;
+        return;
+    }
+
+    /* note that *dy = -1 is unsafe when gy = ty + 1 and gx < tx */
+    if (gy < ny)
+        if (nx == gx || (gy == ty && gx == tx))
+            *dy = +1;
+        else if (!gap_x_on_goal_side)
+            *dx = to_tile_x;
+        else if (ny - ty > abs(nx - tx))
+            *dx = to_tile_x;
+        else *dy = +1;
+
+    else if (gy == ny)
+        if (nx == tx) /* then we know ny > ty */
+            if (gx > nx || ny > ty + 1)
+                *dy = -1; /* ... so this is safe */
+            else
+                *dy = +1;
+        else if (gap_x_on_goal_side)
+            *dx = to_tile_x;
+        else if (gy == ty || (gy == ty + 1 && gx < tx))
+            *dy = +1;
+        else
+            *dy = -1;
+
+    else if (nx == tx) /* gy > ny */
+        if (gx > nx)
+            *dy = -1;
+        else
+            *dx = +1;
+    else if (gx == nx)
+        *dx = to_goal_x;
+    else if (gap_x_on_goal_side)
+        if (gy == ty + 1 && gx < tx)
+            *dx = to_tile_x;
+        else
+            *dy = -1;
+
+    else if (ny - ty > abs(nx - tx))
+        *dy = -1;
+    else
+        *dx = to_tile_x;
+}
+
+static int compute_hint(const game_state *state, int *out_x, int *out_y)
+{
+    /* The overall solving process is this:
+     * 1. Find the next piece to be put in its place
+     * 2. Move it diagonally towards its place
+     * 3. Move it horizontally or vertically towards its place
+     * (Modulo the last two tiles at the end of each row/column)
+     */
+
+    int gx = X(state, state->gap_pos);
+    int gy = Y(state, state->gap_pos);
+
+    int tx, ty, nx, ny, ox, oy, /* {target,next,next2}_{x,y} */ i;
+    int dx = 0, dy = 0;
+
+    /* 1. Find the next piece
+     * if (there are no more unfinished columns than rows) {
+     *     fill the top-most row, left to right
+     * } else { fill the left-most column, top to bottom }
+     */
+    const int w = state->w, h = state->h, n = w*h;
+    int next_piece = 0, next_piece_2 = 0, solr = 0, solc = 0;
+    int unsolved_rows = h, unsolved_cols = w;
+
+    assert(out_x);
+    assert(out_y);
+
+    while (solr < h && solc < w) {
+        int start, step, stop;
+        if (unsolved_cols <= unsolved_rows)
+            start = solr*w + solc, step = 1, stop = unsolved_cols;
+        else
+            start = solr*w + solc, step = w, stop = unsolved_rows;
+        for (i = 0; i < stop; ++i) {
+            const int j = start + i*step;
+            if (state->tiles[j] != j + 1) {
+                next_piece = j + 1;
+                next_piece_2 = next_piece + step;
+                break;
+            }
+        }
+        if (i < stop) break;
+
+        (unsolved_cols <= unsolved_rows)
+            ? (++solr, --unsolved_rows)
+            : (++solc, --unsolved_cols);
+    }
+
+    if (next_piece == n)
+        return FALSE;
+
+    /* 2, 3. Move the next piece towards its place */
+
+    /* gx, gy already set */
+    tx = X(state, next_piece - 1); /* where we're going */
+    ty = Y(state, next_piece - 1);
+    for (i = 0; i < n && state->tiles[i] != next_piece; ++i);
+    nx = X(state, i); /* where we're at */
+    ny = Y(state, i);
+    for (i = 0; i < n && state->tiles[i] != next_piece_2; ++i);
+    ox = X(state, i);
+    oy = Y(state, i);
+
+    if (unsolved_cols <= unsolved_rows)
+        next_move(nx, ny, ox, oy, gx, gy, tx, ty, w, &dx, &dy);
+    else
+        next_move(ny, nx, oy, ox, gy, gx, ty, tx, h, &dy, &dx);
+
+    assert (dx || dy);
+
+    *out_x = gx + dx;
+    *out_y = gy + dy;
+    return TRUE;
+}
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    int cx = X(state, state->gap_pos), nx = cx;
+    int cy = Y(state, state->gap_pos), ny = cy;
+    char buf[80];
+
+    button &= ~MOD_MASK;
+
+    if (button == LEFT_BUTTON) {
+        nx = FROMCOORD(x);
+        ny = FROMCOORD(y);
+        if (nx < 0 || nx >= state->w || ny < 0 || ny >= state->h)
+            return NULL;               /* out of bounds */
+    } else if (IS_CURSOR_MOVE(button)) {
+        static int invert_cursor = -1;
+        if (invert_cursor == -1) {
+            char *env = getenv("FIFTEEN_INVERT_CURSOR");
+            invert_cursor = (env && (env[0] == 'y' || env[0] == 'Y'));
+        }
+        button = flip_cursor(button); /* the default */
+        if (invert_cursor)
+            button = flip_cursor(button); /* undoes the first flip */
+       move_cursor(button, &nx, &ny, state->w, state->h, FALSE);
+    } else if ((button == 'h' || button == 'H') && !state->completed) {
+        if (!compute_hint(state, &nx, &ny))
+            return NULL; /* shouldn't happen, since ^^we^^checked^^ */
+    } else
+        return NULL;                   /* no move */
+
+    /*
+     * Any click location should be equal to the gap location
+     * in _precisely_ one coordinate.
+     */
+    if ((cx == nx) ^ (cy == ny)) {
+       sprintf(buf, "M%d,%d", nx, ny);
+       return dupstr(buf);
+    }
+
+    return NULL;
+}
+
+static game_state *execute_move(const game_state *from, const char *move)
+{
+    int gx, gy, dx, dy, ux, uy, up, p;
+    game_state *ret;
+
+    if (!strcmp(move, "S")) {
+       int i;
+
+       ret = dup_game(from);
+
+       /*
+        * Simply replace the grid with a solved one. For this game,
+        * this isn't a useful operation for actually telling the user
+        * what they should have done, but it is useful for
+        * conveniently being able to get hold of a clean state from
+        * which to practise manoeuvres.
+        */
+       for (i = 0; i < ret->n; i++)
+           ret->tiles[i] = (i+1) % ret->n;
+       ret->gap_pos = ret->n-1;
+       ret->used_solve = TRUE;
+       ret->completed = ret->movecount = 1;
+
+       return ret;
+    }
+
+    gx = X(from, from->gap_pos);
+    gy = Y(from, from->gap_pos);
+
+    if (move[0] != 'M' ||
+       sscanf(move+1, "%d,%d", &dx, &dy) != 2 ||
+       (dx == gx && dy == gy) || (dx != gx && dy != gy) ||
+       dx < 0 || dx >= from->w || dy < 0 || dy >= from->h)
+       return NULL;
+
+    /*
+     * Find the unit displacement from the original gap
+     * position towards this one.
+     */
+    ux = (dx < gx ? -1 : dx > gx ? +1 : 0);
+    uy = (dy < gy ? -1 : dy > gy ? +1 : 0);
+    up = C(from, ux, uy);
+
+    ret = dup_game(from);
+
+    ret->gap_pos = C(from, dx, dy);
+    assert(ret->gap_pos >= 0 && ret->gap_pos < ret->n);
+
+    ret->tiles[ret->gap_pos] = 0;
+
+    for (p = from->gap_pos; p != ret->gap_pos; p += up) {
+        assert(p >= 0 && p < from->n);
+        ret->tiles[p] = from->tiles[p + up];
+       ret->movecount++;
+    }
+
+    /*
+     * See if the game has been completed.
+     */
+    if (!ret->completed) {
+        ret->completed = ret->movecount;
+        for (p = 0; p < ret->n; p++)
+            if (ret->tiles[p] != (p < ret->n-1 ? p+1 : 0))
+                ret->completed = 0;
+    }
+
+    return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    struct { int tilesize; } ads, *ds = &ads;
+    ads.tilesize = tilesize;
+
+    *x = TILE_SIZE * params->w + 2 * BORDER;
+    *y = TILE_SIZE * params->h + 2 * BORDER;
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+    int i;
+
+    game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT);
+
+    for (i = 0; i < 3; i++)
+        ret[COL_TEXT * 3 + i] = 0.0;
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    struct game_drawstate *ds = snew(struct game_drawstate);
+    int i;
+
+    ds->started = FALSE;
+    ds->w = state->w;
+    ds->h = state->h;
+    ds->bgcolour = COL_BACKGROUND;
+    ds->tiles = snewn(ds->w*ds->h, int);
+    ds->tilesize = 0;                  /* haven't decided yet */
+    for (i = 0; i < ds->w*ds->h; i++)
+        ds->tiles[i] = -1;
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds->tiles);
+    sfree(ds);
+}
+
+static void draw_tile(drawing *dr, game_drawstate *ds, const game_state *state,
+                      int x, int y, int tile, int flash_colour)
+{
+    if (tile == 0) {
+        draw_rect(dr, x, y, TILE_SIZE, TILE_SIZE,
+                  flash_colour);
+    } else {
+        int coords[6];
+        char str[40];
+
+        coords[0] = x + TILE_SIZE - 1;
+        coords[1] = y + TILE_SIZE - 1;
+        coords[2] = x + TILE_SIZE - 1;
+        coords[3] = y;
+        coords[4] = x;
+        coords[5] = y + TILE_SIZE - 1;
+        draw_polygon(dr, coords, 3, COL_LOWLIGHT, COL_LOWLIGHT);
+
+        coords[0] = x;
+        coords[1] = y;
+        draw_polygon(dr, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT);
+
+        draw_rect(dr, x + HIGHLIGHT_WIDTH, y + HIGHLIGHT_WIDTH,
+                  TILE_SIZE - 2*HIGHLIGHT_WIDTH, TILE_SIZE - 2*HIGHLIGHT_WIDTH,
+                  flash_colour);
+
+        sprintf(str, "%d", tile);
+        draw_text(dr, x + TILE_SIZE/2, y + TILE_SIZE/2,
+                  FONT_VARIABLE, TILE_SIZE/3, ALIGN_VCENTRE | ALIGN_HCENTRE,
+                  COL_TEXT, str);
+    }
+    draw_update(dr, x, y, TILE_SIZE, TILE_SIZE);
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int i, pass, bgcolour;
+
+    if (flashtime > 0) {
+        int frame = (int)(flashtime / FLASH_FRAME);
+        bgcolour = (frame % 2 ? COL_LOWLIGHT : COL_HIGHLIGHT);
+    } else
+        bgcolour = COL_BACKGROUND;
+
+    if (!ds->started) {
+        int coords[10];
+
+       draw_rect(dr, 0, 0,
+                 TILE_SIZE * state->w + 2 * BORDER,
+                 TILE_SIZE * state->h + 2 * BORDER, COL_BACKGROUND);
+       draw_update(dr, 0, 0,
+                   TILE_SIZE * state->w + 2 * BORDER,
+                   TILE_SIZE * state->h + 2 * BORDER);
+
+        /*
+         * Recessed area containing the whole puzzle.
+         */
+        coords[0] = COORD(state->w) + HIGHLIGHT_WIDTH - 1;
+        coords[1] = COORD(state->h) + HIGHLIGHT_WIDTH - 1;
+        coords[2] = COORD(state->w) + HIGHLIGHT_WIDTH - 1;
+        coords[3] = COORD(0) - HIGHLIGHT_WIDTH;
+        coords[4] = coords[2] - TILE_SIZE;
+        coords[5] = coords[3] + TILE_SIZE;
+        coords[8] = COORD(0) - HIGHLIGHT_WIDTH;
+        coords[9] = COORD(state->h) + HIGHLIGHT_WIDTH - 1;
+        coords[6] = coords[8] + TILE_SIZE;
+        coords[7] = coords[9] - TILE_SIZE;
+        draw_polygon(dr, coords, 5, COL_HIGHLIGHT, COL_HIGHLIGHT);
+
+        coords[1] = COORD(0) - HIGHLIGHT_WIDTH;
+        coords[0] = COORD(0) - HIGHLIGHT_WIDTH;
+        draw_polygon(dr, coords, 5, COL_LOWLIGHT, COL_LOWLIGHT);
+
+        ds->started = TRUE;
+    }
+
+    /*
+     * Now draw each tile. We do this in two passes to make
+     * animation easy.
+     */
+    for (pass = 0; pass < 2; pass++) {
+        for (i = 0; i < state->n; i++) {
+            int t, t0;
+            /*
+             * Figure out what should be displayed at this
+             * location. It's either a simple tile, or it's a
+             * transition between two tiles (in which case we say
+             * -1 because it must always be drawn).
+             */
+
+            if (oldstate && oldstate->tiles[i] != state->tiles[i])
+                t = -1;
+            else
+                t = state->tiles[i];
+
+            t0 = t;
+
+            if (ds->bgcolour != bgcolour ||   /* always redraw when flashing */
+                ds->tiles[i] != t || ds->tiles[i] == -1 || t == -1) {
+                int x, y;
+
+                /*
+                 * Figure out what to _actually_ draw, and where to
+                 * draw it.
+                 */
+                if (t == -1) {
+                    int x0, y0, x1, y1;
+                    int j;
+
+                    /*
+                     * On the first pass, just blank the tile.
+                     */
+                    if (pass == 0) {
+                        x = COORD(X(state, i));
+                        y = COORD(Y(state, i));
+                        t = 0;
+                    } else {
+                        float c;
+
+                        t = state->tiles[i];
+
+                        /*
+                         * Don't bother moving the gap; just don't
+                         * draw it.
+                         */
+                        if (t == 0)
+                            continue;
+
+                        /*
+                         * Find the coordinates of this tile in the old and
+                         * new states.
+                         */
+                        x1 = COORD(X(state, i));
+                        y1 = COORD(Y(state, i));
+                        for (j = 0; j < oldstate->n; j++)
+                            if (oldstate->tiles[j] == state->tiles[i])
+                                break;
+                        assert(j < oldstate->n);
+                        x0 = COORD(X(state, j));
+                        y0 = COORD(Y(state, j));
+
+                        c = (animtime / ANIM_TIME);
+                        if (c < 0.0F) c = 0.0F;
+                        if (c > 1.0F) c = 1.0F;
+
+                        x = x0 + (int)(c * (x1 - x0));
+                        y = y0 + (int)(c * (y1 - y0));
+                    }
+
+                } else {
+                    if (pass == 0)
+                        continue;
+                    x = COORD(X(state, i));
+                    y = COORD(Y(state, i));
+                }
+
+                draw_tile(dr, ds, state, x, y, t, bgcolour);
+            }
+            ds->tiles[i] = t0;
+        }
+    }
+    ds->bgcolour = bgcolour;
+
+    /*
+     * Update the status bar.
+     */
+    {
+       char statusbuf[256];
+
+        /*
+         * Don't show the new status until we're also showing the
+         * new _state_ - after the game animation is complete.
+         */
+        if (oldstate)
+            state = oldstate;
+
+       if (state->used_solve)
+           sprintf(statusbuf, "Moves since auto-solve: %d",
+                   state->movecount - state->completed);
+       else
+           sprintf(statusbuf, "%sMoves: %d",
+                   (state->completed ? "COMPLETED! " : ""),
+                   (state->completed ? state->completed : state->movecount));
+
+       status_bar(dr, statusbuf);
+    }
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return ANIM_TIME;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    if (!oldstate->completed && newstate->completed &&
+       !oldstate->used_solve && !newstate->used_solve)
+        return 2 * FLASH_FRAME;
+    else
+        return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    return state->completed ? +1 : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+}
+
+#ifdef COMBINED
+#define thegame fifteen
+#endif
+
+const struct game thegame = {
+    "Fifteen", "games.fifteen", "fifteen",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    TRUE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    FALSE, FALSE, game_print_size, game_print,
+    TRUE,                             /* wants_statusbar */
+    FALSE, game_timing_state,
+    0,                                /* flags */
+};
+
+#ifdef STANDALONE_SOLVER
+
+int main(int argc, char **argv)
+{
+    game_params *params;
+    game_state *state;
+    char *id = NULL, *desc, *err;
+    int grade = FALSE;
+    char *progname = argv[0];
+
+    char buf[80];
+    int limit, x, y, solvable;
+
+    while (--argc > 0) {
+        char *p = *++argv;
+        if (!strcmp(p, "-v")) {
+            /* solver_show_working = TRUE; */
+        } else if (!strcmp(p, "-g")) {
+            grade = TRUE;
+        } else if (*p == '-') {
+            fprintf(stderr, "%s: unrecognised option `%s'\n", progname, p);
+            return 1;
+        } else {
+            id = p;
+        }
+    }
+
+    if (!id) {
+        fprintf(stderr, "usage: %s [-g | -v] <game_id>\n", argv[0]);
+        return 1;
+    }
+
+    desc = strchr(id, ':');
+    if (!desc) {
+        fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]);
+        return 1;
+    }
+    *desc++ = '\0';
+
+    params = default_params();
+    decode_params(params, id);
+    err = validate_desc(params, desc);
+    if (err) {
+        free_params(params);
+        fprintf(stderr, "%s: %s\n", argv[0], err);
+        return 1;
+    }
+
+    state = new_game(NULL, params, desc);
+    free_params(params);
+
+    solvable = (PARITY_S(state) == perm_parity(state->tiles, state->n));
+    if (grade || !solvable) {
+        free_game(state);
+        fputs(solvable ? "Game is solvable" : "Game is unsolvable",
+              grade ? stdout : stderr);
+        return !grade;
+    }
+
+    for (limit = 5 * state->n * state->n * state->n; limit; --limit) {
+        game_state *next_state;
+        if (!compute_hint(state, &x, &y)) {
+            fprintf(stderr, "couldn't compute next move while solving %s:%s",
+                    id, desc);
+            return 1;
+        }
+        printf("Move the space to (%d, %d), moving %d into the space\n",
+               x + 1, y + 1, state->tiles[C(state, x, y)]);
+        sprintf(buf, "M%d,%d", x, y);
+        next_state = execute_move(state, buf);
+
+        free_game(state);
+        if (!next_state) {
+            fprintf(stderr, "invalid move when solving %s:%s\n", id, desc);
+            return 1;
+        }
+        state = next_state;
+        if (next_state->completed) {
+            free_game(state);
+            return 0;
+        }
+    }
+
+    free_game(state);
+    fprintf(stderr, "ran out of moves for %s:%s\n", id, desc);
+    return 1;
+}
+
+#endif
diff --git a/filling.R b/filling.R
new file mode 100644 (file)
index 0000000..cffbafa
--- /dev/null
+++ b/filling.R
@@ -0,0 +1,24 @@
+# -*- makefile -*-
+
+FILLING_EXTRA = dsf
+
+fillingsolver :        [U] filling[STANDALONE_SOLVER] FILLING_EXTRA STANDALONE
+fillingsolver :        [C] filling[STANDALONE_SOLVER] FILLING_EXTRA STANDALONE
+
+filling : [X] GTK COMMON filling FILLING_EXTRA filling-icon|no-icon
+
+filling : [G] WINDOWS COMMON filling FILLING_EXTRA filling.res|noicon.res
+
+ALL += filling[COMBINED] FILLING_EXTRA
+
+!begin am gtk
+GAMES += filling
+!end
+
+!begin >list.c
+    A(filling) \
+!end
+
+!begin >gamedesc.txt
+filling:filling.exe:Filling:Polyomino puzzle:Mark every square with the area of its containing region.
+!end
diff --git a/filling.c b/filling.c
new file mode 100644 (file)
index 0000000..3797e5c
--- /dev/null
+++ b/filling.c
@@ -0,0 +1,2179 @@
+/* -*- tab-width: 8; indent-tabs-mode: t -*-
+ * filling.c: An implementation of the Nikoli game fillomino.
+ * Copyright (C) 2007 Jonas Kölker.  See LICENSE for the license.
+ */
+
+/* TODO:
+ *
+ *  - use a typedef instead of int for numbers on the board
+ *     + replace int with something else (signed short?)
+ *        - the type should be signed (for -board[i] and -SENTINEL)
+ *        - the type should be somewhat big: board[i] = i
+ *        - Using shorts gives us 181x181 puzzles as upper bound.
+ *
+ *  - in board generation, after having merged regions such that no
+ *    more merges are necessary, try splitting (big) regions.
+ *     + it seems that smaller regions make for better puzzles; see
+ *       for instance the 7x7 puzzle in this file (grep for 7x7:).
+ *
+ *  - symmetric hints (solo-style)
+ *     + right now that means including _many_ hints, and the puzzles
+ *       won't look any nicer.  Not worth it (at the moment).
+ *
+ *  - make the solver do recursion/backtracking.
+ *     + This is for user-submitted puzzles, not for puzzle
+ *       generation (on the other hand, never say never).
+ *
+ *  - prove that only w=h=2 needs a special case
+ *
+ *  - solo-like pencil marks?
+ *
+ *  - a user says that the difficulty is unevenly distributed.
+ *     + partition into levels?  Will they be non-crap?
+ *
+ *  - Allow square contents > 9?
+ *     + I could use letters for digits (solo does this), but
+ *       letters don't have numeric significance (normal people hate
+ *       base36), which is relevant here (much more than in solo).
+ *     + [click, 1, 0, enter] => [10 in clicked square]?
+ *     + How much information is needed to solve?  Does one need to
+ *       know the algorithm by which the largest number is set?
+ *
+ *  - eliminate puzzle instances with done chunks (1's in particular)?
+ *     + that's what the qsort call is all about.
+ *     + the 1's don't bother me that much.
+ *     + but this takes a LONG time (not always possible)?
+ *        - this may be affected by solver (lack of) quality.
+ *        - weed them out by construction instead of post-cons check
+ *           + but that interleaves make_board and new_game_desc: you
+ *             have to alternate between changing the board and
+ *             changing the hint set (instead of just creating the
+ *             board once, then changing the hint set once -> done).
+ *
+ *  - use binary search when discovering the minimal sovable point
+ *     + profile to show a need (but when the solver gets slower...)
+ *     + 7x9 @ .011s, 9x13 @ .075s, 17x13 @ .661s (all avg with n=100)
+ *     + but the hints are independent, not linear, so... what?
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "puzzles.h"
+
+static unsigned char verbose;
+
+static void printv(char *fmt, ...) {
+#ifndef PALM
+    if (verbose) {
+       va_list va;
+       va_start(va, fmt);
+       vprintf(fmt, va);
+       va_end(va);
+    }
+#endif
+}
+
+/*****************************************************************************
+ * GAME CONFIGURATION AND PARAMETERS                                         *
+ *****************************************************************************/
+
+struct game_params {
+    int w, h;
+};
+
+struct shared_state {
+    struct game_params params;
+    int *clues;
+    int refcnt;
+};
+
+struct game_state {
+    int *board;
+    struct shared_state *shared;
+    int completed, cheated;
+};
+
+static const struct game_params filling_defaults[3] = {
+    {9, 7}, {13, 9}, {17, 13}
+};
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+
+    *ret = filling_defaults[1]; /* struct copy */
+
+    return ret;
+}
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    char buf[64];
+
+    if (i < 0 || i >= lenof(filling_defaults)) return FALSE;
+    *params = snew(game_params);
+    **params = filling_defaults[i]; /* struct copy */
+    sprintf(buf, "%dx%d", filling_defaults[i].w, filling_defaults[i].h);
+    *name = dupstr(buf);
+
+    return TRUE;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params; /* struct copy */
+    return ret;
+}
+
+static void decode_params(game_params *ret, char const *string)
+{
+    ret->w = ret->h = atoi(string);
+    while (*string && isdigit((unsigned char) *string)) ++string;
+    if (*string == 'x') ret->h = atoi(++string);
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char buf[64];
+    sprintf(buf, "%dx%d", params->w, params->h);
+    return dupstr(buf);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+    char buf[64];
+
+    ret = snewn(3, config_item);
+
+    ret[0].name = "Width";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->w);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Height";
+    ret[1].type = C_STRING;
+    sprintf(buf, "%d", params->h);
+    ret[1].sval = dupstr(buf);
+    ret[1].ival = 0;
+
+    ret[2].name = NULL;
+    ret[2].type = C_END;
+    ret[2].sval = NULL;
+    ret[2].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = atoi(cfg[0].sval);
+    ret->h = atoi(cfg[1].sval);
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    if (params->w < 1) return "Width must be at least one";
+    if (params->h < 1) return "Height must be at least one";
+
+    return NULL;
+}
+
+/*****************************************************************************
+ * STRINGIFICATION OF GAME STATE                                             *
+ *****************************************************************************/
+
+#define EMPTY 0
+
+/* Example of plaintext rendering:
+ *  +---+---+---+---+---+---+---+
+ *  | 6 |   |   | 2 |   |   | 2 |
+ *  +---+---+---+---+---+---+---+
+ *  |   | 3 |   | 6 |   | 3 |   |
+ *  +---+---+---+---+---+---+---+
+ *  | 3 |   |   |   |   |   | 1 |
+ *  +---+---+---+---+---+---+---+
+ *  |   | 2 | 3 |   | 4 | 2 |   |
+ *  +---+---+---+---+---+---+---+
+ *  | 2 |   |   |   |   |   | 3 |
+ *  +---+---+---+---+---+---+---+
+ *  |   | 5 |   | 1 |   | 4 |   |
+ *  +---+---+---+---+---+---+---+
+ *  | 4 |   |   | 3 |   |   | 3 |
+ *  +---+---+---+---+---+---+---+
+ *
+ * This puzzle instance is taken from the nikoli website
+ * Encoded (unsolved and solved), the strings are these:
+ * 7x7:6002002030603030000010230420200000305010404003003
+ * 7x7:6662232336663232331311235422255544325413434443313
+ */
+static char *board_to_string(int *board, int w, int h) {
+    const int sz = w * h;
+    const int chw = (4*w + 2); /* +2 for trailing '+' and '\n' */
+    const int chh = (2*h + 1); /* +1: n fence segments, n+1 posts */
+    const int chlen = chw * chh;
+    char *repr = snewn(chlen + 1, char);
+    int i;
+
+    assert(board);
+
+    /* build the first line ("^(\+---){n}\+$") */
+    for (i = 0; i < w; ++i) {
+        repr[4*i + 0] = '+';
+        repr[4*i + 1] = '-';
+        repr[4*i + 2] = '-';
+        repr[4*i + 3] = '-';
+    }
+    repr[4*i + 0] = '+';
+    repr[4*i + 1] = '\n';
+
+    /* ... and copy it onto the odd-numbered lines */
+    for (i = 0; i < h; ++i) memcpy(repr + (2*i + 2) * chw, repr, chw);
+
+    /* build the second line ("^(\|\t){n}\|$") */
+    for (i = 0; i < w; ++i) {
+        repr[chw + 4*i + 0] = '|';
+        repr[chw + 4*i + 1] = ' ';
+        repr[chw + 4*i + 2] = ' ';
+        repr[chw + 4*i + 3] = ' ';
+    }
+    repr[chw + 4*i + 0] = '|';
+    repr[chw + 4*i + 1] = '\n';
+
+    /* ... and copy it onto the even-numbered lines */
+    for (i = 1; i < h; ++i) memcpy(repr + (2*i + 1) * chw, repr + chw, chw);
+
+    /* fill in the numbers */
+    for (i = 0; i < sz; ++i) {
+        const int x = i % w;
+       const int y = i / w;
+       if (board[i] == EMPTY) continue;
+       repr[chw*(2*y + 1) + (4*x + 2)] = board[i] + '0';
+    }
+
+    repr[chlen] = '\0';
+    return repr;
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    const int w = state->shared->params.w;
+    const int h = state->shared->params.h;
+    return board_to_string(state->board, w, h);
+}
+
+/*****************************************************************************
+ * GAME GENERATION AND SOLVER                                                *
+ *****************************************************************************/
+
+static const int dx[4] = {-1, 1, 0, 0};
+static const int dy[4] = {0, 0, -1, 1};
+
+struct solver_state
+{
+    int *dsf;
+    int *board;
+    int *connected;
+    int nempty;
+
+    /* Used internally by learn_bitmap_deductions; kept here to avoid
+     * mallocing/freeing them every time that function is called. */
+    int *bm, *bmdsf, *bmminsize;
+};
+
+static void print_board(int *board, int w, int h) {
+    if (verbose) {
+       char *repr = board_to_string(board, w, h);
+       printv("%s\n", repr);
+       free(repr);
+    }
+}
+
+static game_state *new_game(midend *, const game_params *, const char *);
+static void free_game(game_state *);
+
+#define SENTINEL sz
+
+static int mark_region(int *board, int w, int h, int i, int n, int m) {
+    int j;
+
+    board[i] = -1;
+
+    for (j = 0; j < 4; ++j) {
+        const int x = (i % w) + dx[j], y = (i / w) + dy[j], ii = w*y + x;
+        if (x < 0 || x >= w || y < 0 || y >= h) continue;
+        if (board[ii] == m) return FALSE;
+        if (board[ii] != n) continue;
+        if (!mark_region(board, w, h, ii, n, m)) return FALSE;
+    }
+    return TRUE;
+}
+
+static int region_size(int *board, int w, int h, int i) {
+    const int sz = w * h;
+    int j, size, copy;
+    if (board[i] == 0) return 0;
+    copy = board[i];
+    mark_region(board, w, h, i, board[i], SENTINEL);
+    for (size = j = 0; j < sz; ++j) {
+        if (board[j] != -1) continue;
+        ++size;
+        board[j] = copy;
+    }
+    return size;
+}
+
+static void merge_ones(int *board, int w, int h)
+{
+    const int sz = w * h;
+    const int maxsize = min(max(max(w, h), 3), 9);
+    int i, j, k, change;
+    do {
+        change = FALSE;
+        for (i = 0; i < sz; ++i) {
+            if (board[i] != 1) continue;
+
+            for (j = 0; j < 4; ++j, board[i] = 1) {
+                const int x = (i % w) + dx[j], y = (i / w) + dy[j];
+                int oldsize, newsize, ok, ii = w*y + x;
+                if (x < 0 || x >= w || y < 0 || y >= h) continue;
+                if (board[ii] == maxsize) continue;
+
+                oldsize = board[ii];
+                board[i] = oldsize;
+                newsize = region_size(board, w, h, i);
+
+                if (newsize > maxsize) continue;
+
+                ok = mark_region(board, w, h, i, oldsize, newsize);
+
+                for (k = 0; k < sz; ++k)
+                    if (board[k] == -1)
+                        board[k] = ok ? newsize : oldsize;
+
+                if (ok) break;
+            }
+            if (j < 4) change = TRUE;
+        }
+    } while (change);
+}
+
+/* generate a random valid board; uses validate_board. */
+static void make_board(int *board, int w, int h, random_state *rs) {
+    const int sz = w * h;
+
+    /* w=h=2 is a special case which requires a number > max(w, h) */
+    /* TODO prove that this is the case ONLY for w=h=2. */
+    const int maxsize = min(max(max(w, h), 3), 9);
+
+    /* Note that if 1 in {w, h} then it's impossible to have a region
+     * of size > w*h, so the special case only affects w=h=2. */
+
+    int i, change, *dsf;
+
+    assert(w >= 1);
+    assert(h >= 1);
+    assert(board);
+
+    /* I abuse the board variable: when generating the puzzle, it
+     * contains a shuffled list of numbers {0, ..., sz-1}. */
+    for (i = 0; i < sz; ++i) board[i] = i;
+
+    dsf = snewn(sz, int);
+retry:
+    dsf_init(dsf, sz);
+    shuffle(board, sz, sizeof (int), rs);
+
+    do {
+        change = FALSE; /* as long as the board potentially has errors */
+        for (i = 0; i < sz; ++i) {
+            const int square = dsf_canonify(dsf, board[i]);
+            const int size = dsf_size(dsf, square);
+            int merge = SENTINEL, min = maxsize - size + 1, error = FALSE;
+            int neighbour, neighbour_size, j;
+
+            for (j = 0; j < 4; ++j) {
+                const int x = (board[i] % w) + dx[j];
+                const int y = (board[i] / w) + dy[j];
+                if (x < 0 || x >= w || y < 0 || y >= h) continue;
+
+                neighbour = dsf_canonify(dsf, w*y + x);
+                if (square == neighbour) continue;
+
+                neighbour_size = dsf_size(dsf, neighbour);
+                if (size == neighbour_size) error = TRUE;
+
+                /* find the smallest neighbour to merge with, which
+                 * wouldn't make the region too large.  (This is
+                 * guaranteed by the initial value of `min'.) */
+                if (neighbour_size < min) {
+                    min = neighbour_size;
+                    merge = neighbour;
+                }
+            }
+
+            /* if this square is not in error, leave it be */
+            if (!error) continue;
+
+            /* if it is, but we can't fix it, retry the whole board.
+             * Maybe we could fix it by merging the conflicting
+             * neighbouring region(s) into some of their neighbours,
+             * but just restarting works out fine. */
+            if (merge == SENTINEL) goto retry;
+
+            /* merge with the smallest neighbouring workable region. */
+            dsf_merge(dsf, square, merge);
+            change = TRUE;
+        }
+    } while (change);
+
+    for (i = 0; i < sz; ++i) board[i] = dsf_size(dsf, i);
+    merge_ones(board, w, h);
+
+    sfree(dsf);
+}
+
+static void merge(int *dsf, int *connected, int a, int b) {
+    int c;
+    assert(dsf);
+    assert(connected);
+    a = dsf_canonify(dsf, a);
+    b = dsf_canonify(dsf, b);
+    if (a == b) return;
+    dsf_merge(dsf, a, b);
+    c = connected[a];
+    connected[a] = connected[b];
+    connected[b] = c;
+}
+
+static void *memdup(const void *ptr, size_t len, size_t esz) {
+    void *dup = smalloc(len * esz);
+    assert(ptr);
+    memcpy(dup, ptr, len * esz);
+    return dup;
+}
+
+static void expand(struct solver_state *s, int w, int h, int t, int f) {
+    int j;
+    assert(s);
+    assert(s->board[t] == EMPTY); /* expand to empty square */
+    assert(s->board[f] != EMPTY); /* expand from non-empty square */
+    printv(
+       "learn: expanding %d from (%d, %d) into (%d, %d)\n",
+       s->board[f], f % w, f / w, t % w, t / w);
+    s->board[t] = s->board[f];
+    for (j = 0; j < 4; ++j) {
+        const int x = (t % w) + dx[j];
+        const int y = (t / w) + dy[j];
+        const int idx = w*y + x;
+        if (x < 0 || x >= w || y < 0 || y >= h) continue;
+        if (s->board[idx] != s->board[t]) continue;
+        merge(s->dsf, s->connected, t, idx);
+    }
+    --s->nempty;
+}
+
+static void clear_count(int *board, int sz) {
+    int i;
+    for (i = 0; i < sz; ++i) {
+        if (board[i] >= 0) continue;
+        else if (board[i] == -SENTINEL) board[i] = EMPTY;
+        else board[i] = -board[i];
+    }
+}
+
+static void flood_count(int *board, int w, int h, int i, int n, int *c) {
+    const int sz = w * h;
+    int k;
+
+    if (board[i] == EMPTY) board[i] = -SENTINEL;
+    else if (board[i] == n) board[i] = -board[i];
+    else return;
+
+    if (--*c == 0) return;
+
+    for (k = 0; k < 4; ++k) {
+        const int x = (i % w) + dx[k];
+        const int y = (i / w) + dy[k];
+        const int idx = w*y + x;
+        if (x < 0 || x >= w || y < 0 || y >= h) continue;
+        flood_count(board, w, h, idx, n, c);
+       if (*c == 0) return;
+    }
+}
+
+static int check_capacity(int *board, int w, int h, int i) {
+    int n = board[i];
+    flood_count(board, w, h, i, board[i], &n);
+    clear_count(board, w * h);
+    return n == 0;
+}
+
+static int expandsize(const int *board, int *dsf, int w, int h, int i, int n) {
+    int j;
+    int nhits = 0;
+    int hits[4];
+    int size = 1;
+    for (j = 0; j < 4; ++j) {
+        const int x = (i % w) + dx[j];
+        const int y = (i / w) + dy[j];
+        const int idx = w*y + x;
+        int root;
+        int m;
+        if (x < 0 || x >= w || y < 0 || y >= h) continue;
+        if (board[idx] != n) continue;
+        root = dsf_canonify(dsf, idx);
+        for (m = 0; m < nhits && root != hits[m]; ++m);
+        if (m < nhits) continue;
+       printv("\t  (%d, %d) contrib %d to size\n", x, y, dsf[root] >> 2);
+        size += dsf_size(dsf, root);
+        assert(dsf_size(dsf, root) >= 1);
+        hits[nhits++] = root;
+    }
+    return size;
+}
+
+/*
+ *  +---+---+---+---+---+---+---+
+ *  | 6 |   |   | 2 |   |   | 2 |
+ *  +---+---+---+---+---+---+---+
+ *  |   | 3 |   | 6 |   | 3 |   |
+ *  +---+---+---+---+---+---+---+
+ *  | 3 |   |   |   |   |   | 1 |
+ *  +---+---+---+---+---+---+---+
+ *  |   | 2 | 3 |   | 4 | 2 |   |
+ *  +---+---+---+---+---+---+---+
+ *  | 2 |   |   |   |   |   | 3 |
+ *  +---+---+---+---+---+---+---+
+ *  |   | 5 |   | 1 |   | 4 |   |
+ *  +---+---+---+---+---+---+---+
+ *  | 4 |   |   | 3 |   |   | 3 |
+ *  +---+---+---+---+---+---+---+
+ */
+
+/* Solving techniques:
+ *
+ * CONNECTED COMPONENT FORCED EXPANSION (too big):
+ * When a CC can only be expanded in one direction, because all the
+ * other ones would make the CC too big.
+ *  +---+---+---+---+---+
+ *  | 2 | 2 |   | 2 | _ |
+ *  +---+---+---+---+---+
+ *
+ * CONNECTED COMPONENT FORCED EXPANSION (too small):
+ * When a CC must include a particular square, because otherwise there
+ * would not be enough room to complete it.  This includes squares not
+ * adjacent to the CC through learn_critical_square.
+ *  +---+---+
+ *  | 2 | _ |
+ *  +---+---+
+ *
+ * DROPPING IN A ONE:
+ * When an empty square has no neighbouring empty squares and only a 1
+ * will go into the square (or other CCs would be too big).
+ *  +---+---+---+
+ *  | 2 | 2 | _ |
+ *  +---+---+---+
+ *
+ * TODO: generalise DROPPING IN A ONE: find the size of the CC of
+ * empty squares and a list of all adjacent numbers.  See if only one
+ * number in {1, ..., size} u {all adjacent numbers} is possible.
+ * Probably this is only effective for a CC size < n for some n (4?)
+ *
+ * TODO: backtracking.
+ */
+
+static void filled_square(struct solver_state *s, int w, int h, int i) {
+    int j;
+    for (j = 0; j < 4; ++j) {
+       const int x = (i % w) + dx[j];
+       const int y = (i / w) + dy[j];
+       const int idx = w*y + x;
+       if (x < 0 || x >= w || y < 0 || y >= h) continue;
+       if (s->board[i] == s->board[idx])
+           merge(s->dsf, s->connected, i, idx);
+    }
+}
+
+static void init_solver_state(struct solver_state *s, int w, int h) {
+    const int sz = w * h;
+    int i;
+    assert(s);
+
+    s->nempty = 0;
+    for (i = 0; i < sz; ++i) s->connected[i] = i;
+    for (i = 0; i < sz; ++i)
+        if (s->board[i] == EMPTY) ++s->nempty;
+        else filled_square(s, w, h, i);
+}
+
+static int learn_expand_or_one(struct solver_state *s, int w, int h) {
+    const int sz = w * h;
+    int i;
+    int learn = FALSE;
+
+    assert(s);
+
+    for (i = 0; i < sz; ++i) {
+       int j;
+       int one = TRUE;
+
+       if (s->board[i] != EMPTY) continue;
+
+       for (j = 0; j < 4; ++j) {
+           const int x = (i % w) + dx[j];
+           const int y = (i / w) + dy[j];
+           const int idx = w*y + x;
+           if (x < 0 || x >= w || y < 0 || y >= h) continue;
+           if (s->board[idx] == EMPTY) {
+               one = FALSE;
+               continue;
+           }
+           if (one &&
+               (s->board[idx] == 1 ||
+                (s->board[idx] >= expandsize(s->board, s->dsf, w, h,
+                                             i, s->board[idx]))))
+               one = FALSE;
+           if (dsf_size(s->dsf, idx) == s->board[idx]) continue;
+           assert(s->board[i] == EMPTY);
+           s->board[i] = -SENTINEL;
+           if (check_capacity(s->board, w, h, idx)) continue;
+           assert(s->board[i] == EMPTY);
+           printv("learn: expanding in one\n");
+           expand(s, w, h, i, idx);
+           learn = TRUE;
+           break;
+       }
+
+       if (j == 4 && one) {
+           printv("learn: one at (%d, %d)\n", i % w, i / w);
+           assert(s->board[i] == EMPTY);
+           s->board[i] = 1;
+           assert(s->nempty);
+           --s->nempty;
+           learn = TRUE;
+       }
+    }
+    return learn;
+}
+
+static int learn_blocked_expansion(struct solver_state *s, int w, int h) {
+    const int sz = w * h;
+    int i;
+    int learn = FALSE;
+
+    assert(s);
+    /* for every connected component */
+    for (i = 0; i < sz; ++i) {
+        int exp = SENTINEL;
+        int j;
+
+       if (s->board[i] == EMPTY) continue;
+        j = dsf_canonify(s->dsf, i);
+
+        /* (but only for each connected component) */
+        if (i != j) continue;
+
+        /* (and not if it's already complete) */
+        if (dsf_size(s->dsf, j) == s->board[j]) continue;
+
+        /* for each square j _in_ the connected component */
+        do {
+            int k;
+            printv("  looking at (%d, %d)\n", j % w, j / w);
+
+            /* for each neighbouring square (idx) */
+            for (k = 0; k < 4; ++k) {
+                const int x = (j % w) + dx[k];
+                const int y = (j / w) + dy[k];
+                const int idx = w*y + x;
+                int size;
+                /* int l;
+                   int nhits = 0;
+                   int hits[4]; */
+                if (x < 0 || x >= w || y < 0 || y >= h) continue;
+                if (s->board[idx] != EMPTY) continue;
+                if (exp == idx) continue;
+                printv("\ttrying to expand onto (%d, %d)\n", x, y);
+
+                /* find out the would-be size of the new connected
+                 * component if we actually expanded into idx */
+                /*
+                size = 1;
+                for (l = 0; l < 4; ++l) {
+                    const int lx = x + dx[l];
+                    const int ly = y + dy[l];
+                    const int idxl = w*ly + lx;
+                    int root;
+                    int m;
+                    if (lx < 0 || lx >= w || ly < 0 || ly >= h) continue;
+                    if (board[idxl] != board[j]) continue;
+                    root = dsf_canonify(dsf, idxl);
+                    for (m = 0; m < nhits && root != hits[m]; ++m);
+                    if (m != nhits) continue;
+                    // printv("\t  (%d, %d) contributed %d to size\n", lx, ly, dsf[root] >> 2);
+                    size += dsf_size(dsf, root);
+                    assert(dsf_size(dsf, root) >= 1);
+                    hits[nhits++] = root;
+                }
+                */
+
+                size = expandsize(s->board, s->dsf, w, h, idx, s->board[j]);
+
+                /* ... and see if that size is too big, or if we
+                 * have other expansion candidates.  Otherwise
+                 * remember the (so far) only candidate. */
+
+                printv("\tthat would give a size of %d\n", size);
+                if (size > s->board[j]) continue;
+                /* printv("\tnow knowing %d expansions\n", nexpand + 1); */
+                if (exp != SENTINEL) goto next_i;
+                assert(exp != idx);
+                exp = idx;
+            }
+
+            j = s->connected[j]; /* next square in the same CC */
+            assert(s->board[i] == s->board[j]);
+        } while (j != i);
+        /* end: for each square j _in_ the connected component */
+
+       if (exp == SENTINEL) continue;
+       printv("learning to expand\n");
+       expand(s, w, h, exp, i);
+       learn = TRUE;
+
+        next_i:
+        ;
+    }
+    /* end: for each connected component */
+    return learn;
+}
+
+static int learn_critical_square(struct solver_state *s, int w, int h) {
+    const int sz = w * h;
+    int i;
+    int learn = FALSE;
+    assert(s);
+
+    /* for each connected component */
+    for (i = 0; i < sz; ++i) {
+       int j, slack;
+       if (s->board[i] == EMPTY) continue;
+       if (i != dsf_canonify(s->dsf, i)) continue;
+       slack = s->board[i] - dsf_size(s->dsf, i);
+       if (slack == 0) continue;
+       assert(s->board[i] != 1);
+       /* for each empty square */
+       for (j = 0; j < sz; ++j) {
+           if (s->board[j] == EMPTY) {
+               /* if it's too far away from the CC, don't bother */
+               int k = i, jx = j % w, jy = j / w;
+               do {
+                   int kx = k % w, ky = k / w;
+                   if (abs(kx - jx) + abs(ky - jy) <= slack) break;
+                   k = s->connected[k];
+               } while (i != k);
+               if (i == k) continue; /* not within range */
+           } else continue;
+           s->board[j] = -SENTINEL;
+           if (check_capacity(s->board, w, h, i)) continue;
+           /* if not expanding s->board[i] to s->board[j] implies
+            * that s->board[i] can't reach its full size, ... */
+           assert(s->nempty);
+           printv(
+               "learn: ds %d at (%d, %d) blocking (%d, %d)\n",
+               s->board[i], j % w, j / w, i % w, i / w);
+           --s->nempty;
+           s->board[j] = s->board[i];
+           filled_square(s, w, h, j);
+           learn = TRUE;
+       }
+    }
+    return learn;
+}
+
+#if 0
+static void print_bitmap(int *bitmap, int w, int h) {
+    if (verbose) {
+       int x, y;
+       for (y = 0; y < h; y++) {
+           for (x = 0; x < w; x++) {
+               printv(" %03x", bm[y*w+x]);
+           }
+           printv("\n");
+       }
+    }
+}
+#endif
+
+static int learn_bitmap_deductions(struct solver_state *s, int w, int h)
+{
+    const int sz = w * h;
+    int *bm = s->bm;
+    int *dsf = s->bmdsf;
+    int *minsize = s->bmminsize;
+    int x, y, i, j, n;
+    int learn = FALSE;
+
+    /*
+     * This function does deductions based on building up a bitmap
+     * which indicates the possible numbers that can appear in each
+     * grid square. If we can rule out all but one possibility for a
+     * particular square, then we've found out the value of that
+     * square. In particular, this is one of the few forms of
+     * deduction capable of inferring the existence of a 'ghost
+     * region', i.e. a region which has none of its squares filled in
+     * at all.
+     *
+     * The reasoning goes like this. A currently unfilled square S can
+     * turn out to contain digit n in exactly two ways: either S is
+     * part of an n-region which also includes some currently known
+     * connected component of squares with n in, or S is part of an
+     * n-region separate from _all_ currently known connected
+     * components. If we can rule out both possibilities, then square
+     * S can't contain digit n at all.
+     *
+     * The former possibility: if there's a region of size n
+     * containing both S and some existing component C, then that
+     * means the distance from S to C must be small enough that C
+     * could be extended to include S without becoming too big. So we
+     * can do a breadth-first search out from all existing components
+     * with n in them, to identify all the squares which could be
+     * joined to any of them.
+     *
+     * The latter possibility: if there's a region of size n that
+     * doesn't contain _any_ existing component, then it also can't
+     * contain any square adjacent to an existing component either. So
+     * we can identify all the EMPTY squares not adjacent to any
+     * existing square with n in, and group them into connected
+     * components; then any component of size less than n is ruled
+     * out, because there wouldn't be room to create a completely new
+     * n-region in it.
+     *
+     * In fact we process these possibilities in the other order.
+     * First we find all the squares not adjacent to an existing
+     * square with n in; then we winnow those by removing too-small
+     * connected components, to get the set of squares which could
+     * possibly be part of a brand new n-region; and finally we do the
+     * breadth-first search to add in the set of squares which could
+     * possibly be added to some existing n-region.
+     */
+
+    /*
+     * Start by initialising our bitmap to 'all numbers possible in
+     * all squares'.
+     */
+    for (y = 0; y < h; y++)
+       for (x = 0; x < w; x++)
+           bm[y*w+x] = (1 << 10) - (1 << 1); /* bits 1,2,...,9 now set */
+#if 0
+    printv("initial bitmap:\n");
+    print_bitmap(bm, w, h);
+#endif
+
+    /*
+     * Now completely zero out the bitmap for squares that are already
+     * filled in (we aren't interested in those anyway). Also, for any
+     * filled square, eliminate its number from all its neighbours
+     * (because, as discussed above, the neighbours couldn't be part
+     * of a _new_ region with that number in it, and that's the case
+     * we consider first).
+     */
+    for (y = 0; y < h; y++) {
+       for (x = 0; x < w; x++) {
+           i = y*w+x;
+           n = s->board[i];
+
+           if (n != EMPTY) {
+               bm[i] = 0;
+
+               if (x > 0)
+                   bm[i-1] &= ~(1 << n);
+               if (x+1 < w)
+                   bm[i+1] &= ~(1 << n);
+               if (y > 0)
+                   bm[i-w] &= ~(1 << n);
+               if (y+1 < h)
+                   bm[i+w] &= ~(1 << n);
+           }
+       }
+    }
+#if 0
+    printv("bitmap after filled squares:\n");
+    print_bitmap(bm, w, h);
+#endif
+
+    /*
+     * Now, for each n, we separately find the connected components of
+     * squares for which n is still a possibility. Then discard any
+     * component of size < n, because that component is too small to
+     * have a completely new n-region in it.
+     */
+    for (n = 1; n <= 9; n++) {
+       dsf_init(dsf, sz);
+
+       /* Build the dsf */
+       for (y = 0; y < h; y++)
+           for (x = 0; x+1 < w; x++)
+               if (bm[y*w+x] & bm[y*w+(x+1)] & (1 << n))
+                   dsf_merge(dsf, y*w+x, y*w+(x+1));
+       for (y = 0; y+1 < h; y++)
+           for (x = 0; x < w; x++)
+               if (bm[y*w+x] & bm[(y+1)*w+x] & (1 << n))
+                   dsf_merge(dsf, y*w+x, (y+1)*w+x);
+
+       /* Query the dsf */
+       for (i = 0; i < sz; i++)
+           if ((bm[i] & (1 << n)) && dsf_size(dsf, i) < n)
+               bm[i] &= ~(1 << n);
+    }
+#if 0
+    printv("bitmap after winnowing small components:\n");
+    print_bitmap(bm, w, h);
+#endif
+
+    /*
+     * Now our bitmap includes every square which could be part of a
+     * completely new region, of any size. Extend it to include
+     * squares which could be part of an existing region.
+     */
+    for (n = 1; n <= 9; n++) {
+       /*
+        * We're going to do a breadth-first search starting from
+        * existing connected components with cell value n, to find
+        * all cells they might possibly extend into.
+        *
+        * The quantity we compute, for each square, is 'minimum size
+        * that any existing CC would have to have if extended to
+        * include this square'. So squares already _in_ an existing
+        * CC are initialised to the size of that CC; then we search
+        * outwards using the rule that if a square's score is j, then
+        * its neighbours can't score more than j+1.
+        *
+        * Scores are capped at n+1, because if a square scores more
+        * than n then that's enough to know it can't possibly be
+        * reached by extending an existing region - we don't need to
+        * know exactly _how far_ out of reach it is.
+        */
+       for (i = 0; i < sz; i++) {
+           if (s->board[i] == n) {
+               /* Square is part of an existing CC. */
+               minsize[i] = dsf_size(s->dsf, i);
+           } else {
+               /* Otherwise, initialise to the maximum score n+1;
+                * we'll reduce this later if we find a neighbouring
+                * square with a lower score. */
+               minsize[i] = n+1;
+           }
+       }
+
+       for (j = 1; j < n; j++) {
+           /*
+            * Find neighbours of cells scoring j, and set their score
+            * to at most j+1.
+            *
+            * Doing the BFS this way means we need n passes over the
+            * grid, which isn't entirely optimal but it seems to be
+            * fast enough for the moment. This could probably be
+            * improved by keeping a linked-list queue of cells in
+            * some way, but I think you'd have to be a bit careful to
+            * insert things into the right place in the queue; this
+            * way is easier not to get wrong.
+            */
+           for (y = 0; y < h; y++) {
+               for (x = 0; x < w; x++) {
+                   i = y*w+x;
+                   if (minsize[i] == j) {
+                       if (x > 0 && minsize[i-1] > j+1)
+                           minsize[i-1] = j+1;
+                       if (x+1 < w && minsize[i+1] > j+1)
+                           minsize[i+1] = j+1;
+                       if (y > 0 && minsize[i-w] > j+1)
+                           minsize[i-w] = j+1;
+                       if (y+1 < h && minsize[i+w] > j+1)
+                           minsize[i+w] = j+1;
+                   }
+               }
+           }
+       }
+
+       /*
+        * Now, every cell scoring at most n should have its 1<<n bit
+        * in the bitmap reinstated, because we've found that it's
+        * potentially reachable by extending an existing CC.
+        */
+       for (i = 0; i < sz; i++)
+           if (minsize[i] <= n)
+               bm[i] |= 1<<n;
+    }
+#if 0
+    printv("bitmap after bfs:\n");
+    print_bitmap(bm, w, h);
+#endif
+
+    /*
+     * Now our bitmap is complete. Look for entries with only one bit
+     * set; those are squares with only one possible number, in which
+     * case we can fill that number in.
+     */
+    for (i = 0; i < sz; i++) {
+       if (bm[i] && !(bm[i] & (bm[i]-1))) { /* is bm[i] a power of two? */
+           int val = bm[i];
+
+           /* Integer log2, by simple binary search. */
+           n = 0;
+           if (val >> 8) { val >>= 8; n += 8; }
+           if (val >> 4) { val >>= 4; n += 4; }
+           if (val >> 2) { val >>= 2; n += 2; }
+           if (val >> 1) { val >>= 1; n += 1; }
+
+           /* Double-check that we ended up with a sensible
+            * answer. */
+           assert(1 <= n);
+           assert(n <= 9);
+           assert(bm[i] == (1 << n));
+
+           if (s->board[i] == EMPTY) {
+               printv("learn: %d is only possibility at (%d, %d)\n",
+                      n, i % w, i / w);
+               s->board[i] = n;
+               filled_square(s, w, h, i);
+               assert(s->nempty);
+               --s->nempty;
+               learn = TRUE;
+           }
+       }
+    }
+
+    return learn;
+}
+
+static int solver(const int *orig, int w, int h, char **solution) {
+    const int sz = w * h;
+
+    struct solver_state ss;
+    ss.board = memdup(orig, sz, sizeof (int));
+    ss.dsf = snew_dsf(sz); /* eqv classes: connected components */
+    ss.connected = snewn(sz, int); /* connected[n] := n.next; */
+    /* cyclic disjoint singly linked lists, same partitioning as dsf.
+     * The lists lets you iterate over a partition given any member */
+    ss.bm = snewn(sz, int);
+    ss.bmdsf = snew_dsf(sz);
+    ss.bmminsize = snewn(sz, int);
+
+    printv("trying to solve this:\n");
+    print_board(ss.board, w, h);
+
+    init_solver_state(&ss, w, h);
+    do {
+       if (learn_blocked_expansion(&ss, w, h)) continue;
+       if (learn_expand_or_one(&ss, w, h)) continue;
+       if (learn_critical_square(&ss, w, h)) continue;
+       if (learn_bitmap_deductions(&ss, w, h)) continue;
+       break;
+    } while (ss.nempty);
+
+    printv("best guess:\n");
+    print_board(ss.board, w, h);
+
+    if (solution) {
+        int i;
+        *solution = snewn(sz + 2, char);
+        **solution = 's';
+        for (i = 0; i < sz; ++i) (*solution)[i + 1] = ss.board[i] + '0';
+        (*solution)[sz + 1] = '\0';
+        /* We don't need the \0 for execute_move (the only user)
+         * I'm just being printf-friendly in case I wanna print */
+    }
+
+    sfree(ss.dsf);
+    sfree(ss.board);
+    sfree(ss.connected);
+    sfree(ss.bm);
+    sfree(ss.bmdsf);
+    sfree(ss.bmminsize);
+
+    return !ss.nempty;
+}
+
+static int *make_dsf(int *dsf, int *board, const int w, const int h) {
+    const int sz = w * h;
+    int i;
+
+    if (!dsf)
+        dsf = snew_dsf(w * h);
+    else
+        dsf_init(dsf, w * h);
+
+    for (i = 0; i < sz; ++i) {
+        int j;
+        for (j = 0; j < 4; ++j) {
+            const int x = (i % w) + dx[j];
+            const int y = (i / w) + dy[j];
+            const int k = w*y + x;
+            if (x < 0 || x >= w || y < 0 || y >= h) continue;
+            if (board[i] == board[k]) dsf_merge(dsf, i, k);
+        }
+    }
+    return dsf;
+}
+
+static void minimize_clue_set(int *board, int w, int h, random_state *rs)
+{
+    const int sz = w * h;
+    int *shuf = snewn(sz, int), i;
+    int *dsf, *next;
+
+    for (i = 0; i < sz; ++i) shuf[i] = i;
+    shuffle(shuf, sz, sizeof (int), rs);
+
+    /*
+     * First, try to eliminate an entire region at a time if possible,
+     * because inferring the existence of a completely unclued region
+     * is a particularly good aspect of this puzzle type and we want
+     * to encourage it to happen.
+     *
+     * Begin by identifying the regions as linked lists of cells using
+     * the 'next' array.
+     */
+    dsf = make_dsf(NULL, board, w, h);
+    next = snewn(sz, int);
+    for (i = 0; i < sz; ++i) {
+       int j = dsf_canonify(dsf, i);
+       if (i == j) {
+           /* First cell of a region; set next[i] = -1 to indicate
+            * end-of-list. */
+           next[i] = -1;
+       } else {
+           /* Add this cell to a region which already has a
+            * linked-list head, by pointing the canonical element j
+            * at this one, and pointing this one in turn at wherever
+            * j previously pointed. (This should end up with the
+            * elements linked in the order 1,n,n-1,n-2,...,2, which
+            * is a bit weird-looking, but any order is fine.)
+            */
+           assert(j < i);
+           next[i] = next[j];
+           next[j] = i;
+       }
+    }
+
+    /*
+     * Now loop over the grid cells in our shuffled order, and each
+     * time we encounter a region for the first time, try to remove it
+     * all. Then we set next[canonical index] to -2 rather than -1, to
+     * mark it as already tried.
+     *
+     * Doing this in a loop over _cells_, rather than extracting and
+     * shuffling a list of _regions_, is intended to skew the
+     * probabilities towards trying to remove larger regions first
+     * (but without anything as crudely predictable as enforcing that
+     * we _always_ process regions in descending size order). Region
+     * removals might well be mutually exclusive, and larger ghost
+     * regions are more interesting, so we want to bias towards them
+     * if we can.
+     */
+    for (i = 0; i < sz; ++i) {
+       int j = dsf_canonify(dsf, shuf[i]);
+       if (next[j] != -2) {
+           int tmp = board[j];
+           int k;
+
+           /* Blank out the whole thing. */
+           for (k = j; k >= 0; k = next[k])
+               board[k] = EMPTY;
+
+           if (!solver(board, w, h, NULL)) {
+               /* Wasn't still solvable; reinstate it all */
+               for (k = j; k >= 0; k = next[k])
+                   board[k] = tmp;
+           }
+
+           /* Either way, don't try this region again. */
+           next[j] = -2;
+       }
+    }
+    sfree(next);
+    sfree(dsf);
+
+    /*
+     * Now go through individual cells, in the same shuffled order,
+     * and try to remove each one by itself.
+     */
+    for (i = 0; i < sz; ++i) {
+        int tmp = board[shuf[i]];
+        board[shuf[i]] = EMPTY;
+        if (!solver(board, w, h, NULL)) board[shuf[i]] = tmp;
+    }
+
+    sfree(shuf);
+}
+
+static int encode_run(char *buffer, int run)
+{
+    int i = 0;
+    for (; run > 26; run -= 26)
+       buffer[i++] = 'z';
+    if (run)
+       buffer[i++] = 'a' - 1 + run;
+    return i;
+}
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                           char **aux, int interactive)
+{
+    const int w = params->w, h = params->h, sz = w * h;
+    int *board = snewn(sz, int), i, j, run;
+    char *description = snewn(sz + 1, char);
+
+    make_board(board, w, h, rs);
+    minimize_clue_set(board, w, h, rs);
+
+    for (run = j = i = 0; i < sz; ++i) {
+        assert(board[i] >= 0);
+        assert(board[i] < 10);
+       if (board[i] == 0) {
+           ++run;
+       } else {
+           j += encode_run(description + j, run);
+           run = 0;
+           description[j++] = board[i] + '0';
+       }
+    }
+    j += encode_run(description + j, run);
+    description[j++] = '\0';
+
+    sfree(board);
+
+    return sresize(description, j, char);
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    const int sz = params->w * params->h;
+    const char m = '0' + max(max(params->w, params->h), 3);
+    int area;
+
+    for (area = 0; *desc; ++desc) {
+       if (*desc >= 'a' && *desc <= 'z') area += *desc - 'a' + 1;
+       else if (*desc >= '0' && *desc <= m) ++area;
+       else {
+           static char s[] =  "Invalid character '%""' in game description";
+           int n = sprintf(s, "Invalid character '%1c' in game description",
+                           *desc);
+           assert(n + 1 <= lenof(s)); /* +1 for the terminating NUL */
+           return s;
+       }
+       if (area > sz) return "Too much data to fit in grid";
+    }
+    return (area < sz) ? "Not enough data to fill grid" : NULL;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    game_state *state = snew(game_state);
+    int sz = params->w * params->h;
+    int i;
+
+    state->cheated = state->completed = FALSE;
+    state->shared = snew(struct shared_state);
+    state->shared->refcnt = 1;
+    state->shared->params = *params; /* struct copy */
+    state->shared->clues = snewn(sz, int);
+
+    for (i = 0; *desc; ++desc) {
+       if (*desc >= 'a' && *desc <= 'z') {
+           int j = *desc - 'a' + 1;
+           assert(i + j <= sz);
+           for (; j; --j) state->shared->clues[i++] = 0;
+       } else state->shared->clues[i++] = *desc - '0';
+    }
+    state->board = memdup(state->shared->clues, sz, sizeof (int));
+
+    return state;
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    const int sz = state->shared->params.w * state->shared->params.h;
+    game_state *ret = snew(game_state);
+
+    ret->board = memdup(state->board, sz, sizeof (int));
+    ret->shared = state->shared;
+    ret->cheated = state->cheated;
+    ret->completed = state->completed;
+    ++ret->shared->refcnt;
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    assert(state);
+    sfree(state->board);
+    if (--state->shared->refcnt == 0) {
+        sfree(state->shared->clues);
+        sfree(state->shared);
+    }
+    sfree(state);
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    if (aux == NULL) {
+        const int w = state->shared->params.w;
+        const int h = state->shared->params.h;
+       char *new_aux;
+        if (!solver(state->board, w, h, &new_aux))
+            *error = "Sorry, I couldn't find a solution";
+       return new_aux;
+    }
+    return dupstr(aux);
+}
+
+/*****************************************************************************
+ * USER INTERFACE STATE AND ACTION                                           *
+ *****************************************************************************/
+
+struct game_ui {
+    int *sel; /* w*h highlighted squares, or NULL */
+    int cur_x, cur_y, cur_visible, keydragging;
+};
+
+static game_ui *new_ui(const game_state *state)
+{
+    game_ui *ui = snew(game_ui);
+
+    ui->sel = NULL;
+    ui->cur_x = ui->cur_y = ui->cur_visible = ui->keydragging = 0;
+
+    return ui;
+}
+
+static void free_ui(game_ui *ui)
+{
+    if (ui->sel)
+        sfree(ui->sel);
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+    /* Clear any selection */
+    if (ui->sel) {
+        sfree(ui->sel);
+        ui->sel = NULL;
+    }
+    ui->keydragging = FALSE;
+}
+
+#define PREFERRED_TILE_SIZE 32
+#define TILE_SIZE (ds->tilesize)
+#define BORDER (TILE_SIZE / 2)
+#define BORDER_WIDTH (max(TILE_SIZE / 32, 1))
+
+struct game_drawstate {
+    struct game_params params;
+    int tilesize;
+    int started;
+    int *v, *flags;
+    int *dsf_scratch, *border_scratch;
+};
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    const int w = state->shared->params.w;
+    const int h = state->shared->params.h;
+
+    const int tx = (x + TILE_SIZE - BORDER) / TILE_SIZE - 1;
+    const int ty = (y + TILE_SIZE - BORDER) / TILE_SIZE - 1;
+
+    char *move = NULL;
+    int i;
+
+    assert(ui);
+    assert(ds);
+
+    button &= ~MOD_MASK;
+
+    if (button == LEFT_BUTTON || button == LEFT_DRAG) {
+        /* A left-click anywhere will clear the current selection. */
+        if (button == LEFT_BUTTON) {
+            if (ui->sel) {
+                sfree(ui->sel);
+                ui->sel = NULL;
+            }
+        }
+        if (tx >= 0 && tx < w && ty >= 0 && ty < h) {
+            if (!ui->sel) {
+                ui->sel = snewn(w*h, int);
+                memset(ui->sel, 0, w*h*sizeof(int));
+            }
+            if (!state->shared->clues[w*ty+tx])
+                ui->sel[w*ty+tx] = 1;
+        }
+        ui->cur_visible = 0;
+        return ""; /* redraw */
+    }
+
+    if (IS_CURSOR_MOVE(button)) {
+        ui->cur_visible = 1;
+        move_cursor(button, &ui->cur_x, &ui->cur_y, w, h, 0);
+       if (ui->keydragging) goto select_square;
+        return "";
+    }
+    if (button == CURSOR_SELECT) {
+        if (!ui->cur_visible) {
+            ui->cur_visible = 1;
+            return "";
+        }
+       ui->keydragging = !ui->keydragging;
+       if (!ui->keydragging) return "";
+
+      select_square:
+        if (!ui->sel) {
+            ui->sel = snewn(w*h, int);
+            memset(ui->sel, 0, w*h*sizeof(int));
+        }
+       if (!state->shared->clues[w*ui->cur_y + ui->cur_x])
+           ui->sel[w*ui->cur_y + ui->cur_x] = 1;
+       return "";
+    }
+    if (button == CURSOR_SELECT2) {
+       if (!ui->cur_visible) {
+           ui->cur_visible = 1;
+           return "";
+       }
+        if (!ui->sel) {
+            ui->sel = snewn(w*h, int);
+            memset(ui->sel, 0, w*h*sizeof(int));
+        }
+       ui->keydragging = FALSE;
+       if (!state->shared->clues[w*ui->cur_y + ui->cur_x])
+           ui->sel[w*ui->cur_y + ui->cur_x] ^= 1;
+       for (i = 0; i < w*h && !ui->sel[i]; i++);
+       if (i == w*h) {
+           sfree(ui->sel);
+           ui->sel = NULL;
+       }
+       return "";
+    }
+
+    if (button == '\b' || button == 27) {
+       sfree(ui->sel);
+       ui->sel = NULL;
+       ui->keydragging = FALSE;
+       return "";
+    }
+
+    if (button < '0' || button > '9') return NULL;
+    button -= '0';
+    if (button > (w == 2 && h == 2 ? 3 : max(w, h))) return NULL;
+    ui->keydragging = FALSE;
+
+    for (i = 0; i < w*h; i++) {
+        char buf[32];
+        if ((ui->sel && ui->sel[i]) ||
+            (!ui->sel && ui->cur_visible && (w*ui->cur_y+ui->cur_x) == i)) {
+            if (state->shared->clues[i] != 0) continue; /* in case cursor is on clue */
+            if (state->board[i] != button) {
+                sprintf(buf, "%s%d", move ? "," : "", i);
+                if (move) {
+                    move = srealloc(move, strlen(move)+strlen(buf)+1);
+                    strcat(move, buf);
+                } else {
+                    move = smalloc(strlen(buf)+1);
+                    strcpy(move, buf);
+                }
+            }
+        }
+    }
+    if (move) {
+        char buf[32];
+        sprintf(buf, "_%d", button);
+        move = srealloc(move, strlen(move)+strlen(buf)+1);
+        strcat(move, buf);
+    }
+    if (!ui->sel) return move ? move : NULL;
+    sfree(ui->sel);
+    ui->sel = NULL;
+    /* Need to update UI at least, as we cleared the selection */
+    return move ? move : "";
+}
+
+static game_state *execute_move(const game_state *state, const char *move)
+{
+    game_state *new_state = NULL;
+    const int sz = state->shared->params.w * state->shared->params.h;
+
+    if (*move == 's') {
+        int i = 0;
+        new_state = dup_game(state);
+        for (++move; i < sz; ++i) new_state->board[i] = move[i] - '0';
+        new_state->cheated = TRUE;
+    } else {
+        int value;
+        char *endptr, *delim = strchr(move, '_');
+        if (!delim) goto err;
+        value = strtol(delim+1, &endptr, 0);
+        if (*endptr || endptr == delim+1) goto err;
+        if (value < 0 || value > 9) goto err;
+        new_state = dup_game(state);
+        while (*move) {
+            const int i = strtol(move, &endptr, 0);
+            if (endptr == move) goto err;
+            if (i < 0 || i >= sz) goto err;
+            new_state->board[i] = value;
+            if (*endptr == '_') break;
+            if (*endptr != ',') goto err;
+            move = endptr + 1;
+        }
+    }
+
+    /*
+     * Check for completion.
+     */
+    if (!new_state->completed) {
+        const int w = new_state->shared->params.w;
+        const int h = new_state->shared->params.h;
+        const int sz = w * h;
+        int *dsf = make_dsf(NULL, new_state->board, w, h);
+        int i;
+        for (i = 0; i < sz && new_state->board[i] == dsf_size(dsf, i); ++i);
+        sfree(dsf);
+        if (i == sz)
+            new_state->completed = TRUE;
+    }
+
+    return new_state;
+
+err:
+    if (new_state) free_game(new_state);
+    return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+#define FLASH_TIME 0.4F
+
+#define COL_CLUE COL_GRID
+enum {
+    COL_BACKGROUND,
+    COL_GRID,
+    COL_HIGHLIGHT,
+    COL_CORRECT,
+    COL_ERROR,
+    COL_USER,
+    COL_CURSOR,
+    NCOLOURS
+};
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    *x = (params->w + 1) * tilesize;
+    *y = (params->h + 1) * tilesize;
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+
+    frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
+
+    ret[COL_GRID * 3 + 0] = 0.0F;
+    ret[COL_GRID * 3 + 1] = 0.0F;
+    ret[COL_GRID * 3 + 2] = 0.0F;
+
+    ret[COL_HIGHLIGHT * 3 + 0] = 0.85F * ret[COL_BACKGROUND * 3 + 0];
+    ret[COL_HIGHLIGHT * 3 + 1] = 0.85F * ret[COL_BACKGROUND * 3 + 1];
+    ret[COL_HIGHLIGHT * 3 + 2] = 0.85F * ret[COL_BACKGROUND * 3 + 2];
+
+    ret[COL_CORRECT * 3 + 0] = 0.9F * ret[COL_BACKGROUND * 3 + 0];
+    ret[COL_CORRECT * 3 + 1] = 0.9F * ret[COL_BACKGROUND * 3 + 1];
+    ret[COL_CORRECT * 3 + 2] = 0.9F * ret[COL_BACKGROUND * 3 + 2];
+
+    ret[COL_CURSOR * 3 + 0] = 0.5F * ret[COL_BACKGROUND * 3 + 0];
+    ret[COL_CURSOR * 3 + 1] = 0.5F * ret[COL_BACKGROUND * 3 + 1];
+    ret[COL_CURSOR * 3 + 2] = 0.5F * ret[COL_BACKGROUND * 3 + 2];
+
+    ret[COL_ERROR * 3 + 0] = 1.0F;
+    ret[COL_ERROR * 3 + 1] = 0.85F * ret[COL_BACKGROUND * 3 + 1];
+    ret[COL_ERROR * 3 + 2] = 0.85F * ret[COL_BACKGROUND * 3 + 2];
+
+    ret[COL_USER * 3 + 0] = 0.0F;
+    ret[COL_USER * 3 + 1] = 0.6F * ret[COL_BACKGROUND * 3 + 1];
+    ret[COL_USER * 3 + 2] = 0.0F;
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    struct game_drawstate *ds = snew(struct game_drawstate);
+    int i;
+
+    ds->tilesize = PREFERRED_TILE_SIZE;
+    ds->started = 0;
+    ds->params = state->shared->params;
+    ds->v = snewn(ds->params.w * ds->params.h, int);
+    ds->flags = snewn(ds->params.w * ds->params.h, int);
+    for (i = 0; i < ds->params.w * ds->params.h; i++)
+       ds->v[i] = ds->flags[i] = -1;
+    ds->border_scratch = snewn(ds->params.w * ds->params.h, int);
+    ds->dsf_scratch = NULL;
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds->v);
+    sfree(ds->flags);
+    sfree(ds->border_scratch);
+    sfree(ds->dsf_scratch);
+    sfree(ds);
+}
+
+#define BORDER_U   0x001
+#define BORDER_D   0x002
+#define BORDER_L   0x004
+#define BORDER_R   0x008
+#define BORDER_UR  0x010
+#define BORDER_DR  0x020
+#define BORDER_UL  0x040
+#define BORDER_DL  0x080
+#define HIGH_BG    0x100
+#define CORRECT_BG 0x200
+#define ERROR_BG   0x400
+#define USER_COL   0x800
+#define CURSOR_SQ 0x1000
+
+static void draw_square(drawing *dr, game_drawstate *ds, int x, int y,
+                        int n, int flags)
+{
+    assert(dr);
+    assert(ds);
+
+    /*
+     * Clip to the grid square.
+     */
+    clip(dr, BORDER + x*TILE_SIZE, BORDER + y*TILE_SIZE,
+        TILE_SIZE, TILE_SIZE);
+
+    /*
+     * Clear the square.
+     */
+    draw_rect(dr,
+              BORDER + x*TILE_SIZE,
+              BORDER + y*TILE_SIZE,
+              TILE_SIZE,
+              TILE_SIZE,
+              (flags & HIGH_BG ? COL_HIGHLIGHT :
+               flags & ERROR_BG ? COL_ERROR :
+               flags & CORRECT_BG ? COL_CORRECT : COL_BACKGROUND));
+
+    /*
+     * Draw the grid lines.
+     */
+    draw_line(dr, BORDER + x*TILE_SIZE, BORDER + y*TILE_SIZE,
+             BORDER + (x+1)*TILE_SIZE, BORDER + y*TILE_SIZE, COL_GRID);
+    draw_line(dr, BORDER + x*TILE_SIZE, BORDER + y*TILE_SIZE,
+             BORDER + x*TILE_SIZE, BORDER + (y+1)*TILE_SIZE, COL_GRID);
+
+    /*
+     * Draw the number.
+     */
+    if (n) {
+        char buf[2];
+        buf[0] = n + '0';
+        buf[1] = '\0';
+        draw_text(dr,
+                  (x + 1) * TILE_SIZE,
+                  (y + 1) * TILE_SIZE,
+                  FONT_VARIABLE,
+                  TILE_SIZE / 2,
+                  ALIGN_VCENTRE | ALIGN_HCENTRE,
+                  flags & USER_COL ? COL_USER : COL_CLUE,
+                  buf);
+    }
+
+    /*
+     * Draw bold lines around the borders.
+     */
+    if (flags & BORDER_L)
+        draw_rect(dr,
+                  BORDER + x*TILE_SIZE + 1,
+                  BORDER + y*TILE_SIZE + 1,
+                  BORDER_WIDTH,
+                  TILE_SIZE - 1,
+                  COL_GRID);
+    if (flags & BORDER_U)
+        draw_rect(dr,
+                  BORDER + x*TILE_SIZE + 1,
+                  BORDER + y*TILE_SIZE + 1,
+                  TILE_SIZE - 1,
+                  BORDER_WIDTH,
+                  COL_GRID);
+    if (flags & BORDER_R)
+        draw_rect(dr,
+                  BORDER + (x+1)*TILE_SIZE - BORDER_WIDTH,
+                  BORDER + y*TILE_SIZE + 1,
+                  BORDER_WIDTH,
+                  TILE_SIZE - 1,
+                  COL_GRID);
+    if (flags & BORDER_D)
+        draw_rect(dr,
+                  BORDER + x*TILE_SIZE + 1,
+                  BORDER + (y+1)*TILE_SIZE - BORDER_WIDTH,
+                  TILE_SIZE - 1,
+                  BORDER_WIDTH,
+                  COL_GRID);
+    if (flags & BORDER_UL)
+        draw_rect(dr,
+                  BORDER + x*TILE_SIZE + 1,
+                  BORDER + y*TILE_SIZE + 1,
+                  BORDER_WIDTH,
+                  BORDER_WIDTH,
+                  COL_GRID);
+    if (flags & BORDER_UR)
+        draw_rect(dr,
+                  BORDER + (x+1)*TILE_SIZE - BORDER_WIDTH,
+                  BORDER + y*TILE_SIZE + 1,
+                  BORDER_WIDTH,
+                  BORDER_WIDTH,
+                  COL_GRID);
+    if (flags & BORDER_DL)
+        draw_rect(dr,
+                  BORDER + x*TILE_SIZE + 1,
+                  BORDER + (y+1)*TILE_SIZE - BORDER_WIDTH,
+                  BORDER_WIDTH,
+                  BORDER_WIDTH,
+                  COL_GRID);
+    if (flags & BORDER_DR)
+        draw_rect(dr,
+                  BORDER + (x+1)*TILE_SIZE - BORDER_WIDTH,
+                  BORDER + (y+1)*TILE_SIZE - BORDER_WIDTH,
+                  BORDER_WIDTH,
+                  BORDER_WIDTH,
+                  COL_GRID);
+
+    if (flags & CURSOR_SQ) {
+        int coff = TILE_SIZE/8;
+        draw_rect_outline(dr,
+                          BORDER + x*TILE_SIZE + coff,
+                          BORDER + y*TILE_SIZE + coff,
+                          TILE_SIZE - coff*2,
+                          TILE_SIZE - coff*2,
+                          COL_CURSOR);
+    }
+
+    unclip(dr);
+
+    draw_update(dr,
+               BORDER + x*TILE_SIZE,
+               BORDER + y*TILE_SIZE,
+               TILE_SIZE,
+               TILE_SIZE);
+}
+
+static void draw_grid(drawing *dr, game_drawstate *ds, const game_state *state,
+                      const game_ui *ui, int flashy, int borders, int shading)
+{
+    const int w = state->shared->params.w;
+    const int h = state->shared->params.h;
+    int x;
+    int y;
+
+    /*
+     * Build a dsf for the board in its current state, to use for
+     * highlights and hints.
+     */
+    ds->dsf_scratch = make_dsf(ds->dsf_scratch, state->board, w, h);
+
+    /*
+     * Work out where we're putting borders between the cells.
+     */
+    for (y = 0; y < w*h; y++)
+       ds->border_scratch[y] = 0;
+
+    for (y = 0; y < h; y++)
+        for (x = 0; x < w; x++) {
+            int dx, dy;
+            int v1, s1, v2, s2;
+
+            for (dx = 0; dx <= 1; dx++) {
+                int border = FALSE;
+
+                dy = 1 - dx;
+
+                if (x+dx >= w || y+dy >= h)
+                    continue;
+
+                v1 = state->board[y*w+x];
+                v2 = state->board[(y+dy)*w+(x+dx)];
+                s1 = dsf_size(ds->dsf_scratch, y*w+x);
+                s2 = dsf_size(ds->dsf_scratch, (y+dy)*w+(x+dx));
+
+                /*
+                 * We only ever draw a border between two cells if
+                 * they don't have the same contents.
+                 */
+                if (v1 != v2) {
+                    /*
+                     * But in that situation, we don't always draw
+                     * a border. We do if the two cells both
+                     * contain actual numbers...
+                     */
+                    if (v1 && v2)
+                        border = TRUE;
+
+                    /*
+                     * ... or if at least one of them is a
+                     * completed or overfull omino.
+                     */
+                    if (v1 && s1 >= v1)
+                        border = TRUE;
+                    if (v2 && s2 >= v2)
+                        border = TRUE;
+                }
+
+                if (border)
+                    ds->border_scratch[y*w+x] |= (dx ? 1 : 2);
+            }
+        }
+
+    /*
+     * Actually do the drawing.
+     */
+    for (y = 0; y < h; ++y)
+        for (x = 0; x < w; ++x) {
+            /*
+             * Determine what we need to draw in this square.
+             */
+            int i = y*w+x, v = state->board[i];
+            int flags = 0;
+
+            if (flashy || !shading) {
+                /* clear all background flags */
+            } else if (ui && ui->sel && ui->sel[i]) {
+                flags |= HIGH_BG;
+            } else if (v) {
+                int size = dsf_size(ds->dsf_scratch, i);
+                if (size == v)
+                    flags |= CORRECT_BG;
+                else if (size > v)
+                    flags |= ERROR_BG;
+               else {
+                   int rt = dsf_canonify(ds->dsf_scratch, i), j;
+                   for (j = 0; j < w*h; ++j) {
+                       int k;
+                       if (dsf_canonify(ds->dsf_scratch, j) != rt) continue;
+                       for (k = 0; k < 4; ++k) {
+                           const int xx = j % w + dx[k], yy = j / w + dy[k];
+                           if (xx >= 0 && xx < w && yy >= 0 && yy < h &&
+                               state->board[yy*w + xx] == EMPTY)
+                               goto noflag;
+                       }
+                   }
+                   flags |= ERROR_BG;
+                 noflag:
+                   ;
+               }
+            }
+            if (ui && ui->cur_visible && x == ui->cur_x && y == ui->cur_y)
+              flags |= CURSOR_SQ;
+
+            /*
+             * Borders at the very edges of the grid are
+             * independent of the `borders' flag.
+             */
+            if (x == 0)
+                flags |= BORDER_L;
+            if (y == 0)
+                flags |= BORDER_U;
+            if (x == w-1)
+                flags |= BORDER_R;
+            if (y == h-1)
+                flags |= BORDER_D;
+
+            if (borders) {
+                if (x == 0 || (ds->border_scratch[y*w+(x-1)] & 1))
+                    flags |= BORDER_L;
+                if (y == 0 || (ds->border_scratch[(y-1)*w+x] & 2))
+                    flags |= BORDER_U;
+                if (x == w-1 || (ds->border_scratch[y*w+x] & 1))
+                    flags |= BORDER_R;
+                if (y == h-1 || (ds->border_scratch[y*w+x] & 2))
+                    flags |= BORDER_D;
+
+                if (y > 0 && x > 0 && (ds->border_scratch[(y-1)*w+(x-1)]))
+                    flags |= BORDER_UL;
+                if (y > 0 && x < w-1 &&
+                    ((ds->border_scratch[(y-1)*w+x] & 1) ||
+                     (ds->border_scratch[(y-1)*w+(x+1)] & 2)))
+                    flags |= BORDER_UR;
+                if (y < h-1 && x > 0 &&
+                    ((ds->border_scratch[y*w+(x-1)] & 2) ||
+                     (ds->border_scratch[(y+1)*w+(x-1)] & 1)))
+                    flags |= BORDER_DL;
+                if (y < h-1 && x < w-1 &&
+                    ((ds->border_scratch[y*w+(x+1)] & 2) ||
+                     (ds->border_scratch[(y+1)*w+x] & 1)))
+                    flags |= BORDER_DR;
+            }
+
+            if (!state->shared->clues[y*w+x])
+                flags |= USER_COL;
+
+            if (ds->v[y*w+x] != v || ds->flags[y*w+x] != flags) {
+                draw_square(dr, ds, x, y, v, flags);
+                ds->v[y*w+x] = v;
+                ds->flags[y*w+x] = flags;
+            }
+        }
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    const int w = state->shared->params.w;
+    const int h = state->shared->params.h;
+
+    const int flashy =
+        flashtime > 0 &&
+        (flashtime <= FLASH_TIME/3 || flashtime >= FLASH_TIME*2/3);
+
+    if (!ds->started) {
+        /*
+         * The initial contents of the window are not guaranteed and
+         * can vary with front ends. To be on the safe side, all games
+         * should start by drawing a big background-colour rectangle
+         * covering the whole window.
+         */
+        draw_rect(dr, 0, 0, w*TILE_SIZE + 2*BORDER, h*TILE_SIZE + 2*BORDER,
+                  COL_BACKGROUND);
+
+       /*
+        * Smaller black rectangle which is the main grid.
+        */
+       draw_rect(dr, BORDER - BORDER_WIDTH, BORDER - BORDER_WIDTH,
+                 w*TILE_SIZE + 2*BORDER_WIDTH + 1,
+                 h*TILE_SIZE + 2*BORDER_WIDTH + 1,
+                 COL_GRID);
+
+        draw_update(dr, 0, 0, w*TILE_SIZE + 2*BORDER, h*TILE_SIZE + 2*BORDER);
+
+        ds->started = TRUE;
+    }
+
+    draw_grid(dr, ds, state, ui, flashy, TRUE, TRUE);
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return 0.0F;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    assert(oldstate);
+    assert(newstate);
+    assert(newstate->shared);
+    assert(oldstate->shared == newstate->shared);
+    if (!oldstate->completed && newstate->completed &&
+       !oldstate->cheated && !newstate->cheated)
+        return FLASH_TIME;
+    return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    return state->completed ? +1 : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+    int pw, ph;
+
+    /*
+     * I'll use 6mm squares by default.
+     */
+    game_compute_size(params, 600, &pw, &ph);
+    *x = pw / 100.0F;
+    *y = ph / 100.0F;
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+    const int w = state->shared->params.w;
+    const int h = state->shared->params.h;
+    int c, i, borders;
+
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    game_drawstate *ds = game_new_drawstate(dr, state);
+    game_set_size(dr, ds, NULL, tilesize);
+
+    c = print_mono_colour(dr, 1); assert(c == COL_BACKGROUND);
+    c = print_mono_colour(dr, 0); assert(c == COL_GRID);
+    c = print_mono_colour(dr, 1); assert(c == COL_HIGHLIGHT);
+    c = print_mono_colour(dr, 1); assert(c == COL_CORRECT);
+    c = print_mono_colour(dr, 1); assert(c == COL_ERROR);
+    c = print_mono_colour(dr, 0); assert(c == COL_USER);
+
+    /*
+     * Border.
+     */
+    draw_rect(dr, BORDER - BORDER_WIDTH, BORDER - BORDER_WIDTH,
+              w*TILE_SIZE + 2*BORDER_WIDTH + 1,
+              h*TILE_SIZE + 2*BORDER_WIDTH + 1,
+              COL_GRID);
+
+    /*
+     * We'll draw borders between the ominoes iff the grid is not
+     * pristine. So scan it to see if it is.
+     */
+    borders = FALSE;
+    for (i = 0; i < w*h; i++)
+        if (state->board[i] && !state->shared->clues[i])
+            borders = TRUE;
+
+    /*
+     * Draw grid.
+     */
+    print_line_width(dr, TILE_SIZE / 64);
+    draw_grid(dr, ds, state, NULL, FALSE, borders, FALSE);
+
+    /*
+     * Clean up.
+     */
+    game_free_drawstate(dr, ds);
+}
+
+#ifdef COMBINED
+#define thegame filling
+#endif
+
+const struct game thegame = {
+    "Filling", "games.filling", "filling",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    TRUE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    TRUE, FALSE, game_print_size, game_print,
+    FALSE,                                /* wants_statusbar */
+    FALSE, game_timing_state,
+    REQUIRE_NUMPAD,                   /* flags */
+};
+
+#ifdef STANDALONE_SOLVER /* solver? hah! */
+
+int main(int argc, char **argv) {
+    while (*++argv) {
+        game_params *params;
+        game_state *state;
+        char *par;
+        char *desc;
+
+        for (par = desc = *argv; *desc != '\0' && *desc != ':'; ++desc);
+        if (*desc == '\0') {
+            fprintf(stderr, "bad puzzle id: %s", par);
+            continue;
+        }
+
+        *desc++ = '\0';
+
+        params = snew(game_params);
+        decode_params(params, par);
+        state = new_game(NULL, params, desc);
+        if (solver(state->board, params->w, params->h, NULL))
+            printf("%s:%s: solvable\n", par, desc);
+        else
+            printf("%s:%s: not solvable\n", par, desc);
+    }
+    return 0;
+}
+
+#endif
+
+/* vim: set shiftwidth=4 tabstop=8: */
diff --git a/findloop.c b/findloop.c
new file mode 100644 (file)
index 0000000..e6b2654
--- /dev/null
@@ -0,0 +1,500 @@
+/*
+ * Routine for finding loops in graphs, reusable across multiple
+ * puzzles.
+ *
+ * The strategy is Tarjan's bridge-finding algorithm, which is
+ * designed to list all edges whose removal would disconnect a
+ * previously connected component of the graph. We're interested in
+ * exactly the reverse - edges that are part of a loop in the graph
+ * are precisely those which _wouldn't_ disconnect anything if removed
+ * (individually) - but of course flipping the sense of the output is
+ * easy.
+ */
+
+#include "puzzles.h"
+
+struct findloopstate {
+    int parent, child, sibling, visited;
+    int index, minindex, maxindex;
+    int minreachable, maxreachable;
+    int bridge;
+};
+
+struct findloopstate *findloop_new_state(int nvertices)
+{
+    /*
+     * Allocate a findloopstate structure for each vertex, and one
+     * extra one at the end which will be the overall root of a
+     * 'super-tree', which links the whole graph together to make it
+     * as easy as possible to iterate over all the connected
+     * components.
+     */
+    return snewn(nvertices + 1, struct findloopstate);
+}
+
+void findloop_free_state(struct findloopstate *state)
+{
+    sfree(state);
+}
+
+int findloop_is_loop_edge(struct findloopstate *pv, int u, int v)
+{
+    /*
+     * Since the algorithm is intended for finding bridges, and a
+     * bridge must be part of any spanning tree, it follows that there
+     * is at most one bridge per vertex.
+     *
+     * Furthermore, by finding a _rooted_ spanning tree (so that each
+     * bridge is a parent->child link), you can find an injection from
+     * bridges to vertices (namely, map each bridge to the vertex at
+     * its child end).
+     *
+     * So if the u-v edge is a bridge, then either v was u's parent
+     * when the algorithm ran and we set pv[u].bridge = v, or vice
+     * versa.
+     */
+    return !(pv[u].bridge == v || pv[v].bridge == u);
+}
+
+int findloop_run(struct findloopstate *pv, int nvertices,
+                 neighbour_fn_t neighbour, void *ctx)
+{
+    int u, v, w, root, index;
+    int nbridges, nedges;
+
+    root = nvertices;
+
+    /*
+     * First pass: organise the graph into a rooted spanning forest.
+     * That is, a tree structure with a clear up/down orientation -
+     * every node has exactly one parent (which may be 'root') and
+     * zero or more children, and every parent-child link corresponds
+     * to a graph edge.
+     *
+     * (A side effect of this is to find all the connected components,
+     * which of course we could do less confusingly with a dsf - but
+     * then we'd have to do that *and* build the tree, so it's less
+     * effort to do it all at once.)
+     */
+    for (v = 0; v <= nvertices; v++) {
+        pv[v].parent = root;
+        pv[v].child = -2;
+        pv[v].sibling = -1;
+        pv[v].visited = FALSE;
+    }
+    pv[root].child = -1;
+    nedges = 0;
+    debug(("------------- new find_loops, nvertices=%d\n", nvertices));
+    for (v = 0; v < nvertices; v++) {
+        if (pv[v].parent == root) {
+            /*
+             * Found a new connected component. Enumerate and treeify
+             * it.
+             */
+            pv[v].sibling = pv[root].child;
+            pv[root].child = v;
+            debug(("%d is new child of root\n", v));
+
+            u = v;
+            while (1) {
+                if (!pv[u].visited) {
+                    pv[u].visited = TRUE;
+
+                    /*
+                     * Enumerate the neighbours of u, and any that are
+                     * as yet not in the tree structure (indicated by
+                     * child==-2, and distinct from the 'visited'
+                     * flag) become children of u.
+                     */
+                    debug(("  component pass: processing %d\n", u));
+                    for (w = neighbour(u, ctx); w >= 0;
+                         w = neighbour(-1, ctx)) {
+                        debug(("    edge %d-%d\n", u, w));
+                        if (pv[w].child == -2) {
+                            debug(("      -> new child\n"));
+                            pv[w].child = -1;
+                            pv[w].sibling = pv[u].child;
+                            pv[w].parent = u;
+                            pv[u].child = w;
+                        }
+
+                        /* While we're here, count the edges in the whole
+                         * graph, so that we can easily check at the end
+                         * whether all of them are bridges, i.e. whether
+                         * no loop exists at all. */
+                        if (w > u) /* count each edge only in one direction */
+                            nedges++;
+                    }
+
+                    /*
+                     * Now descend in depth-first search.
+                     */
+                    if (pv[u].child >= 0) {
+                        u = pv[u].child;
+                        debug(("    descending to %d\n", u));
+                        continue;
+                    }
+                }
+
+                if (u == v) {
+                    debug(("      back at %d, done this component\n", u));
+                    break;
+                } else if (pv[u].sibling >= 0) {
+                    u = pv[u].sibling;
+                    debug(("    sideways to %d\n", u));
+                } else {
+                    u = pv[u].parent;
+                    debug(("    ascending to %d\n", u));
+                }
+            }
+        }
+    }
+
+    /*
+     * Second pass: index all the vertices in such a way that every
+     * subtree has a contiguous range of indices. (Easily enough done,
+     * by iterating through the tree structure we just built and
+     * numbering its elements as if they were those of a sorted list.)
+     *
+     * For each vertex, we compute the min and max index of the
+     * subtree starting there.
+     *
+     * (We index the vertices in preorder, per Tarjan's original
+     * description, so that each vertex's min subtree index is its own
+     * index; but that doesn't actually matter; either way round would
+     * do. The important thing is that we have a simple arithmetic
+     * criterion that tells us whether a vertex is in a given subtree
+     * or not.)
+     */
+    debug(("--- begin indexing pass\n"));
+    index = 0;
+    for (v = 0; v < nvertices; v++)
+        pv[v].visited = FALSE;
+    pv[root].visited = TRUE;
+    u = pv[root].child;
+    while (1) {
+        if (!pv[u].visited) {
+            pv[u].visited = TRUE;
+
+            /*
+             * Index this node.
+             */
+            pv[u].minindex = pv[u].index = index;
+            debug(("  vertex %d <- index %d\n", u, index));
+            index++;
+
+            /*
+             * Now descend in depth-first search.
+             */
+            if (pv[u].child >= 0) {
+                u = pv[u].child;
+                debug(("    descending to %d\n", u));
+                continue;
+            }
+        }
+
+        if (u == root) {
+            debug(("      back at %d, done indexing\n", u));
+            break;
+        }
+
+        /*
+         * As we re-ascend to here from its children (or find that we
+         * had no children to descend to in the first place), fill in
+         * its maxindex field.
+         */
+        pv[u].maxindex = index-1;
+        debug(("  vertex %d <- maxindex %d\n", u, pv[u].maxindex));
+
+        if (pv[u].sibling >= 0) {
+            u = pv[u].sibling;
+            debug(("    sideways to %d\n", u));
+        } else {
+            u = pv[u].parent;
+            debug(("    ascending to %d\n", u));
+        }
+    }
+
+    /*
+     * We're ready to generate output now, so initialise the output
+     * fields.
+     */
+    for (v = 0; v < nvertices; v++)
+        pv[v].bridge = -1;
+
+    /*
+     * Final pass: determine the min and max index of the vertices
+     * reachable from every subtree, not counting the link back to
+     * each vertex's parent. Then our criterion is: given a vertex u,
+     * defining a subtree consisting of u and all its descendants, we
+     * compare the range of vertex indices _in_ that subtree (which is
+     * just the minindex and maxindex of u) with the range of vertex
+     * indices in the _neighbourhood_ of the subtree (computed in this
+     * final pass, and not counting u's own edge to its parent), and
+     * if the latter includes anything outside the former, then there
+     * must be some path from u to outside its subtree which does not
+     * go through the parent edge - i.e. the edge from u to its parent
+     * is part of a loop.
+     */
+    debug(("--- begin min-max pass\n"));
+    nbridges = 0;
+    for (v = 0; v < nvertices; v++)
+        pv[v].visited = FALSE;
+    u = pv[root].child;
+    pv[root].visited = TRUE;
+    while (1) {
+        if (!pv[u].visited) {
+            pv[u].visited = TRUE;
+
+            /*
+             * Look for vertices reachable directly from u, including
+             * u itself.
+             */
+            debug(("  processing vertex %d\n", u));
+            pv[u].minreachable = pv[u].maxreachable = pv[u].minindex;
+            for (w = neighbour(u, ctx); w >= 0; w = neighbour(-1, ctx)) {
+                debug(("    edge %d-%d\n", u, w));
+                if (w != pv[u].parent) {
+                    int i = pv[w].index;
+                    if (pv[u].minreachable > i)
+                        pv[u].minreachable = i;
+                    if (pv[u].maxreachable < i)
+                        pv[u].maxreachable = i;
+                }
+            }
+            debug(("    initial min=%d max=%d\n",
+                   pv[u].minreachable, pv[u].maxreachable));
+
+            /*
+             * Now descend in depth-first search.
+             */
+            if (pv[u].child >= 0) {
+                u = pv[u].child;
+                debug(("    descending to %d\n", u));
+                continue;
+            }
+        }
+
+        if (u == root) {
+            debug(("      back at %d, done min-maxing\n", u));
+            break;
+        }
+
+        /*
+         * As we re-ascend to this vertex, go back through its
+         * immediate children and do a post-update of its min/max.
+         */
+        for (v = pv[u].child; v >= 0; v = pv[v].sibling) {
+            if (pv[u].minreachable > pv[v].minreachable)
+                pv[u].minreachable = pv[v].minreachable;
+            if (pv[u].maxreachable < pv[v].maxreachable)
+                pv[u].maxreachable = pv[v].maxreachable;
+        }
+
+        debug(("  postorder update of %d: min=%d max=%d (indices %d-%d)\n", u,
+               pv[u].minreachable, pv[u].maxreachable,
+               pv[u].minindex, pv[u].maxindex));
+
+        /*
+         * And now we know whether each to our own parent is a bridge.
+         */
+        if ((v = pv[u].parent) != root) {
+            if (pv[u].minreachable >= pv[u].minindex &&
+                pv[u].maxreachable <= pv[u].maxindex) {
+                /* Yes, it's a bridge. */
+                pv[u].bridge = v;
+                nbridges++;
+                debug(("  %d-%d is a bridge\n", v, u));
+            } else {
+                debug(("  %d-%d is not a bridge\n", v, u));
+            }
+        }
+
+        if (pv[u].sibling >= 0) {
+            u = pv[u].sibling;
+            debug(("    sideways to %d\n", u));
+        } else {
+            u = pv[u].parent;
+            debug(("    ascending to %d\n", u));
+        }
+    }
+
+    debug(("finished, nedges=%d nbridges=%d\n", nedges, nbridges));
+
+    /*
+     * Done.
+     */
+    return nbridges < nedges;
+}
+
+/*
+ * Appendix: the long and painful history of loop detection in these puzzles
+ * =========================================================================
+ *
+ * For interest, I thought I'd write up the five loop-finding methods
+ * I've gone through before getting to this algorithm. It's a case
+ * study in all the ways you can solve this particular problem
+ * wrongly, and also how much effort you can waste by not managing to
+ * find the existing solution in the literature :-(
+ *
+ * Vertex dsf
+ * ----------
+ *
+ * Initially, in puzzles where you need to not have any loops in the
+ * solution graph, I detected them by using a dsf to track connected
+ * components of vertices. Iterate over each edge unifying the two
+ * vertices it connects; but before that, check if the two vertices
+ * are _already_ known to be connected. If so, then the new edge is
+ * providing a second path between them, i.e. a loop exists.
+ *
+ * That's adequate for automated solvers, where you just need to know
+ * _whether_ a loop exists, so as to rule out that move and do
+ * something else. But during play, you want to do better than that:
+ * you want to _point out_ the loops with error highlighting.
+ *
+ * Graph pruning
+ * -------------
+ *
+ * So my second attempt worked by iteratively pruning the graph. Find
+ * a vertex with degree 1; remove that edge; repeat until you can't
+ * find such a vertex any more. This procedure will remove *every*
+ * edge of the graph if and only if there were no loops; so if there
+ * are any edges remaining, highlight them.
+ *
+ * This successfully highlights loops, but not _only_ loops. If the
+ * graph contains a 'dumb-bell' shaped subgraph consisting of two
+ * loops connected by a path, then we'll end up highlighting the
+ * connecting path as well as the loops. That's not what we wanted.
+ *
+ * Vertex dsf with ad-hoc loop tracing
+ * -----------------------------------
+ *
+ * So my third attempt was to go back to the dsf strategy, only this
+ * time, when you detect that a particular edge connects two
+ * already-connected vertices (and hence is part of a loop), you try
+ * to trace round that loop to highlight it - before adding the new
+ * edge, search for a path between its endpoints among the edges the
+ * algorithm has already visited, and when you find one (which you
+ * must), highlight the loop consisting of that path plus the new
+ * edge.
+ *
+ * This solves the dumb-bell problem - we definitely now cannot
+ * accidentally highlight any edge that is *not* part of a loop. But
+ * it's far from clear that we'll highlight *every* edge that *is*
+ * part of a loop - what if there were multiple paths between the two
+ * vertices? It would be difficult to guarantee that we'd always catch
+ * every single one.
+ *
+ * On the other hand, it is at least guaranteed that we'll highlight
+ * _something_ if any loop exists, and in other error highlighting
+ * situations (see in particular the Tents connected component
+ * analysis) I've been known to consider that sufficient. So this
+ * version hung around for quite a while, until I had a better idea.
+ *
+ * Face dsf
+ * --------
+ *
+ * Round about the time Loopy was being revamped to include non-square
+ * grids, I had a much cuter idea, making use of the fact that the
+ * graph is planar, and hence has a concept of faces.
+ *
+ * In Loopy, there are really two graphs: the 'grid', consisting of
+ * all the edges that the player *might* fill in, and the solution
+ * graph of the edges the player actually *has* filled in. The
+ * algorithm is: set up a dsf on the *faces* of the grid. Iterate over
+ * each edge of the grid which is _not_ marked by the player as an
+ * edge of the solution graph, unifying the faces on either side of
+ * that edge. This groups the faces into connected components. Now,
+ * there is more than one connected component iff a loop exists, and
+ * moreover, an edge of the solution graph is part of a loop iff the
+ * faces on either side of it are in different connected components!
+ *
+ * This is the first algorithm I came up with that I was confident
+ * would successfully highlight exactly the correct set of edges in
+ * all cases. It's also conceptually elegant, and very easy to
+ * implement and to be confident you've got it right (since it just
+ * consists of two very simple loops over the edge set, one building
+ * the dsf and one reading it off). I was very pleased with it.
+ *
+ * Doing the same thing in Slant is slightly more difficult because
+ * the set of edges the user can fill in do not form a planar graph
+ * (the two potential edges in each square cross in the middle). But
+ * you can still apply the same principle by considering the 'faces'
+ * to be diamond-shaped regions of space around each horizontal or
+ * vertical grid line. Equivalently, pretend each edge added by the
+ * player is really divided into two edges, each from a square-centre
+ * to one of the square's corners, and now the grid graph is planar
+ * again.
+ *
+ * However, it fell down when - much later - I tried to implement the
+ * same algorithm in Net.
+ *
+ * Net doesn't *absolutely need* loop detection, because of its system
+ * of highlighting squares connected to the source square: an argument
+ * involving counting vertex degrees shows that if any loop exists,
+ * then it must be counterbalanced by some disconnected square, so
+ * there will be _some_ error highlight in any invalid grid even
+ * without loop detection. However, in large complicated cases, it's
+ * still nice to highlight the loop itself, so that once the player is
+ * clued in to its existence by a disconnected square elsewhere, they
+ * don't have to spend forever trying to find it.
+ *
+ * The new wrinkle in Net, compared to other loop-disallowing puzzles,
+ * is that it can be played with wrapping walls, or - topologically
+ * speaking - on a torus. And a torus has a property that algebraic
+ * topologists would know of as a 'non-trivial H_1 homology group',
+ * which essentially means that there can exist a loop on a torus
+ * which *doesn't* separate the surface into two regions disconnected
+ * from each other.
+ *
+ * In other words, using this algorithm in Net will do fine at finding
+ * _small_ localised loops, but a large-scale loop that goes (say) off
+ * the top of the grid, back on at the bottom, and meets up in the
+ * middle again will not be detected.
+ *
+ * Footpath dsf
+ * ------------
+ *
+ * To solve this homology problem in Net, I hastily thought up another
+ * dsf-based algorithm.
+ *
+ * This time, let's consider each edge of the graph to be a road, with
+ * a separate pedestrian footpath down each side. We'll form a dsf on
+ * those imaginary segments of footpath.
+ *
+ * At each vertex of the graph, we go round the edges leaving that
+ * vertex, in order around the vertex. For each pair of edges adjacent
+ * in this order, we unify their facing pair of footpaths (e.g. if
+ * edge E appears anticlockwise of F, then we unify the anticlockwise
+ * footpath of F with the clockwise one of E) . In particular, if a
+ * vertex has degree 1, then the two footpaths on either side of its
+ * single edge are unified.
+ *
+ * Then, an edge is part of a loop iff its two footpaths are not
+ * reachable from one another.
+ *
+ * This algorithm is almost as simple to implement as the face dsf,
+ * and it works on a wider class of graphs embedded in plane-like
+ * surfaces; in particular, it fixes the torus bug in the face-dsf
+ * approach. However, it still depends on the graph having _some_ sort
+ * of embedding in a 2-manifold, because it relies on there being a
+ * meaningful notion of 'order of edges around a vertex' in the first
+ * place, so you couldn't use it on a wildly nonplanar graph like the
+ * diamond lattice. Also, more subtly, it depends on the graph being
+ * embedded in an _orientable_ surface - and that's a thing that might
+ * much more plausibly change in future puzzles, because it's not at
+ * all unlikely that at some point I might feel moved to implement a
+ * puzzle that can be played on the surface of a Mobius strip or a
+ * Klein bottle. And then even this algorithm won't work.
+ *
+ * Tarjan's bridge-finding algorithm
+ * ---------------------------------
+ *
+ * And so, finally, we come to the algorithm above. This one is pure
+ * graph theory: it doesn't depend on any concept of 'faces', or 'edge
+ * ordering around a vertex', or any other trapping of a planar or
+ * quasi-planar graph embedding. It should work on any graph
+ * whatsoever, and reliably identify precisely the set of edges that
+ * form part of some loop. So *hopefully* this long string of failures
+ * has finally come to an end...
+ */
diff --git a/flip.R b/flip.R
new file mode 100644 (file)
index 0000000..03241f0
--- /dev/null
+++ b/flip.R
@@ -0,0 +1,21 @@
+# -*- makefile -*-
+
+FLIP_EXTRA = tree234
+
+flip     : [X] GTK COMMON flip FLIP_EXTRA flip-icon|no-icon
+
+flip     : [G] WINDOWS COMMON flip FLIP_EXTRA flip.res|noicon.res
+
+ALL += flip[COMBINED] FLIP_EXTRA
+
+!begin am gtk
+GAMES += flip
+!end
+
+!begin >list.c
+    A(flip) \
+!end
+
+!begin >gamedesc.txt
+flip:flip.exe:Flip:Tile inversion puzzle:Flip groups of squares to light them all up at once.
+!end
diff --git a/flip.c b/flip.c
new file mode 100644 (file)
index 0000000..2249698
--- /dev/null
+++ b/flip.c
@@ -0,0 +1,1349 @@
+/*
+ * flip.c: Puzzle involving lighting up all the squares on a grid,
+ * where each click toggles an overlapping set of lights.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+#include "tree234.h"
+
+enum {
+    COL_BACKGROUND,
+    COL_WRONG,
+    COL_RIGHT,
+    COL_GRID,
+    COL_DIAG,
+    COL_HINT,
+    COL_CURSOR,
+    NCOLOURS
+};
+
+#define PREFERRED_TILE_SIZE 48
+#define TILE_SIZE (ds->tilesize)
+#define BORDER    (TILE_SIZE / 2)
+#define COORD(x)  ( (x) * TILE_SIZE + BORDER )
+#define FROMCOORD(x)  ( ((x) - BORDER + TILE_SIZE) / TILE_SIZE - 1 )
+
+#define ANIM_TIME 0.25F
+#define FLASH_FRAME 0.07F
+
+/*
+ * Possible ways to decide which lights are toggled by each click.
+ * Essentially, each of these describes a means of inventing a
+ * matrix over GF(2).
+ */
+enum {
+    CROSSES, RANDOM
+};
+
+struct game_params {
+    int w, h;
+    int matrix_type;
+};
+
+/*
+ * This structure is shared between all the game_states describing
+ * a particular game, so it's reference-counted.
+ */
+struct matrix {
+    int refcount;
+    unsigned char *matrix;             /* array of (w*h) by (w*h) */
+};
+
+struct game_state {
+    int w, h;
+    int moves, completed, cheated, hints_active;
+    unsigned char *grid;               /* array of w*h */
+    struct matrix *matrix;
+};
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = ret->h = 5;
+    ret->matrix_type = CROSSES;
+
+    return ret;
+}
+
+static const struct game_params flip_presets[] = {
+    {3, 3, CROSSES},
+    {4, 4, CROSSES},
+    {5, 5, CROSSES},
+    {3, 3, RANDOM},
+    {4, 4, RANDOM},
+    {5, 5, RANDOM},
+};
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params *ret;
+    char str[80];
+
+    if (i < 0 || i >= lenof(flip_presets))
+        return FALSE;
+
+    ret = snew(game_params);
+    *ret = flip_presets[i];
+
+    sprintf(str, "%dx%d %s", ret->w, ret->h,
+            ret->matrix_type == CROSSES ? "Crosses" : "Random");
+
+    *name = dupstr(str);
+    *params = ret;
+    return TRUE;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+static void decode_params(game_params *ret, char const *string)
+{
+    ret->w = ret->h = atoi(string);
+    while (*string && isdigit((unsigned char)*string)) string++;
+    if (*string == 'x') {
+        string++;
+        ret->h = atoi(string);
+        while (*string && isdigit((unsigned char)*string)) string++;
+    }
+    if (*string == 'r') {
+        string++;
+        ret->matrix_type = RANDOM;
+    } else if (*string == 'c') {
+        string++;
+        ret->matrix_type = CROSSES;
+    }
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char data[256];
+
+    sprintf(data, "%dx%d%s", params->w, params->h,
+            !full ? "" : params->matrix_type == CROSSES ? "c" : "r");
+
+    return dupstr(data);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret = snewn(4, config_item);
+    char buf[80];
+
+    ret[0].name = "Width";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->w);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Height";
+    ret[1].type = C_STRING;
+    sprintf(buf, "%d", params->h);
+    ret[1].sval = dupstr(buf);
+    ret[1].ival = 0;
+
+    ret[2].name = "Shape type";
+    ret[2].type = C_CHOICES;
+    ret[2].sval = ":Crosses:Random";
+    ret[2].ival = params->matrix_type;
+
+    ret[3].name = NULL;
+    ret[3].type = C_END;
+    ret[3].sval = NULL;
+    ret[3].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = atoi(cfg[0].sval);
+    ret->h = atoi(cfg[1].sval);
+    ret->matrix_type = cfg[2].ival;
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    if (params->w <= 0 || params->h <= 0)
+        return "Width and height must both be greater than zero";
+    return NULL;
+}
+
+static char *encode_bitmap(unsigned char *bmp, int len)
+{
+    int slen = (len + 3) / 4;
+    char *ret;
+    int i;
+
+    ret = snewn(slen + 1, char);
+    for (i = 0; i < slen; i++) {
+        int j, v;
+        v = 0;
+        for (j = 0; j < 4; j++)
+            if (i*4+j < len && bmp[i*4+j])
+                v |= 8 >> j;
+        ret[i] = "0123456789abcdef"[v];
+    }
+    ret[slen] = '\0';
+    return ret;
+}
+
+static void decode_bitmap(unsigned char *bmp, int len, const char *hex)
+{
+    int slen = (len + 3) / 4;
+    int i;
+
+    for (i = 0; i < slen; i++) {
+        int j, v, c = hex[i];
+        if (c >= '0' && c <= '9')
+            v = c - '0';
+        else if (c >= 'A' && c <= 'F')
+            v = c - 'A' + 10;
+        else if (c >= 'a' && c <= 'f')
+            v = c - 'a' + 10;
+        else
+            v = 0;                     /* shouldn't happen */
+        for (j = 0; j < 4; j++) {
+            if (i*4+j < len) {
+                if (v & (8 >> j))
+                    bmp[i*4+j] = 1;
+                else
+                    bmp[i*4+j] = 0;
+            }
+        }
+    }
+}
+
+/*
+ * Structure used during random matrix generation, and a compare
+ * function to permit storage in a tree234.
+ */
+struct sq {
+    int cx, cy;                        /* coords of click square */
+    int x, y;                          /* coords of output square */
+    /*
+     * Number of click squares which currently affect this output
+     * square.
+     */
+    int coverage;
+    /*
+     * Number of output squares currently affected by this click
+     * square.
+     */
+    int ominosize;
+};
+#define SORT(field) do { \
+    if (a->field < b->field) \
+        return -1; \
+    else if (a->field > b->field) \
+        return +1; \
+} while (0)
+/*
+ * Compare function for choosing the next square to add. We must
+ * sort by coverage, then by omino size, then everything else.
+ */
+static int sqcmp_pick(void *av, void *bv)
+{
+    struct sq *a = (struct sq *)av;
+    struct sq *b = (struct sq *)bv;
+    SORT(coverage);
+    SORT(ominosize);
+    SORT(cy);
+    SORT(cx);
+    SORT(y);
+    SORT(x);
+    return 0;
+}
+/*
+ * Compare function for adjusting the coverage figures after a
+ * change. We sort first by coverage and output square, then by
+ * everything else.
+ */
+static int sqcmp_cov(void *av, void *bv)
+{
+    struct sq *a = (struct sq *)av;
+    struct sq *b = (struct sq *)bv;
+    SORT(coverage);
+    SORT(y);
+    SORT(x);
+    SORT(ominosize);
+    SORT(cy);
+    SORT(cx);
+    return 0;
+}
+/*
+ * Compare function for adjusting the omino sizes after a change.
+ * We sort first by omino size and input square, then by everything
+ * else.
+ */
+static int sqcmp_osize(void *av, void *bv)
+{
+    struct sq *a = (struct sq *)av;
+    struct sq *b = (struct sq *)bv;
+    SORT(ominosize);
+    SORT(cy);
+    SORT(cx);
+    SORT(coverage);
+    SORT(y);
+    SORT(x);
+    return 0;
+}
+static void addsq(tree234 *t, int w, int h, int cx, int cy,
+                  int x, int y, unsigned char *matrix)
+{
+    int wh = w * h;
+    struct sq *sq;
+    int i;
+
+    if (x < 0 || x >= w || y < 0 || y >= h)
+        return;
+    if (abs(x-cx) > 1 || abs(y-cy) > 1)
+        return;
+    if (matrix[(cy*w+cx) * wh + y*w+x])
+        return;
+
+    sq = snew(struct sq);
+    sq->cx = cx;
+    sq->cy = cy;
+    sq->x = x;
+    sq->y = y;
+    sq->coverage = sq->ominosize = 0;
+    for (i = 0; i < wh; i++) {
+        if (matrix[i * wh + y*w+x])
+            sq->coverage++;
+        if (matrix[(cy*w+cx) * wh + i])
+            sq->ominosize++;
+    }
+
+    if (add234(t, sq) != sq)
+        sfree(sq);                     /* already there */
+}
+static void addneighbours(tree234 *t, int w, int h, int cx, int cy,
+                          int x, int y, unsigned char *matrix)
+{
+    addsq(t, w, h, cx, cy, x-1, y, matrix);
+    addsq(t, w, h, cx, cy, x+1, y, matrix);
+    addsq(t, w, h, cx, cy, x, y-1, matrix);
+    addsq(t, w, h, cx, cy, x, y+1, matrix);
+}
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                          char **aux, int interactive)
+{
+    int w = params->w, h = params->h, wh = w * h;
+    int i, j;
+    unsigned char *matrix, *grid;
+    char *mbmp, *gbmp, *ret;
+
+    matrix = snewn(wh * wh, unsigned char);
+    grid = snewn(wh, unsigned char);
+
+    /*
+     * First set up the matrix.
+     */
+    switch (params->matrix_type) {
+      case CROSSES:
+        for (i = 0; i < wh; i++) {
+            int ix = i % w, iy = i / w;
+            for (j = 0; j < wh; j++) {
+                int jx = j % w, jy = j / w;
+                if (abs(jx - ix) + abs(jy - iy) <= 1)
+                    matrix[i*wh+j] = 1;
+                else
+                    matrix[i*wh+j] = 0;
+            }
+        }
+        break;
+      case RANDOM:
+        while (1) {
+            tree234 *pick, *cov, *osize;
+            int limit;
+
+            pick = newtree234(sqcmp_pick);
+            cov = newtree234(sqcmp_cov);
+            osize = newtree234(sqcmp_osize);
+
+            memset(matrix, 0, wh * wh);
+            for (i = 0; i < wh; i++) {
+                matrix[i*wh+i] = 1;
+            }
+
+            for (i = 0; i < wh; i++) {
+                int ix = i % w, iy = i / w;
+                addneighbours(pick, w, h, ix, iy, ix, iy, matrix);
+                addneighbours(cov, w, h, ix, iy, ix, iy, matrix);
+                addneighbours(osize, w, h, ix, iy, ix, iy, matrix);
+            }
+
+            /*
+             * Repeatedly choose a square to add to the matrix,
+             * until we have enough. I'll arbitrarily choose our
+             * limit to be the same as the total number of set bits
+             * in the crosses matrix.
+             */
+            limit = 4*wh - 2*(w+h);    /* centre squares already present */
+
+            while (limit-- > 0) {
+                struct sq *sq, *sq2, sqlocal;
+                int k;
+
+                /*
+                 * Find the lowest element in the pick tree.
+                 */
+                sq = index234(pick, 0);
+
+                /*
+                 * Find the highest element with the same coverage
+                 * and omino size, by setting all other elements to
+                 * lots.
+                 */
+                sqlocal = *sq;
+                sqlocal.cx = sqlocal.cy = sqlocal.x = sqlocal.y = wh;
+                sq = findrelpos234(pick, &sqlocal, NULL, REL234_LT, &k);
+                assert(sq != 0);
+
+                /*
+                 * Pick at random from all elements up to k of the
+                 * pick tree.
+                 */
+                k = random_upto(rs, k+1);
+                sq = delpos234(pick, k);
+                del234(cov, sq);
+                del234(osize, sq);
+
+                /*
+                 * Add this square to the matrix.
+                 */
+                matrix[(sq->cy * w + sq->cx) * wh + (sq->y * w + sq->x)] = 1;
+
+                /*
+                 * Correct the matrix coverage field of any sq
+                 * which points at this output square.
+                 */
+                sqlocal = *sq;
+                sqlocal.cx = sqlocal.cy = sqlocal.ominosize = -1;
+                while ((sq2 = findrel234(cov, &sqlocal, NULL,
+                                         REL234_GT)) != NULL &&
+                       sq2->coverage == sq->coverage &&
+                       sq2->x == sq->x && sq2->y == sq->y) {
+                    del234(pick, sq2);
+                    del234(cov, sq2);
+                    del234(osize, sq2);
+                    sq2->coverage++;
+                    add234(pick, sq2);
+                    add234(cov, sq2);
+                    add234(osize, sq2);
+                }
+
+                /*
+                 * Correct the omino size field of any sq which
+                 * points at this input square.
+                 */
+                sqlocal = *sq;
+                sqlocal.x = sqlocal.y = sqlocal.coverage = -1;
+                while ((sq2 = findrel234(osize, &sqlocal, NULL,
+                                         REL234_GT)) != NULL &&
+                       sq2->ominosize == sq->ominosize &&
+                       sq2->cx == sq->cx && sq2->cy == sq->cy) {
+                    del234(pick, sq2);
+                    del234(cov, sq2);
+                    del234(osize, sq2);
+                    sq2->ominosize++;
+                    add234(pick, sq2);
+                    add234(cov, sq2);
+                    add234(osize, sq2);
+                }
+
+                /*
+                 * The sq we actually picked out of the tree is
+                 * finished with; but its neighbours now need to
+                 * appear.
+                 */
+                addneighbours(pick, w,h, sq->cx,sq->cy, sq->x,sq->y, matrix);
+                addneighbours(cov, w,h, sq->cx,sq->cy, sq->x,sq->y, matrix);
+                addneighbours(osize, w,h, sq->cx,sq->cy, sq->x,sq->y, matrix);
+                sfree(sq);
+            }
+
+            /*
+             * Free all remaining sq structures.
+             */
+            {
+                struct sq *sq;
+                while ((sq = delpos234(pick, 0)) != NULL)
+                    sfree(sq);
+            }
+            freetree234(pick);
+            freetree234(cov);
+            freetree234(osize);
+
+            /*
+             * Finally, check to see if any two matrix rows are
+             * exactly identical. If so, this is not an acceptable
+             * matrix, and we give up and go round again.
+             * 
+             * I haven't been immediately able to think of a
+             * plausible means of algorithmically avoiding this
+             * situation (by, say, making a small perturbation to
+             * an offending matrix), so for the moment I'm just
+             * going to deal with it by throwing the whole thing
+             * away. I suspect this will lead to scalability
+             * problems (since most of the things happening in
+             * these matrices are local, the chance of _some_
+             * neighbourhood having two identical regions will
+             * increase with the grid area), but so far this puzzle
+             * seems to be really hard at large sizes so I'm not
+             * massively worried yet. Anyone needs this done
+             * better, they're welcome to submit a patch.
+             */
+            for (i = 0; i < wh; i++) {
+                for (j = 0; j < wh; j++)
+                    if (i != j &&
+                        !memcmp(matrix + i * wh, matrix + j * wh, wh))
+                        break;
+                if (j < wh)
+                    break;
+            }
+            if (i == wh)
+                break;                 /* no matches found */
+        }
+        break;
+    }
+
+    /*
+     * Now invent a random initial set of lights.
+     * 
+     * At first glance it looks as if it might be quite difficult
+     * to choose equiprobably from all soluble light sets. After
+     * all, soluble light sets are those in the image space of the
+     * transformation matrix; so first we'd have to identify that
+     * space and its dimension, then pick a random coordinate for
+     * each basis vector and recombine. Lot of fiddly matrix
+     * algebra there.
+     * 
+     * However, vector spaces are nicely orthogonal and relieve us
+     * of all that difficulty. For every point in the image space,
+     * there are precisely as many points in the input space that
+     * map to it as there are elements in the kernel of the
+     * transformation matrix (because adding any kernel element to
+     * the input does not change the output, and because any two
+     * inputs mapping to the same output must differ by an element
+     * of the kernel because that's what the kernel _is_); and
+     * these cosets are all disjoint (obviously, since no input
+     * point can map to more than one output point) and cover the
+     * whole space (equally obviously, because no input point can
+     * map to fewer than one output point!).
+     *
+     * So the input space contains the same number of points for
+     * each point in the output space; thus, we can simply choose
+     * equiprobably from elements of the _input_ space, and filter
+     * the result through the transformation matrix in the obvious
+     * way, and we thereby guarantee to choose equiprobably from
+     * all the output points. Phew!
+     */
+    while (1) {
+        memset(grid, 0, wh);
+        for (i = 0; i < wh; i++) {
+            int v = random_upto(rs, 2);
+            if (v) {
+                for (j = 0; j < wh; j++)
+                    grid[j] ^= matrix[i*wh+j];
+            }
+        }
+        /*
+         * Ensure we don't have the starting state already!
+         */
+        for (i = 0; i < wh; i++)
+            if (grid[i])
+                break;
+        if (i < wh)
+            break;
+    }
+
+    /*
+     * Now encode the matrix and the starting grid as a game
+     * description. We'll do this by concatenating two great big
+     * hex bitmaps.
+     */
+    mbmp = encode_bitmap(matrix, wh*wh);
+    gbmp = encode_bitmap(grid, wh);
+    ret = snewn(strlen(mbmp) + strlen(gbmp) + 2, char);
+    sprintf(ret, "%s,%s", mbmp, gbmp);
+    sfree(mbmp);
+    sfree(gbmp);
+    sfree(matrix);
+    sfree(grid);
+    return ret;
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    int w = params->w, h = params->h, wh = w * h;
+    int mlen = (wh*wh+3)/4, glen = (wh+3)/4;
+
+    if (strspn(desc, "0123456789abcdefABCDEF") != mlen)
+        return "Matrix description is wrong length";
+    if (desc[mlen] != ',')
+        return "Expected comma after matrix description";
+    if (strspn(desc+mlen+1, "0123456789abcdefABCDEF") != glen)
+        return "Grid description is wrong length";
+    if (desc[mlen+1+glen])
+        return "Unexpected data after grid description";
+
+    return NULL;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    int w = params->w, h = params->h, wh = w * h;
+    int mlen = (wh*wh+3)/4;
+
+    game_state *state = snew(game_state);
+
+    state->w = w;
+    state->h = h;
+    state->completed = FALSE;
+    state->cheated = FALSE;
+    state->hints_active = FALSE;
+    state->moves = 0;
+    state->matrix = snew(struct matrix);
+    state->matrix->refcount = 1;
+    state->matrix->matrix = snewn(wh*wh, unsigned char);
+    decode_bitmap(state->matrix->matrix, wh*wh, desc);
+    state->grid = snewn(wh, unsigned char);
+    decode_bitmap(state->grid, wh, desc + mlen + 1);
+
+    return state;
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    game_state *ret = snew(game_state);
+
+    ret->w = state->w;
+    ret->h = state->h;
+    ret->completed = state->completed;
+    ret->cheated = state->cheated;
+    ret->hints_active = state->hints_active;
+    ret->moves = state->moves;
+    ret->matrix = state->matrix;
+    state->matrix->refcount++;
+    ret->grid = snewn(ret->w * ret->h, unsigned char);
+    memcpy(ret->grid, state->grid, ret->w * ret->h);
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    sfree(state->grid);
+    if (--state->matrix->refcount <= 0) {
+        sfree(state->matrix->matrix);
+        sfree(state->matrix);
+    }
+    sfree(state);
+}
+
+static void rowxor(unsigned char *row1, unsigned char *row2, int len)
+{
+    int i;
+    for (i = 0; i < len; i++)
+       row1[i] ^= row2[i];
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    int w = state->w, h = state->h, wh = w * h;
+    unsigned char *equations, *solution, *shortest;
+    int *und, nund;
+    int rowsdone, colsdone;
+    int i, j, k, len, bestlen;
+    char *ret;
+
+    /*
+     * Set up a list of simultaneous equations. Each one is of
+     * length (wh+1) and has wh coefficients followed by a value.
+     */
+    equations = snewn((wh + 1) * wh, unsigned char);
+    for (i = 0; i < wh; i++) {
+       for (j = 0; j < wh; j++)
+           equations[i * (wh+1) + j] = currstate->matrix->matrix[j*wh+i];
+       equations[i * (wh+1) + wh] = currstate->grid[i] & 1;
+    }
+
+    /*
+     * Perform Gaussian elimination over GF(2).
+     */
+    rowsdone = colsdone = 0;
+    nund = 0;
+    und = snewn(wh, int);
+    do {
+       /*
+        * Find the leftmost column which has a 1 in it somewhere
+        * outside the first `rowsdone' rows.
+        */
+       j = -1;
+       for (i = colsdone; i < wh; i++) {
+           for (j = rowsdone; j < wh; j++)
+               if (equations[j * (wh+1) + i])
+                   break;
+           if (j < wh)
+               break;                 /* found one */
+           /*
+            * This is a column which will not have an equation
+            * controlling it. Mark it as undetermined.
+            */
+           und[nund++] = i;
+       }
+
+       /*
+        * If there wasn't one, then we've finished: all remaining
+        * equations are of the form 0 = constant. Check to see if
+        * any of them wants 0 to be equal to 1; this is the
+        * condition which indicates an insoluble problem
+        * (therefore _hopefully_ one typed in by a user!).
+        */
+       if (i == wh) {
+           for (j = rowsdone; j < wh; j++)
+               if (equations[j * (wh+1) + wh]) {
+                   *error = "No solution exists for this position";
+                   sfree(equations);
+                   sfree(und);
+                   return NULL;
+               }
+           break;
+       }
+
+       /*
+        * We've found a 1. It's in column i, and the topmost 1 in
+        * that column is in row j. Do a row-XOR to move it up to
+        * the topmost row if it isn't already there.
+        */
+       assert(j != -1);
+       if (j > rowsdone)
+           rowxor(equations + rowsdone*(wh+1), equations + j*(wh+1), wh+1);
+
+       /*
+        * Do row-XORs to eliminate that 1 from all rows below the
+        * topmost row.
+        */
+       for (j = rowsdone + 1; j < wh; j++)
+           if (equations[j*(wh+1) + i])
+               rowxor(equations + j*(wh+1),
+                      equations + rowsdone*(wh+1), wh+1);
+
+       /*
+        * Mark this row and column as done.
+        */
+       rowsdone++;
+       colsdone = i+1;
+
+       /*
+        * If we've done all the rows, terminate.
+        */
+    } while (rowsdone < wh);
+
+    /*
+     * If we reach here, we have the ability to produce a solution.
+     * So we go through _all_ possible solutions (each
+     * corresponding to a set of arbitrary choices of those
+     * components not directly determined by an equation), and pick
+     * one requiring the smallest number of flips.
+     */
+    solution = snewn(wh, unsigned char);
+    shortest = snewn(wh, unsigned char);
+    memset(solution, 0, wh);
+    bestlen = wh + 1;
+    while (1) {
+       /*
+        * Find a solution based on the current values of the
+        * undetermined variables.
+        */
+       for (j = rowsdone; j-- ;) {
+           int v;
+
+           /*
+            * Find the leftmost set bit in this equation.
+            */
+           for (i = 0; i < wh; i++)
+               if (equations[j * (wh+1) + i])
+                   break;
+           assert(i < wh);                    /* there must have been one! */
+
+           /*
+            * Compute this variable using the rest.
+            */
+           v = equations[j * (wh+1) + wh];
+           for (k = i+1; k < wh; k++)
+               if (equations[j * (wh+1) + k])
+                   v ^= solution[k];
+
+           solution[i] = v;
+       }
+
+       /*
+        * Compare this solution to the current best one, and
+        * replace the best one if this one is shorter.
+        */
+       len = 0;
+       for (i = 0; i < wh; i++)
+           if (solution[i])
+               len++;
+       if (len < bestlen) {
+           bestlen = len;
+           memcpy(shortest, solution, wh);
+       }
+
+       /*
+        * Now increment the binary number given by the
+        * undetermined variables: turn all 1s into 0s until we see
+        * a 0, at which point we turn it into a 1.
+        */
+       for (i = 0; i < nund; i++) {
+           solution[und[i]] = !solution[und[i]];
+           if (solution[und[i]])
+               break;
+       }
+
+       /*
+        * If we didn't find a 0 at any point, we have wrapped
+        * round and are back at the start, i.e. we have enumerated
+        * all solutions.
+        */
+       if (i == nund)
+           break;
+    }
+
+    /*
+     * We have a solution. Produce a move string encoding the
+     * solution.
+     */
+    ret = snewn(wh + 2, char);
+    ret[0] = 'S';
+    for (i = 0; i < wh; i++)
+       ret[i+1] = shortest[i] ? '1' : '0';
+    ret[wh+1] = '\0';
+
+    sfree(shortest);
+    sfree(solution);
+    sfree(equations);
+    sfree(und);
+
+    return ret;
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+#define RIGHT 1
+#define DOWN gw
+
+static char *game_text_format(const game_state *state)
+{
+    int w = state->w, h = state->h, wh = w*h, r, c, dx, dy;
+    int cw = 4, ch = 4, gw = w * cw + 2, gh = h * ch + 1, len = gw * gh;
+    char *board = snewn(len + 1, char);
+
+    memset(board, ' ', len - 1);
+
+    for (r = 0; r < h; ++r) {
+       for (c = 0; c < w; ++c) {
+           int cell = r*ch*gw + c*cw, center = cell+(ch/2)*DOWN + cw/2*RIGHT;
+           char flip = (state->grid[r*w + c] & 1) ? '#' : '.';
+           for (dy = -1 + (r == 0); dy <= 1 - (r == h - 1); ++dy)
+               for (dx = -1 + (c == 0); dx <= 1 - (c == w - 1); ++dx)
+                   if (state->matrix->matrix[(r*w+c)*wh + ((r+dy)*w + c+dx)])
+                       board[center + dy*DOWN + dx*RIGHT] = flip;
+           board[cell] = '+';
+           for (dx = 1; dx < cw; ++dx) board[cell+dx*RIGHT] = '-';
+           for (dy = 1; dy < ch; ++dy) board[cell+dy*DOWN] = '|';
+       }
+       board[r*ch*gw + gw - 2] = '+';
+       board[r*ch*gw + gw - 1] = '\n';
+       for (dy = 1; dy < ch; ++dy) {
+           board[r*ch*gw + gw - 2 + dy*DOWN] = '|';
+           board[r*ch*gw + gw - 1 + dy*DOWN] = '\n';
+       }
+    }
+    memset(board + len - gw, '-', gw - 2);
+    for (c = 0; c <= w; ++c) board[len - gw + cw*c] = '+';
+    board[len - 1] = '\n';
+    board[len] = '\0';
+    return board;
+}
+
+#undef RIGHT
+#undef DOWN
+
+struct game_ui {
+    int cx, cy, cdraw;
+};
+
+static game_ui *new_ui(const game_state *state)
+{
+    game_ui *ui = snew(game_ui);
+    ui->cx = ui->cy = ui->cdraw = 0;
+    return ui;
+}
+
+static void free_ui(game_ui *ui)
+{
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+}
+
+struct game_drawstate {
+    int w, h, started;
+    unsigned char *tiles;
+    int tilesize;
+};
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    int w = state->w, h = state->h, wh = w * h;
+    char buf[80], *nullret = NULL;
+
+    if (button == LEFT_BUTTON || IS_CURSOR_SELECT(button)) {
+        int tx, ty;
+        if (button == LEFT_BUTTON) {
+            tx = FROMCOORD(x), ty = FROMCOORD(y);
+            ui->cdraw = 0;
+        } else {
+            tx = ui->cx; ty = ui->cy;
+            ui->cdraw = 1;
+        }
+        nullret = "";
+
+        if (tx >= 0 && tx < w && ty >= 0 && ty < h) {
+            /*
+             * It's just possible that a manually entered game ID
+             * will have at least one square do nothing whatsoever.
+             * If so, we avoid encoding a move at all.
+             */
+            int i = ty*w+tx, j, makemove = FALSE;
+            for (j = 0; j < wh; j++) {
+                if (state->matrix->matrix[i*wh+j])
+                    makemove = TRUE;
+            }
+            if (makemove) {
+                sprintf(buf, "M%d,%d", tx, ty);
+                return dupstr(buf);
+            } else {
+                return NULL;
+            }
+        }
+    }
+    else if (IS_CURSOR_MOVE(button)) {
+        int dx = 0, dy = 0;
+        switch (button) {
+        case CURSOR_UP:         dy = -1; break;
+        case CURSOR_DOWN:       dy = 1; break;
+        case CURSOR_RIGHT:      dx = 1; break;
+        case CURSOR_LEFT:       dx = -1; break;
+        default: assert(!"shouldn't get here");
+        }
+        ui->cx += dx; ui->cy += dy;
+        ui->cx = min(max(ui->cx, 0), state->w - 1);
+        ui->cy = min(max(ui->cy, 0), state->h - 1);
+        ui->cdraw = 1;
+        nullret = "";
+    }
+
+    return nullret;
+}
+
+static game_state *execute_move(const game_state *from, const char *move)
+{
+    int w = from->w, h = from->h, wh = w * h;
+    game_state *ret;
+    int x, y;
+
+    if (move[0] == 'S' && strlen(move) == wh+1) {
+       int i;
+
+       ret = dup_game(from);
+       ret->hints_active = TRUE;
+       ret->cheated = TRUE;
+       for (i = 0; i < wh; i++) {
+           ret->grid[i] &= ~2;
+           if (move[i+1] != '0')
+               ret->grid[i] |= 2;
+       }
+       return ret;
+    } else if (move[0] == 'M' &&
+              sscanf(move+1, "%d,%d", &x, &y) == 2 &&
+       x >= 0 && x < w && y >= 0 && y < h) {
+       int i, j, done;
+
+       ret = dup_game(from);
+
+       if (!ret->completed)
+           ret->moves++;
+
+       i = y * w + x;
+
+       done = TRUE;
+       for (j = 0; j < wh; j++) {
+           ret->grid[j] ^= ret->matrix->matrix[i*wh+j];
+           if (ret->grid[j] & 1)
+               done = FALSE;
+       }
+       ret->grid[i] ^= 2;             /* toggle hint */
+       if (done) {
+           ret->completed = TRUE;
+           ret->hints_active = FALSE;
+       }
+
+       return ret;
+    } else
+       return NULL;                   /* can't parse move string */
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    struct { int tilesize; } ads, *ds = &ads;
+    ads.tilesize = tilesize;
+
+    *x = TILE_SIZE * params->w + 2 * BORDER;
+    *y = TILE_SIZE * params->h + 2 * BORDER;
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+
+    frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
+
+    ret[COL_WRONG * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] / 3;
+    ret[COL_WRONG * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] / 3;
+    ret[COL_WRONG * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] / 3;
+
+    ret[COL_RIGHT * 3 + 0] = 1.0F;
+    ret[COL_RIGHT * 3 + 1] = 1.0F;
+    ret[COL_RIGHT * 3 + 2] = 1.0F;
+
+    ret[COL_GRID * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] / 1.5F;
+    ret[COL_GRID * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] / 1.5F;
+    ret[COL_GRID * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] / 1.5F;
+
+    ret[COL_DIAG * 3 + 0] = ret[COL_GRID * 3 + 0];
+    ret[COL_DIAG * 3 + 1] = ret[COL_GRID * 3 + 1];
+    ret[COL_DIAG * 3 + 2] = ret[COL_GRID * 3 + 2];
+
+    ret[COL_HINT * 3 + 0] = 1.0F;
+    ret[COL_HINT * 3 + 1] = 0.0F;
+    ret[COL_HINT * 3 + 2] = 0.0F;
+
+    ret[COL_CURSOR * 3 + 0] = 0.8F;
+    ret[COL_CURSOR * 3 + 1] = 0.0F;
+    ret[COL_CURSOR * 3 + 2] = 0.0F;
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    struct game_drawstate *ds = snew(struct game_drawstate);
+    int i;
+
+    ds->started = FALSE;
+    ds->w = state->w;
+    ds->h = state->h;
+    ds->tiles = snewn(ds->w*ds->h, unsigned char);
+    ds->tilesize = 0;                  /* haven't decided yet */
+    for (i = 0; i < ds->w*ds->h; i++)
+        ds->tiles[i] = -1;
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds->tiles);
+    sfree(ds);
+}
+
+static void draw_tile(drawing *dr, game_drawstate *ds, const game_state *state,
+                      int x, int y, int tile, int anim, float animtime)
+{
+    int w = ds->w, h = ds->h, wh = w * h;
+    int bx = x * TILE_SIZE + BORDER, by = y * TILE_SIZE + BORDER;
+    int i, j, dcol = (tile & 4) ? COL_CURSOR : COL_DIAG;
+
+    clip(dr, bx+1, by+1, TILE_SIZE-1, TILE_SIZE-1);
+
+    draw_rect(dr, bx+1, by+1, TILE_SIZE-1, TILE_SIZE-1,
+              anim ? COL_BACKGROUND : tile & 1 ? COL_WRONG : COL_RIGHT);
+    if (anim) {
+       /*
+        * Draw a polygon indicating that the square is diagonally
+        * flipping over.
+        */
+       int coords[8], colour;
+
+       coords[0] = bx + TILE_SIZE;
+       coords[1] = by;
+       coords[2] = bx + (int)((float)TILE_SIZE * animtime);
+       coords[3] = by + (int)((float)TILE_SIZE * animtime);
+       coords[4] = bx;
+       coords[5] = by + TILE_SIZE;
+       coords[6] = bx + TILE_SIZE - (int)((float)TILE_SIZE * animtime);
+       coords[7] = by + TILE_SIZE - (int)((float)TILE_SIZE * animtime);
+
+       colour = (tile & 1 ? COL_WRONG : COL_RIGHT);
+       if (animtime < 0.5)
+           colour = COL_WRONG + COL_RIGHT - colour;
+
+       draw_polygon(dr, coords, 4, colour, COL_GRID);
+    }
+
+    /*
+     * Draw a little diagram in the tile which indicates which
+     * surrounding tiles flip when this one is clicked.
+     */
+    for (i = 0; i < h; i++)
+       for (j = 0; j < w; j++)
+           if (state->matrix->matrix[(y*w+x)*wh + i*w+j]) {
+               int ox = j - x, oy = i - y;
+               int td = TILE_SIZE / 16;
+               int cx = (bx + TILE_SIZE/2) + (2 * ox - 1) * td;
+               int cy = (by + TILE_SIZE/2) + (2 * oy - 1) * td;
+               if (ox == 0 && oy == 0)
+                    draw_rect(dr, cx, cy, 2*td+1, 2*td+1, dcol);
+                else {
+                    draw_line(dr, cx, cy, cx+2*td, cy, dcol);
+                    draw_line(dr, cx, cy+2*td, cx+2*td, cy+2*td, dcol);
+                    draw_line(dr, cx, cy, cx, cy+2*td, dcol);
+                    draw_line(dr, cx+2*td, cy, cx+2*td, cy+2*td, dcol);
+                }
+           }
+
+    /*
+     * Draw a hint rectangle if required.
+     */
+    if (tile & 2) {
+       int x1 = bx + TILE_SIZE / 20, x2 = bx + TILE_SIZE - TILE_SIZE / 20;
+       int y1 = by + TILE_SIZE / 20, y2 = by + TILE_SIZE - TILE_SIZE / 20;
+       int i = 3;
+       while (i--) {
+           draw_line(dr, x1, y1, x2, y1, COL_HINT);
+           draw_line(dr, x1, y2, x2, y2, COL_HINT);
+           draw_line(dr, x1, y1, x1, y2, COL_HINT);
+           draw_line(dr, x2, y1, x2, y2, COL_HINT);
+           x1++, y1++, x2--, y2--;
+       }
+    }
+
+    unclip(dr);
+
+    draw_update(dr, bx+1, by+1, TILE_SIZE-1, TILE_SIZE-1);
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int w = ds->w, h = ds->h, wh = w * h;
+    int i, flashframe;
+
+    if (!ds->started) {
+        draw_rect(dr, 0, 0, TILE_SIZE * w + 2 * BORDER,
+                  TILE_SIZE * h + 2 * BORDER, COL_BACKGROUND);
+
+        /*
+         * Draw the grid lines.
+         */
+        for (i = 0; i <= w; i++)
+            draw_line(dr, i * TILE_SIZE + BORDER, BORDER,
+                      i * TILE_SIZE + BORDER, h * TILE_SIZE + BORDER,
+                      COL_GRID);
+        for (i = 0; i <= h; i++)
+            draw_line(dr, BORDER, i * TILE_SIZE + BORDER,
+                      w * TILE_SIZE + BORDER, i * TILE_SIZE + BORDER,
+                      COL_GRID);
+
+        draw_update(dr, 0, 0, TILE_SIZE * w + 2 * BORDER,
+                    TILE_SIZE * h + 2 * BORDER);
+
+        ds->started = TRUE;
+    }
+
+    if (flashtime)
+       flashframe = (int)(flashtime / FLASH_FRAME);
+    else
+       flashframe = -1;
+
+    animtime /= ANIM_TIME;            /* scale it so it goes from 0 to 1 */
+
+    for (i = 0; i < wh; i++) {
+        int x = i % w, y = i / w;
+       int fx, fy, fd;
+       int v = state->grid[i];
+       int vv;
+
+       if (flashframe >= 0) {
+           fx = (w+1)/2 - min(x+1, w-x);
+           fy = (h+1)/2 - min(y+1, h-y);
+           fd = max(fx, fy);
+           if (fd == flashframe)
+               v |= 1;
+           else if (fd == flashframe - 1)
+               v &= ~1;
+       }
+
+       if (!state->hints_active)
+           v &= ~2;
+        if (ui->cdraw && ui->cx == x && ui->cy == y)
+            v |= 4;
+
+       if (oldstate && ((state->grid[i] ^ oldstate->grid[i]) &~ 2))
+           vv = 255;                  /* means `animated' */
+       else
+           vv = v;
+
+        if (ds->tiles[i] == 255 || vv == 255 || ds->tiles[i] != vv) {
+            draw_tile(dr, ds, state, x, y, v, vv == 255, animtime);
+            ds->tiles[i] = vv;
+        }
+    }
+
+    {
+       char buf[256];
+
+       sprintf(buf, "%sMoves: %d",
+               (state->completed ? 
+                (state->cheated ? "Auto-solved. " : "COMPLETED! ") :
+                (state->cheated ? "Auto-solver used. " : "")),
+               state->moves);
+
+       status_bar(dr, buf);
+    }
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return ANIM_TIME;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    if (!oldstate->completed && newstate->completed)
+        return FLASH_FRAME * (max((newstate->w+1)/2, (newstate->h+1)/2)+1);
+
+    return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    return state->completed ? +1 : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+}
+
+#ifdef COMBINED
+#define thegame flip
+#endif
+
+const struct game thegame = {
+    "Flip", "games.flip", "flip",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    TRUE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    FALSE, FALSE, game_print_size, game_print,
+    TRUE,                             /* wants_statusbar */
+    FALSE, game_timing_state,
+    0,                                /* flags */
+};
diff --git a/flood.R b/flood.R
new file mode 100644 (file)
index 0000000..359bbb5
--- /dev/null
+++ b/flood.R
@@ -0,0 +1,19 @@
+# -*- makefile -*-
+
+flood     : [X] GTK COMMON flood flood-icon|no-icon
+
+flood     : [G] WINDOWS COMMON flood flood.res|noicon.res
+
+ALL += flood[COMBINED]
+
+!begin am gtk
+GAMES += flood
+!end
+
+!begin >list.c
+    A(flood) \
+!end
+
+!begin >gamedesc.txt
+flood:flood.exe:Flood:Flood-filling puzzle:Turn the grid the same colour in as few flood fills as possible.
+!end
diff --git a/flood.c b/flood.c
new file mode 100644 (file)
index 0000000..f97de2f
--- /dev/null
+++ b/flood.c
@@ -0,0 +1,1372 @@
+/*
+ * flood.c: puzzle in which you make a grid all the same colour by
+ * repeatedly flood-filling the top left corner, and try to do so in
+ * as few moves as possible.
+ */
+
+/*
+ * Possible further work:
+ *
+ *  - UI: perhaps we should only permit clicking on regions that can
+ *    actually be reached by the next flood-fill - i.e. a click is
+ *    only interpreted as a move if it would cause the clicked-on
+ *    square to become part of the controlled area. This provides a
+ *    hint in cases where you mistakenly thought that would happen,
+ *    and protects you against typos in cases where you just
+ *    mis-aimed.
+ *
+ *  - UI: perhaps mark the fill square in some way? Or even mark the
+ *    whole connected component _containing_ the fill square. Pro:
+ *    that would make it easier to tell apart cases where almost all
+ *    the yellow squares in the grid are part of the target component
+ *    (hence, yellow is _done_ and you never have to fill in that
+ *    colour again) from cases where there's still one yellow square
+ *    that's only diagonally adjacent and hence will need coming back
+ *    to. Con: but it would almost certainly be ugly.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+
+enum {
+    COL_BACKGROUND, COL_SEPARATOR,
+    COL_1, COL_2, COL_3, COL_4, COL_5, COL_6, COL_7, COL_8, COL_9, COL_10,
+    COL_HIGHLIGHT, COL_LOWLIGHT,
+    NCOLOURS
+};
+
+struct game_params {
+    int w, h;
+    int colours;
+    int leniency;
+};
+
+/* Just in case I want to make this changeable later, I'll put the
+ * coordinates of the flood-fill point here so that it'll be easy to
+ * find everywhere later that has to change. */
+#define FILLX 0
+#define FILLY 0
+
+typedef struct soln {
+    int refcount;
+    int nmoves;
+    char *moves;
+} soln;
+
+struct game_state {
+    int w, h, colours;
+    int moves, movelimit;
+    int complete;
+    char *grid;
+    int cheated;
+    int solnpos;
+    soln *soln;
+};
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = ret->h = 12;
+    ret->colours = 6;
+    ret->leniency = 5;
+
+    return ret;
+}
+
+static const struct {
+    struct game_params preset;
+    const char *name;
+} flood_presets[] = {
+    /* Default 12x12 size, three difficulty levels. */
+    {{12, 12, 6, 5}, "12x12 Easy"},
+    {{12, 12, 6, 2}, "12x12 Medium"},
+    {{12, 12, 6, 0}, "12x12 Hard"},
+    /* Larger puzzles, leaving off Easy in the expectation that people
+     * wanting a bigger grid will have played it enough to find Easy
+     * easy. */
+    {{16, 16, 6, 2}, "16x16 Medium"},
+    {{16, 16, 6, 0}, "16x16 Hard"},
+    /* A couple of different colour counts. It seems generally not too
+     * hard with fewer colours (probably because fewer choices), so no
+     * extra moves for these modes. */
+    {{12, 12, 3, 0}, "12x12, 3 colours"},
+    {{12, 12, 4, 0}, "12x12, 4 colours"},
+};
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params *ret;
+
+    if (i < 0 || i >= lenof(flood_presets))
+        return FALSE;
+
+    ret = snew(game_params);
+    *ret = flood_presets[i].preset;
+    *name = dupstr(flood_presets[i].name);
+    *params = ret;
+    return TRUE;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+static void decode_params(game_params *ret, char const *string)
+{
+    ret->w = ret->h = atoi(string);
+    while (*string && isdigit((unsigned char)*string)) string++;
+    if (*string == 'x') {
+        string++;
+        ret->h = atoi(string);
+        while (*string && isdigit((unsigned char)*string)) string++;
+    }
+    while (*string) {
+        if (*string == 'c') {
+            string++;
+           ret->colours = atoi(string);
+            while (string[1] && isdigit((unsigned char)string[1])) string++;
+       } else if (*string == 'm') {
+            string++;
+           ret->leniency = atoi(string);
+            while (string[1] && isdigit((unsigned char)string[1])) string++;
+       }
+       string++;
+    }
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char buf[256];
+    sprintf(buf, "%dx%d", params->w, params->h);
+    if (full)
+        sprintf(buf + strlen(buf), "c%dm%d",
+                params->colours, params->leniency);
+    return dupstr(buf);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+    char buf[80];
+
+    ret = snewn(5, config_item);
+
+    ret[0].name = "Width";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->w);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Height";
+    ret[1].type = C_STRING;
+    sprintf(buf, "%d", params->h);
+    ret[1].sval = dupstr(buf);
+    ret[1].ival = 0;
+
+    ret[2].name = "Colours";
+    ret[2].type = C_STRING;
+    sprintf(buf, "%d", params->colours);
+    ret[2].sval = dupstr(buf);
+    ret[2].ival = 0;
+
+    ret[3].name = "Extra moves permitted";
+    ret[3].type = C_STRING;
+    sprintf(buf, "%d", params->leniency);
+    ret[3].sval = dupstr(buf);
+    ret[3].ival = 0;
+
+    ret[4].name = NULL;
+    ret[4].type = C_END;
+    ret[4].sval = NULL;
+    ret[4].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = atoi(cfg[0].sval);
+    ret->h = atoi(cfg[1].sval);
+    ret->colours = atoi(cfg[2].sval);
+    ret->leniency = atoi(cfg[3].sval);
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    if (params->w < 2 && params->h < 2)
+        return "Grid must contain at least two squares";
+    if (params->colours < 3 || params->colours > 10)
+        return "Must have between 3 and 10 colours";
+    if (params->leniency < 0)
+        return "Leniency must be non-negative";
+    return NULL;
+}
+
+#if 0
+/*
+ * Bodge to permit varying the recursion depth for testing purposes.
+
+To test two Floods against each other:
+
+paste <(./flood.1 --generate 100 12x12c6m0#12345 | cut -f2 -d,) <(./flood.2 --generate 100 12x12c6m0#12345 | cut -f2 -d,) | awk '{print $2-$1}' | sort -n | uniq -c | awk '{print $2,$1}' | tee z
+
+and then run gnuplot and plot "z".
+
+ */
+static int rdepth = 0;
+#define RECURSION_DEPTH (rdepth)
+void check_recursion_depth(void)
+{
+    if (!rdepth) {
+        const char *depthstr = getenv("FLOOD_DEPTH");
+        rdepth = depthstr ? atoi(depthstr) : 1;
+        rdepth = rdepth > 0 ? rdepth : 1;
+    }
+}
+#else
+/*
+ * Last time I empirically checked this, depth 3 was a noticeable
+ * improvement on 2, but 4 only negligibly better than 3.
+ */
+#define RECURSION_DEPTH 3
+#define check_recursion_depth() (void)0
+#endif
+
+struct solver_scratch {
+    int *queue[2];
+    int *dist;
+    char *grid, *grid2;
+    char *rgrids;
+};
+
+static struct solver_scratch *new_scratch(int w, int h)
+{
+    int wh = w*h;
+    struct solver_scratch *scratch = snew(struct solver_scratch);
+    check_recursion_depth();
+    scratch->queue[0] = snewn(wh, int);
+    scratch->queue[1] = snewn(wh, int);
+    scratch->dist = snewn(wh, int);
+    scratch->grid = snewn(wh, char);
+    scratch->grid2 = snewn(wh, char);
+    scratch->rgrids = snewn(wh * RECURSION_DEPTH, char);
+    return scratch;
+}
+
+static void free_scratch(struct solver_scratch *scratch)
+{
+    sfree(scratch->queue[0]);
+    sfree(scratch->queue[1]);
+    sfree(scratch->dist);
+    sfree(scratch->grid);
+    sfree(scratch->grid2);
+    sfree(scratch->rgrids);
+    sfree(scratch);
+}
+
+#if 0
+/* Diagnostic routines you can uncomment if you need them */
+void dump_grid(int w, int h, const char *grid, const char *titlefmt, ...)
+{
+    int x, y;
+    if (titlefmt) {
+        va_list ap;
+        va_start(ap, titlefmt);
+        vprintf(titlefmt, ap);
+        va_end(ap);
+        printf(":\n");
+    } else {
+        printf("Grid:\n");
+    }
+    for (y = 0; y < h; y++) {
+        printf("  ");
+        for (x = 0; x < w; x++) {
+            printf("%1x", grid[y*w+x]);
+        }
+        printf("\n");
+    }
+}
+
+void dump_dist(int w, int h, const int *dists, const char *titlefmt, ...)
+{
+    int x, y;
+    if (titlefmt) {
+        va_list ap;
+        va_start(ap, titlefmt);
+        vprintf(titlefmt, ap);
+        va_end(ap);
+        printf(":\n");
+    } else {
+        printf("Distances:\n");
+    }
+    for (y = 0; y < h; y++) {
+        printf("  ");
+        for (x = 0; x < w; x++) {
+            printf("%3d", dists[y*w+x]);
+        }
+        printf("\n");
+    }
+}
+#endif
+
+/*
+ * Search a grid to find the most distant square(s). Return their
+ * distance and the number of them, and also the number of squares in
+ * the current controlled set (i.e. at distance zero).
+ */
+static void search(int w, int h, char *grid, int x0, int y0,
+                   struct solver_scratch *scratch,
+                   int *rdist, int *rnumber, int *rcontrol)
+{
+    int wh = w*h;
+    int i, qcurr, qhead, qtail, qnext, currdist, remaining;
+
+    for (i = 0; i < wh; i++)
+        scratch->dist[i] = -1;
+    scratch->queue[0][0] = y0*w+x0;
+    scratch->queue[1][0] = y0*w+x0;
+    scratch->dist[y0*w+x0] = 0;
+    currdist = 0;
+    qcurr = 0;
+    qtail = 0;
+    qhead = 1;
+    qnext = 1;
+    remaining = wh - 1;
+
+    while (1) {
+        if (qtail == qhead) {
+            /* Switch queues. */
+            if (currdist == 0)
+                *rcontrol = qhead;
+            currdist++;
+            qcurr ^= 1;                    /* switch queues */
+            qhead = qnext;
+            qtail = 0;
+            qnext = 0;
+#if 0
+            printf("switch queue, new dist %d, queue %d\n", currdist, qhead);
+#endif
+        } else if (remaining == 0 && qnext == 0) {
+            break;
+        } else {
+            int pos = scratch->queue[qcurr][qtail++];
+            int y = pos / w;
+            int x = pos % w;
+#if 0
+            printf("checking neighbours of %d,%d\n", x, y);
+#endif
+            int dir;
+            for (dir = 0; dir < 4; dir++) {
+                int y1 = y + (dir == 1 ? 1 : dir == 3 ? -1 : 0);
+                int x1 = x + (dir == 0 ? 1 : dir == 2 ? -1 : 0);
+                if (0 <= x1 && x1 < w && 0 <= y1 && y1 < h) {
+                    int pos1 = y1*w+x1;
+#if 0
+                    printf("trying %d,%d: colours %d-%d dist %d\n", x1, y1,
+                           grid[pos], grid[pos1], scratch->dist[pos]);
+#endif
+                    if (scratch->dist[pos1] == -1 &&
+                        ((grid[pos1] == grid[pos] &&
+                          scratch->dist[pos] == currdist) ||
+                         (grid[pos1] != grid[pos] &&
+                          scratch->dist[pos] == currdist - 1))) {
+#if 0
+                        printf("marking %d,%d dist %d\n", x1, y1, currdist);
+#endif
+                        scratch->queue[qcurr][qhead++] = pos1;
+                        scratch->queue[qcurr^1][qnext++] = pos1;
+                        scratch->dist[pos1] = currdist;
+                        remaining--;
+                    }
+                }
+            }
+        }
+    }
+
+    *rdist = currdist;
+    *rnumber = qhead;
+    if (currdist == 0)
+        *rcontrol = qhead;
+}
+
+/*
+ * Enact a flood-fill move on a grid.
+ */
+static void fill(int w, int h, char *grid, int x0, int y0, char newcolour,
+                 int *queue)
+{
+    char oldcolour;
+    int qhead, qtail;
+
+    oldcolour = grid[y0*w+x0];
+    assert(oldcolour != newcolour);
+    grid[y0*w+x0] = newcolour;
+    queue[0] = y0*w+x0;
+    qtail = 0;
+    qhead = 1;
+
+    while (qtail < qhead) {
+        int pos = queue[qtail++];
+        int y = pos / w;
+        int x = pos % w;
+        int dir;
+        for (dir = 0; dir < 4; dir++) {
+            int y1 = y + (dir == 1 ? 1 : dir == 3 ? -1 : 0);
+            int x1 = x + (dir == 0 ? 1 : dir == 2 ? -1 : 0);
+            if (0 <= x1 && x1 < w && 0 <= y1 && y1 < h) {
+                int pos1 = y1*w+x1;
+                if (grid[pos1] == oldcolour) {
+                    grid[pos1] = newcolour;
+                    queue[qhead++] = pos1;
+                }
+            }
+        }
+    }
+}
+
+/*
+ * Detect a completed grid.
+ */
+static int completed(int w, int h, char *grid)
+{
+    int wh = w*h;
+    int i;
+
+    for (i = 1; i < wh; i++)
+        if (grid[i] != grid[0])
+            return FALSE;
+
+    return TRUE;
+}
+
+/*
+ * Try out every possible move on a grid, and choose whichever one
+ * reduced the result of search() by the most.
+ */
+static char choosemove_recurse(int w, int h, char *grid, int x0, int y0,
+                               int maxmove, struct solver_scratch *scratch,
+                               int depth, int *rbestdist, int *rbestnumber, int *rbestcontrol)
+{
+    int wh = w*h;
+    char move, bestmove;
+    int dist, number, control, bestdist, bestnumber, bestcontrol;
+    char *tmpgrid;
+
+    assert(0 <= depth && depth < RECURSION_DEPTH);
+    tmpgrid = scratch->rgrids + depth*wh;
+
+    bestdist = wh + 1;
+    bestnumber = 0;
+    bestcontrol = 0;
+    bestmove = -1;
+
+#if 0
+    dump_grid(w, h, grid, "before choosemove_recurse %d", depth);
+#endif
+    for (move = 0; move < maxmove; move++) {
+        if (grid[y0*w+x0] == move)
+            continue;
+        memcpy(tmpgrid, grid, wh * sizeof(*grid));
+        fill(w, h, tmpgrid, x0, y0, move, scratch->queue[0]);
+        if (completed(w, h, tmpgrid)) {
+            /*
+             * A move that wins is immediately the best, so stop
+             * searching. Record what depth of recursion that happened
+             * at, so that higher levels will choose a move that gets
+             * to a winning position sooner.
+             */
+            *rbestdist = -1;
+            *rbestnumber = depth;
+            *rbestcontrol = wh;
+            return move;
+        }
+        if (depth < RECURSION_DEPTH-1) {
+            choosemove_recurse(w, h, tmpgrid, x0, y0, maxmove, scratch,
+                               depth+1, &dist, &number, &control);
+        } else {
+#if 0
+            dump_grid(w, h, tmpgrid, "after move %d at depth %d",
+                      move, depth);
+#endif
+            search(w, h, tmpgrid, x0, y0, scratch, &dist, &number, &control);
+#if 0
+            dump_dist(w, h, scratch->dist, "after move %d at depth %d",
+                      move, depth);
+            printf("move %d at depth %d: %d at %d\n",
+                   depth, move, number, dist);
+#endif
+        }
+        if (dist < bestdist ||
+            (dist == bestdist &&
+             (number < bestnumber ||
+              (number == bestnumber &&
+               (control > bestcontrol))))) {
+            bestdist = dist;
+            bestnumber = number;
+            bestcontrol = control;
+            bestmove = move;
+        }
+    }
+#if 0
+    printf("best at depth %d was %d (%d at %d, %d controlled)\n",
+           depth, bestmove, bestnumber, bestdist, bestcontrol);
+#endif
+
+    *rbestdist = bestdist;
+    *rbestnumber = bestnumber;
+    *rbestcontrol = bestcontrol;
+    return bestmove;
+}
+static char choosemove(int w, int h, char *grid, int x0, int y0,
+                       int maxmove, struct solver_scratch *scratch)
+{
+    int tmp0, tmp1, tmp2;
+    return choosemove_recurse(w, h, grid, x0, y0, maxmove, scratch,
+                              0, &tmp0, &tmp1, &tmp2);
+}
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                          char **aux, int interactive)
+{
+    int w = params->w, h = params->h, wh = w*h;
+    int i, moves;
+    char *desc;
+    struct solver_scratch *scratch;
+
+    scratch = new_scratch(w, h);
+
+    /*
+     * Invent a random grid.
+     */
+    for (i = 0; i < wh; i++)
+        scratch->grid[i] = random_upto(rs, params->colours);
+
+    /*
+     * Run the solver, and count how many moves it uses.
+     */
+    memcpy(scratch->grid2, scratch->grid, wh * sizeof(*scratch->grid2));
+    moves = 0;
+    check_recursion_depth();
+    while (!completed(w, h, scratch->grid2)) {
+        char move = choosemove(w, h, scratch->grid2, FILLX, FILLY,
+                               params->colours, scratch);
+        fill(w, h, scratch->grid2, FILLX, FILLY, move, scratch->queue[0]);
+        moves++;
+    }
+
+    /*
+     * Adjust for difficulty.
+     */
+    moves += params->leniency;
+
+    /*
+     * Encode the game id.
+     */
+    desc = snewn(wh + 40, char);
+    for (i = 0; i < wh; i++) {
+        char colour = scratch->grid[i];
+        char textcolour = (colour > 9 ? 'A' : '0') + colour;
+        desc[i] = textcolour;
+    }
+    sprintf(desc+i, ",%d", moves);
+
+    free_scratch(scratch);
+
+    return desc;
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    int w = params->w, h = params->h, wh = w*h;
+    int i;
+    for (i = 0; i < wh; i++) {
+        char c = *desc++;
+        if (c == 0)
+            return "Not enough data in grid description";
+        if (c >= '0' && c <= '9')
+            c -= '0';
+        else if (c >= 'A' && c <= 'Z')
+            c = 10 + (c - 'A');
+        else
+            return "Bad character in grid description";
+        if ((unsigned)c >= params->colours)
+            return "Colour out of range in grid description";
+    }
+    if (*desc != ',')
+        return "Expected ',' after grid description";
+    desc++;
+    if (desc[strspn(desc, "0123456789")])
+        return "Badly formatted move limit after grid description";
+    return NULL;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    int w = params->w, h = params->h, wh = w*h;
+    game_state *state = snew(game_state);
+    int i;
+
+    state->w = w;
+    state->h = h;
+    state->colours = params->colours;
+    state->moves = 0;
+    state->grid = snewn(wh, char);
+
+    for (i = 0; i < wh; i++) {
+        char c = *desc++;
+        assert(c);
+        if (c >= '0' && c <= '9')
+            c -= '0';
+        else if (c >= 'A' && c <= 'Z')
+            c = 10 + (c - 'A');
+        else
+            assert(!"bad colour");
+        state->grid[i] = c;
+    }
+    assert(*desc == ',');
+    desc++;
+
+    state->movelimit = atoi(desc);
+    state->complete = FALSE;
+    state->cheated = FALSE;
+    state->solnpos = 0;
+    state->soln = NULL;
+
+    return state;
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    game_state *ret = snew(game_state);
+
+    ret->w = state->w;
+    ret->h = state->h;
+    ret->colours = state->colours;
+    ret->moves = state->moves;
+    ret->movelimit = state->movelimit;
+    ret->complete = state->complete;
+    ret->grid = snewn(state->w * state->h, char);
+    memcpy(ret->grid, state->grid, state->w * state->h * sizeof(*ret->grid));
+
+    ret->cheated = state->cheated;
+    ret->soln = state->soln;
+    if (ret->soln)
+       ret->soln->refcount++;
+    ret->solnpos = state->solnpos;
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    if (state->soln && --state->soln->refcount == 0) {
+       sfree(state->soln->moves);
+       sfree(state->soln);
+    }
+    sfree(state->grid);
+    sfree(state);
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    int w = state->w, h = state->h, wh = w*h;
+    char *moves, *ret, *p;
+    int i, len, nmoves;
+    char buf[256];
+    struct solver_scratch *scratch;
+
+    if (currstate->complete) {
+        *error = "Puzzle is already solved";
+        return NULL;
+    }
+
+    /*
+     * Find the best solution our solver can give.
+     */
+    moves = snewn(wh, char);           /* sure to be enough */
+    nmoves = 0;
+    scratch = new_scratch(w, h);
+    memcpy(scratch->grid2, currstate->grid, wh * sizeof(*scratch->grid2));
+    check_recursion_depth();
+    while (!completed(w, h, scratch->grid2)) {
+        char move = choosemove(w, h, scratch->grid2, FILLX, FILLY,
+                               currstate->colours, scratch);
+        fill(w, h, scratch->grid2, FILLX, FILLY, move, scratch->queue[0]);
+        assert(nmoves < wh);
+        moves[nmoves++] = move;
+    }
+    free_scratch(scratch);
+
+    /*
+     * Encode it as a move string.
+     */
+    len = 1;                           /* trailing NUL */
+    for (i = 0; i < nmoves; i++)
+        len += sprintf(buf, ",%d", moves[i]);
+    ret = snewn(len, char);
+    p = ret;
+    for (i = 0; i < nmoves; i++)
+        p += sprintf(p, "%c%d", (i==0 ? 'S' : ','), moves[i]);
+    assert(p - ret == len - 1);
+
+    sfree(moves);
+    return ret;
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    int w = state->w, h = state->h;
+    char *ret, *p;
+    int x, y, len;
+
+    len = h * (w+1);                   /* +1 for newline after each row */
+    ret = snewn(len+1, char);          /* and +1 for terminating \0 */
+    p = ret;
+
+    for (y = 0; y < h; y++) {
+       for (x = 0; x < w; x++) {
+            char colour = state->grid[y*w+x];
+            char textcolour = (colour > 9 ? 'A' : '0') + colour;
+            *p++ = textcolour;
+       }
+       *p++ = '\n';
+    }
+
+    assert(p - ret == len);
+    *p = '\0';
+
+    return ret;
+}
+
+struct game_ui {
+    int cursor_visible;
+    int cx, cy;
+    enum { VICTORY, DEFEAT } flash_type;
+};
+
+static game_ui *new_ui(const game_state *state)
+{
+    struct game_ui *ui = snew(struct game_ui);
+    ui->cursor_visible = FALSE;
+    ui->cx = FILLX;
+    ui->cy = FILLY;
+    return ui;
+}
+
+static void free_ui(game_ui *ui)
+{
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+}
+
+struct game_drawstate {
+    int started;
+    int tilesize;
+    int *grid;
+};
+
+#define TILESIZE (ds->tilesize)
+#define PREFERRED_TILESIZE 32
+#define BORDER (TILESIZE / 2)
+#define SEP_WIDTH (TILESIZE / 32)
+#define CURSOR_INSET (TILESIZE / 8)
+#define HIGHLIGHT_WIDTH (TILESIZE / 10)
+#define COORD(x)  ( (x) * TILESIZE + BORDER )
+#define FROMCOORD(x)  ( ((x) - BORDER + TILESIZE) / TILESIZE - 1 )
+#define VICTORY_FLASH_FRAME 0.03F
+#define DEFEAT_FLASH_FRAME 0.10F
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    int w = state->w, h = state->h;
+    int tx = -1, ty = -1, move = -1;
+
+    if (button == LEFT_BUTTON) {
+       tx = FROMCOORD(x);
+        ty = FROMCOORD(y);
+        ui->cursor_visible = FALSE;
+    } else if (button == CURSOR_LEFT && ui->cx > 0) {
+        ui->cx--;
+        ui->cursor_visible = TRUE;
+        return "";
+    } else if (button == CURSOR_RIGHT && ui->cx+1 < w) {
+        ui->cx++;
+        ui->cursor_visible = TRUE;
+        return "";
+    } else if (button == CURSOR_UP && ui->cy > 0) {
+        ui->cy--;
+        ui->cursor_visible = TRUE;
+        return "";
+    } else if (button == CURSOR_DOWN && ui->cy+1 < h) {
+        ui->cy++;
+        ui->cursor_visible = TRUE;
+        return "";
+    } else if (button == CURSOR_SELECT) {
+        tx = ui->cx;
+        ty = ui->cy;
+    } else if (button == CURSOR_SELECT2 &&
+               state->soln && state->solnpos < state->soln->nmoves) {
+       move = state->soln->moves[state->solnpos];
+    } else {
+        return NULL;
+    }
+
+    if (tx >= 0 && tx < w && ty >= 0 && ty < h &&
+        state->grid[0] != state->grid[ty*w+tx])
+        move = state->grid[ty*w+tx];
+
+    if (move >= 0 && !state->complete) {
+        char buf[256];
+        sprintf(buf, "M%d", move);
+        return dupstr(buf);
+    }
+
+    return NULL;
+}
+
+static game_state *execute_move(const game_state *state, const char *move)
+{
+    game_state *ret;
+    int c;
+
+    if (move[0] == 'M' &&
+        sscanf(move+1, "%d", &c) == 1 &&
+        c >= 0 &&
+        !state->complete) {
+        int *queue = snewn(state->w * state->h, int);
+       ret = dup_game(state);
+        fill(ret->w, ret->h, ret->grid, FILLX, FILLY, c, queue);
+        ret->moves++;
+        ret->complete = completed(ret->w, ret->h, ret->grid);
+
+        if (ret->soln) {
+            /*
+             * If this move is the correct next one in the stored
+             * solution path, advance solnpos.
+             */
+            if (c == ret->soln->moves[ret->solnpos] &&
+                ret->solnpos+1 < ret->soln->nmoves) {
+                ret->solnpos++;
+            } else {
+                /*
+                 * Otherwise, the user has strayed from the path or
+                 * else the path has come to an end; either way, the
+                 * path is no longer valid.
+                 */
+                ret->soln->refcount--;
+                assert(ret->soln->refcount > 0);/* `state' at least still exists */
+                ret->soln = NULL;
+                ret->solnpos = 0;
+            }
+        }
+
+        sfree(queue);
+        return ret;
+    } else if (*move == 'S') {
+       soln *sol;
+        const char *p;
+        int i;
+
+       /*
+        * This is a solve move, so we don't actually _change_ the
+        * grid but merely set up a stored solution path.
+        */
+       move++;
+       sol = snew(soln);
+
+        sol->nmoves = 1;
+        for (p = move; *p; p++) {
+            if (*p == ',')
+                sol->nmoves++;
+        }
+
+        sol->moves = snewn(sol->nmoves, char);
+        for (i = 0, p = move; i < sol->nmoves; i++) {
+            assert(*p);
+            sol->moves[i] = atoi(p);
+            p += strspn(p, "0123456789");
+            if (*p) {
+                assert(*p == ',');
+                p++;
+            }
+        }
+
+       ret = dup_game(state);
+       ret->cheated = TRUE;
+       if (ret->soln && --ret->soln->refcount == 0) {
+           sfree(ret->soln->moves);
+           sfree(ret->soln);
+       }
+       ret->soln = sol;
+       ret->solnpos = 0;
+       sol->refcount = 1;
+       return ret;
+    }
+
+    return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    struct { int tilesize; } ads, *ds = &ads;
+    ads.tilesize = tilesize;
+
+    *x = BORDER * 2 + TILESIZE * params->w;
+    *y = BORDER * 2 + TILESIZE * params->h;
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+
+    game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT);
+
+    ret[COL_SEPARATOR * 3 + 0] = 0.0F;
+    ret[COL_SEPARATOR * 3 + 1] = 0.0F;
+    ret[COL_SEPARATOR * 3 + 2] = 0.0F;
+
+    /* red */
+    ret[COL_1 * 3 + 0] = 1.0F;
+    ret[COL_1 * 3 + 1] = 0.0F;
+    ret[COL_1 * 3 + 2] = 0.0F;
+
+    /* yellow */
+    ret[COL_2 * 3 + 0] = 1.0F;
+    ret[COL_2 * 3 + 1] = 1.0F;
+    ret[COL_2 * 3 + 2] = 0.0F;
+
+    /* green */
+    ret[COL_3 * 3 + 0] = 0.0F;
+    ret[COL_3 * 3 + 1] = 1.0F;
+    ret[COL_3 * 3 + 2] = 0.0F;
+
+    /* blue */
+    ret[COL_4 * 3 + 0] = 0.2F;
+    ret[COL_4 * 3 + 1] = 0.3F;
+    ret[COL_4 * 3 + 2] = 1.0F;
+
+    /* orange */
+    ret[COL_5 * 3 + 0] = 1.0F;
+    ret[COL_5 * 3 + 1] = 0.5F;
+    ret[COL_5 * 3 + 2] = 0.0F;
+
+    /* purple */
+    ret[COL_6 * 3 + 0] = 0.5F;
+    ret[COL_6 * 3 + 1] = 0.0F;
+    ret[COL_6 * 3 + 2] = 0.7F;
+
+    /* brown */
+    ret[COL_7 * 3 + 0] = 0.5F;
+    ret[COL_7 * 3 + 1] = 0.3F;
+    ret[COL_7 * 3 + 2] = 0.3F;
+
+    /* light blue */
+    ret[COL_8 * 3 + 0] = 0.4F;
+    ret[COL_8 * 3 + 1] = 0.8F;
+    ret[COL_8 * 3 + 2] = 1.0F;
+
+    /* light green */
+    ret[COL_9 * 3 + 0] = 0.7F;
+    ret[COL_9 * 3 + 1] = 1.0F;
+    ret[COL_9 * 3 + 2] = 0.7F;
+
+    /* pink */
+    ret[COL_10 * 3 + 0] = 1.0F;
+    ret[COL_10 * 3 + 1] = 0.6F;
+    ret[COL_10 * 3 + 2] = 1.0F;
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    struct game_drawstate *ds = snew(struct game_drawstate);
+    int w = state->w, h = state->h, wh = w*h;
+    int i;
+
+    ds->started = FALSE;
+    ds->tilesize = 0;
+    ds->grid = snewn(wh, int);
+    for (i = 0; i < wh; i++)
+        ds->grid[i] = -1;
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds->grid);
+    sfree(ds);
+}
+
+#define BORDER_L  0x001
+#define BORDER_R  0x002
+#define BORDER_U  0x004
+#define BORDER_D  0x008
+#define CORNER_UL 0x010
+#define CORNER_UR 0x020
+#define CORNER_DL 0x040
+#define CORNER_DR 0x080
+#define CURSOR    0x100
+#define BADFLASH  0x200
+#define SOLNNEXT  0x400
+#define COLOUR_SHIFT 11
+
+static void draw_tile(drawing *dr, game_drawstate *ds,
+                      int x, int y, int tile)
+{
+    int colour;
+    int tx = COORD(x), ty = COORD(y);
+
+    colour = tile >> COLOUR_SHIFT;
+    if (tile & BADFLASH)
+        colour = COL_SEPARATOR;
+    else
+        colour += COL_1;
+    draw_rect(dr, tx, ty, TILESIZE, TILESIZE, colour);
+
+    if (tile & BORDER_L)
+        draw_rect(dr, tx, ty,
+                  SEP_WIDTH, TILESIZE, COL_SEPARATOR);
+    if (tile & BORDER_R)
+        draw_rect(dr, tx + TILESIZE - SEP_WIDTH, ty,
+                  SEP_WIDTH, TILESIZE, COL_SEPARATOR);
+    if (tile & BORDER_U)
+        draw_rect(dr, tx, ty,
+                  TILESIZE, SEP_WIDTH, COL_SEPARATOR);
+    if (tile & BORDER_D)
+        draw_rect(dr, tx, ty + TILESIZE - SEP_WIDTH,
+                  TILESIZE, SEP_WIDTH, COL_SEPARATOR);
+
+    if (tile & CORNER_UL)
+        draw_rect(dr, tx, ty,
+                  SEP_WIDTH, SEP_WIDTH, COL_SEPARATOR);
+    if (tile & CORNER_UR)
+        draw_rect(dr, tx + TILESIZE - SEP_WIDTH, ty,
+                  SEP_WIDTH, SEP_WIDTH, COL_SEPARATOR);
+    if (tile & CORNER_DL)
+        draw_rect(dr, tx, ty + TILESIZE - SEP_WIDTH,
+                  SEP_WIDTH, SEP_WIDTH, COL_SEPARATOR);
+    if (tile & CORNER_DR)
+        draw_rect(dr, tx + TILESIZE - SEP_WIDTH, ty + TILESIZE - SEP_WIDTH,
+                  SEP_WIDTH, SEP_WIDTH, COL_SEPARATOR);
+
+    if (tile & CURSOR)
+        draw_rect_outline(dr, tx + CURSOR_INSET, ty + CURSOR_INSET,
+                          TILESIZE - 1 - CURSOR_INSET * 2,
+                          TILESIZE - 1 - CURSOR_INSET * 2,
+                          COL_SEPARATOR);
+
+    if (tile & SOLNNEXT) {
+        draw_circle(dr, tx + TILESIZE/2, ty + TILESIZE/2, TILESIZE/6,
+                    COL_SEPARATOR, COL_SEPARATOR);
+    }
+
+    draw_update(dr, tx, ty, TILESIZE, TILESIZE);
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int w = state->w, h = state->h, wh = w*h;
+    int x, y, flashframe, solnmove;
+    char *grid;
+
+    /* This was entirely cloned from fifteen.c; it should probably be
+     * moved into some generic 'draw-recessed-rectangle' utility fn. */
+    if (!ds->started) {
+       int coords[10];
+
+       draw_rect(dr, 0, 0,
+                 TILESIZE * w + 2 * BORDER,
+                 TILESIZE * h + 2 * BORDER, COL_BACKGROUND);
+       draw_update(dr, 0, 0,
+                   TILESIZE * w + 2 * BORDER,
+                   TILESIZE * h + 2 * BORDER);
+
+       /*
+        * Recessed area containing the whole puzzle.
+        */
+       coords[0] = COORD(w) + HIGHLIGHT_WIDTH - 1;
+       coords[1] = COORD(h) + HIGHLIGHT_WIDTH - 1;
+       coords[2] = COORD(w) + HIGHLIGHT_WIDTH - 1;
+       coords[3] = COORD(0) - HIGHLIGHT_WIDTH;
+       coords[4] = coords[2] - TILESIZE;
+       coords[5] = coords[3] + TILESIZE;
+       coords[8] = COORD(0) - HIGHLIGHT_WIDTH;
+       coords[9] = COORD(h) + HIGHLIGHT_WIDTH - 1;
+       coords[6] = coords[8] + TILESIZE;
+       coords[7] = coords[9] - TILESIZE;
+       draw_polygon(dr, coords, 5, COL_HIGHLIGHT, COL_HIGHLIGHT);
+
+       coords[1] = COORD(0) - HIGHLIGHT_WIDTH;
+       coords[0] = COORD(0) - HIGHLIGHT_WIDTH;
+       draw_polygon(dr, coords, 5, COL_LOWLIGHT, COL_LOWLIGHT);
+
+        draw_rect(dr, COORD(0) - SEP_WIDTH, COORD(0) - SEP_WIDTH,
+                  TILESIZE * w + 2 * SEP_WIDTH, TILESIZE * h + 2 * SEP_WIDTH,
+                  COL_SEPARATOR);
+
+       ds->started = 1;
+    }
+
+    if (flashtime > 0) {
+        float frame = (ui->flash_type == VICTORY ?
+                       VICTORY_FLASH_FRAME : DEFEAT_FLASH_FRAME);
+        flashframe = (int)(flashtime / frame);
+    } else {
+        flashframe = -1;
+    }
+
+    grid = snewn(wh, char);
+    memcpy(grid, state->grid, wh * sizeof(*grid));
+
+    if (state->soln && state->solnpos < state->soln->nmoves) {
+        int i, *queue;
+
+        /*
+         * Highlight as 'next auto-solver move' every square of the
+         * target colour which is adjacent to the currently controlled
+         * region. We do this by first enacting the actual move, then
+         * flood-filling again in a nonexistent colour, and finally
+         * reverting to the original grid anything in the new colour
+         * that was part of the original controlled region. Then
+         * regions coloured in the dummy colour should be displayed as
+         * soln_move with the SOLNNEXT flag.
+         */
+        solnmove = state->soln->moves[state->solnpos];
+
+        queue = snewn(wh, int);
+        fill(w, h, grid, FILLX, FILLY, solnmove, queue);
+        fill(w, h, grid, FILLX, FILLY, state->colours, queue);
+        sfree(queue);
+
+        for (i = 0; i < wh; i++)
+            if (grid[i] == state->colours && state->grid[i] != solnmove)
+                grid[i] = state->grid[i];
+    } else {
+        solnmove = 0;                  /* placate optimiser */
+    }
+
+    if (flashframe >= 0 && ui->flash_type == VICTORY) {
+        /*
+         * Modify the display grid by superimposing our rainbow flash
+         * on it.
+         */
+        for (x = 0; x < w; x++) {
+            for (y = 0; y < h; y++) {
+                int flashpos = flashframe - (abs(x - FILLX) + abs(y - FILLY));
+                if (flashpos >= 0 && flashpos < state->colours)
+                    grid[y*w+x] = flashpos;
+            }
+        }
+    }
+
+    for (x = 0; x < w; x++) {
+       for (y = 0; y < h; y++) {
+            int pos = y*w+x;
+            int tile;
+
+            if (grid[pos] == state->colours) {
+                tile = (solnmove << COLOUR_SHIFT) | SOLNNEXT;
+            } else {
+                tile = (int)grid[pos] << COLOUR_SHIFT;
+            }
+
+            if (x == 0 || grid[pos-1] != grid[pos])
+                tile |= BORDER_L;
+            if (x==w-1 || grid[pos+1] != grid[pos])
+                tile |= BORDER_R;
+            if (y == 0 || grid[pos-w] != grid[pos])
+                tile |= BORDER_U;
+            if (y==h-1 || grid[pos+w] != grid[pos])
+                tile |= BORDER_D;
+            if (x == 0 || y == 0 || grid[pos-w-1] != grid[pos])
+                tile |= CORNER_UL;
+            if (x==w-1 || y == 0 || grid[pos-w+1] != grid[pos])
+                tile |= CORNER_UR;
+            if (x == 0 || y==h-1 || grid[pos+w-1] != grid[pos])
+                tile |= CORNER_DL;
+            if (x==w-1 || y==h-1 || grid[pos+w+1] != grid[pos])
+                tile |= CORNER_DR;
+            if (ui->cursor_visible && ui->cx == x && ui->cy == y)
+                tile |= CURSOR;
+
+            if (flashframe >= 0 && ui->flash_type == DEFEAT && flashframe != 1)
+                tile |= BADFLASH;
+
+            if (ds->grid[pos] != tile) {
+               draw_tile(dr, ds, x, y, tile);
+               ds->grid[pos] = tile;
+           }
+       }
+    }
+
+    sfree(grid);
+
+    {
+       char status[255];
+
+        sprintf(status, "%s%d / %d moves",
+                (state->complete && state->moves <= state->movelimit ?
+                 (state->cheated ? "Auto-solved. " : "COMPLETED! ") :
+                 state->moves >= state->movelimit ? "FAILED! " :
+                 state->cheated ? "Auto-solver used. " :
+                 ""),
+                state->moves,
+                state->movelimit);
+
+       status_bar(dr, status);
+    }
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    if (state->complete && state->moves <= state->movelimit) {
+        return +1;                     /* victory! */
+    } else if (state->moves >= state->movelimit) {
+        return -1;                     /* defeat */
+    } else {
+        return 0;                      /* still playing */
+    }
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    if (dir == +1) {
+        int old_status = game_status(oldstate);
+        int new_status = game_status(newstate);
+        if (old_status != new_status) {
+            assert(old_status == 0);
+
+            if (new_status == +1) {
+                int frames = newstate->w + newstate->h + newstate->colours - 2;
+                ui->flash_type = VICTORY;
+                return VICTORY_FLASH_FRAME * frames;
+            } else {
+                ui->flash_type = DEFEAT;
+                return DEFEAT_FLASH_FRAME * 3;
+            }
+        }
+    }
+    return 0.0F;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+}
+
+#ifdef COMBINED
+#define thegame flood
+#endif
+
+const struct game thegame = {
+    "Flood", "games.flood", "flood",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    TRUE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILESIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    FALSE, FALSE, game_print_size, game_print,
+    TRUE,                             /* wants_statusbar */
+    FALSE, game_timing_state,
+    0,                                /* flags */
+};
diff --git a/galaxies.R b/galaxies.R
new file mode 100644 (file)
index 0000000..957e5da
--- /dev/null
@@ -0,0 +1,28 @@
+# -*- makefile -*-
+
+GALAXIES_EXTRA = dsf
+
+galaxies : [X] GTK COMMON galaxies GALAXIES_EXTRA galaxies-icon|no-icon
+
+galaxies : [G] WINDOWS COMMON galaxies GALAXIES_EXTRA galaxies.res|noicon.res
+
+galaxiessolver : [U] galaxies[STANDALONE_SOLVER] GALAXIES_EXTRA STANDALONE m.lib
+galaxiessolver : [C] galaxies[STANDALONE_SOLVER] GALAXIES_EXTRA STANDALONE
+
+galaxiespicture : [U] galaxies[STANDALONE_PICTURE_GENERATOR] GALAXIES_EXTRA STANDALONE
+                + m.lib
+galaxiespicture : [C] galaxies[STANDALONE_PICTURE_GENERATOR] GALAXIES_EXTRA STANDALONE
+
+ALL += galaxies[COMBINED] GALAXIES_EXTRA
+
+!begin am gtk
+GAMES += galaxies
+!end
+
+!begin >list.c
+    A(galaxies) \
+!end
+
+!begin >gamedesc.txt
+galaxies:galaxies.exe:Galaxies:Symmetric polyomino puzzle:Divide the grid into rotationally symmetric regions each centred on a dot.
+!end
diff --git a/galaxies.c b/galaxies.c
new file mode 100644 (file)
index 0000000..53d6fab
--- /dev/null
@@ -0,0 +1,3995 @@
+/*
+ * galaxies.c: implementation of 'Tentai Show' from Nikoli,
+ *             also sometimes called 'Spiral Galaxies'.
+ *
+ * Notes:
+ *
+ * Grid is stored as size (2n-1), holding edges as well as spaces
+ * (and thus vertices too, at edge intersections).
+ *
+ * Any dot will thus be positioned at one of our grid points,
+ * which saves any faffing with half-of-a-square stuff.
+ *
+ * Edges have on/off state; obviously the actual edges of the
+ * board are fixed to on, and everything else starts as off.
+ *
+ * TTD:
+   * Cleverer solver
+   * Think about how to display remote groups of tiles?
+ *
+ * Bugs:
+ *
+ * Notable puzzle IDs:
+ *
+ * Nikoli's example [web site has wrong highlighting]
+ * (at http://www.nikoli.co.jp/en/puzzles/astronomical_show/):
+ *  5x5:eBbbMlaBbOEnf
+ *
+ * The 'spiral galaxies puzzles are NP-complete' paper
+ * (at http://www.stetson.edu/~efriedma/papers/spiral.pdf):
+ *  7x7:chpgdqqqoezdddki
+ *
+ * Puzzle competition pdf examples
+ * (at http://www.puzzleratings.org/Yurekli2006puz.pdf):
+ *  6x6:EDbaMucCohbrecEi
+ *  10x10:beFbufEEzowDlxldibMHezBQzCdcFzjlci
+ *  13x13:dCemIHFFkJajjgDfdbdBzdzEgjccoPOcztHjBczLDjczqktJjmpreivvNcggFi
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+
+#ifdef DEBUGGING
+#define solvep debug
+#else
+int solver_show_working;
+#define solvep(x) do { if (solver_show_working) { printf x; } } while(0)
+#endif
+
+#ifdef STANDALONE_PICTURE_GENERATOR
+/*
+ * Dirty hack to enable the generator to construct a game ID which
+ * solves to a specified black-and-white bitmap. We define a global
+ * variable here which gives the desired colour of each square, and
+ * we arrange that the grid generator never merges squares of
+ * different colours.
+ *
+ * The bitmap as stored here is a simple int array (at these sizes
+ * it isn't worth doing fiddly bit-packing). picture[y*w+x] is 1
+ * iff the pixel at (x,y) is intended to be black.
+ *
+ * (It might be nice to be able to specify some pixels as
+ * don't-care, to give the generator more leeway. But that might be
+ * fiddly.)
+ */
+static int *picture;
+#endif
+
+enum {
+    COL_BACKGROUND,
+    COL_WHITEBG,
+    COL_BLACKBG,
+    COL_WHITEDOT,
+    COL_BLACKDOT,
+    COL_GRID,
+    COL_EDGE,
+    COL_ARROW,
+    COL_CURSOR,
+    NCOLOURS
+};
+
+#define DIFFLIST(A)             \
+    A(NORMAL,Normal,n)          \
+    A(UNREASONABLE,Unreasonable,u)
+
+#define ENUM(upper,title,lower) DIFF_ ## upper,
+#define TITLE(upper,title,lower) #title,
+#define ENCODE(upper,title,lower) #lower
+#define CONFIG(upper,title,lower) ":" #title
+enum { DIFFLIST(ENUM)
+    DIFF_IMPOSSIBLE, DIFF_AMBIGUOUS, DIFF_UNFINISHED, DIFF_MAX };
+static char const *const galaxies_diffnames[] = {
+    DIFFLIST(TITLE) "Impossible", "Ambiguous", "Unfinished" };
+static char const galaxies_diffchars[] = DIFFLIST(ENCODE);
+#define DIFFCONFIG DIFFLIST(CONFIG)
+
+struct game_params {
+    /* X and Y is the area of the board as seen by
+     * the user, not the (2n+1) area the game uses. */
+    int w, h, diff;
+};
+
+enum { s_tile, s_edge, s_vertex };
+
+#define F_DOT           1       /* there's a dot here */
+#define F_EDGE_SET      2       /* the edge is set */
+#define F_TILE_ASSOC    4       /* this tile is associated with a dot. */
+#define F_DOT_BLACK     8       /* (ui only) dot is black. */
+#define F_MARK          16      /* scratch flag */
+#define F_REACHABLE     32
+#define F_SCRATCH       64
+#define F_MULTIPLE      128
+#define F_DOT_HOLD      256
+#define F_GOOD          512
+
+typedef struct space {
+    int x, y;           /* its position */
+    int type;
+    unsigned int flags;
+    int dotx, doty;     /* if flags & F_TILE_ASSOC */
+    int nassoc;         /* if flags & F_DOT */
+} space;
+
+#define INGRID(s,x,y) ((x) >= 0 && (y) >= 0 &&                  \
+                       (x) < (state)->sx && (y) < (state)->sy)
+#define INUI(s,x,y)   ((x) > 0 && (y) > 0 &&                  \
+                       (x) < ((state)->sx-1) && (y) < ((state)->sy-1))
+
+#define GRID(s,g,x,y) ((s)->g[((y)*(s)->sx)+(x)])
+#define SPACE(s,x,y) GRID(s,grid,x,y)
+
+struct game_state {
+    int w, h;           /* size from params */
+    int sx, sy;         /* allocated size, (2x-1)*(2y-1) */
+    space *grid;
+    int completed, used_solve;
+    int ndots;
+    space **dots;
+
+    midend *me;         /* to call supersede_game_desc */
+    int cdiff;          /* difficulty of current puzzle (for status bar),
+                           or -1 if stale. */
+};
+
+static int check_complete(const game_state *state, int *dsf, int *colours);
+static int solver_state(game_state *state, int maxdiff);
+static int solver_obvious(game_state *state);
+static int solver_obvious_dot(game_state *state, space *dot);
+static space *space_opposite_dot(const game_state *state, const space *sp,
+                                 const space *dot);
+static space *tile_opposite(const game_state *state, const space *sp);
+
+/* ----------------------------------------------------------
+ * Game parameters and presets
+ */
+
+/* make up some sensible default sizes */
+
+#define DEFAULT_PRESET 0
+
+static const game_params galaxies_presets[] = {
+    {  7,  7, DIFF_NORMAL },
+    {  7,  7, DIFF_UNREASONABLE },
+    { 10, 10, DIFF_NORMAL },
+    { 15, 15, DIFF_NORMAL },
+};
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params *ret;
+    char buf[80];
+
+    if (i < 0 || i >= lenof(galaxies_presets))
+        return FALSE;
+
+    ret = snew(game_params);
+    *ret = galaxies_presets[i]; /* structure copy */
+
+    sprintf(buf, "%dx%d %s", ret->w, ret->h,
+            galaxies_diffnames[ret->diff]);
+
+    if (name) *name = dupstr(buf);
+    *params = ret;
+    return TRUE;
+}
+
+static game_params *default_params(void)
+{
+    game_params *ret;
+    game_fetch_preset(DEFAULT_PRESET, NULL, &ret);
+    return ret;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+static void decode_params(game_params *params, char const *string)
+{
+    params->h = params->w = atoi(string);
+    params->diff = DIFF_NORMAL;
+    while (*string && isdigit((unsigned char)*string)) string++;
+    if (*string == 'x') {
+        string++;
+        params->h = atoi(string);
+        while (*string && isdigit((unsigned char)*string)) string++;
+    }
+    if (*string == 'd') {
+        int i;
+        string++;
+        for (i = 0; i <= DIFF_UNREASONABLE; i++)
+            if (*string == galaxies_diffchars[i])
+                params->diff = i;
+        if (*string) string++;
+    }
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char str[80];
+    sprintf(str, "%dx%d", params->w, params->h);
+    if (full)
+        sprintf(str + strlen(str), "d%c", galaxies_diffchars[params->diff]);
+    return dupstr(str);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+    char buf[80];
+
+    ret = snewn(4, config_item);
+
+    ret[0].name = "Width";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->w);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Height";
+    ret[1].type = C_STRING;
+    sprintf(buf, "%d", params->h);
+    ret[1].sval = dupstr(buf);
+    ret[1].ival = 0;
+
+    ret[2].name = "Difficulty";
+    ret[2].type = C_CHOICES;
+    ret[2].sval = DIFFCONFIG;
+    ret[2].ival = params->diff;
+
+    ret[3].name = NULL;
+    ret[3].type = C_END;
+    ret[3].sval = NULL;
+    ret[3].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = atoi(cfg[0].sval);
+    ret->h = atoi(cfg[1].sval);
+    ret->diff = cfg[2].ival;
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    if (params->w < 3 || params->h < 3)
+        return "Width and height must both be at least 3";
+    /*
+     * This shouldn't be able to happen at all, since decode_params
+     * and custom_params will never generate anything that isn't
+     * within range.
+     */
+    assert(params->diff <= DIFF_UNREASONABLE);
+
+    return NULL;
+}
+
+/* ----------------------------------------------------------
+ * Game utility functions.
+ */
+
+static void add_dot(space *space) {
+    assert(!(space->flags & F_DOT));
+    space->flags |= F_DOT;
+    space->nassoc = 0;
+}
+
+static void remove_dot(space *space) {
+    assert(space->flags & F_DOT);
+    space->flags &= ~F_DOT;
+}
+
+static void remove_assoc(const game_state *state, space *tile) {
+    if (tile->flags & F_TILE_ASSOC) {
+        SPACE(state, tile->dotx, tile->doty).nassoc--;
+        tile->flags &= ~F_TILE_ASSOC;
+        tile->dotx = -1;
+        tile->doty = -1;
+    }
+}
+
+static void remove_assoc_with_opposite(game_state *state, space *tile) {
+    space *opposite;
+
+    if (!(tile->flags & F_TILE_ASSOC)) {
+        return;
+    }
+
+    opposite = tile_opposite(state, tile);
+    remove_assoc(state, tile);
+
+    if (opposite != NULL && opposite != tile) {
+        remove_assoc(state, opposite);
+    }
+}
+
+static void add_assoc(const game_state *state, space *tile, space *dot) {
+    remove_assoc(state, tile);
+
+#ifdef STANDALONE_PICTURE_GENERATOR
+    if (picture)
+       assert(!picture[(tile->y/2) * state->w + (tile->x/2)] ==
+              !(dot->flags & F_DOT_BLACK));
+#endif
+    tile->flags |= F_TILE_ASSOC;
+    tile->dotx = dot->x;
+    tile->doty = dot->y;
+    dot->nassoc++;
+    /*debug(("add_assoc sp %d %d --> dot %d,%d, new nassoc %d.\n",
+           tile->x, tile->y, dot->x, dot->y, dot->nassoc));*/
+}
+
+static void add_assoc_with_opposite(game_state *state, space *tile, space *dot) {
+    int *colors;
+    space *opposite = space_opposite_dot(state, tile, dot);
+
+    if (opposite == NULL) {
+        return;
+    }
+    if (opposite->flags & F_DOT) {
+        return;
+    }
+
+    colors = snewn(state->w * state->h, int);
+    check_complete(state, NULL, colors);
+    if (colors[(tile->y - 1)/2 * state->w + (tile->x - 1)/2]) {
+        sfree(colors);
+        return;
+    }
+    if (colors[(opposite->y - 1)/2 * state->w + (opposite->x - 1)/2]) {
+        sfree(colors);
+        return;
+    }
+
+    sfree(colors);
+    remove_assoc_with_opposite(state, tile);
+    add_assoc(state, tile, dot);
+    remove_assoc_with_opposite(state, opposite);
+    add_assoc(state, opposite, dot);
+}
+
+static space *sp2dot(const game_state *state, int x, int y)
+{
+    space *sp = &SPACE(state, x, y);
+    if (!(sp->flags & F_TILE_ASSOC)) return NULL;
+    return &SPACE(state, sp->dotx, sp->doty);
+}
+
+#define IS_VERTICAL_EDGE(x) ((x % 2) == 0)
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    int maxlen = (state->sx+1)*state->sy, x, y;
+    char *ret, *p;
+    space *sp;
+
+    ret = snewn(maxlen+1, char);
+    p = ret;
+
+    for (y = 0; y < state->sy; y++) {
+        for (x = 0; x < state->sx; x++) {
+            sp = &SPACE(state, x, y);
+            if (sp->flags & F_DOT)
+                *p++ = 'o';
+#if 0
+            else if (sp->flags & (F_REACHABLE|F_MULTIPLE|F_MARK))
+                *p++ = (sp->flags & F_MULTIPLE) ? 'M' :
+                    (sp->flags & F_REACHABLE) ? 'R' : 'X';
+#endif
+            else {
+                switch (sp->type) {
+                case s_tile:
+                    if (sp->flags & F_TILE_ASSOC) {
+                        space *dot = sp2dot(state, sp->x, sp->y);
+                        if (dot && dot->flags & F_DOT)
+                            *p++ = (dot->flags & F_DOT_BLACK) ? 'B' : 'W';
+                        else
+                            *p++ = '?'; /* association with not-a-dot. */
+                    } else
+                        *p++ = ' ';
+                    break;
+
+                case s_vertex:
+                    *p++ = '+';
+                    break;
+
+                case s_edge:
+                    if (sp->flags & F_EDGE_SET)
+                        *p++ = (IS_VERTICAL_EDGE(x)) ? '|' : '-';
+                    else
+                        *p++ = ' ';
+                    break;
+
+                default:
+                    assert(!"shouldn't get here!");
+                }
+            }
+        }
+        *p++ = '\n';
+    }
+
+    assert(p - ret == maxlen);
+    *p = '\0';
+
+    return ret;
+}
+
+static void dbg_state(const game_state *state)
+{
+#ifdef DEBUGGING
+    char *temp = game_text_format(state);
+    debug(("%s\n", temp));
+    sfree(temp);
+#endif
+}
+
+/* Space-enumeration callbacks should all return 1 for 'progress made',
+ * -1 for 'impossible', and 0 otherwise. */
+typedef int (*space_cb)(game_state *state, space *sp, void *ctx);
+
+#define IMPOSSIBLE_QUITS        1
+
+static int foreach_sub(game_state *state, space_cb cb, unsigned int f,
+                       void *ctx, int startx, int starty)
+{
+    int x, y, progress = 0, impossible = 0, ret;
+    space *sp;
+
+    for (y = starty; y < state->sy; y += 2) {
+        sp = &SPACE(state, startx, y);
+        for (x = startx; x < state->sx; x += 2) {
+            ret = cb(state, sp, ctx);
+            if (ret == -1) {
+                if (f & IMPOSSIBLE_QUITS) return -1;
+                impossible = -1;
+            } else if (ret == 1) {
+                progress = 1;
+            }
+            sp += 2;
+        }
+    }
+    return impossible ? -1 : progress;
+}
+
+static int foreach_tile(game_state *state, space_cb cb, unsigned int f,
+                        void *ctx)
+{
+    return foreach_sub(state, cb, f, ctx, 1, 1);
+}
+
+static int foreach_edge(game_state *state, space_cb cb, unsigned int f,
+                        void *ctx)
+{
+    int ret1, ret2;
+
+    ret1 = foreach_sub(state, cb, f, ctx, 0, 1);
+    ret2 = foreach_sub(state, cb, f, ctx, 1, 0);
+
+    if (ret1 == -1 || ret2 == -1) return -1;
+    return (ret1 || ret2) ? 1 : 0;
+}
+
+#if 0
+static int foreach_vertex(game_state *state, space_cb cb, unsigned int f,
+                          void *ctx)
+{
+    return foreach_sub(state, cb, f, ctx, 0, 0);
+}
+#endif
+
+#if 0
+static int is_same_assoc(game_state *state,
+                         int x1, int y1, int x2, int y2)
+{
+    space *s1, *s2;
+
+    if (!INGRID(state, x1, y1) || !INGRID(state, x2, y2))
+        return 0;
+
+    s1 = &SPACE(state, x1, y1);
+    s2 = &SPACE(state, x2, y2);
+    assert(s1->type == s_tile && s2->type == s_tile);
+    if ((s1->flags & F_TILE_ASSOC) && (s2->flags & F_TILE_ASSOC) &&
+        s1->dotx == s2->dotx && s1->doty == s2->doty)
+        return 1;
+    return 0; /* 0 if not same or not both associated. */
+}
+#endif
+
+#if 0
+static int edges_into_vertex(game_state *state,
+                             int x, int y)
+{
+    int dx, dy, nx, ny, count = 0;
+
+    assert(SPACE(state, x, y).type == s_vertex);
+    for (dx = -1; dx <= 1; dx++) {
+        for (dy = -1; dy <= 1; dy++) {
+            if (dx != 0 && dy != 0) continue;
+            if (dx == 0 && dy == 0) continue;
+
+            nx = x+dx; ny = y+dy;
+            if (!INGRID(state, nx, ny)) continue;
+            assert(SPACE(state, nx, ny).type == s_edge);
+            if (SPACE(state, nx, ny).flags & F_EDGE_SET)
+                count++;
+        }
+    }
+    return count;
+}
+#endif
+
+static space *space_opposite_dot(const game_state *state, const space *sp,
+                                 const space *dot)
+{
+    int dx, dy, tx, ty;
+    space *sp2;
+
+    dx = sp->x - dot->x;
+    dy = sp->y - dot->y;
+    tx = dot->x - dx;
+    ty = dot->y - dy;
+    if (!INGRID(state, tx, ty)) return NULL;
+
+    sp2 = &SPACE(state, tx, ty);
+    assert(sp2->type == sp->type);
+    return sp2;
+}
+
+static space *tile_opposite(const game_state *state, const space *sp)
+{
+    space *dot;
+
+    assert(sp->flags & F_TILE_ASSOC);
+    dot = &SPACE(state, sp->dotx, sp->doty);
+    return space_opposite_dot(state, sp, dot);
+}
+
+static int dotfortile(game_state *state, space *tile, space *dot)
+{
+    space *tile_opp = space_opposite_dot(state, tile, dot);
+
+    if (!tile_opp) return 0; /* opposite would be off grid */
+    if (tile_opp->flags & F_TILE_ASSOC &&
+            (tile_opp->dotx != dot->x || tile_opp->doty != dot->y))
+            return 0; /* opposite already associated with diff. dot */
+    return 1;
+}
+
+static void adjacencies(game_state *state, space *sp, space **a1s, space **a2s)
+{
+    int dxs[4] = {-1, 1, 0, 0}, dys[4] = {0, 0, -1, 1};
+    int n, x, y;
+
+    /* this function needs optimising. */
+
+    for (n = 0; n < 4; n++) {
+        x = sp->x+dxs[n];
+        y = sp->y+dys[n];
+
+        if (INGRID(state, x, y)) {
+            a1s[n] = &SPACE(state, x, y);
+
+            x += dxs[n]; y += dys[n];
+
+            if (INGRID(state, x, y))
+                a2s[n] = &SPACE(state, x, y);
+            else
+                a2s[n] = NULL;
+        } else {
+            a1s[n] = a2s[n] = NULL;
+        }
+    }
+}
+
+static int outline_tile_fordot(game_state *state, space *tile, int mark)
+{
+    space *tadj[4], *eadj[4];
+    int i, didsth = 0, edge, same;
+
+    assert(tile->type == s_tile);
+    adjacencies(state, tile, eadj, tadj);
+    for (i = 0; i < 4; i++) {
+        if (!eadj[i]) continue;
+
+        edge = (eadj[i]->flags & F_EDGE_SET) ? 1 : 0;
+        if (tadj[i]) {
+            if (!(tile->flags & F_TILE_ASSOC))
+                same = (tadj[i]->flags & F_TILE_ASSOC) ? 0 : 1;
+            else
+                same = ((tadj[i]->flags & F_TILE_ASSOC) &&
+                    tile->dotx == tadj[i]->dotx &&
+                    tile->doty == tadj[i]->doty) ? 1 : 0;
+        } else
+            same = 0;
+
+        if (!edge && !same) {
+            if (mark) eadj[i]->flags |= F_EDGE_SET;
+            didsth = 1;
+        } else if (edge && same) {
+            if (mark) eadj[i]->flags &= ~F_EDGE_SET;
+            didsth = 1;
+        }
+    }
+    return didsth;
+}
+
+static void tiles_from_edge(game_state *state, space *sp, space **ts)
+{
+    int xs[2], ys[2];
+
+    if (IS_VERTICAL_EDGE(sp->x)) {
+        xs[0] = sp->x-1; ys[0] = sp->y;
+        xs[1] = sp->x+1; ys[1] = sp->y;
+    } else {
+        xs[0] = sp->x; ys[0] = sp->y-1;
+        xs[1] = sp->x; ys[1] = sp->y+1;
+    }
+    ts[0] = INGRID(state, xs[0], ys[0]) ? &SPACE(state, xs[0], ys[0]) : NULL;
+    ts[1] = INGRID(state, xs[1], ys[1]) ? &SPACE(state, xs[1], ys[1]) : NULL;
+}
+
+/* Returns a move string for use by 'solve', including the initial
+ * 'S' if issolve is true. */
+static char *diff_game(const game_state *src, const game_state *dest,
+                       int issolve)
+{
+    int movelen = 0, movesize = 256, x, y, len;
+    char *move = snewn(movesize, char), buf[80], *sep = "";
+    char achar = issolve ? 'a' : 'A';
+    space *sps, *spd;
+
+    assert(src->sx == dest->sx && src->sy == dest->sy);
+
+    if (issolve) {
+        move[movelen++] = 'S';
+        sep = ";";
+    }
+    move[movelen] = '\0';
+    for (x = 0; x < src->sx; x++) {
+        for (y = 0; y < src->sy; y++) {
+            sps = &SPACE(src, x, y);
+            spd = &SPACE(dest, x, y);
+
+            assert(sps->type == spd->type);
+
+            len = 0;
+            if (sps->type == s_tile) {
+                if ((sps->flags & F_TILE_ASSOC) &&
+                    (spd->flags & F_TILE_ASSOC)) {
+                    if (sps->dotx != spd->dotx ||
+                        sps->doty != spd->doty)
+                    /* Both associated; change association, if different */
+                        len = sprintf(buf, "%s%c%d,%d,%d,%d", sep,
+                                      (int)achar, x, y, spd->dotx, spd->doty);
+                } else if (sps->flags & F_TILE_ASSOC)
+                    /* Only src associated; remove. */
+                    len = sprintf(buf, "%sU%d,%d", sep, x, y);
+                else if (spd->flags & F_TILE_ASSOC)
+                    /* Only dest associated; add. */
+                    len = sprintf(buf, "%s%c%d,%d,%d,%d", sep,
+                                  (int)achar, x, y, spd->dotx, spd->doty);
+            } else if (sps->type == s_edge) {
+                if ((sps->flags & F_EDGE_SET) != (spd->flags & F_EDGE_SET))
+                    /* edge flags are different; flip them. */
+                    len = sprintf(buf, "%sE%d,%d", sep, x, y);
+            }
+            if (len) {
+                if (movelen + len >= movesize) {
+                    movesize = movelen + len + 256;
+                    move = sresize(move, movesize, char);
+                }
+                strcpy(move + movelen, buf);
+                movelen += len;
+                sep = ";";
+            }
+        }
+    }
+    debug(("diff_game src then dest:\n"));
+    dbg_state(src);
+    dbg_state(dest);
+    debug(("diff string %s\n", move));
+    return move;
+}
+
+/* Returns 1 if a dot here would not be too close to any other dots
+ * (and would avoid other game furniture). */
+static int dot_is_possible(game_state *state, space *sp, int allow_assoc)
+{
+    int bx = 0, by = 0, dx, dy;
+    space *adj;
+#ifdef STANDALONE_PICTURE_GENERATOR
+    int col = -1;
+#endif
+
+    switch (sp->type) {
+    case s_tile:
+        bx = by = 1; break;
+    case s_edge:
+        if (IS_VERTICAL_EDGE(sp->x)) {
+            bx = 2; by = 1;
+        } else {
+            bx = 1; by = 2;
+        }
+        break;
+    case s_vertex:
+        bx = by = 2; break;
+    }
+
+    for (dx = -bx; dx <= bx; dx++) {
+        for (dy = -by; dy <= by; dy++) {
+            if (!INGRID(state, sp->x+dx, sp->y+dy)) continue;
+
+            adj = &SPACE(state, sp->x+dx, sp->y+dy);
+
+#ifdef STANDALONE_PICTURE_GENERATOR
+            /*
+             * Check that all the squares we're looking at have the
+             * same colour.
+             */
+            if (picture) {
+               if (adj->type == s_tile) {
+                   int c = picture[(adj->y / 2) * state->w + (adj->x / 2)];
+                   if (col < 0)
+                       col = c;
+                   if (c != col)
+                       return 0;          /* colour mismatch */
+               }
+           }
+#endif
+
+           if (!allow_assoc && (adj->flags & F_TILE_ASSOC))
+               return 0;
+
+            if (dx != 0 || dy != 0) {
+                /* Other than our own square, no dots nearby. */
+                if (adj->flags & (F_DOT))
+                    return 0;
+            }
+
+            /* We don't want edges within our rectangle
+             * (but don't care about edges on the edge) */
+            if (abs(dx) < bx && abs(dy) < by &&
+                adj->flags & F_EDGE_SET)
+                return 0;
+        }
+    }
+    return 1;
+}
+
+/* ----------------------------------------------------------
+ * Game generation, structure creation, and descriptions.
+ */
+
+static game_state *blank_game(int w, int h)
+{
+    game_state *state = snew(game_state);
+    int x, y;
+
+    state->w = w;
+    state->h = h;
+
+    state->sx = (w*2)+1;
+    state->sy = (h*2)+1;
+    state->grid = snewn(state->sx * state->sy, space);
+    state->completed = state->used_solve = 0;
+
+    for (x = 0; x < state->sx; x++) {
+        for (y = 0; y < state->sy; y++) {
+            space *sp = &SPACE(state, x, y);
+            memset(sp, 0, sizeof(space));
+            sp->x = x;
+            sp->y = y;
+            if ((x % 2) == 0 && (y % 2) == 0)
+                sp->type = s_vertex;
+            else if ((x % 2) == 0 || (y % 2) == 0) {
+                sp->type = s_edge;
+                if (x == 0 || y == 0 || x == state->sx-1 || y == state->sy-1)
+                    sp->flags |= F_EDGE_SET;
+            } else
+                sp->type = s_tile;
+        }
+    }
+
+    state->ndots = 0;
+    state->dots = NULL;
+
+    state->me = NULL; /* filled in by new_game. */
+    state->cdiff = -1;
+
+    return state;
+}
+
+static void game_update_dots(game_state *state)
+{
+    int i, n, sz = state->sx * state->sy;
+
+    if (state->dots) sfree(state->dots);
+    state->ndots = 0;
+
+    for (i = 0; i < sz; i++) {
+        if (state->grid[i].flags & F_DOT) state->ndots++;
+    }
+    state->dots = snewn(state->ndots, space *);
+    n = 0;
+    for (i = 0; i < sz; i++) {
+        if (state->grid[i].flags & F_DOT)
+            state->dots[n++] = &state->grid[i];
+    }
+}
+
+static void clear_game(game_state *state, int cleardots)
+{
+    int x, y;
+
+    /* don't erase edge flags around outline! */
+    for (x = 1; x < state->sx-1; x++) {
+        for (y = 1; y < state->sy-1; y++) {
+            if (cleardots)
+                SPACE(state, x, y).flags = 0;
+            else
+                SPACE(state, x, y).flags &= (F_DOT|F_DOT_BLACK);
+        }
+    }
+    if (cleardots) game_update_dots(state);
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    game_state *ret = blank_game(state->w, state->h);
+
+    ret->completed = state->completed;
+    ret->used_solve = state->used_solve;
+
+    memcpy(ret->grid, state->grid,
+           ret->sx*ret->sy*sizeof(space));
+
+    game_update_dots(ret);
+
+    ret->me = state->me;
+    ret->cdiff = state->cdiff;
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    if (state->dots) sfree(state->dots);
+    sfree(state->grid);
+    sfree(state);
+}
+
+/* Game description is a sequence of letters representing the number
+ * of spaces (a = 0, y = 24) before the next dot; a-y for a white dot,
+ * and A-Y for a black dot. 'z' is 25 spaces (and no dot).
+ *
+ * I know it's a bitch to generate by hand, so we provide
+ * an edit mode.
+ */
+
+static char *encode_game(game_state *state)
+{
+    char *desc, *p;
+    int run, x, y, area;
+    unsigned int f;
+
+    area = (state->sx-2) * (state->sy-2);
+
+    desc = snewn(area, char);
+    p = desc;
+    run = 0;
+    for (y = 1; y < state->sy-1; y++) {
+        for (x = 1; x < state->sx-1; x++) {
+            f = SPACE(state, x, y).flags;
+
+            /* a/A is 0 spaces between, b/B is 1 space, ...
+             * y/Y is 24 spaces, za/zA is 25 spaces, ...
+             * It's easier to count from 0 because we then
+             * don't have to special-case the top left-hand corner
+             * (which could be a dot with 0 spaces before it). */
+            if (!(f & F_DOT))
+                run++;
+            else {
+                while (run > 24) {
+                    *p++ = 'z';
+                    run -= 25;
+                }
+                *p++ = ((f & F_DOT_BLACK) ? 'A' : 'a') + run;
+                run = 0;
+            }
+        }
+    }
+    assert(p - desc < area);
+    *p++ = '\0';
+    desc = sresize(desc, p - desc, char);
+
+    return desc;
+}
+
+struct movedot {
+    int op;
+    space *olddot, *newdot;
+};
+
+enum { MD_CHECK, MD_MOVE };
+
+static int movedot_cb(game_state *state, space *tile, void *vctx)
+{
+   struct movedot *md = (struct movedot *)vctx;
+   space *newopp = NULL;
+
+   assert(tile->type == s_tile);
+   assert(md->olddot && md->newdot);
+
+   if (!(tile->flags & F_TILE_ASSOC)) return 0;
+   if (tile->dotx != md->olddot->x || tile->doty != md->olddot->y)
+       return 0;
+
+   newopp = space_opposite_dot(state, tile, md->newdot);
+
+   switch (md->op) {
+   case MD_CHECK:
+       /* If the tile is associated with the old dot, check its
+        * opposite wrt the _new_ dot is empty or same assoc. */
+       if (!newopp) return -1; /* no new opposite */
+       if (newopp->flags & F_TILE_ASSOC) {
+           if (newopp->dotx != md->olddot->x ||
+               newopp->doty != md->olddot->y)
+               return -1; /* associated, but wrong dot. */
+       }
+#ifdef STANDALONE_PICTURE_GENERATOR
+       if (picture) {
+          /*
+           * Reject if either tile and the dot don't match in colour.
+           */
+          if (!(picture[(tile->y/2) * state->w + (tile->x/2)]) ^
+              !(md->newdot->flags & F_DOT_BLACK))
+              return -1;
+          if (!(picture[(newopp->y/2) * state->w + (newopp->x/2)]) ^
+              !(md->newdot->flags & F_DOT_BLACK))
+              return -1;
+       }
+#endif
+       break;
+
+   case MD_MOVE:
+       /* Move dot associations: anything that was associated
+        * with the old dot, and its opposite wrt the new dot,
+        * become associated with the new dot. */
+       assert(newopp);
+       debug(("Associating %d,%d and %d,%d with new dot %d,%d.\n",
+              tile->x, tile->y, newopp->x, newopp->y,
+              md->newdot->x, md->newdot->y));
+       add_assoc(state, tile, md->newdot);
+       add_assoc(state, newopp, md->newdot);
+       return 1; /* we did something! */
+   }
+   return 0;
+}
+
+/* For the given dot, first see if we could expand it into all the given
+ * extra spaces (by checking for empty spaces on the far side), and then
+ * see if we can move the dot to shift the CoG to include the new spaces.
+ */
+static int dot_expand_or_move(game_state *state, space *dot,
+                              space **toadd, int nadd)
+{
+    space *tileopp;
+    int i, ret, nnew, cx, cy;
+    struct movedot md;
+
+    debug(("dot_expand_or_move: %d tiles for dot %d,%d\n",
+           nadd, dot->x, dot->y));
+    for (i = 0; i < nadd; i++)
+        debug(("dot_expand_or_move:   dot %d,%d\n",
+               toadd[i]->x, toadd[i]->y));
+    assert(dot->flags & F_DOT);
+
+#ifdef STANDALONE_PICTURE_GENERATOR
+    if (picture) {
+       /*
+        * Reject the expansion totally if any of the new tiles are
+        * the wrong colour.
+        */
+       for (i = 0; i < nadd; i++) {
+           if (!(picture[(toadd[i]->y/2) * state->w + (toadd[i]->x/2)]) ^
+               !(dot->flags & F_DOT_BLACK))
+               return 0;
+       }
+    }
+#endif
+
+    /* First off, could we just expand the current dot's tile to cover
+     * the space(s) passed in and their opposites? */
+    for (i = 0; i < nadd; i++) {
+        tileopp = space_opposite_dot(state, toadd[i], dot);
+        if (!tileopp) goto noexpand;
+        if (tileopp->flags & F_TILE_ASSOC) goto noexpand;
+#ifdef STANDALONE_PICTURE_GENERATOR
+       if (picture) {
+           /*
+            * The opposite tiles have to be the right colour as well.
+            */
+           if (!(picture[(tileopp->y/2) * state->w + (tileopp->x/2)]) ^
+               !(dot->flags & F_DOT_BLACK))
+               goto noexpand;
+       }
+#endif
+    }
+    /* OK, all spaces have valid empty opposites: associate spaces and
+     * opposites with our dot. */
+    for (i = 0; i < nadd; i++) {
+        tileopp = space_opposite_dot(state, toadd[i], dot);
+        add_assoc(state, toadd[i], dot);
+        add_assoc(state, tileopp, dot);
+        debug(("Added associations %d,%d and %d,%d --> %d,%d\n",
+               toadd[i]->x, toadd[i]->y,
+               tileopp->x, tileopp->y,
+               dot->x, dot->y));
+        dbg_state(state);
+    }
+    return 1;
+
+noexpand:
+    /* Otherwise, try to move dot so as to encompass given spaces: */
+    /* first, calculate the 'centre of gravity' of the new dot. */
+    nnew = dot->nassoc + nadd; /* number of tiles assoc. with new dot. */
+    cx = dot->x * dot->nassoc;
+    cy = dot->y * dot->nassoc;
+    for (i = 0; i < nadd; i++) {
+        cx += toadd[i]->x;
+        cy += toadd[i]->y;
+    }
+    /* If the CoG isn't a whole number, it's not possible. */
+    if ((cx % nnew) != 0 || (cy % nnew) != 0) {
+        debug(("Unable to move dot %d,%d, CoG not whole number.\n",
+               dot->x, dot->y));
+        return 0;
+    }
+    cx /= nnew; cy /= nnew;
+
+    /* Check whether all spaces in the old tile would have a good
+     * opposite wrt the new dot. */
+    md.olddot = dot;
+    md.newdot = &SPACE(state, cx, cy);
+    md.op = MD_CHECK;
+    ret = foreach_tile(state, movedot_cb, IMPOSSIBLE_QUITS, &md);
+    if (ret == -1) {
+        debug(("Unable to move dot %d,%d, new dot not symmetrical.\n",
+               dot->x, dot->y));
+        return 0;
+    }
+    /* Also check whether all spaces we're adding would have a good
+     * opposite wrt the new dot. */
+    for (i = 0; i < nadd; i++) {
+        tileopp = space_opposite_dot(state, toadd[i], md.newdot);
+        if (tileopp && (tileopp->flags & F_TILE_ASSOC) &&
+            (tileopp->dotx != dot->x || tileopp->doty != dot->y)) {
+            tileopp = NULL;
+        }
+        if (!tileopp) {
+            debug(("Unable to move dot %d,%d, new dot not symmetrical.\n",
+               dot->x, dot->y));
+            return 0;
+        }
+#ifdef STANDALONE_PICTURE_GENERATOR
+       if (picture) {
+           if (!(picture[(tileopp->y/2) * state->w + (tileopp->x/2)]) ^
+               !(dot->flags & F_DOT_BLACK))
+               return 0;
+       }
+#endif
+    }
+
+    /* If we've got here, we're ok. First, associate all of 'toadd'
+     * with the _old_ dot (so they'll get fixed up, with their opposites,
+     * in the next step). */
+    for (i = 0; i < nadd; i++) {
+        debug(("Associating to-add %d,%d with old dot %d,%d.\n",
+               toadd[i]->x, toadd[i]->y, dot->x, dot->y));
+        add_assoc(state, toadd[i], dot);
+    }
+
+    /* Finally, move the dot and fix up all the old associations. */
+    debug(("Moving dot at %d,%d to %d,%d\n",
+           dot->x, dot->y, md.newdot->x, md.newdot->y));
+    {
+#ifdef STANDALONE_PICTURE_GENERATOR
+        int f = dot->flags & F_DOT_BLACK;
+#endif
+        remove_dot(dot);
+        add_dot(md.newdot);
+#ifdef STANDALONE_PICTURE_GENERATOR
+        md.newdot->flags |= f;
+#endif
+    }
+
+    md.op = MD_MOVE;
+    ret = foreach_tile(state, movedot_cb, 0, &md);
+    assert(ret == 1);
+    dbg_state(state);
+
+    return 1;
+}
+
+/* Hard-code to a max. of 2x2 squares, for speed (less malloc) */
+#define MAX_TOADD 4
+#define MAX_OUTSIDE 8
+
+#define MAX_TILE_PERC 20
+
+static int generate_try_block(game_state *state, random_state *rs,
+                              int x1, int y1, int x2, int y2)
+{
+    int x, y, nadd = 0, nout = 0, i, maxsz;
+    space *sp, *toadd[MAX_TOADD], *outside[MAX_OUTSIDE], *dot;
+
+    if (!INGRID(state, x1, y1) || !INGRID(state, x2, y2)) return 0;
+
+    /* We limit the maximum size of tiles to be ~2*sqrt(area); so,
+     * a 5x5 grid shouldn't have anything >10 tiles, a 20x20 grid
+     * nothing >40 tiles. */
+    maxsz = (int)sqrt((double)(state->w * state->h)) * 2;
+    debug(("generate_try_block, maxsz %d\n", maxsz));
+
+    /* Make a static list of the spaces; if any space is already
+     * associated then quit immediately. */
+    for (x = x1; x <= x2; x += 2) {
+        for (y = y1; y <= y2; y += 2) {
+            assert(nadd < MAX_TOADD);
+            sp = &SPACE(state, x, y);
+            assert(sp->type == s_tile);
+            if (sp->flags & F_TILE_ASSOC) return 0;
+            toadd[nadd++] = sp;
+        }
+    }
+
+    /* Make a list of the spaces outside of our block, and shuffle it. */
+#define OUTSIDE(x, y) do {                              \
+    if (INGRID(state, (x), (y))) {                      \
+        assert(nout < MAX_OUTSIDE);                     \
+        outside[nout++] = &SPACE(state, (x), (y));      \
+    }                                                   \
+} while(0)
+    for (x = x1; x <= x2; x += 2) {
+        OUTSIDE(x, y1-2);
+        OUTSIDE(x, y2+2);
+    }
+    for (y = y1; y <= y2; y += 2) {
+        OUTSIDE(x1-2, y);
+        OUTSIDE(x2+2, y);
+    }
+    shuffle(outside, nout, sizeof(space *), rs);
+
+    for (i = 0; i < nout; i++) {
+        if (!(outside[i]->flags & F_TILE_ASSOC)) continue;
+        dot = &SPACE(state, outside[i]->dotx, outside[i]->doty);
+        if (dot->nassoc >= maxsz) {
+            debug(("Not adding to dot %d,%d, large enough (%d) already.\n",
+                   dot->x, dot->y, dot->nassoc));
+            continue;
+        }
+        if (dot_expand_or_move(state, dot, toadd, nadd)) return 1;
+    }
+    return 0;
+}
+
+#ifdef STANDALONE_SOLVER
+int maxtries;
+#define MAXTRIES maxtries
+#else
+#define MAXTRIES 50
+#endif
+
+#define GP_DOTS   1
+
+static void generate_pass(game_state *state, random_state *rs, int *scratch,
+                         int perc, unsigned int flags)
+{
+    int sz = state->sx*state->sy, nspc, i, ret;
+
+    shuffle(scratch, sz, sizeof(int), rs);
+
+    /* This bug took me a, er, little while to track down. On PalmOS,
+     * which has 16-bit signed ints, puzzles over about 9x9 started
+     * failing to generate because the nspc calculation would start
+     * to overflow, causing the dots not to be filled in properly. */
+    nspc = (int)(((long)perc * (long)sz) / 100L);
+    debug(("generate_pass: %d%% (%d of %dx%d) squares, flags 0x%x\n",
+           perc, nspc, state->sx, state->sy, flags));
+
+    for (i = 0; i < nspc; i++) {
+        space *sp = &state->grid[scratch[i]];
+        int x1 = sp->x, y1 = sp->y, x2 = sp->x, y2 = sp->y;
+
+        if (sp->type == s_edge) {
+            if (IS_VERTICAL_EDGE(sp->x)) {
+                x1--; x2++;
+            } else {
+                y1--; y2++;
+            }
+        }
+        if (sp->type != s_vertex) {
+            /* heuristic; expanding from vertices tends to generate lots of
+             * too-big regions of tiles. */
+            if (generate_try_block(state, rs, x1, y1, x2, y2))
+                continue; /* we expanded successfully. */
+        }
+
+        if (!(flags & GP_DOTS)) continue;
+
+        if ((sp->type == s_edge) && (i % 2)) {
+            debug(("Omitting edge %d,%d as half-of.\n", sp->x, sp->y));
+            continue;
+        }
+
+        /* If we've got here we might want to put a dot down. Check
+         * if we can, and add one if so. */
+        if (dot_is_possible(state, sp, 0)) {
+            add_dot(sp);
+#ifdef STANDALONE_PICTURE_GENERATOR
+           if (picture) {
+               if (picture[(sp->y/2) * state->w + (sp->x/2)])
+                   sp->flags |= F_DOT_BLACK;
+           }
+#endif
+            ret = solver_obvious_dot(state, sp);
+            assert(ret != -1);
+            debug(("Added dot (and obvious associations) at %d,%d\n",
+                   sp->x, sp->y));
+            dbg_state(state);
+        }
+    }
+    dbg_state(state);
+}
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                          char **aux, int interactive)
+{
+    game_state *state = blank_game(params->w, params->h), *copy;
+    char *desc;
+    int *scratch, sz = state->sx*state->sy, i;
+    int diff, ntries = 0, cc;
+
+    /* Random list of squares to try and process, one-by-one. */
+    scratch = snewn(sz, int);
+    for (i = 0; i < sz; i++) scratch[i] = i;
+
+generate:
+    clear_game(state, 1);
+    ntries++;
+
+    /* generate_pass(state, rs, scratch, 10, GP_DOTS); */
+    /* generate_pass(state, rs, scratch, 100, 0); */
+    generate_pass(state, rs, scratch, 100, GP_DOTS);
+
+    game_update_dots(state);
+
+    if (state->ndots == 1) goto generate;
+
+#ifdef DEBUGGING
+    {
+        char *tmp = encode_game(state);
+        debug(("new_game_desc state %dx%d:%s\n", params->w, params->h, tmp));
+        sfree(tmp);
+    }
+#endif
+
+    for (i = 0; i < state->sx*state->sy; i++)
+        if (state->grid[i].type == s_tile)
+            outline_tile_fordot(state, &state->grid[i], TRUE);
+    cc = check_complete(state, NULL, NULL);
+    assert(cc);
+
+    copy = dup_game(state);
+    clear_game(copy, 0);
+    dbg_state(copy);
+    diff = solver_state(copy, params->diff);
+    free_game(copy);
+
+    assert(diff != DIFF_IMPOSSIBLE);
+    if (diff != params->diff) {
+        /*
+         * We'll grudgingly accept a too-easy puzzle, but we must
+         * _not_ permit a too-hard one (one which the solver
+         * couldn't handle at all).
+         */
+        if (diff > params->diff ||
+            ntries < MAXTRIES) goto generate;
+    }
+
+#ifdef STANDALONE_PICTURE_GENERATOR
+    /*
+     * Postprocessing pass to prevent excessive numbers of adjacent
+     * singletons. Iterate over all edges in random shuffled order;
+     * for each edge that separates two regions, investigate
+     * whether removing that edge and merging the regions would
+     * still yield a valid and soluble puzzle. (The two regions
+     * must also be the same colour, of course.) If so, do it.
+     * 
+     * This postprocessing pass is slow (due to repeated solver
+     * invocations), and seems to be unnecessary during normal
+     * unconstrained game generation. However, when generating a
+     * game under colour constraints, excessive singletons seem to
+     * turn up more often, so it's worth doing this.
+     */
+    {
+       int *posns, nposns;
+       int i, j, newdiff;
+       game_state *copy2;
+
+       nposns = params->w * (params->h+1) + params->h * (params->w+1);
+       posns = snewn(nposns, int);
+       for (i = j = 0; i < state->sx*state->sy; i++)
+           if (state->grid[i].type == s_edge)
+               posns[j++] = i;
+       assert(j == nposns);
+
+       shuffle(posns, nposns, sizeof(*posns), rs);
+
+       for (i = 0; i < nposns; i++) {
+           int x, y, x0, y0, x1, y1, cx, cy, cn, cx0, cy0, cx1, cy1, tx, ty;
+           space *s0, *s1, *ts, *d0, *d1, *dn;
+           int ok;
+
+           /* Coordinates of edge space */
+           x = posns[i] % state->sx;
+           y = posns[i] / state->sx;
+
+           /* Coordinates of square spaces on either side of edge */
+           x0 = ((x+1) & ~1) - 1;     /* round down to next odd number */
+           y0 = ((y+1) & ~1) - 1;
+           x1 = 2*x-x0;               /* and reflect about x to get x1 */
+           y1 = 2*y-y0;
+
+           if (!INGRID(state, x0, y0) || !INGRID(state, x1, y1))
+               continue;              /* outermost edge of grid */
+           s0 = &SPACE(state, x0, y0);
+           s1 = &SPACE(state, x1, y1);
+           assert(s0->type == s_tile && s1->type == s_tile);
+
+           if (s0->dotx == s1->dotx && s0->doty == s1->doty)
+               continue;              /* tiles _already_ owned by same dot */
+
+           d0 = &SPACE(state, s0->dotx, s0->doty);
+           d1 = &SPACE(state, s1->dotx, s1->doty);
+
+           if ((d0->flags ^ d1->flags) & F_DOT_BLACK)
+               continue;              /* different colours: cannot merge */
+
+           /*
+            * Work out where the centre of gravity of the new
+            * region would be.
+            */
+           cx = d0->nassoc * d0->x + d1->nassoc * d1->x;
+           cy = d0->nassoc * d0->y + d1->nassoc * d1->y;
+           cn = d0->nassoc + d1->nassoc;
+           if (cx % cn || cy % cn)
+               continue;              /* CoG not at integer coordinates */
+           cx /= cn;
+           cy /= cn;
+           assert(INUI(state, cx, cy));
+
+           /*
+            * Ensure that the CoG would actually be _in_ the new
+            * region, by verifying that all its surrounding tiles
+            * belong to one or other of our two dots.
+            */
+           cx0 = ((cx+1) & ~1) - 1;   /* round down to next odd number */
+           cy0 = ((cy+1) & ~1) - 1;
+           cx1 = 2*cx-cx0;            /* and reflect about cx to get cx1 */
+           cy1 = 2*cy-cy0;
+           ok = TRUE;
+           for (ty = cy0; ty <= cy1; ty += 2)
+               for (tx = cx0; tx <= cx1; tx += 2) {
+                   ts = &SPACE(state, tx, ty);
+                   assert(ts->type == s_tile);
+                   if ((ts->dotx != d0->x || ts->doty != d0->y) &&
+                       (ts->dotx != d1->x || ts->doty != d1->y))
+                       ok = FALSE;
+               }
+           if (!ok)
+               continue;
+
+           /*
+            * Verify that for every tile in either source region,
+            * that tile's image in the new CoG is also in one of
+            * the two source regions.
+            */
+           for (ty = 1; ty < state->sy; ty += 2) {
+               for (tx = 1; tx < state->sx; tx += 2) {
+                   int tx1, ty1;
+
+                   ts = &SPACE(state, tx, ty);
+                   assert(ts->type == s_tile);
+                   if ((ts->dotx != d0->x || ts->doty != d0->y) &&
+                       (ts->dotx != d1->x || ts->doty != d1->y))
+                       continue;      /* not part of these tiles anyway */
+                   tx1 = 2*cx-tx;
+                   ty1 = 2*cy-ty;
+                   if (!INGRID(state, tx1, ty1)) {
+                       ok = FALSE;
+                       break;
+                   }
+                   ts = &SPACE(state, cx+cx-tx, cy+cy-ty);
+                   if ((ts->dotx != d0->x || ts->doty != d0->y) &&
+                       (ts->dotx != d1->x || ts->doty != d1->y)) {
+                       ok = FALSE;
+                       break;
+                   }
+               }
+               if (!ok)
+                   break;
+           }
+           if (!ok)
+               continue;
+
+           /*
+            * Now we're clear to attempt the merge. We take a copy
+            * of the game state first, so we can revert it easily
+            * if the resulting puzzle turns out to have become
+            * insoluble.
+            */
+           copy2 = dup_game(state);
+
+           remove_dot(d0);
+           remove_dot(d1);
+           dn = &SPACE(state, cx, cy);
+           add_dot(dn);
+           dn->flags |= (d0->flags & F_DOT_BLACK);
+           for (ty = 1; ty < state->sy; ty += 2) {
+               for (tx = 1; tx < state->sx; tx += 2) {
+                   ts = &SPACE(state, tx, ty);
+                   assert(ts->type == s_tile);
+                   if ((ts->dotx != d0->x || ts->doty != d0->y) &&
+                       (ts->dotx != d1->x || ts->doty != d1->y))
+                       continue;      /* not part of these tiles anyway */
+                   add_assoc(state, ts, dn);
+               }
+           }
+
+           copy = dup_game(state);
+           clear_game(copy, 0);
+           dbg_state(copy);
+           newdiff = solver_state(copy, params->diff);
+           free_game(copy);
+           if (diff == newdiff) {
+               /* Still just as soluble. Let the merge stand. */
+               free_game(copy2);
+           } else {
+               /* Became insoluble. Revert. */
+               free_game(state);
+               state = copy2;
+           }
+       }
+        sfree(posns);
+    }
+#endif
+
+    desc = encode_game(state);
+#ifndef STANDALONE_SOLVER
+    debug(("new_game_desc generated: \n"));
+    dbg_state(state);
+#endif
+
+    free_game(state);
+    sfree(scratch);
+
+    return desc;
+}
+
+static int dots_too_close(game_state *state)
+{
+    /* Quick-and-dirty check, using half the solver:
+     * solver_obvious will only fail if the dots are
+     * too close together, so dot-proximity associations
+     * overlap. */
+    game_state *tmp = dup_game(state);
+    int ret = solver_obvious(tmp);
+    free_game(tmp);
+    return (ret == -1) ? 1 : 0;
+}
+
+static game_state *load_game(const game_params *params, const char *desc,
+                             char **why_r)
+{
+    game_state *state = blank_game(params->w, params->h);
+    char *why = NULL;
+    int i, x, y, n;
+    unsigned int df;
+
+    i = 0;
+    while (*desc) {
+        n = *desc++;
+        if (n == 'z') {
+            i += 25;
+            continue;
+        }
+        if (n >= 'a' && n <= 'y') {
+            i += n - 'a';
+            df = 0;
+        } else if (n >= 'A' && n <= 'Y') {
+            i += n - 'A';
+            df = F_DOT_BLACK;
+        } else {
+            why = "Invalid characters in game description"; goto fail;
+        }
+        /* if we got here we incremented i and have a dot to add. */
+        y = (i / (state->sx-2)) + 1;
+        x = (i % (state->sx-2)) + 1;
+        if (!INUI(state, x, y)) {
+            why = "Too much data to fit in grid"; goto fail;
+        }
+        add_dot(&SPACE(state, x, y));
+        SPACE(state, x, y).flags |= df;
+        i++;
+    }
+    game_update_dots(state);
+
+    if (dots_too_close(state)) {
+        why = "Dots too close together"; goto fail;
+    }
+
+    return state;
+
+fail:
+    free_game(state);
+    if (why_r) *why_r = why;
+    return NULL;
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    char *why = NULL;
+    game_state *dummy = load_game(params, desc, &why);
+    if (dummy) {
+        free_game(dummy);
+        assert(!why);
+    } else
+        assert(why);
+    return why;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    game_state *state = load_game(params, desc, NULL);
+    if (!state) {
+        assert("Unable to load ?validated game.");
+        return NULL;
+    }
+#ifdef EDITOR
+    state->me = me;
+#endif
+    return state;
+}
+
+/* ----------------------------------------------------------
+ * Solver and all its little wizards.
+ */
+
+int solver_recurse_depth;
+
+typedef struct solver_ctx {
+    game_state *state;
+    int sz;             /* state->sx * state->sy */
+    space **scratch;    /* size sz */
+
+} solver_ctx;
+
+static solver_ctx *new_solver(game_state *state)
+{
+    solver_ctx *sctx = snew(solver_ctx);
+    sctx->state = state;
+    sctx->sz = state->sx*state->sy;
+    sctx->scratch = snewn(sctx->sz, space *);
+    return sctx;
+}
+
+static void free_solver(solver_ctx *sctx)
+{
+    sfree(sctx->scratch);
+    sfree(sctx);
+}
+
+    /* Solver ideas so far:
+     *
+     * For any empty space, work out how many dots it could associate
+     * with:
+       * it needs line-of-sight
+       * it needs an empty space on the far side
+       * any adjacent lines need corresponding line possibilities.
+     */
+
+/* The solver_ctx should keep a list of dot positions, for quicker looping.
+ *
+ * Solver techniques, in order of difficulty:
+   * obvious adjacency to dots
+   * transferring tiles to opposite side
+   * transferring lines to opposite side
+   * one possible dot for a given tile based on opposite availability
+   * tile with 3 definite edges next to an associated tile must associate
+      with same dot.
+   *
+   * one possible dot for a given tile based on line-of-sight
+ */
+
+static int solver_add_assoc(game_state *state, space *tile, int dx, int dy,
+                            const char *why)
+{
+    space *dot, *tile_opp;
+
+    dot = &SPACE(state, dx, dy);
+    tile_opp = space_opposite_dot(state, tile, dot);
+
+    assert(tile->type == s_tile);
+    if (tile->flags & F_TILE_ASSOC) {
+        if ((tile->dotx != dx) || (tile->doty != dy)) {
+            solvep(("%*sSet %d,%d --> %d,%d (%s) impossible; "
+                    "already --> %d,%d.\n",
+                    solver_recurse_depth*4, "",
+                    tile->x, tile->y, dx, dy, why,
+                    tile->dotx, tile->doty));
+            return -1;
+        }
+        return 0; /* no-op */
+    }
+    if (!tile_opp) {
+        solvep(("%*s%d,%d --> %d,%d impossible, no opposite tile.\n",
+                solver_recurse_depth*4, "", tile->x, tile->y, dx, dy));
+        return -1;
+    }
+    if (tile_opp->flags & F_TILE_ASSOC &&
+        (tile_opp->dotx != dx || tile_opp->doty != dy)) {
+        solvep(("%*sSet %d,%d --> %d,%d (%s) impossible; "
+                "opposite already --> %d,%d.\n",
+                solver_recurse_depth*4, "",
+                tile->x, tile->y, dx, dy, why,
+                tile_opp->dotx, tile_opp->doty));
+        return -1;
+    }
+
+    add_assoc(state, tile, dot);
+    add_assoc(state, tile_opp, dot);
+    solvep(("%*sSetting %d,%d --> %d,%d (%s).\n",
+            solver_recurse_depth*4, "",
+            tile->x, tile->y,dx, dy, why));
+    solvep(("%*sSetting %d,%d --> %d,%d (%s, opposite).\n",
+            solver_recurse_depth*4, "",
+            tile_opp->x, tile_opp->y, dx, dy, why));
+    return 1;
+}
+
+static int solver_obvious_dot(game_state *state, space *dot)
+{
+    int dx, dy, ret, didsth = 0;
+    space *tile;
+
+    debug(("%*ssolver_obvious_dot for %d,%d.\n",
+           solver_recurse_depth*4, "", dot->x, dot->y));
+
+    assert(dot->flags & F_DOT);
+    for (dx = -1; dx <= 1; dx++) {
+        for (dy = -1; dy <= 1; dy++) {
+            if (!INGRID(state, dot->x+dx, dot->y+dy)) continue;
+
+            tile = &SPACE(state, dot->x+dx, dot->y+dy);
+            if (tile->type == s_tile) {
+                ret = solver_add_assoc(state, tile, dot->x, dot->y,
+                                       "next to dot");
+                if (ret < 0) return -1;
+                if (ret > 0) didsth = 1;
+            }
+        }
+    }
+    return didsth;
+}
+
+static int solver_obvious(game_state *state)
+{
+    int i, didsth = 0, ret;
+
+    debug(("%*ssolver_obvious.\n", solver_recurse_depth*4, ""));
+
+    for (i = 0; i < state->ndots; i++) {
+        ret = solver_obvious_dot(state, state->dots[i]);
+        if (ret < 0) return -1;
+        if (ret > 0) didsth = 1;
+    }
+    return didsth;
+}
+
+static int solver_lines_opposite_cb(game_state *state, space *edge, void *ctx)
+{
+    int didsth = 0, n, dx, dy;
+    space *tiles[2], *tile_opp, *edge_opp;
+
+    assert(edge->type == s_edge);
+
+    tiles_from_edge(state, edge, tiles);
+
+    /* if tiles[0] && tiles[1] && they're both associated
+     * and they're both associated with different dots,
+     * ensure the line is set. */
+    if (!(edge->flags & F_EDGE_SET) &&
+        tiles[0] && tiles[1] &&
+        (tiles[0]->flags & F_TILE_ASSOC) &&
+        (tiles[1]->flags & F_TILE_ASSOC) &&
+        (tiles[0]->dotx != tiles[1]->dotx ||
+         tiles[0]->doty != tiles[1]->doty)) {
+        /* No edge, but the two adjacent tiles are both
+         * associated with different dots; add the edge. */
+        solvep(("%*sSetting edge %d,%d - tiles different dots.\n",
+               solver_recurse_depth*4, "", edge->x, edge->y));
+        edge->flags |= F_EDGE_SET;
+        didsth = 1;
+    }
+
+    if (!(edge->flags & F_EDGE_SET)) return didsth;
+    for (n = 0; n < 2; n++) {
+        if (!tiles[n]) continue;
+        assert(tiles[n]->type == s_tile);
+        if (!(tiles[n]->flags & F_TILE_ASSOC)) continue;
+
+        tile_opp = tile_opposite(state, tiles[n]);
+        if (!tile_opp) {
+            solvep(("%*simpossible: edge %d,%d has assoc. tile %d,%d"
+                   " with no opposite.\n",
+                   solver_recurse_depth*4, "",
+                   edge->x, edge->y, tiles[n]->x, tiles[n]->y));
+            /* edge of tile has no opposite edge (off grid?);
+             * this is impossible. */
+            return -1;
+        }
+
+        dx = tiles[n]->x - edge->x;
+        dy = tiles[n]->y - edge->y;
+        assert(INGRID(state, tile_opp->x+dx, tile_opp->y+dy));
+        edge_opp = &SPACE(state, tile_opp->x+dx, tile_opp->y+dy);
+        if (!(edge_opp->flags & F_EDGE_SET)) {
+            solvep(("%*sSetting edge %d,%d as opposite %d,%d\n",
+                   solver_recurse_depth*4, "",
+                   tile_opp->x-dx, tile_opp->y-dy, edge->x, edge->y));
+            edge_opp->flags |= F_EDGE_SET;
+            didsth = 1;
+        }
+    }
+    return didsth;
+}
+
+static int solver_spaces_oneposs_cb(game_state *state, space *tile, void *ctx)
+{
+    int n, eset, ret;
+    space *edgeadj[4], *tileadj[4];
+    int dotx, doty;
+
+    assert(tile->type == s_tile);
+    if (tile->flags & F_TILE_ASSOC) return 0;
+
+    adjacencies(state, tile, edgeadj, tileadj);
+
+    /* Empty tile. If each edge is either set, or associated with
+     * the same dot, we must also associate with dot. */
+    eset = 0; dotx = -1; doty = -1;
+    for (n = 0; n < 4; n++) {
+        assert(edgeadj[n]);
+        assert(edgeadj[n]->type == s_edge);
+        if (edgeadj[n]->flags & F_EDGE_SET) {
+            eset++;
+        } else {
+            assert(tileadj[n]);
+            assert(tileadj[n]->type == s_tile);
+
+            /* If an adjacent tile is empty we can't make any deductions.*/
+            if (!(tileadj[n]->flags & F_TILE_ASSOC))
+                return 0;
+
+            /* If an adjacent tile is assoc. with a different dot
+             * we can't make any deductions. */
+            if (dotx != -1 && doty != -1 &&
+                (tileadj[n]->dotx != dotx ||
+                 tileadj[n]->doty != doty))
+                return 0;
+
+            dotx = tileadj[n]->dotx;
+            doty = tileadj[n]->doty;
+        }
+    }
+    if (eset == 4) {
+        solvep(("%*simpossible: empty tile %d,%d has 4 edges\n",
+               solver_recurse_depth*4, "",
+               tile->x, tile->y));
+        return -1;
+    }
+    assert(dotx != -1 && doty != -1);
+
+    ret = solver_add_assoc(state, tile, dotx, doty, "rest are edges");
+    if (ret == -1) return -1;
+    assert(ret != 0); /* really should have done something. */
+
+    return 1;
+}
+
+/* Improved algorithm for tracking line-of-sight from dots, and not spaces.
+ *
+ * The solver_ctx already stores a list of dots: the algorithm proceeds by
+ * expanding outwards from each dot in turn, expanding first to the boundary
+ * of its currently-connected tile and then to all empty tiles that could see
+ * it. Empty tiles will be flagged with a 'can see dot <x,y>' sticker.
+ *
+ * Expansion will happen by (symmetrically opposite) pairs of squares; if
+ * a square hasn't an opposite number there's no point trying to expand through
+ * it. Empty tiles will therefore also be tagged in pairs.
+ *
+ * If an empty tile already has a 'can see dot <x,y>' tag from a previous dot,
+ * it (and its partner) gets untagged (or, rather, a 'can see two dots' tag)
+ * because we're looking for single-dot possibilities.
+ *
+ * Once we've gone through all the dots, any which still have a 'can see dot'
+ * tag get associated with that dot (because it must have been the only one);
+ * any without any tag (i.e. that could see _no_ dots) cause an impossibility
+ * marked.
+ *
+ * The expansion will happen each time with a stored list of (space *) pairs,
+ * rather than a mark-and-sweep idea; that's horrifically inefficient.
+ *
+ * expansion algorithm:
+ *
+ * * allocate list of (space *) the size of s->sx*s->sy.
+ * * allocate second grid for (flags, dotx, doty) size of sx*sy.
+ *
+ * clear second grid (flags = 0, all dotx and doty = 0)
+ * flags: F_REACHABLE, F_MULTIPLE
+ *
+ *
+ * * for each dot, start with one pair of tiles that are associated with it --
+ *   * vertex --> (dx+1, dy+1), (dx-1, dy-1)
+ *   * edge --> (adj1, adj2)
+ *   * tile --> (tile, tile) ???
+ * * mark that pair of tiles with F_MARK, clear all other F_MARKs.
+ * * add two tiles to start of list.
+ *
+ * set start = 0, end = next = 2
+ *
+ * from (start to end-1, step 2) {
+ * * we have two tiles (t1, t2), opposites wrt our dot.
+ * * for each (at1) sensible adjacent tile to t1 (i.e. not past an edge):
+ *   * work out at2 as the opposite to at1
+ *   * assert at1 and at2 have the same F_MARK values.
+ *   * if at1 & F_MARK ignore it (we've been there on a previous sweep)
+ *   * if either are associated with a different dot
+ *     * mark both with F_MARK (so we ignore them later)
+ *   * otherwise (assoc. with our dot, or empty):
+ *     * mark both with F_MARK
+ *     * add their space * values to the end of the list, set next += 2.
+ * }
+ *
+ * if (end == next)
+ * * we didn't add any new squares; exit the loop.
+ * else
+ * * set start = next+1, end = next. go round again
+ *
+ * We've finished expanding from the dot. Now, for each square we have
+ * in our list (--> each square with F_MARK):
+ * * if the tile is empty:
+ *   * if F_REACHABLE was already set
+ *     * set F_MULTIPLE
+ *   * otherwise
+ *     * set F_REACHABLE, set dotx and doty to our dot.
+ *
+ * Then, continue the whole thing for each dot in turn.
+ *
+ * Once we've done for each dot, go through the entire grid looking for
+ * empty tiles: for each empty tile:
+   * if F_REACHABLE and not F_MULTIPLE, set that dot (and its double)
+   * if !F_REACHABLE, return as impossible.
+ *
+ */
+
+/* Returns 1 if this tile is either already associated with this dot,
+ * or blank. */
+static int solver_expand_checkdot(space *tile, space *dot)
+{
+    if (!(tile->flags & F_TILE_ASSOC)) return 1;
+    if (tile->dotx == dot->x && tile->doty == dot->y) return 1;
+    return 0;
+}
+
+static void solver_expand_fromdot(game_state *state, space *dot, solver_ctx *sctx)
+{
+    int i, j, x, y, start, end, next;
+    space *sp;
+
+    /* Clear the grid of the (space) flags we'll use. */
+
+    /* This is well optimised; analysis showed that:
+        for (i = 0; i < sctx->sz; i++) state->grid[i].flags &= ~F_MARK;
+       took up ~85% of the total function time! */
+    for (y = 1; y < state->sy; y += 2) {
+        sp = &SPACE(state, 1, y);
+        for (x = 1; x < state->sx; x += 2, sp += 2)
+            sp->flags &= ~F_MARK;
+    }
+
+    /* Seed the list of marked squares with two that must be associated
+     * with our dot (possibly the same space) */
+    if (dot->type == s_tile) {
+        sctx->scratch[0] = sctx->scratch[1] = dot;
+    } else if (dot->type == s_edge) {
+        tiles_from_edge(state, dot, sctx->scratch);
+    } else if (dot->type == s_vertex) {
+        /* pick two of the opposite ones arbitrarily. */
+        sctx->scratch[0] = &SPACE(state, dot->x-1, dot->y-1);
+        sctx->scratch[1] = &SPACE(state, dot->x+1, dot->y+1);
+    }
+    assert(sctx->scratch[0]->flags & F_TILE_ASSOC);
+    assert(sctx->scratch[1]->flags & F_TILE_ASSOC);
+
+    sctx->scratch[0]->flags |= F_MARK;
+    sctx->scratch[1]->flags |= F_MARK;
+
+    debug(("%*sexpand from dot %d,%d seeded with %d,%d and %d,%d.\n",
+           solver_recurse_depth*4, "", dot->x, dot->y,
+           sctx->scratch[0]->x, sctx->scratch[0]->y,
+           sctx->scratch[1]->x, sctx->scratch[1]->y));
+
+    start = 0; end = 2; next = 2;
+
+expand:
+    debug(("%*sexpand: start %d, end %d, next %d\n",
+           solver_recurse_depth*4, "", start, end, next));
+    for (i = start; i < end; i += 2) {
+        space *t1 = sctx->scratch[i]/*, *t2 = sctx->scratch[i+1]*/;
+        space *edges[4], *tileadj[4], *tileadj2;
+
+        adjacencies(state, t1, edges, tileadj);
+
+        for (j = 0; j < 4; j++) {
+            assert(edges[j]);
+            if (edges[j]->flags & F_EDGE_SET) continue;
+            assert(tileadj[j]);
+
+            if (tileadj[j]->flags & F_MARK) continue; /* seen before. */
+
+            /* We have a tile adjacent to t1; find its opposite. */
+            tileadj2 = space_opposite_dot(state, tileadj[j], dot);
+            if (!tileadj2) {
+                debug(("%*sMarking %d,%d, no opposite.\n",
+                       solver_recurse_depth*4, "",
+                       tileadj[j]->x, tileadj[j]->y));
+                tileadj[j]->flags |= F_MARK;
+                continue; /* no opposite, so mark for next time. */
+            }
+            /* If the tile had an opposite we should have either seen both of
+             * these, or neither of these, before. */
+            assert(!(tileadj2->flags & F_MARK));
+
+            if (solver_expand_checkdot(tileadj[j], dot) &&
+                solver_expand_checkdot(tileadj2, dot)) {
+                /* Both tiles could associate with this dot; add them to
+                 * our list. */
+                debug(("%*sAdding %d,%d and %d,%d to possibles list.\n",
+                       solver_recurse_depth*4, "",
+                       tileadj[j]->x, tileadj[j]->y, tileadj2->x, tileadj2->y));
+                sctx->scratch[next++] = tileadj[j];
+                sctx->scratch[next++] = tileadj2;
+            }
+            /* Either way, we've seen these tiles already so mark them. */
+            debug(("%*sMarking %d,%d and %d,%d.\n",
+                   solver_recurse_depth*4, "",
+                       tileadj[j]->x, tileadj[j]->y, tileadj2->x, tileadj2->y));
+            tileadj[j]->flags |= F_MARK;
+            tileadj2->flags |= F_MARK;
+        }
+    }
+    if (next > end) {
+        /* We added more squares; go back and try again. */
+        start = end; end = next; goto expand;
+    }
+
+    /* We've expanded as far as we can go. Now we update the main flags
+     * on all tiles we've expanded into -- if they were empty, we have
+     * found possible associations for this dot. */
+    for (i = 0; i < end; i++) {
+        if (sctx->scratch[i]->flags & F_TILE_ASSOC) continue;
+        if (sctx->scratch[i]->flags & F_REACHABLE) {
+            /* This is (at least) the second dot this tile could
+             * associate with. */
+            debug(("%*sempty tile %d,%d could assoc. other dot %d,%d\n",
+                   solver_recurse_depth*4, "",
+                   sctx->scratch[i]->x, sctx->scratch[i]->y, dot->x, dot->y));
+            sctx->scratch[i]->flags |= F_MULTIPLE;
+        } else {
+            /* This is the first (possibly only) dot. */
+            debug(("%*sempty tile %d,%d could assoc. 1st dot %d,%d\n",
+                   solver_recurse_depth*4, "",
+                   sctx->scratch[i]->x, sctx->scratch[i]->y, dot->x, dot->y));
+            sctx->scratch[i]->flags |= F_REACHABLE;
+            sctx->scratch[i]->dotx = dot->x;
+            sctx->scratch[i]->doty = dot->y;
+        }
+    }
+    dbg_state(state);
+}
+
+static int solver_expand_postcb(game_state *state, space *tile, void *ctx)
+{
+    assert(tile->type == s_tile);
+
+    if (tile->flags & F_TILE_ASSOC) return 0;
+
+    if (!(tile->flags & F_REACHABLE)) {
+        solvep(("%*simpossible: space (%d,%d) can reach no dots.\n",
+                solver_recurse_depth*4, "", tile->x, tile->y));
+        return -1;
+    }
+    if (tile->flags & F_MULTIPLE) return 0;
+
+    return solver_add_assoc(state, tile, tile->dotx, tile->doty,
+                            "single possible dot after expansion");
+}
+
+static int solver_expand_dots(game_state *state, solver_ctx *sctx)
+{
+    int i;
+
+    for (i = 0; i < sctx->sz; i++)
+        state->grid[i].flags &= ~(F_REACHABLE|F_MULTIPLE);
+
+    for (i = 0; i < state->ndots; i++)
+        solver_expand_fromdot(state, state->dots[i], sctx);
+
+    return foreach_tile(state, solver_expand_postcb, IMPOSSIBLE_QUITS, sctx);
+}
+
+struct recurse_ctx {
+    space *best;
+    int bestn;
+};
+
+static int solver_recurse_cb(game_state *state, space *tile, void *ctx)
+{
+    struct recurse_ctx *rctx = (struct recurse_ctx *)ctx;
+    int i, n = 0;
+
+    assert(tile->type == s_tile);
+    if (tile->flags & F_TILE_ASSOC) return 0;
+
+    /* We're unassociated: count up all the dots we could associate with. */
+    for (i = 0; i < state->ndots; i++) {
+        if (dotfortile(state, tile, state->dots[i]))
+            n++;
+    }
+    if (n > rctx->bestn) {
+        rctx->bestn = n;
+        rctx->best = tile;
+    }
+    return 0;
+}
+
+#define MAXRECURSE 5
+
+static int solver_recurse(game_state *state, int maxdiff)
+{
+    int diff = DIFF_IMPOSSIBLE, ret, n, gsz = state->sx * state->sy;
+    space *ingrid, *outgrid = NULL, *bestopp;
+    struct recurse_ctx rctx;
+
+    if (solver_recurse_depth >= MAXRECURSE) {
+        solvep(("Limiting recursion to %d, returning.", MAXRECURSE));
+        return DIFF_UNFINISHED;
+    }
+
+    /* Work out the cell to recurse on; go through all unassociated tiles
+     * and find which one has the most possible dots it could associate
+     * with. */
+    rctx.best = NULL;
+    rctx.bestn = 0;
+
+    foreach_tile(state, solver_recurse_cb, 0, &rctx);
+    if (rctx.bestn == 0) return DIFF_IMPOSSIBLE; /* or assert? */
+    assert(rctx.best);
+
+    solvep(("%*sRecursing around %d,%d, with %d possible dots.\n",
+           solver_recurse_depth*4, "",
+           rctx.best->x, rctx.best->y, rctx.bestn));
+
+#ifdef STANDALONE_SOLVER
+    solver_recurse_depth++;
+#endif
+
+    ingrid = snewn(gsz, space);
+    memcpy(ingrid, state->grid, gsz * sizeof(space));
+
+    for (n = 0; n < state->ndots; n++) {
+        memcpy(state->grid, ingrid, gsz * sizeof(space));
+
+        if (!dotfortile(state, rctx.best, state->dots[n])) continue;
+
+        /* set cell (temporarily) pointing to that dot. */
+        solver_add_assoc(state, rctx.best,
+                         state->dots[n]->x, state->dots[n]->y,
+                         "Attempting for recursion");
+
+        ret = solver_state(state, maxdiff);
+
+        if (diff == DIFF_IMPOSSIBLE && ret != DIFF_IMPOSSIBLE) {
+            /* we found our first solved grid; copy it away. */
+            assert(!outgrid);
+            outgrid = snewn(gsz, space);
+            memcpy(outgrid, state->grid, gsz * sizeof(space));
+        }
+        /* reset cell back to unassociated. */
+        bestopp = tile_opposite(state, rctx.best);
+        assert(bestopp && bestopp->flags & F_TILE_ASSOC);
+
+        remove_assoc(state, rctx.best);
+        remove_assoc(state, bestopp);
+
+        if (ret == DIFF_AMBIGUOUS || ret == DIFF_UNFINISHED)
+            diff = ret;
+        else if (ret == DIFF_IMPOSSIBLE)
+            /* no change */;
+        else {
+            /* precisely one solution */
+            if (diff == DIFF_IMPOSSIBLE)
+                diff = DIFF_UNREASONABLE;
+            else
+                diff = DIFF_AMBIGUOUS;
+        }
+        /* if we've found >1 solution, or ran out of recursion,
+         * give up immediately. */
+        if (diff == DIFF_AMBIGUOUS || diff == DIFF_UNFINISHED)
+            break;
+    }
+
+#ifdef STANDALONE_SOLVER
+    solver_recurse_depth--;
+#endif
+
+    if (outgrid) {
+        /* we found (at least one) soln; copy it back to state */
+        memcpy(state->grid, outgrid, gsz * sizeof(space));
+        sfree(outgrid);
+    }
+    sfree(ingrid);
+    return diff;
+}
+
+static int solver_state(game_state *state, int maxdiff)
+{
+    solver_ctx *sctx = new_solver(state);
+    int ret, diff = DIFF_NORMAL;
+
+#ifdef STANDALONE_PICTURE_GENERATOR
+    /* hack, hack: set picture to NULL during solving so that add_assoc
+     * won't complain when we attempt recursive guessing and guess wrong */
+    int *savepic = picture;
+    picture = NULL;
+#endif
+
+    ret = solver_obvious(state);
+    if (ret < 0) {
+        diff = DIFF_IMPOSSIBLE;
+        goto got_result;
+    }
+
+#define CHECKRET(d) do {                                        \
+    if (ret < 0) { diff = DIFF_IMPOSSIBLE; goto got_result; }   \
+    if (ret > 0) { diff = max(diff, (d)); goto cont; }          \
+} while(0)
+
+    while (1) {
+cont:
+        ret = foreach_edge(state, solver_lines_opposite_cb,
+                           IMPOSSIBLE_QUITS, sctx);
+        CHECKRET(DIFF_NORMAL);
+
+        ret = foreach_tile(state, solver_spaces_oneposs_cb,
+                           IMPOSSIBLE_QUITS, sctx);
+        CHECKRET(DIFF_NORMAL);
+
+        ret = solver_expand_dots(state, sctx);
+        CHECKRET(DIFF_NORMAL);
+
+        if (maxdiff <= DIFF_NORMAL)
+            break;
+
+        /* harder still? */
+
+        /* if we reach here, we've made no deductions, so we terminate. */
+        break;
+    }
+
+    if (check_complete(state, NULL, NULL)) goto got_result;
+
+    diff = (maxdiff >= DIFF_UNREASONABLE) ?
+        solver_recurse(state, maxdiff) : DIFF_UNFINISHED;
+
+got_result:
+    free_solver(sctx);
+#ifndef STANDALONE_SOLVER
+    debug(("solver_state ends, diff %s:\n", galaxies_diffnames[diff]));
+    dbg_state(state);
+#endif
+
+#ifdef STANDALONE_PICTURE_GENERATOR
+    picture = savepic;
+#endif
+
+    return diff;
+}
+
+#ifndef EDITOR
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    game_state *tosolve;
+    char *ret;
+    int i;
+    int diff;
+
+    tosolve = dup_game(currstate);
+    diff = solver_state(tosolve, DIFF_UNREASONABLE);
+    if (diff != DIFF_UNFINISHED && diff != DIFF_IMPOSSIBLE) {
+        debug(("solve_game solved with current state.\n"));
+        goto solved;
+    }
+    free_game(tosolve);
+
+    tosolve = dup_game(state);
+    diff = solver_state(tosolve, DIFF_UNREASONABLE);
+    if (diff != DIFF_UNFINISHED && diff != DIFF_IMPOSSIBLE) {
+        debug(("solve_game solved with original state.\n"));
+        goto solved;
+    }
+    free_game(tosolve);
+
+    return NULL;
+
+solved:
+    /*
+     * Clear tile associations: the solution will only include the
+     * edges.
+     */
+    for (i = 0; i < tosolve->sx*tosolve->sy; i++)
+        tosolve->grid[i].flags &= ~F_TILE_ASSOC;
+    ret = diff_game(currstate, tosolve, 1);
+    free_game(tosolve);
+    return ret;
+}
+#endif
+
+/* ----------------------------------------------------------
+ * User interface.
+ */
+
+struct game_ui {
+    int dragging;
+    int dx, dy;         /* pixel coords of drag pos. */
+    int dotx, doty;     /* grid coords of dot we're dragging from. */
+    int srcx, srcy;     /* grid coords of drag start */
+    int cur_x, cur_y, cur_visible;
+};
+
+static game_ui *new_ui(const game_state *state)
+{
+    game_ui *ui = snew(game_ui);
+    ui->dragging = FALSE;
+    ui->cur_x = ui->cur_y = 1;
+    ui->cur_visible = 0;
+    return ui;
+}
+
+static void free_ui(game_ui *ui)
+{
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+}
+
+#define FLASH_TIME 0.15F
+
+#define PREFERRED_TILE_SIZE 32
+#define TILE_SIZE (ds->tilesize)
+#define DOT_SIZE        (TILE_SIZE / 4)
+#define EDGE_THICKNESS (max(TILE_SIZE / 16, 2))
+#define BORDER TILE_SIZE
+
+#define COORD(x) ( (x) * TILE_SIZE + BORDER )
+#define SCOORD(x) ( ((x) * TILE_SIZE)/2 + BORDER )
+#define FROMCOORD(x) ( ((x) - BORDER) / TILE_SIZE )
+
+#define DRAW_WIDTH      (BORDER * 2 + ds->w * TILE_SIZE)
+#define DRAW_HEIGHT     (BORDER * 2 + ds->h * TILE_SIZE)
+
+#define CURSOR_SIZE DOT_SIZE
+
+struct game_drawstate {
+    int started;
+    int w, h;
+    int tilesize;
+    unsigned long *grid;
+    int *dx, *dy;
+    blitter *bl;
+    blitter *blmirror;
+
+    int dragging, dragx, dragy;
+
+    int *colour_scratch;
+
+    int cx, cy, cur_visible;
+    blitter *cur_bl;
+};
+
+#define CORNER_TOLERANCE 0.15F
+#define CENTRE_TOLERANCE 0.15F
+
+/*
+ * Round FP coordinates to the centre of the nearest edge.
+ */
+#ifndef EDITOR
+static void coord_round_to_edge(float x, float y, int *xr, int *yr)
+{
+    float xs, ys, xv, yv, dx, dy;
+
+    /*
+     * Find the nearest square-centre.
+     */
+    xs = (float)floor(x) + 0.5F;
+    ys = (float)floor(y) + 0.5F;
+
+    /*
+     * Find the nearest grid vertex.
+     */
+    xv = (float)floor(x + 0.5F);
+    yv = (float)floor(y + 0.5F);
+
+    /*
+     * Determine whether the horizontal or vertical edge from that
+     * vertex alongside that square is closer to us, by comparing
+     * distances from the square cente.
+     */
+    dx = (float)fabs(x - xs);
+    dy = (float)fabs(y - ys);
+    if (dx > dy) {
+        /* Vertical edge: x-coord of corner,
+         * y-coord of square centre. */
+        *xr = 2 * (int)xv;
+        *yr = 1 + 2 * (int)floor(ys);
+    } else {
+        /* Horizontal edge: x-coord of square centre,
+         * y-coord of corner. */
+        *xr = 1 + 2 * (int)floor(xs);
+        *yr = 2 * (int)yv;
+    }
+}
+#endif
+
+#ifdef EDITOR
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    char buf[80];
+    int px, py;
+    space *sp;
+
+    px = 2*FROMCOORD((float)x) + 0.5;
+    py = 2*FROMCOORD((float)y) + 0.5;
+
+    state->cdiff = -1;
+
+    if (button == 'C' || button == 'c') return dupstr("C");
+
+    if (button == 'S' || button == 's') {
+        char *ret;
+        game_state *tmp = dup_game(state);
+        state->cdiff = solver_state(tmp, DIFF_UNREASONABLE-1);
+        ret = diff_game(state, tmp, 0);
+        free_game(tmp);
+        return ret;
+    }
+
+    if (button == LEFT_BUTTON || button == RIGHT_BUTTON) {
+        if (!INUI(state, px, py)) return NULL;
+        sp = &SPACE(state, px, py);
+        if (!dot_is_possible(state, sp, 1)) return NULL;
+        sprintf(buf, "%c%d,%d",
+                (char)((button == LEFT_BUTTON) ? 'D' : 'd'), px, py);
+        return dupstr(buf);
+    }
+
+    return NULL;
+}
+#else
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    /* UI operations (play mode):
+     *
+     * Toggle edge (set/unset) (left-click on edge)
+     * Associate space with dot (left-drag from dot)
+     * Unassociate space (left-drag from space off grid)
+     * Autofill lines around shape? (right-click?)
+     *
+     * (edit mode; will clear all lines/associations)
+     *
+     * Add or remove dot (left-click)
+     */
+    char buf[80];
+    const char *sep = "";
+    int px, py;
+    space *sp, *dot;
+
+    buf[0] = '\0';
+
+    if (button == 'H' || button == 'h') {
+        char *ret;
+        game_state *tmp = dup_game(state);
+        solver_obvious(tmp);
+        ret = diff_game(state, tmp, 0);
+        free_game(tmp);
+        return ret;
+    }
+
+    if (button == LEFT_BUTTON) {
+        ui->cur_visible = 0;
+        coord_round_to_edge(FROMCOORD((float)x), FROMCOORD((float)y),
+                            &px, &py);
+
+        if (!INUI(state, px, py)) return NULL;
+
+        sp = &SPACE(state, px, py);
+        assert(sp->type == s_edge);
+        {
+            sprintf(buf, "E%d,%d", px, py);
+            return dupstr(buf);
+        }
+    } else if (button == RIGHT_BUTTON) {
+        int px1, py1;
+
+        ui->cur_visible = 0;
+
+        px = (int)(2*FROMCOORD((float)x) + 0.5);
+        py = (int)(2*FROMCOORD((float)y) + 0.5);
+
+        dot = NULL;
+
+        /*
+         * If there's a dot anywhere nearby, we pick up an arrow
+         * pointing at that dot.
+         */
+        for (py1 = py-1; py1 <= py+1; py1++)
+            for (px1 = px-1; px1 <= px+1; px1++) {
+                if (px1 >= 0 && px1 < state->sx &&
+                    py1 >= 0 && py1 < state->sy &&
+                    x >= SCOORD(px1-1) && x < SCOORD(px1+1) &&
+                    y >= SCOORD(py1-1) && y < SCOORD(py1+1) &&
+                    SPACE(state, px1, py1).flags & F_DOT) {
+                    /*
+                     * Found a dot. Begin a drag from it.
+                     */
+                    dot = &SPACE(state, px1, py1);
+                    ui->srcx = px1;
+                    ui->srcy = py1;
+                    goto done;         /* multi-level break */
+                }
+            }
+
+        /*
+         * Otherwise, find the nearest _square_, and pick up the
+         * same arrow as it's got on it, if any.
+         */
+        if (!dot) {
+            px = 2*FROMCOORD(x+TILE_SIZE) - 1;
+            py = 2*FROMCOORD(y+TILE_SIZE) - 1;
+            if (px >= 0 && px < state->sx && py >= 0 && py < state->sy) {
+                sp = &SPACE(state, px, py);
+                if (sp->flags & F_TILE_ASSOC) {
+                    dot = &SPACE(state, sp->dotx, sp->doty);
+                    ui->srcx = px;
+                    ui->srcy = py;
+                }
+            }
+        }
+
+        done:
+        /*
+         * Now, if we've managed to find a dot, begin a drag.
+         */
+        if (dot) {
+            ui->dragging = TRUE;
+            ui->dx = x;
+            ui->dy = y;
+            ui->dotx = dot->x;
+            ui->doty = dot->y;
+            return "";
+        }
+    } else if (button == RIGHT_DRAG && ui->dragging) {
+        /* just move the drag coords. */
+        ui->dx = x;
+        ui->dy = y;
+        return "";
+    } else if (button == RIGHT_RELEASE && ui->dragging) {
+        ui->dragging = FALSE;
+
+        /*
+         * Drags are always targeted at a single square.
+         */
+        px = 2*FROMCOORD(x+TILE_SIZE) - 1;
+        py = 2*FROMCOORD(y+TILE_SIZE) - 1;
+
+       /*
+        * Dragging an arrow on to the same square it started from
+        * is a null move; just update the ui and finish.
+        */
+       if (px == ui->srcx && py == ui->srcy)
+           return "";
+
+       /*
+        * Otherwise, we remove the arrow from its starting
+        * square if we didn't start from a dot...
+        */
+       if ((ui->srcx != ui->dotx || ui->srcy != ui->doty) &&
+           SPACE(state, ui->srcx, ui->srcy).flags & F_TILE_ASSOC) {
+           sprintf(buf + strlen(buf), "%sU%d,%d", sep, ui->srcx, ui->srcy);
+           sep = ";";
+       }
+
+       /*
+        * ... and if the square we're moving it _to_ is valid, we
+        * add one there instead.
+        */
+        if (INUI(state, px, py)) {
+            sp = &SPACE(state, px, py);
+
+            if (!(sp->flags & F_DOT))
+               sprintf(buf + strlen(buf), "%sA%d,%d,%d,%d",
+                       sep, px, py, ui->dotx, ui->doty);
+       }
+
+       if (buf[0])
+           return dupstr(buf);
+       else
+           return "";
+    } else if (IS_CURSOR_MOVE(button)) {
+        move_cursor(button, &ui->cur_x, &ui->cur_y, state->sx-1, state->sy-1, 0);
+        if (ui->cur_x < 1) ui->cur_x = 1;
+        if (ui->cur_y < 1) ui->cur_y = 1;
+        ui->cur_visible = 1;
+        if (ui->dragging) {
+            ui->dx = SCOORD(ui->cur_x);
+            ui->dy = SCOORD(ui->cur_y);
+        }
+        return "";
+    } else if (IS_CURSOR_SELECT(button)) {
+        if (!ui->cur_visible) {
+            ui->cur_visible = 1;
+            return "";
+        }
+        sp = &SPACE(state, ui->cur_x, ui->cur_y);
+        if (ui->dragging) {
+            ui->dragging = FALSE;
+
+            if ((ui->srcx != ui->dotx || ui->srcy != ui->doty) &&
+                SPACE(state, ui->srcx, ui->srcy).flags & F_TILE_ASSOC) {
+                sprintf(buf, "%sU%d,%d", sep, ui->srcx, ui->srcy);
+                sep = ";";
+            }
+            if (sp->type == s_tile && !(sp->flags & F_DOT) && !(sp->flags & F_TILE_ASSOC)) {
+                sprintf(buf + strlen(buf), "%sA%d,%d,%d,%d",
+                        sep, ui->cur_x, ui->cur_y, ui->dotx, ui->doty);
+            }
+            return dupstr(buf);
+        } else if (sp->flags & F_DOT) {
+            ui->dragging = TRUE;
+            ui->dx = SCOORD(ui->cur_x);
+            ui->dy = SCOORD(ui->cur_y);
+            ui->dotx = ui->srcx = ui->cur_x;
+            ui->doty = ui->srcy = ui->cur_y;
+            return "";
+        } else if (sp->flags & F_TILE_ASSOC) {
+            assert(sp->type == s_tile);
+            ui->dragging = TRUE;
+            ui->dx = SCOORD(ui->cur_x);
+            ui->dy = SCOORD(ui->cur_y);
+            ui->dotx = sp->dotx;
+            ui->doty = sp->doty;
+            ui->srcx = ui->cur_x;
+            ui->srcy = ui->cur_y;
+            return "";
+        } else if (sp->type == s_edge) {
+            sprintf(buf, "E%d,%d", ui->cur_x, ui->cur_y);
+            return dupstr(buf);
+        }
+    }
+
+    return NULL;
+}
+#endif
+
+static int check_complete(const game_state *state, int *dsf, int *colours)
+{
+    int w = state->w, h = state->h;
+    int x, y, i, ret;
+
+    int free_dsf;
+    struct sqdata {
+        int minx, miny, maxx, maxy;
+        int cx, cy;
+        int valid, colour;
+    } *sqdata;
+
+    if (!dsf) {
+       dsf = snew_dsf(w*h);
+       free_dsf = TRUE;
+    } else {
+       dsf_init(dsf, w*h);
+       free_dsf = FALSE;
+    }
+
+    /*
+     * During actual game play, completion checking is done on the
+     * basis of the edges rather than the square associations. So
+     * first we must go through the grid figuring out the connected
+     * components into which the edges divide it.
+     */
+    for (y = 0; y < h; y++)
+        for (x = 0; x < w; x++) {
+            if (y+1 < h && !(SPACE(state, 2*x+1, 2*y+2).flags & F_EDGE_SET))
+                dsf_merge(dsf, y*w+x, (y+1)*w+x);
+            if (x+1 < w && !(SPACE(state, 2*x+2, 2*y+1).flags & F_EDGE_SET))
+                dsf_merge(dsf, y*w+x, y*w+(x+1));
+        }
+
+    /*
+     * That gives us our connected components. Now, for each
+     * component, decide whether it's _valid_. A valid component is
+     * one which:
+     *
+     *  - is 180-degree rotationally symmetric
+     *  - has a dot at its centre of symmetry
+     *  - has no other dots anywhere within it (including on its
+     *    boundary)
+     *  - contains no internal edges (i.e. edges separating two
+     *    squares which are both part of the component).
+     */
+
+    /*
+     * First, go through the grid finding the bounding box of each
+     * component.
+     */
+    sqdata = snewn(w*h, struct sqdata);
+    for (i = 0; i < w*h; i++) {
+        sqdata[i].minx = w+1;
+        sqdata[i].miny = h+1;
+        sqdata[i].maxx = sqdata[i].maxy = -1;
+        sqdata[i].valid = FALSE;
+    }
+    for (y = 0; y < h; y++)
+        for (x = 0; x < w; x++) {
+            i = dsf_canonify(dsf, y*w+x);
+            if (sqdata[i].minx > x)
+                sqdata[i].minx = x;
+            if (sqdata[i].maxx < x)
+                sqdata[i].maxx = x;
+            if (sqdata[i].miny > y)
+                sqdata[i].miny = y;
+            if (sqdata[i].maxy < y)
+                sqdata[i].maxy = y;
+            sqdata[i].valid = TRUE;
+        }
+
+    /*
+     * Now we're in a position to loop over each actual component
+     * and figure out where its centre of symmetry has to be if
+     * it's anywhere.
+     */
+    for (i = 0; i < w*h; i++)
+        if (sqdata[i].valid) {
+            int cx, cy;
+            cx = sqdata[i].cx = sqdata[i].minx + sqdata[i].maxx + 1;
+            cy = sqdata[i].cy = sqdata[i].miny + sqdata[i].maxy + 1;
+            if (!(SPACE(state, sqdata[i].cx, sqdata[i].cy).flags & F_DOT))
+                sqdata[i].valid = FALSE;   /* no dot at centre of symmetry */
+            if (dsf_canonify(dsf, (cy-1)/2*w+(cx-1)/2) != i ||
+                dsf_canonify(dsf, (cy)/2*w+(cx-1)/2) != i ||
+                dsf_canonify(dsf, (cy-1)/2*w+(cx)/2) != i ||
+                dsf_canonify(dsf, (cy)/2*w+(cx)/2) != i)
+                sqdata[i].valid = FALSE;   /* dot at cx,cy isn't ours */
+            if (SPACE(state, sqdata[i].cx, sqdata[i].cy).flags & F_DOT_BLACK)
+                sqdata[i].colour = 2;
+            else
+                sqdata[i].colour = 1;
+        }
+
+    /*
+     * Now we loop over the whole grid again, this time finding
+     * extraneous dots (any dot which wholly or partially overlaps
+     * a square and is not at the centre of symmetry of that
+     * square's component disqualifies the component from validity)
+     * and extraneous edges (any edge separating two squares
+     * belonging to the same component also disqualifies that
+     * component).
+     */
+    for (y = 1; y < state->sy-1; y++)
+        for (x = 1; x < state->sx-1; x++) {
+            space *sp = &SPACE(state, x, y);
+
+            if (sp->flags & F_DOT) {
+                /*
+                 * There's a dot here. Use it to disqualify any
+                 * component which deserves it.
+                 */
+                int cx, cy;
+                for (cy = (y-1) >> 1; cy <= y >> 1; cy++)
+                    for (cx = (x-1) >> 1; cx <= x >> 1; cx++) {
+                        i = dsf_canonify(dsf, cy*w+cx);
+                        if (x != sqdata[i].cx || y != sqdata[i].cy)
+                            sqdata[i].valid = FALSE;
+                    }
+            }
+
+            if (sp->flags & F_EDGE_SET) {
+                /*
+                 * There's an edge here. Use it to disqualify a
+                 * component if necessary.
+                 */
+                int cx1 = (x-1) >> 1, cx2 = x >> 1;
+                int cy1 = (y-1) >> 1, cy2 = y >> 1;
+                assert((cx1==cx2) ^ (cy1==cy2));
+                i = dsf_canonify(dsf, cy1*w+cx1);
+                if (i == dsf_canonify(dsf, cy2*w+cx2))
+                    sqdata[i].valid = FALSE;
+            }
+        }
+
+    /*
+     * And finally we test rotational symmetry: for each square in
+     * the grid, find which component it's in, test that that
+     * component also has a square in the symmetric position, and
+     * disqualify it if it doesn't.
+     */
+    for (y = 0; y < h; y++)
+        for (x = 0; x < w; x++) {
+            int x2, y2;
+
+            i = dsf_canonify(dsf, y*w+x);
+
+            x2 = sqdata[i].cx - 1 - x;
+            y2 = sqdata[i].cy - 1 - y;
+            if (i != dsf_canonify(dsf, y2*w+x2))
+                sqdata[i].valid = FALSE;
+        }
+
+    /*
+     * That's it. We now have all the connected components marked
+     * as valid or not valid. So now we return a `colours' array if
+     * we were asked for one, and also we return an overall
+     * true/false value depending on whether _every_ square in the
+     * grid is part of a valid component.
+     */
+    ret = TRUE;
+    for (i = 0; i < w*h; i++) {
+        int ci = dsf_canonify(dsf, i);
+        int thisok = sqdata[ci].valid;
+        if (colours)
+            colours[i] = thisok ? sqdata[ci].colour : 0;
+        ret = ret && thisok;
+    }
+
+    sfree(sqdata);
+    if (free_dsf)
+       sfree(dsf);
+
+    return ret;
+}
+
+static game_state *execute_move(const game_state *state, const char *move)
+{
+    int x, y, ax, ay, n, dx, dy;
+    game_state *ret = dup_game(state);
+    space *sp, *dot;
+    int currently_solving = FALSE;
+
+    debug(("%s\n", move));
+
+    while (*move) {
+        char c = *move;
+        if (c == 'E' || c == 'U' || c == 'M'
+#ifdef EDITOR
+            || c == 'D' || c == 'd'
+#endif
+            ) {
+            move++;
+            if (sscanf(move, "%d,%d%n", &x, &y, &n) != 2 ||
+                !INUI(ret, x, y))
+                goto badmove;
+
+            sp = &SPACE(ret, x, y);
+#ifdef EDITOR
+            if (c == 'D' || c == 'd') {
+                unsigned int currf, newf, maskf;
+
+                if (!dot_is_possible(ret, sp, 1)) goto badmove;
+
+                newf = F_DOT | (c == 'd' ? F_DOT_BLACK : 0);
+                currf = GRID(ret, grid, x, y).flags;
+                maskf = F_DOT | F_DOT_BLACK;
+                /* if we clicked 'white dot':
+                 *   white --> empty, empty --> white, black --> white.
+                 * if we clicked 'black dot':
+                 *   black --> empty, empty --> black, white --> black.
+                 */
+                if (currf & maskf) {
+                    sp->flags &= ~maskf;
+                    if ((currf & maskf) != newf)
+                        sp->flags |= newf;
+                } else
+                    sp->flags |= newf;
+                sp->nassoc = 0; /* edit-mode disallows associations. */
+                game_update_dots(ret);
+            } else
+#endif
+                   if (c == 'E') {
+                if (sp->type != s_edge) goto badmove;
+                sp->flags ^= F_EDGE_SET;
+            } else if (c == 'U') {
+                if (sp->type != s_tile || !(sp->flags & F_TILE_ASSOC))
+                    goto badmove;
+                /* The solver doesn't assume we'll mirror things */
+                if (currently_solving)
+                    remove_assoc(ret, sp);
+                else
+                    remove_assoc_with_opposite(ret, sp);
+            } else if (c == 'M') {
+                if (!(sp->flags & F_DOT)) goto badmove;
+                sp->flags ^= F_DOT_HOLD;
+            }
+            move += n;
+        } else if (c == 'A' || c == 'a') {
+            move++;
+            if (sscanf(move, "%d,%d,%d,%d%n", &x, &y, &ax, &ay, &n) != 4 ||
+                x < 1 || y < 1 || x >= (ret->sx-1) || y >= (ret->sy-1) ||
+                ax < 1 || ay < 1 || ax >= (ret->sx-1) || ay >= (ret->sy-1))
+                goto badmove;
+
+            dot = &GRID(ret, grid, ax, ay);
+            if (!(dot->flags & F_DOT))goto badmove;
+            if (dot->flags & F_DOT_HOLD) goto badmove;
+
+            for (dx = -1; dx <= 1; dx++) {
+                for (dy = -1; dy <= 1; dy++) {
+                    sp = &GRID(ret, grid, x+dx, y+dy);
+                    if (sp->type != s_tile) continue;
+                    if (sp->flags & F_TILE_ASSOC) {
+                        space *dot = &SPACE(ret, sp->dotx, sp->doty);
+                        if (dot->flags & F_DOT_HOLD) continue;
+                    }
+                    /* The solver doesn't assume we'll mirror things */
+                    if (currently_solving)
+                        add_assoc(ret, sp, dot);
+                    else
+                        add_assoc_with_opposite(ret, sp, dot);
+                }
+            }
+            move += n;
+#ifdef EDITOR
+        } else if (c == 'C') {
+            move++;
+            clear_game(ret, 1);
+#endif
+        } else if (c == 'S') {
+            move++;
+           ret->used_solve = 1;
+            currently_solving = TRUE;
+        } else
+            goto badmove;
+
+        if (*move == ';')
+            move++;
+        else if (*move)
+            goto badmove;
+    }
+    if (check_complete(ret, NULL, NULL))
+        ret->completed = 1;
+    return ret;
+
+badmove:
+    free_game(ret);
+    return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+/* Lines will be much smaller size than squares; say, 1/8 the size?
+ *
+ * Need a 'top-left corner of location XxY' to take this into account;
+ * alternaticaly, that could give the middle of that location, and the
+ * drawing code would just know the expected dimensions.
+ *
+ * We also need something to take a click and work out what it was
+ * we were interested in. Clicking on vertices is required because
+ * we may want to drag from them, for example.
+ */
+
+static void game_compute_size(const game_params *params, int sz,
+                              int *x, int *y)
+{
+    struct { int tilesize, w, h; } ads, *ds = &ads;
+
+    ds->tilesize = sz;
+    ds->w = params->w;
+    ds->h = params->h;
+
+    *x = DRAW_WIDTH;
+    *y = DRAW_HEIGHT;
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int sz)
+{
+    ds->tilesize = sz;
+
+    assert(TILE_SIZE > 0);
+
+    assert(!ds->bl);
+    ds->bl = blitter_new(dr, TILE_SIZE, TILE_SIZE);
+
+    assert(!ds->blmirror);
+    ds->blmirror = blitter_new(dr, TILE_SIZE, TILE_SIZE);
+
+    assert(!ds->cur_bl);
+    ds->cur_bl = blitter_new(dr, TILE_SIZE, TILE_SIZE);
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+    int i;
+
+    /*
+     * We call game_mkhighlight to ensure the background colour
+     * isn't completely white. We don't actually use the high- and
+     * lowlight colours it generates.
+     */
+    game_mkhighlight(fe, ret, COL_BACKGROUND, COL_WHITEBG, COL_BLACKBG);
+
+    for (i = 0; i < 3; i++) {
+        /*
+         * Currently, white dots and white-background squares are
+         * both pure white.
+         */
+        ret[COL_WHITEDOT * 3 + i] = 1.0F;
+        ret[COL_WHITEBG * 3 + i] = 1.0F;
+
+        /*
+         * But black-background squares are a dark grey, whereas
+         * black dots are really black.
+         */
+        ret[COL_BLACKDOT * 3 + i] = 0.0F;
+        ret[COL_BLACKBG * 3 + i] = ret[COL_BACKGROUND * 3 + i] * 0.3F;
+
+        /*
+         * In unfilled squares, we draw a faint gridwork.
+         */
+        ret[COL_GRID * 3 + i] = ret[COL_BACKGROUND * 3 + i] * 0.8F;
+
+        /*
+         * Edges and arrows are filled in in pure black.
+         */
+        ret[COL_EDGE * 3 + i] = 0.0F;
+        ret[COL_ARROW * 3 + i] = 0.0F;
+    }
+
+#ifdef EDITOR
+    /* tinge the edit background to bluey */
+    ret[COL_BACKGROUND * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 0.8F;
+    ret[COL_BACKGROUND * 3 + 1] = ret[COL_BACKGROUND * 3 + 0] * 0.8F;
+    ret[COL_BACKGROUND * 3 + 2] = min(ret[COL_BACKGROUND * 3 + 0] * 1.4F, 1.0F);
+#endif
+
+    ret[COL_CURSOR * 3 + 0] = min(ret[COL_BACKGROUND * 3 + 0] * 1.4F, 1.0F);
+    ret[COL_CURSOR * 3 + 1] = ret[COL_BACKGROUND * 3 + 0] * 0.8F;
+    ret[COL_CURSOR * 3 + 2] = ret[COL_BACKGROUND * 3 + 0] * 0.8F;
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    struct game_drawstate *ds = snew(struct game_drawstate);
+    int i;
+
+    ds->started = 0;
+    ds->w = state->w;
+    ds->h = state->h;
+
+    ds->grid = snewn(ds->w*ds->h, unsigned long);
+    for (i = 0; i < ds->w*ds->h; i++)
+        ds->grid[i] = 0xFFFFFFFFUL;
+    ds->dx = snewn(ds->w*ds->h, int);
+    ds->dy = snewn(ds->w*ds->h, int);
+
+    ds->bl = NULL;
+    ds->blmirror = NULL;
+    ds->dragging = FALSE;
+    ds->dragx = ds->dragy = 0;
+
+    ds->colour_scratch = snewn(ds->w * ds->h, int);
+
+    ds->cur_bl = NULL;
+    ds->cx = ds->cy = 0;
+    ds->cur_visible = 0;
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    if (ds->cur_bl) blitter_free(dr, ds->cur_bl);
+    sfree(ds->colour_scratch);
+    if (ds->blmirror) blitter_free(dr, ds->blmirror);
+    if (ds->bl) blitter_free(dr, ds->bl);
+    sfree(ds->dx);
+    sfree(ds->dy);
+    sfree(ds->grid);
+    sfree(ds);
+}
+
+#define DRAW_EDGE_L    0x0001
+#define DRAW_EDGE_R    0x0002
+#define DRAW_EDGE_U    0x0004
+#define DRAW_EDGE_D    0x0008
+#define DRAW_CORNER_UL 0x0010
+#define DRAW_CORNER_UR 0x0020
+#define DRAW_CORNER_DL 0x0040
+#define DRAW_CORNER_DR 0x0080
+#define DRAW_WHITE     0x0100
+#define DRAW_BLACK     0x0200
+#define DRAW_ARROW     0x0400
+#define DRAW_CURSOR    0x0800
+#define DOT_SHIFT_C    12
+#define DOT_SHIFT_M    2
+#define DOT_WHITE      1UL
+#define DOT_BLACK      2UL
+
+/*
+ * Draw an arrow centred on (cx,cy), pointing in the direction
+ * (ddx,ddy). (I.e. pointing at the point (cx+ddx, cy+ddy).
+ */
+static void draw_arrow(drawing *dr, game_drawstate *ds,
+                       int cx, int cy, int ddx, int ddy, int col)
+{
+    float vlen = (float)sqrt(ddx*ddx+ddy*ddy);
+    float xdx = ddx/vlen, xdy = ddy/vlen;
+    float ydx = -xdy, ydy = xdx;
+    int e1x = cx + (int)(xdx*TILE_SIZE/3), e1y = cy + (int)(xdy*TILE_SIZE/3);
+    int e2x = cx - (int)(xdx*TILE_SIZE/3), e2y = cy - (int)(xdy*TILE_SIZE/3);
+    int adx = (int)((ydx-xdx)*TILE_SIZE/8), ady = (int)((ydy-xdy)*TILE_SIZE/8);
+    int adx2 = (int)((-ydx-xdx)*TILE_SIZE/8), ady2 = (int)((-ydy-xdy)*TILE_SIZE/8);
+
+    draw_line(dr, e1x, e1y, e2x, e2y, col);
+    draw_line(dr, e1x, e1y, e1x+adx, e1y+ady, col);
+    draw_line(dr, e1x, e1y, e1x+adx2, e1y+ady2, col);
+}
+
+static void draw_square(drawing *dr, game_drawstate *ds, int x, int y,
+                        unsigned long flags, int ddx, int ddy)
+{
+    int lx = COORD(x), ly = COORD(y);
+    int dx, dy;
+    int gridcol;
+
+    clip(dr, lx, ly, TILE_SIZE, TILE_SIZE);
+
+    /*
+     * Draw the tile background.
+     */
+    draw_rect(dr, lx, ly, TILE_SIZE, TILE_SIZE,
+              (flags & DRAW_WHITE ? COL_WHITEBG :
+               flags & DRAW_BLACK ? COL_BLACKBG : COL_BACKGROUND));
+
+    /*
+     * Draw the grid.
+     */
+    gridcol = (flags & DRAW_BLACK ? COL_BLACKDOT : COL_GRID);
+    draw_rect(dr, lx, ly, 1, TILE_SIZE, gridcol);
+    draw_rect(dr, lx, ly, TILE_SIZE, 1, gridcol);
+
+    /*
+     * Draw the arrow, if present, or the cursor, if here.
+     */
+    if (flags & DRAW_ARROW)
+        draw_arrow(dr, ds, lx + TILE_SIZE/2, ly + TILE_SIZE/2, ddx, ddy,
+                   (flags & DRAW_CURSOR) ? COL_CURSOR : COL_ARROW);
+    else if (flags & DRAW_CURSOR)
+        draw_rect_outline(dr,
+                          lx + TILE_SIZE/2 - CURSOR_SIZE,
+                          ly + TILE_SIZE/2 - CURSOR_SIZE,
+                          2*CURSOR_SIZE+1, 2*CURSOR_SIZE+1,
+                          COL_CURSOR);
+
+    /*
+     * Draw the edges.
+     */
+    if (flags & DRAW_EDGE_L)
+        draw_rect(dr, lx, ly, EDGE_THICKNESS, TILE_SIZE, COL_EDGE);
+    if (flags & DRAW_EDGE_R)
+        draw_rect(dr, lx + TILE_SIZE - EDGE_THICKNESS + 1, ly,
+                  EDGE_THICKNESS - 1, TILE_SIZE, COL_EDGE);
+    if (flags & DRAW_EDGE_U)
+        draw_rect(dr, lx, ly, TILE_SIZE, EDGE_THICKNESS, COL_EDGE);
+    if (flags & DRAW_EDGE_D)
+        draw_rect(dr, lx, ly + TILE_SIZE - EDGE_THICKNESS + 1,
+                  TILE_SIZE, EDGE_THICKNESS - 1, COL_EDGE);
+    if (flags & DRAW_CORNER_UL)
+        draw_rect(dr, lx, ly, EDGE_THICKNESS, EDGE_THICKNESS, COL_EDGE);
+    if (flags & DRAW_CORNER_UR)
+        draw_rect(dr, lx + TILE_SIZE - EDGE_THICKNESS + 1, ly,
+                  EDGE_THICKNESS - 1, EDGE_THICKNESS, COL_EDGE);
+    if (flags & DRAW_CORNER_DL)
+        draw_rect(dr, lx, ly + TILE_SIZE - EDGE_THICKNESS + 1,
+                  EDGE_THICKNESS, EDGE_THICKNESS - 1, COL_EDGE);
+    if (flags & DRAW_CORNER_DR)
+        draw_rect(dr, lx + TILE_SIZE - EDGE_THICKNESS + 1,
+                  ly + TILE_SIZE - EDGE_THICKNESS + 1,
+                  EDGE_THICKNESS - 1, EDGE_THICKNESS - 1, COL_EDGE);
+
+    /*
+     * Draw the dots.
+     */
+    for (dy = 0; dy < 3; dy++)
+        for (dx = 0; dx < 3; dx++) {
+            int dotval = (flags >> (DOT_SHIFT_C + DOT_SHIFT_M*(dy*3+dx)));
+            dotval &= (1 << DOT_SHIFT_M)-1;
+
+            if (dotval)
+                draw_circle(dr, lx+dx*TILE_SIZE/2, ly+dy*TILE_SIZE/2,
+                            DOT_SIZE,
+                            (dotval == 1 ? COL_WHITEDOT : COL_BLACKDOT),
+                            COL_BLACKDOT);
+        }
+
+    unclip(dr);
+    draw_update(dr, lx, ly, TILE_SIZE, TILE_SIZE);
+}
+
+static void calculate_opposite_point(const game_ui *ui,
+                                     const game_drawstate *ds, const int x,
+                                     const int y, int *oppositex,
+                                     int *oppositey)
+{
+    /* oppositex - dotx = dotx - x <=> oppositex = 2 * dotx - x */
+    *oppositex = 2 * SCOORD(ui->dotx) - x;
+    *oppositey = 2 * SCOORD(ui->doty) - y;
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int w = ds->w, h = ds->h;
+    int x, y, flashing = FALSE;
+    int oppx, oppy;
+
+    if (flashtime > 0) {
+        int frame = (int)(flashtime / FLASH_TIME);
+        flashing = (frame % 2 == 0);
+    }
+
+    if (ds->dragging) {
+        assert(ds->bl);
+        assert(ds->blmirror);
+        calculate_opposite_point(ui, ds, ds->dragx + TILE_SIZE/2,
+                                 ds->dragy + TILE_SIZE/2, &oppx, &oppy);
+        oppx -= TILE_SIZE/2;
+        oppy -= TILE_SIZE/2;
+        blitter_load(dr, ds->bl, ds->dragx, ds->dragy);
+        draw_update(dr, ds->dragx, ds->dragy, TILE_SIZE, TILE_SIZE);
+        blitter_load(dr, ds->blmirror, oppx, oppy);
+        draw_update(dr, oppx, oppy, TILE_SIZE, TILE_SIZE);
+        ds->dragging = FALSE;
+    }
+    if (ds->cur_visible) {
+        assert(ds->cur_bl);
+        blitter_load(dr, ds->cur_bl, ds->cx, ds->cy);
+        draw_update(dr, ds->cx, ds->cy, CURSOR_SIZE*2+1, CURSOR_SIZE*2+1);
+        ds->cur_visible = FALSE;
+    }
+
+    if (!ds->started) {
+        draw_rect(dr, 0, 0, DRAW_WIDTH, DRAW_HEIGHT, COL_BACKGROUND);
+        draw_rect(dr, BORDER - EDGE_THICKNESS + 1, BORDER - EDGE_THICKNESS + 1,
+                  w*TILE_SIZE + EDGE_THICKNESS*2 - 1,
+                  h*TILE_SIZE + EDGE_THICKNESS*2 - 1, COL_EDGE);
+        draw_update(dr, 0, 0, DRAW_WIDTH, DRAW_HEIGHT);
+        ds->started = TRUE;
+    }
+
+    check_complete(state, NULL, ds->colour_scratch);
+
+    for (y = 0; y < h; y++)
+        for (x = 0; x < w; x++) {
+            unsigned long flags = 0;
+            int ddx = 0, ddy = 0;
+            space *sp, *opp;
+            int dx, dy;
+
+            /*
+             * Set up the flags for this square. Firstly, see if we
+             * have edges.
+             */
+            if (SPACE(state, x*2, y*2+1).flags & F_EDGE_SET)
+                flags |= DRAW_EDGE_L;
+            if (SPACE(state, x*2+2, y*2+1).flags & F_EDGE_SET)
+                flags |= DRAW_EDGE_R;
+            if (SPACE(state, x*2+1, y*2).flags & F_EDGE_SET)
+                flags |= DRAW_EDGE_U;
+            if (SPACE(state, x*2+1, y*2+2).flags & F_EDGE_SET)
+                flags |= DRAW_EDGE_D;
+
+            /*
+             * Also, mark corners of neighbouring edges.
+             */
+            if ((x > 0 && SPACE(state, x*2-1, y*2).flags & F_EDGE_SET) ||
+                (y > 0 && SPACE(state, x*2, y*2-1).flags & F_EDGE_SET))
+                flags |= DRAW_CORNER_UL;
+            if ((x+1 < w && SPACE(state, x*2+3, y*2).flags & F_EDGE_SET) ||
+                (y > 0 && SPACE(state, x*2+2, y*2-1).flags & F_EDGE_SET))
+                flags |= DRAW_CORNER_UR;
+            if ((x > 0 && SPACE(state, x*2-1, y*2+2).flags & F_EDGE_SET) ||
+                (y+1 < h && SPACE(state, x*2, y*2+3).flags & F_EDGE_SET))
+                flags |= DRAW_CORNER_DL;
+            if ((x+1 < w && SPACE(state, x*2+3, y*2+2).flags & F_EDGE_SET) ||
+                (y+1 < h && SPACE(state, x*2+2, y*2+3).flags & F_EDGE_SET))
+                flags |= DRAW_CORNER_DR;
+
+            /*
+             * If this square is part of a valid region, paint it
+             * that region's colour. Exception: if we're flashing,
+             * everything goes briefly back to background colour.
+             */
+            sp = &SPACE(state, x*2+1, y*2+1);
+            if (sp->flags & F_TILE_ASSOC) {
+                opp = tile_opposite(state, sp);
+            } else {
+                opp = NULL;
+            }
+            if (ds->colour_scratch[y*w+x] && !flashing) {
+                flags |= (ds->colour_scratch[y*w+x] == 2 ?
+                          DRAW_BLACK : DRAW_WHITE);
+            }
+
+            /*
+             * If this square is associated with a dot but it isn't
+             * part of a valid region, draw an arrow in it pointing
+             * in the direction of that dot.
+            * 
+            * Exception: if this is the source point of an active
+            * drag, we don't draw the arrow.
+             */
+            if ((sp->flags & F_TILE_ASSOC) && !ds->colour_scratch[y*w+x]) {
+               if (ui->dragging && ui->srcx == x*2+1 && ui->srcy == y*2+1) {
+                    /* tile is the source, don't do it */
+                } else if (ui->dragging && opp && ui->srcx == opp->x && ui->srcy == opp->y) {
+                    /* opposite tile is the source, don't do it */
+               } else if (sp->doty != y*2+1 || sp->dotx != x*2+1) {
+                    flags |= DRAW_ARROW;
+                    ddy = sp->doty - (y*2+1);
+                    ddx = sp->dotx - (x*2+1);
+                }
+            }
+
+            /*
+             * Now go through the nine possible places we could
+             * have dots.
+             */
+            for (dy = 0; dy < 3; dy++)
+                for (dx = 0; dx < 3; dx++) {
+                    sp = &SPACE(state, x*2+dx, y*2+dy);
+                    if (sp->flags & F_DOT) {
+                        unsigned long dotval = (sp->flags & F_DOT_BLACK ?
+                                                DOT_BLACK : DOT_WHITE);
+                        flags |= dotval << (DOT_SHIFT_C +
+                                            DOT_SHIFT_M*(dy*3+dx));
+                    }
+                }
+
+            /*
+             * Now work out if we have to draw a cursor for this square;
+             * cursors-on-lines are taken care of below.
+             */
+            if (ui->cur_visible &&
+                ui->cur_x == x*2+1 && ui->cur_y == y*2+1 &&
+                !(SPACE(state, x*2+1, y*2+1).flags & F_DOT))
+                flags |= DRAW_CURSOR;
+
+            /*
+             * Now we have everything we're going to need. Draw the
+             * square.
+             */
+            if (ds->grid[y*w+x] != flags ||
+                ds->dx[y*w+x] != ddx ||
+                ds->dy[y*w+x] != ddy) {
+                draw_square(dr, ds, x, y, flags, ddx, ddy);
+                ds->grid[y*w+x] = flags;
+                ds->dx[y*w+x] = ddx;
+                ds->dy[y*w+x] = ddy;
+            }
+        }
+
+    /*
+     * Draw a cursor. This secondary blitter is much less invasive than trying
+     * to fix up all of the rest of the code with sufficient flags to be able to
+     * display this sensibly.
+     */
+    if (ui->cur_visible) {
+        space *sp = &SPACE(state, ui->cur_x, ui->cur_y);
+        ds->cur_visible = TRUE;
+        ds->cx = SCOORD(ui->cur_x) - CURSOR_SIZE;
+        ds->cy = SCOORD(ui->cur_y) - CURSOR_SIZE;
+        blitter_save(dr, ds->cur_bl, ds->cx, ds->cy);
+        if (sp->flags & F_DOT) {
+            /* draw a red dot (over the top of whatever would be there already) */
+            draw_circle(dr, SCOORD(ui->cur_x), SCOORD(ui->cur_y), DOT_SIZE,
+                        COL_CURSOR, COL_BLACKDOT);
+        } else if (sp->type != s_tile) {
+            /* draw an edge/vertex square; tile cursors are dealt with above. */
+            int dx = (ui->cur_x % 2) ? CURSOR_SIZE : CURSOR_SIZE/3;
+            int dy = (ui->cur_y % 2) ? CURSOR_SIZE : CURSOR_SIZE/3;
+            int x1 = SCOORD(ui->cur_x)-dx, y1 = SCOORD(ui->cur_y)-dy;
+            int xs = dx*2+1, ys = dy*2+1;
+
+            draw_rect(dr, x1, y1, xs, ys, COL_CURSOR);
+        }
+        draw_update(dr, ds->cx, ds->cy, CURSOR_SIZE*2+1, CURSOR_SIZE*2+1);
+    }
+
+    if (ui->dragging) {
+        ds->dragging = TRUE;
+        ds->dragx = ui->dx - TILE_SIZE/2;
+        ds->dragy = ui->dy - TILE_SIZE/2;
+        calculate_opposite_point(ui, ds, ui->dx, ui->dy, &oppx, &oppy);
+        blitter_save(dr, ds->bl, ds->dragx, ds->dragy);
+        blitter_save(dr, ds->blmirror, oppx - TILE_SIZE/2, oppy - TILE_SIZE/2);
+        draw_arrow(dr, ds, ui->dx, ui->dy, SCOORD(ui->dotx) - ui->dx,
+                   SCOORD(ui->doty) - ui->dy, COL_ARROW);
+        draw_arrow(dr, ds, oppx, oppy, SCOORD(ui->dotx) - oppx,
+                   SCOORD(ui->doty) - oppy, COL_ARROW);
+    }
+#ifdef EDITOR
+    {
+        char buf[256];
+        if (state->cdiff != -1)
+            sprintf(buf, "Puzzle is %s.", galaxies_diffnames[state->cdiff]);
+        else
+            buf[0] = '\0';
+        status_bar(dr, buf);
+    }
+#endif
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return 0.0F;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    if ((!oldstate->completed && newstate->completed) &&
+        !(newstate->used_solve))
+        return 3 * FLASH_TIME;
+    else
+        return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    return state->completed ? +1 : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+#ifndef EDITOR
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+   int pw, ph;
+
+   /*
+    * 8mm squares by default. (There isn't all that much detail
+    * that needs to go in each square.)
+    */
+   game_compute_size(params, 800, &pw, &ph);
+   *x = pw / 100.0F;
+   *y = ph / 100.0F;
+}
+
+static void game_print(drawing *dr, const game_state *state, int sz)
+{
+    int w = state->w, h = state->h;
+    int white, black, blackish;
+    int x, y, i, j;
+    int *colours, *dsf;
+    int *coords = NULL;
+    int ncoords = 0, coordsize = 0;
+
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    game_drawstate ads, *ds = &ads;
+    ds->tilesize = sz;
+
+    white = print_mono_colour(dr, 1);
+    black = print_mono_colour(dr, 0);
+    blackish = print_hatched_colour(dr, HATCH_X);
+
+    /*
+     * Get the completion information.
+     */
+    dsf = snewn(w * h, int);
+    colours = snewn(w * h, int);
+    check_complete(state, dsf, colours);
+
+    /*
+     * Draw the grid.
+     */
+    print_line_width(dr, TILE_SIZE / 64);
+    for (x = 1; x < w; x++)
+       draw_line(dr, COORD(x), COORD(0), COORD(x), COORD(h), black);
+    for (y = 1; y < h; y++)
+       draw_line(dr, COORD(0), COORD(y), COORD(w), COORD(y), black);
+
+    /*
+     * Shade the completed regions. Just in case any particular
+     * printing platform deals badly with adjacent
+     * similarly-hatched regions, we'll fill each one as a single
+     * polygon.
+     */
+    for (i = 0; i < w*h; i++) {
+       j = dsf_canonify(dsf, i);
+       if (colours[j] != 0) {
+           int dx, dy, t;
+
+           /*
+            * This is the first square we've run into belonging to
+            * this polyomino, which means an edge of the polyomino
+            * is certain to be to our left. (After we finish
+            * tracing round it, we'll set the colours[] entry to
+            * zero to prevent accidentally doing it again.)
+            */
+
+           x = i % w;
+           y = i / w;
+           dx = -1;
+           dy = 0;
+           ncoords = 0;
+           while (1) {
+               /*
+                * We are currently sitting on square (x,y), which
+                * we know to be in our polyomino, and we also know
+                * that (x+dx,y+dy) is not. The way I visualise
+                * this is that we're standing to the right of a
+                * boundary line, stretching our left arm out to
+                * point to the exterior square on the far side.
+                */
+
+               /*
+                * First, check if we've gone round the entire
+                * polyomino.
+                */
+               if (ncoords > 0 &&
+                   (x == i%w && y == i/w && dx == -1 && dy == 0))
+                   break;
+
+               /*
+                * Add to our coordinate list the coordinate
+                * backwards and to the left of where we are.
+                */
+               if (ncoords + 2 > coordsize) {
+                   coordsize = (ncoords * 3 / 2) + 64;
+                   coords = sresize(coords, coordsize, int);
+               }
+               coords[ncoords++] = COORD((2*x+1 + dx + dy) / 2);
+               coords[ncoords++] = COORD((2*y+1 + dy - dx) / 2);
+
+               /*
+                * Follow the edge round. If the square directly in
+                * front of us is not part of the polyomino, we
+                * turn right; if it is and so is the square in
+                * front of (x+dx,y+dy), we turn left; otherwise we
+                * go straight on.
+                */
+               if (x-dy < 0 || x-dy >= w || y+dx < 0 || y+dx >= h ||
+                   dsf_canonify(dsf, (y+dx)*w+(x-dy)) != j) {
+                   /* Turn right. */
+                   t = dx;
+                   dx = -dy;
+                   dy = t;
+               } else if (x+dx-dy >= 0 && x+dx-dy < w &&
+                          y+dy+dx >= 0 && y+dy+dx < h &&
+                          dsf_canonify(dsf, (y+dy+dx)*w+(x+dx-dy)) == j) {
+                   /* Turn left. */
+                   x += dx;
+                   y += dy;
+                   t = dx;
+                   dx = dy;
+                   dy = -t;
+                   x -= dx;
+                   y -= dy;
+               } else {
+                   /* Straight on. */
+                   x -= dy;
+                   y += dx;
+               }
+           }
+
+           /*
+            * Now we have our polygon complete, so fill it.
+            */
+           draw_polygon(dr, coords, ncoords/2,
+                        colours[j] == 2 ? blackish : -1, black);
+
+           /*
+            * And mark this polyomino as done.
+            */
+           colours[j] = 0;
+       }
+    }
+
+    /*
+     * Draw the edges.
+     */
+    for (y = 0; y <= h; y++)
+       for (x = 0; x <= w; x++) {
+           if (x < w && SPACE(state, x*2+1, y*2).flags & F_EDGE_SET)
+               draw_rect(dr, COORD(x)-EDGE_THICKNESS, COORD(y)-EDGE_THICKNESS,
+                         EDGE_THICKNESS * 2 + TILE_SIZE, EDGE_THICKNESS * 2,
+                         black);
+           if (y < h && SPACE(state, x*2, y*2+1).flags & F_EDGE_SET)
+               draw_rect(dr, COORD(x)-EDGE_THICKNESS, COORD(y)-EDGE_THICKNESS,
+                         EDGE_THICKNESS * 2, EDGE_THICKNESS * 2 + TILE_SIZE,
+                         black);
+       }
+
+    /*
+     * Draw the dots.
+     */
+    for (y = 0; y <= 2*h; y++)
+       for (x = 0; x <= 2*w; x++)
+           if (SPACE(state, x, y).flags & F_DOT) {
+                draw_circle(dr, (int)COORD(x/2.0), (int)COORD(y/2.0), DOT_SIZE,
+                            (SPACE(state, x, y).flags & F_DOT_BLACK ?
+                            black : white), black);
+           }
+
+    sfree(dsf);
+    sfree(colours);
+    sfree(coords);
+}
+#endif
+
+#ifdef COMBINED
+#define thegame galaxies
+#endif
+
+const struct game thegame = {
+    "Galaxies", "games.galaxies", "galaxies",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+#ifdef EDITOR
+    FALSE, NULL,
+#else
+    TRUE, solve_game,
+#endif
+    TRUE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+#ifdef EDITOR
+    FALSE, FALSE, NULL, NULL,
+    TRUE,                              /* wants_statusbar */
+#else
+    TRUE, FALSE, game_print_size, game_print,
+    FALSE,                            /* wants_statusbar */
+#endif
+    FALSE, game_timing_state,
+    REQUIRE_RBUTTON,                  /* flags */
+};
+
+#ifdef STANDALONE_SOLVER
+
+const char *quis;
+
+#include <time.h>
+
+static void usage_exit(const char *msg)
+{
+    if (msg)
+        fprintf(stderr, "%s: %s\n", quis, msg);
+    fprintf(stderr, "Usage: %s [--seed SEED] --soak <params> | [game_id [game_id ...]]\n", quis);
+    exit(1);
+}
+
+static void dump_state(game_state *state)
+{
+    char *temp = game_text_format(state);
+    printf("%s\n", temp);
+    sfree(temp);
+}
+
+static int gen(game_params *p, random_state *rs, int debug)
+{
+    char *desc;
+    int diff;
+    game_state *state;
+
+#ifndef DEBUGGING
+    solver_show_working = debug;
+#endif
+    printf("Generating a %dx%d %s puzzle.\n",
+           p->w, p->h, galaxies_diffnames[p->diff]);
+
+    desc = new_game_desc(p, rs, NULL, 0);
+    state = new_game(NULL, p, desc);
+    dump_state(state);
+
+    diff = solver_state(state, DIFF_UNREASONABLE);
+    printf("Generated %s game %dx%d:%s\n",
+           galaxies_diffnames[diff], p->w, p->h, desc);
+    dump_state(state);
+
+    free_game(state);
+    sfree(desc);
+
+    return diff;
+}
+
+static void soak(game_params *p, random_state *rs)
+{
+    time_t tt_start, tt_now, tt_last;
+    char *desc;
+    game_state *st;
+    int diff, n = 0, i, diffs[DIFF_MAX], ndots = 0, nspaces = 0;
+
+#ifndef DEBUGGING
+    solver_show_working = 0;
+#endif
+    tt_start = tt_now = time(NULL);
+    for (i = 0; i < DIFF_MAX; i++) diffs[i] = 0;
+    maxtries = 1;
+
+    printf("Soak-generating a %dx%d grid, max. diff %s.\n",
+           p->w, p->h, galaxies_diffnames[p->diff]);
+    printf("   [");
+    for (i = 0; i < DIFF_MAX; i++)
+        printf("%s%s", (i == 0) ? "" : ", ", galaxies_diffnames[i]);
+    printf("]\n");
+
+    while (1) {
+        desc = new_game_desc(p, rs, NULL, 0);
+        st = new_game(NULL, p, desc);
+        diff = solver_state(st, p->diff);
+        nspaces += st->w*st->h;
+        for (i = 0; i < st->sx*st->sy; i++)
+            if (st->grid[i].flags & F_DOT) ndots++;
+        free_game(st);
+        sfree(desc);
+
+        diffs[diff]++;
+        n++;
+        tt_last = time(NULL);
+        if (tt_last > tt_now) {
+            tt_now = tt_last;
+            printf("%d total, %3.1f/s, [",
+                   n, (double)n / ((double)tt_now - tt_start));
+            for (i = 0; i < DIFF_MAX; i++)
+                printf("%s%.1f%%", (i == 0) ? "" : ", ",
+                       100.0 * ((double)diffs[i] / (double)n));
+            printf("], %.1f%% dots\n",
+                   100.0 * ((double)ndots / (double)nspaces));
+        }
+    }
+}
+
+int main(int argc, char **argv)
+{
+    game_params *p;
+    char *id = NULL, *desc, *err;
+    game_state *s;
+    int diff, do_soak = 0, verbose = 0;
+    random_state *rs;
+    time_t seed = time(NULL);
+
+    quis = argv[0];
+    while (--argc > 0) {
+        char *p = *++argv;
+        if (!strcmp(p, "-v")) {
+            verbose = 1;
+        } else if (!strcmp(p, "--seed")) {
+            if (argc == 0) usage_exit("--seed needs an argument");
+            seed = (time_t)atoi(*++argv);
+            argc--;
+        } else if (!strcmp(p, "--soak")) {
+            do_soak = 1;
+        } else if (*p == '-') {
+            usage_exit("unrecognised option");
+        } else {
+            id = p;
+        }
+    }
+
+    maxtries = 50;
+
+    p = default_params();
+    rs = random_new((void*)&seed, sizeof(time_t));
+
+    if (do_soak) {
+        if (!id) usage_exit("need one argument for --soak");
+        decode_params(p, *argv);
+        soak(p, rs);
+        return 0;
+    }
+
+    if (!id) {
+        while (1) {
+            p->w = random_upto(rs, 15) + 3;
+            p->h = random_upto(rs, 15) + 3;
+            p->diff = random_upto(rs, DIFF_UNREASONABLE);
+            diff = gen(p, rs, 0);
+        }
+        return 0;
+    }
+
+    desc = strchr(id, ':');
+    if (!desc) {
+        decode_params(p, id);
+        gen(p, rs, verbose);
+    } else {
+#ifndef DEBUGGING
+        solver_show_working = 1;
+#endif
+        *desc++ = '\0';
+        decode_params(p, id);
+        err = validate_desc(p, desc);
+        if (err) {
+            fprintf(stderr, "%s: %s\n", argv[0], err);
+            exit(1);
+        }
+        s = new_game(NULL, p, desc);
+        diff = solver_state(s, DIFF_UNREASONABLE);
+        dump_state(s);
+        printf("Puzzle is %s.\n", galaxies_diffnames[diff]);
+        free_game(s);
+    }
+
+    free_params(p);
+
+    return 0;
+}
+
+#endif
+
+#ifdef STANDALONE_PICTURE_GENERATOR
+
+/*
+ * Main program for the standalone picture generator. To use it,
+ * simply provide it with an XBM-format bitmap file (note XBM, not
+ * XPM) on standard input, and it will output a game ID in return.
+ * For example:
+ *
+ *   $ ./galaxiespicture < badly-drawn-cat.xbm
+ *   11x11:eloMBLzFeEzLNMWifhaWYdDbixCymBbBMLoDdewGg
+ *
+ * If you want a puzzle with a non-standard difficulty level, pass
+ * a partial parameters string as a command-line argument (e.g.
+ * `./galaxiespicture du < foo.xbm', where `du' is the same suffix
+ * which if it appeared in a random-seed game ID would set the
+ * difficulty level to Unreasonable). However, be aware that if the
+ * generator fails to produce an adequately difficult puzzle too
+ * many times then it will give up and return an easier one (just
+ * as it does during normal GUI play). To be sure you really have
+ * the difficulty you asked for, use galaxiessolver to
+ * double-check.
+ * 
+ * (Perhaps I ought to include an option to make this standalone
+ * generator carry on looping until it really does get the right
+ * difficulty. Hmmm.)
+ */
+
+#include <time.h>
+
+int main(int argc, char **argv)
+{
+    game_params *par;
+    char *params, *desc;
+    random_state *rs;
+    time_t seed = time(NULL);
+    char buf[4096];
+    int i;
+    int x, y;
+
+    par = default_params();
+    if (argc > 1)
+       decode_params(par, argv[1]);   /* get difficulty */
+    par->w = par->h = -1;
+
+    /*
+     * Now read an XBM file from standard input. This is simple and
+     * hacky and will do very little error detection, so don't feed
+     * it bogus data.
+     */
+    picture = NULL;
+    x = y = 0;
+    while (fgets(buf, sizeof(buf), stdin)) {
+       buf[strcspn(buf, "\r\n")] = '\0';
+       if (!strncmp(buf, "#define", 7)) {
+           /*
+            * Lines starting `#define' give the width and height.
+            */
+           char *num = buf + strlen(buf);
+           char *symend;
+
+           while (num > buf && isdigit((unsigned char)num[-1]))
+               num--;
+           symend = num;
+           while (symend > buf && isspace((unsigned char)symend[-1]))
+               symend--;
+
+           if (symend-5 >= buf && !strncmp(symend-5, "width", 5))
+               par->w = atoi(num);
+           else if (symend-6 >= buf && !strncmp(symend-6, "height", 6))
+               par->h = atoi(num);
+       } else {
+           /*
+            * Otherwise, break the string up into words and take
+            * any word of the form `0x' plus hex digits to be a
+            * byte.
+            */
+           char *p, *wordstart;
+
+           if (!picture) {
+               if (par->w < 0 || par->h < 0) {
+                   printf("failed to read width and height\n");
+                   return 1;
+               }
+               picture = snewn(par->w * par->h, int);
+               for (i = 0; i < par->w * par->h; i++)
+                   picture[i] = -1;
+           }
+
+           p = buf;
+           while (*p) {
+               while (*p && (*p == ',' || isspace((unsigned char)*p)))
+                   p++;
+               wordstart = p;
+               while (*p && !(*p == ',' || *p == '}' ||
+                              isspace((unsigned char)*p)))
+                   p++;
+               if (*p)
+                   *p++ = '\0';
+
+               if (wordstart[0] == '0' &&
+                   (wordstart[1] == 'x' || wordstart[1] == 'X') &&
+                   !wordstart[2 + strspn(wordstart+2,
+                                         "0123456789abcdefABCDEF")]) {
+                   unsigned long byte = strtoul(wordstart+2, NULL, 16);
+                   for (i = 0; i < 8; i++) {
+                       int bit = (byte >> i) & 1;
+                       if (y < par->h && x < par->w)
+                           picture[y * par->w + x] = bit;
+                       x++;
+                   }
+
+                   if (x >= par->w) {
+                       x = 0;
+                       y++;
+                   }
+               }
+           }
+       }
+    }
+
+    for (i = 0; i < par->w * par->h; i++)
+       if (picture[i] < 0) {
+           fprintf(stderr, "failed to read enough bitmap data\n");
+           return 1;
+       }
+
+    rs = random_new((void*)&seed, sizeof(time_t));
+
+    desc = new_game_desc(par, rs, NULL, FALSE);
+    params = encode_params(par, FALSE);
+    printf("%s:%s\n", params, desc);
+
+    sfree(desc);
+    sfree(params);
+    free_params(par);
+    random_free(rs);
+
+    return 0;
+}
+
+#endif
+
+/* vim: set shiftwidth=4 tabstop=8: */
diff --git a/grid.c b/grid.c
new file mode 100644 (file)
index 0000000..6a90c99
--- /dev/null
+++ b/grid.c
@@ -0,0 +1,2896 @@
+/*
+ * (c) Lambros Lambrou 2008
+ *
+ * Code for working with general grids, which can be any planar graph
+ * with faces, edges and vertices (dots).  Includes generators for a few
+ * types of grid, including square, hexagonal, triangular and others.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+#include <float.h>
+
+#include "puzzles.h"
+#include "tree234.h"
+#include "grid.h"
+#include "penrose.h"
+
+/* Debugging options */
+
+/*
+#define DEBUG_GRID
+*/
+
+/* ----------------------------------------------------------------------
+ * Deallocate or dereference a grid
+ */
+void grid_free(grid *g)
+{
+    assert(g->refcount);
+
+    g->refcount--;
+    if (g->refcount == 0) {
+        int i;
+        for (i = 0; i < g->num_faces; i++) {
+            sfree(g->faces[i].dots);
+            sfree(g->faces[i].edges);
+        }
+        for (i = 0; i < g->num_dots; i++) {
+            sfree(g->dots[i].faces);
+            sfree(g->dots[i].edges);
+        }
+        sfree(g->faces);
+        sfree(g->edges);
+        sfree(g->dots);
+        sfree(g);
+    }
+}
+
+/* Used by the other grid generators.  Create a brand new grid with nothing
+ * initialised (all lists are NULL) */
+static grid *grid_empty(void)
+{
+    grid *g = snew(grid);
+    g->faces = NULL;
+    g->edges = NULL;
+    g->dots = NULL;
+    g->num_faces = g->num_edges = g->num_dots = 0;
+    g->refcount = 1;
+    g->lowest_x = g->lowest_y = g->highest_x = g->highest_y = 0;
+    return g;
+}
+
+/* Helper function to calculate perpendicular distance from
+ * a point P to a line AB.  A and B mustn't be equal here.
+ *
+ * Well-known formula for area A of a triangle:
+ *                             /  1   1   1 \
+ * 2A = determinant of matrix  | px  ax  bx |
+ *                             \ py  ay  by /
+ *
+ * Also well-known: 2A = base * height
+ *                     = perpendicular distance * line-length.
+ *
+ * Combining gives: distance = determinant / line-length(a,b)
+ */
+static double point_line_distance(long px, long py,
+                                  long ax, long ay,
+                                  long bx, long by)
+{
+    long det = ax*by - bx*ay + bx*py - px*by + px*ay - ax*py;
+    double len;
+    det = max(det, -det);
+    len = sqrt(SQ(ax - bx) + SQ(ay - by));
+    return det / len;
+}
+
+/* Determine nearest edge to where the user clicked.
+ * (x, y) is the clicked location, converted to grid coordinates.
+ * Returns the nearest edge, or NULL if no edge is reasonably
+ * near the position.
+ *
+ * Just judging edges by perpendicular distance is not quite right -
+ * the edge might be "off to one side". So we insist that the triangle
+ * with (x,y) has acute angles at the edge's dots.
+ *
+ *     edge1
+ *  *---------*------
+ *            |
+ *            |      *(x,y)
+ *      edge2 |
+ *            |   edge2 is OK, but edge1 is not, even though
+ *            |   edge1 is perpendicularly closer to (x,y)
+ *            *
+ *
+ */
+grid_edge *grid_nearest_edge(grid *g, int x, int y)
+{
+    grid_edge *best_edge;
+    double best_distance = 0;
+    int i;
+
+    best_edge = NULL;
+
+    for (i = 0; i < g->num_edges; i++) {
+        grid_edge *e = &g->edges[i];
+        long e2; /* squared length of edge */
+        long a2, b2; /* squared lengths of other sides */
+        double dist;
+
+        /* See if edge e is eligible - the triangle must have acute angles
+         * at the edge's dots.
+         * Pythagoras formula h^2 = a^2 + b^2 detects right-angles,
+         * so detect acute angles by testing for h^2 < a^2 + b^2 */
+        e2 = SQ((long)e->dot1->x - (long)e->dot2->x) + SQ((long)e->dot1->y - (long)e->dot2->y);
+        a2 = SQ((long)e->dot1->x - (long)x) + SQ((long)e->dot1->y - (long)y);
+        b2 = SQ((long)e->dot2->x - (long)x) + SQ((long)e->dot2->y - (long)y);
+        if (a2 >= e2 + b2) continue;
+        if (b2 >= e2 + a2) continue;
+         
+        /* e is eligible so far.  Now check the edge is reasonably close
+         * to where the user clicked.  Don't want to toggle an edge if the
+         * click was way off the grid.
+         * There is room for experimentation here.  We could check the
+         * perpendicular distance is within a certain fraction of the length
+         * of the edge.  That amounts to testing a rectangular region around
+         * the edge.
+         * Alternatively, we could check that the angle at the point is obtuse.
+         * That would amount to testing a circular region with the edge as
+         * diameter. */
+        dist = point_line_distance((long)x, (long)y,
+                                   (long)e->dot1->x, (long)e->dot1->y,
+                                   (long)e->dot2->x, (long)e->dot2->y);
+        /* Is dist more than half edge length ? */
+        if (4 * SQ(dist) > e2)
+            continue;
+
+        if (best_edge == NULL || dist < best_distance) {
+            best_edge = e;
+            best_distance = dist;
+        }
+    }
+    return best_edge;
+}
+
+/* ----------------------------------------------------------------------
+ * Grid generation
+ */
+
+#ifdef SVG_GRID
+
+#define SVG_DOTS  1
+#define SVG_EDGES 2
+#define SVG_FACES 4
+
+#define FACE_COLOUR "red"
+#define EDGE_COLOUR "blue"
+#define DOT_COLOUR "black"
+
+static void grid_output_svg(FILE *fp, grid *g, int which)
+{
+    int i, j;
+
+    fprintf(fp,"\
+<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n\
+<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 20010904//EN\"\n\
+\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n\
+\n\
+<svg xmlns=\"http://www.w3.org/2000/svg\"\n\
+xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n\n");
+
+    if (which & SVG_FACES) {
+        fprintf(fp, "<g>\n");
+        for (i = 0; i < g->num_faces; i++) {
+            grid_face *f = g->faces + i;
+            fprintf(fp, "<polygon points=\"");
+            for (j = 0; j < f->order; j++) {
+                grid_dot *d = f->dots[j];
+                fprintf(fp, "%s%d,%d", (j == 0) ? "" : " ",
+                        d->x, d->y);
+            }
+            fprintf(fp, "\" style=\"fill: %s; fill-opacity: 0.2; stroke: %s\" />\n",
+                    FACE_COLOUR, FACE_COLOUR);
+        }
+        fprintf(fp, "</g>\n");
+    }
+    if (which & SVG_EDGES) {
+        fprintf(fp, "<g>\n");
+        for (i = 0; i < g->num_edges; i++) {
+            grid_edge *e = g->edges + i;
+            grid_dot *d1 = e->dot1, *d2 = e->dot2;
+
+            fprintf(fp, "<line x1=\"%d\" y1=\"%d\" x2=\"%d\" y2=\"%d\" "
+                        "style=\"stroke: %s\" />\n",
+                        d1->x, d1->y, d2->x, d2->y, EDGE_COLOUR);
+        }
+        fprintf(fp, "</g>\n");
+    }
+
+    if (which & SVG_DOTS) {
+        fprintf(fp, "<g>\n");
+        for (i = 0; i < g->num_dots; i++) {
+            grid_dot *d = g->dots + i;
+            fprintf(fp, "<ellipse cx=\"%d\" cy=\"%d\" rx=\"%d\" ry=\"%d\" fill=\"%s\" />",
+                    d->x, d->y, g->tilesize/20, g->tilesize/20, DOT_COLOUR);
+        }
+        fprintf(fp, "</g>\n");
+    }
+
+    fprintf(fp, "</svg>\n");
+}
+#endif
+
+#ifdef SVG_GRID
+#include <errno.h>
+
+static void grid_try_svg(grid *g, int which)
+{
+    char *svg = getenv("PUZZLES_SVG_GRID");
+    if (svg) {
+        FILE *svgf = fopen(svg, "w");
+        if (svgf) {
+            grid_output_svg(svgf, g, which);
+            fclose(svgf);
+        } else {
+            fprintf(stderr, "Unable to open file `%s': %s", svg, strerror(errno));
+        }
+    }
+}
+#endif
+
+/* Show the basic grid information, before doing grid_make_consistent */
+static void grid_debug_basic(grid *g)
+{
+    /* TODO: Maybe we should generate an SVG image of the dots and lines
+     * of the grid here, before grid_make_consistent.
+     * Would help with debugging grid generation. */
+#ifdef DEBUG_GRID
+    int i;
+    printf("--- Basic Grid Data ---\n");
+    for (i = 0; i < g->num_faces; i++) {
+        grid_face *f = g->faces + i;
+        printf("Face %d: dots[", i);
+        int j;
+        for (j = 0; j < f->order; j++) {
+            grid_dot *d = f->dots[j];
+            printf("%s%d", j ? "," : "", (int)(d - g->dots)); 
+        }
+        printf("]\n");
+    }
+#endif
+#ifdef SVG_GRID
+    grid_try_svg(g, SVG_FACES);
+#endif
+}
+
+/* Show the derived grid information, computed by grid_make_consistent */
+static void grid_debug_derived(grid *g)
+{
+#ifdef DEBUG_GRID
+    /* edges */
+    int i;
+    printf("--- Derived Grid Data ---\n");
+    for (i = 0; i < g->num_edges; i++) {
+        grid_edge *e = g->edges + i;
+        printf("Edge %d: dots[%d,%d] faces[%d,%d]\n",
+            i, (int)(e->dot1 - g->dots), (int)(e->dot2 - g->dots),
+            e->face1 ? (int)(e->face1 - g->faces) : -1,
+            e->face2 ? (int)(e->face2 - g->faces) : -1);
+    }
+    /* faces */
+    for (i = 0; i < g->num_faces; i++) {
+        grid_face *f = g->faces + i;
+        int j;
+        printf("Face %d: faces[", i);
+        for (j = 0; j < f->order; j++) {
+            grid_edge *e = f->edges[j];
+            grid_face *f2 = (e->face1 == f) ? e->face2 : e->face1;
+            printf("%s%d", j ? "," : "", f2 ? (int)(f2 - g->faces) : -1);
+        }
+        printf("]\n");
+    }
+    /* dots */
+    for (i = 0; i < g->num_dots; i++) {
+        grid_dot *d = g->dots + i;
+        int j;
+        printf("Dot %d: dots[", i);
+        for (j = 0; j < d->order; j++) {
+            grid_edge *e = d->edges[j];
+            grid_dot *d2 = (e->dot1 == d) ? e->dot2 : e->dot1;
+            printf("%s%d", j ? "," : "", (int)(d2 - g->dots));
+        }
+        printf("] faces[");
+        for (j = 0; j < d->order; j++) {
+            grid_face *f = d->faces[j];
+            printf("%s%d", j ? "," : "", f ? (int)(f - g->faces) : -1);
+        }
+        printf("]\n");
+    }
+#endif
+#ifdef SVG_GRID
+    grid_try_svg(g, SVG_DOTS | SVG_EDGES | SVG_FACES);
+#endif
+}
+
+/* Helper function for building incomplete-edges list in
+ * grid_make_consistent() */
+static int grid_edge_bydots_cmpfn(void *v1, void *v2)
+{
+    grid_edge *a = v1;
+    grid_edge *b = v2;
+    grid_dot *da, *db;
+
+    /* Pointer subtraction is valid here, because all dots point into the
+     * same dot-list (g->dots).
+     * Edges are not "normalised" - the 2 dots could be stored in any order,
+     * so we need to take this into account when comparing edges. */
+
+    /* Compare first dots */
+    da = (a->dot1 < a->dot2) ? a->dot1 : a->dot2;
+    db = (b->dot1 < b->dot2) ? b->dot1 : b->dot2;
+    if (da != db)
+        return db - da;
+    /* Compare last dots */
+    da = (a->dot1 < a->dot2) ? a->dot2 : a->dot1;
+    db = (b->dot1 < b->dot2) ? b->dot2 : b->dot1;
+    if (da != db)
+        return db - da;
+
+    return 0;
+}
+
+/*
+ * 'Vigorously trim' a grid, by which I mean deleting any isolated or
+ * uninteresting faces. By which, in turn, I mean: ensure that the
+ * grid is composed solely of faces adjacent to at least one
+ * 'landlocked' dot (i.e. one not in contact with the infinite
+ * exterior face), and that all those dots are in a single connected
+ * component.
+ *
+ * This function operates on, and returns, a grid satisfying the
+ * preconditions to grid_make_consistent() rather than the
+ * postconditions. (So call it first.)
+ */
+static void grid_trim_vigorously(grid *g)
+{
+    int *dotpairs, *faces, *dots;
+    int *dsf;
+    int i, j, k, size, newfaces, newdots;
+
+    /*
+     * First construct a matrix in which each ordered pair of dots is
+     * mapped to the index of the face in which those dots occur in
+     * that order.
+     */
+    dotpairs = snewn(g->num_dots * g->num_dots, int);
+    for (i = 0; i < g->num_dots; i++)
+        for (j = 0; j < g->num_dots; j++)
+            dotpairs[i*g->num_dots+j] = -1;
+    for (i = 0; i < g->num_faces; i++) {
+        grid_face *f = g->faces + i;
+        int dot0 = f->dots[f->order-1] - g->dots;
+        for (j = 0; j < f->order; j++) {
+            int dot1 = f->dots[j] - g->dots;
+            dotpairs[dot0 * g->num_dots + dot1] = i;
+            dot0 = dot1;
+        }
+    }
+
+    /*
+     * Now we can identify landlocked dots: they're the ones all of
+     * whose edges have a mirror-image counterpart in this matrix.
+     */
+    dots = snewn(g->num_dots, int);
+    for (i = 0; i < g->num_dots; i++) {
+        dots[i] = TRUE;
+        for (j = 0; j < g->num_dots; j++) {
+            if ((dotpairs[i*g->num_dots+j] >= 0) ^
+                (dotpairs[j*g->num_dots+i] >= 0))
+                dots[i] = FALSE;    /* non-duplicated edge: coastal dot */
+        }
+    }
+
+    /*
+     * Now identify connected pairs of landlocked dots, and form a dsf
+     * unifying them.
+     */
+    dsf = snew_dsf(g->num_dots);
+    for (i = 0; i < g->num_dots; i++)
+        for (j = 0; j < i; j++)
+            if (dots[i] && dots[j] &&
+                dotpairs[i*g->num_dots+j] >= 0 &&
+                dotpairs[j*g->num_dots+i] >= 0)
+                dsf_merge(dsf, i, j);
+
+    /*
+     * Now look for the largest component.
+     */
+    size = 0;
+    j = -1;
+    for (i = 0; i < g->num_dots; i++) {
+        int newsize;
+        if (dots[i] && dsf_canonify(dsf, i) == i &&
+            (newsize = dsf_size(dsf, i)) > size) {
+            j = i;
+            size = newsize;
+        }
+    }
+
+    /*
+     * Work out which faces we're going to keep (precisely those with
+     * at least one dot in the same connected component as j) and
+     * which dots (those required by any face we're keeping).
+     *
+     * At this point we reuse the 'dots' array to indicate the dots
+     * we're keeping, rather than the ones that are landlocked.
+     */
+    faces = snewn(g->num_faces, int);
+    for (i = 0; i < g->num_faces; i++)
+        faces[i] = 0;
+    for (i = 0; i < g->num_dots; i++)
+        dots[i] = 0;
+    for (i = 0; i < g->num_faces; i++) {
+        grid_face *f = g->faces + i;
+        int keep = FALSE;
+        for (k = 0; k < f->order; k++)
+            if (dsf_canonify(dsf, f->dots[k] - g->dots) == j)
+                keep = TRUE;
+        if (keep) {
+            faces[i] = TRUE;
+            for (k = 0; k < f->order; k++)
+                dots[f->dots[k]-g->dots] = TRUE;
+        }
+    }
+
+    /*
+     * Work out the new indices of those faces and dots, when we
+     * compact the arrays containing them.
+     */
+    for (i = newfaces = 0; i < g->num_faces; i++)
+        faces[i] = (faces[i] ? newfaces++ : -1);
+    for (i = newdots = 0; i < g->num_dots; i++)
+        dots[i] = (dots[i] ? newdots++ : -1);
+
+    /*
+     * Free the dynamically allocated 'dots' pointer lists in faces
+     * we're going to discard.
+     */
+    for (i = 0; i < g->num_faces; i++)
+        if (faces[i] < 0)
+            sfree(g->faces[i].dots);
+
+    /*
+     * Go through and compact the arrays.
+     */
+    for (i = 0; i < g->num_dots; i++)
+        if (dots[i] >= 0) {
+            grid_dot *dnew = g->dots + dots[i], *dold = g->dots + i;
+            *dnew = *dold;             /* structure copy */
+        }
+    for (i = 0; i < g->num_faces; i++)
+        if (faces[i] >= 0) {
+            grid_face *fnew = g->faces + faces[i], *fold = g->faces + i;
+            *fnew = *fold;             /* structure copy */
+            for (j = 0; j < fnew->order; j++) {
+                /*
+                 * Reindex the dots in this face.
+                 */
+                k = fnew->dots[j] - g->dots;
+                fnew->dots[j] = g->dots + dots[k];
+            }
+        }
+    g->num_faces = newfaces;
+    g->num_dots = newdots;
+
+    sfree(dotpairs);
+    sfree(dsf);
+    sfree(dots);
+    sfree(faces);
+}
+
+/* Input: grid has its dots and faces initialised:
+ * - dots have (optionally) x and y coordinates, but no edges or faces
+ * (pointers are NULL).
+ * - edges not initialised at all
+ * - faces initialised and know which dots they have (but no edges yet).  The
+ * dots around each face are assumed to be clockwise.
+ *
+ * Output: grid is complete and valid with all relationships defined.
+ */
+static void grid_make_consistent(grid *g)
+{
+    int i;
+    tree234 *incomplete_edges;
+    grid_edge *next_new_edge; /* Where new edge will go into g->edges */
+
+    grid_debug_basic(g);
+
+    /* ====== Stage 1 ======
+     * Generate edges
+     */
+
+    /* We know how many dots and faces there are, so we can find the exact
+     * number of edges from Euler's polyhedral formula: F + V = E + 2 .
+     * We use "-1", not "-2" here, because Euler's formula includes the
+     * infinite face, which we don't count. */
+    g->num_edges = g->num_faces + g->num_dots - 1;
+    g->edges = snewn(g->num_edges, grid_edge);
+    next_new_edge = g->edges;
+
+    /* Iterate over faces, and over each face's dots, generating edges as we
+     * go.  As we find each new edge, we can immediately fill in the edge's
+     * dots, but only one of the edge's faces.  Later on in the iteration, we
+     * will find the same edge again (unless it's on the border), but we will
+     * know the other face.
+     * For efficiency, maintain a list of the incomplete edges, sorted by
+     * their dots. */
+    incomplete_edges = newtree234(grid_edge_bydots_cmpfn);
+    for (i = 0; i < g->num_faces; i++) {
+        grid_face *f = g->faces + i;
+        int j;
+        for (j = 0; j < f->order; j++) {
+            grid_edge e; /* fake edge for searching */
+            grid_edge *edge_found;
+            int j2 = j + 1;
+            if (j2 == f->order)
+                j2 = 0;
+            e.dot1 = f->dots[j];
+            e.dot2 = f->dots[j2];
+            /* Use del234 instead of find234, because we always want to
+             * remove the edge if found */
+            edge_found = del234(incomplete_edges, &e);
+            if (edge_found) {
+                /* This edge already added, so fill out missing face.
+                 * Edge is already removed from incomplete_edges. */
+                edge_found->face2 = f;
+            } else {
+                assert(next_new_edge - g->edges < g->num_edges);
+                next_new_edge->dot1 = e.dot1;
+                next_new_edge->dot2 = e.dot2;
+                next_new_edge->face1 = f;
+                next_new_edge->face2 = NULL; /* potentially infinite face */
+                add234(incomplete_edges, next_new_edge);
+                ++next_new_edge;
+            }
+        }
+    }
+    freetree234(incomplete_edges);
+    
+    /* ====== Stage 2 ======
+     * For each face, build its edge list.
+     */
+
+    /* Allocate space for each edge list.  Can do this, because each face's
+     * edge-list is the same size as its dot-list. */
+    for (i = 0; i < g->num_faces; i++) {
+        grid_face *f = g->faces + i;
+        int j;
+        f->edges = snewn(f->order, grid_edge*);
+        /* Preload with NULLs, to help detect potential bugs. */
+        for (j = 0; j < f->order; j++)
+            f->edges[j] = NULL;
+    }
+    
+    /* Iterate over each edge, and over both its faces.  Add this edge to
+     * the face's edge-list, after finding where it should go in the
+     * sequence. */
+    for (i = 0; i < g->num_edges; i++) {
+        grid_edge *e = g->edges + i;
+        int j;
+        for (j = 0; j < 2; j++) {
+            grid_face *f = j ? e->face2 : e->face1;
+            int k, k2;
+            if (f == NULL) continue;
+            /* Find one of the dots around the face */
+            for (k = 0; k < f->order; k++) {
+                if (f->dots[k] == e->dot1)
+                    break; /* found dot1 */
+            }
+            assert(k != f->order); /* Must find the dot around this face */
+
+            /* Labelling scheme: as we walk clockwise around the face,
+             * starting at dot0 (f->dots[0]), we hit:
+             * (dot0), edge0, dot1, edge1, dot2,...
+             *
+             *     0
+             *  0-----1
+             *        |
+             *        |1
+             *        |
+             *  3-----2
+             *     2
+             *
+             * Therefore, edgeK joins dotK and dot{K+1}
+             */
+            
+            /* Around this face, either the next dot or the previous dot
+             * must be e->dot2.  Otherwise the edge is wrong. */
+            k2 = k + 1;
+            if (k2 == f->order)
+                k2 = 0;
+            if (f->dots[k2] == e->dot2) {
+                /* dot1(k) and dot2(k2) go clockwise around this face, so add
+                 * this edge at position k (see diagram). */
+                assert(f->edges[k] == NULL);
+                f->edges[k] = e;
+                continue;
+            }
+            /* Try previous dot */
+            k2 = k - 1;
+            if (k2 == -1)
+                k2 = f->order - 1;
+            if (f->dots[k2] == e->dot2) {
+                /* dot1(k) and dot2(k2) go anticlockwise around this face. */
+                assert(f->edges[k2] == NULL);
+                f->edges[k2] = e;
+                continue;
+            }
+            assert(!"Grid broken: bad edge-face relationship");
+        }
+    }
+
+    /* ====== Stage 3 ======
+     * For each dot, build its edge-list and face-list.
+     */
+
+    /* We don't know how many edges/faces go around each dot, so we can't
+     * allocate the right space for these lists.  Pre-compute the sizes by
+     * iterating over each edge and recording a tally against each dot. */
+    for (i = 0; i < g->num_dots; i++) {
+        g->dots[i].order = 0;
+    }
+    for (i = 0; i < g->num_edges; i++) {
+        grid_edge *e = g->edges + i;
+        ++(e->dot1->order);
+        ++(e->dot2->order);
+    }
+    /* Now we have the sizes, pre-allocate the edge and face lists. */
+    for (i = 0; i < g->num_dots; i++) {
+        grid_dot *d = g->dots + i;
+        int j;
+        assert(d->order >= 2); /* sanity check */
+        d->edges = snewn(d->order, grid_edge*);
+        d->faces = snewn(d->order, grid_face*);
+        for (j = 0; j < d->order; j++) {
+            d->edges[j] = NULL;
+            d->faces[j] = NULL;
+        }
+    }
+    /* For each dot, need to find a face that touches it, so we can seed
+     * the edge-face-edge-face process around each dot. */
+    for (i = 0; i < g->num_faces; i++) {
+        grid_face *f = g->faces + i;
+        int j;
+        for (j = 0; j < f->order; j++) {
+            grid_dot *d = f->dots[j];
+            d->faces[0] = f;
+        }
+    }
+    /* Each dot now has a face in its first slot.  Generate the remaining
+     * faces and edges around the dot, by searching both clockwise and
+     * anticlockwise from the first face.  Need to do both directions,
+     * because of the possibility of hitting the infinite face, which
+     * blocks progress.  But there's only one such face, so we will
+     * succeed in finding every edge and face this way. */
+    for (i = 0; i < g->num_dots; i++) {
+        grid_dot *d = g->dots + i;
+        int current_face1 = 0; /* ascends clockwise */
+        int current_face2 = 0; /* descends anticlockwise */
+        
+        /* Labelling scheme: as we walk clockwise around the dot, starting
+         * at face0 (d->faces[0]), we hit:
+         * (face0), edge0, face1, edge1, face2,...
+         *
+         *       0
+         *       |
+         *    0  |  1
+         *       |
+         *  -----d-----1
+         *       |
+         *       |  2
+         *       |
+         *       2
+         *
+         * So, for example, face1 should be joined to edge0 and edge1,
+         * and those edges should appear in an anticlockwise sense around
+         * that face (see diagram). */
+        /* clockwise search */
+        while (TRUE) {
+            grid_face *f = d->faces[current_face1];
+            grid_edge *e;
+            int j;
+            assert(f != NULL);
+            /* find dot around this face */
+            for (j = 0; j < f->order; j++) {
+                if (f->dots[j] == d)
+                    break;
+            }
+            assert(j != f->order); /* must find dot */
+            
+            /* Around f, required edge is anticlockwise from the dot.  See
+             * the other labelling scheme higher up, for why we subtract 1
+             * from j. */
+            j--;
+            if (j == -1)
+                j = f->order - 1;
+            e = f->edges[j];
+            d->edges[current_face1] = e; /* set edge */
+            current_face1++;
+            if (current_face1 == d->order)
+                break;
+            else {
+                /* set face */
+                d->faces[current_face1] =
+                    (e->face1 == f) ? e->face2 : e->face1;
+                if (d->faces[current_face1] == NULL)
+                    break; /* cannot progress beyond infinite face */
+            }
+        }
+        /* If the clockwise search made it all the way round, don't need to
+         * bother with the anticlockwise search. */
+        if (current_face1 == d->order)
+            continue; /* this dot is complete, move on to next dot */
+        
+        /* anticlockwise search */
+        while (TRUE) {
+            grid_face *f = d->faces[current_face2];
+            grid_edge *e;
+            int j;
+            assert(f != NULL);
+            /* find dot around this face */
+            for (j = 0; j < f->order; j++) {
+                if (f->dots[j] == d)
+                    break;
+            }
+            assert(j != f->order); /* must find dot */
+            
+            /* Around f, required edge is clockwise from the dot. */
+            e = f->edges[j];
+            
+            current_face2--;
+            if (current_face2 == -1)
+                current_face2 = d->order - 1;
+            d->edges[current_face2] = e; /* set edge */
+
+            /* set face */
+            if (current_face2 == current_face1)
+                break;
+            d->faces[current_face2] =
+                    (e->face1 == f) ? e->face2 : e->face1;
+            /* There's only 1 infinite face, so we must get all the way
+             * to current_face1 before we hit it. */
+            assert(d->faces[current_face2]);
+        }
+    }
+
+    /* ====== Stage 4 ======
+     * Compute other grid settings
+     */
+
+    /* Bounding rectangle */
+    for (i = 0; i < g->num_dots; i++) {
+        grid_dot *d = g->dots + i;
+        if (i == 0) {
+            g->lowest_x = g->highest_x = d->x;
+            g->lowest_y = g->highest_y = d->y;
+        } else {
+            g->lowest_x = min(g->lowest_x, d->x);
+            g->highest_x = max(g->highest_x, d->x);
+            g->lowest_y = min(g->lowest_y, d->y);
+            g->highest_y = max(g->highest_y, d->y);
+        }
+    }
+
+    grid_debug_derived(g);
+}
+
+/* Helpers for making grid-generation easier.  These functions are only
+ * intended for use during grid generation. */
+
+/* Comparison function for the (tree234) sorted dot list */
+static int grid_point_cmp_fn(void *v1, void *v2)
+{
+    grid_dot *p1 = v1;
+    grid_dot *p2 = v2;
+    if (p1->y != p2->y)
+        return p2->y - p1->y;
+    else
+        return p2->x - p1->x;
+}
+/* Add a new face to the grid, with its dot list allocated.
+ * Assumes there's enough space allocated for the new face in grid->faces */
+static void grid_face_add_new(grid *g, int face_size)
+{
+    int i;
+    grid_face *new_face = g->faces + g->num_faces;
+    new_face->order = face_size;
+    new_face->dots = snewn(face_size, grid_dot*);
+    for (i = 0; i < face_size; i++)
+        new_face->dots[i] = NULL;
+    new_face->edges = NULL;
+    new_face->has_incentre = FALSE;
+    g->num_faces++;
+}
+/* Assumes dot list has enough space */
+static grid_dot *grid_dot_add_new(grid *g, int x, int y)
+{
+    grid_dot *new_dot = g->dots + g->num_dots;
+    new_dot->order = 0;
+    new_dot->edges = NULL;
+    new_dot->faces = NULL;
+    new_dot->x = x;
+    new_dot->y = y;
+    g->num_dots++;
+    return new_dot;
+}
+/* Retrieve a dot with these (x,y) coordinates.  Either return an existing dot
+ * in the dot_list, or add a new dot to the grid (and the dot_list) and
+ * return that.
+ * Assumes g->dots has enough capacity allocated */
+static grid_dot *grid_get_dot(grid *g, tree234 *dot_list, int x, int y)
+{
+    grid_dot test, *ret;
+
+    test.order = 0;
+    test.edges = NULL;
+    test.faces = NULL;
+    test.x = x;
+    test.y = y;
+    ret = find234(dot_list, &test, NULL);
+    if (ret)
+        return ret;
+
+    ret = grid_dot_add_new(g, x, y);
+    add234(dot_list, ret);
+    return ret;
+}
+
+/* Sets the last face of the grid to include this dot, at this position
+ * around the face. Assumes num_faces is at least 1 (a new face has
+ * previously been added, with the required number of dots allocated) */
+static void grid_face_set_dot(grid *g, grid_dot *d, int position)
+{
+    grid_face *last_face = g->faces + g->num_faces - 1;
+    last_face->dots[position] = d;
+}
+
+/*
+ * Helper routines for grid_find_incentre.
+ */
+static int solve_2x2_matrix(double mx[4], double vin[2], double vout[2])
+{
+    double inv[4];
+    double det;
+    det = (mx[0]*mx[3] - mx[1]*mx[2]);
+    if (det == 0)
+        return FALSE;
+
+    inv[0] = mx[3] / det;
+    inv[1] = -mx[1] / det;
+    inv[2] = -mx[2] / det;
+    inv[3] = mx[0] / det;
+
+    vout[0] = inv[0]*vin[0] + inv[1]*vin[1];
+    vout[1] = inv[2]*vin[0] + inv[3]*vin[1];
+
+    return TRUE;
+}
+static int solve_3x3_matrix(double mx[9], double vin[3], double vout[3])
+{
+    double inv[9];
+    double det;
+
+    det = (mx[0]*mx[4]*mx[8] + mx[1]*mx[5]*mx[6] + mx[2]*mx[3]*mx[7] -
+           mx[0]*mx[5]*mx[7] - mx[1]*mx[3]*mx[8] - mx[2]*mx[4]*mx[6]);
+    if (det == 0)
+        return FALSE;
+
+    inv[0] = (mx[4]*mx[8] - mx[5]*mx[7]) / det;
+    inv[1] = (mx[2]*mx[7] - mx[1]*mx[8]) / det;
+    inv[2] = (mx[1]*mx[5] - mx[2]*mx[4]) / det;
+    inv[3] = (mx[5]*mx[6] - mx[3]*mx[8]) / det;
+    inv[4] = (mx[0]*mx[8] - mx[2]*mx[6]) / det;
+    inv[5] = (mx[2]*mx[3] - mx[0]*mx[5]) / det;
+    inv[6] = (mx[3]*mx[7] - mx[4]*mx[6]) / det;
+    inv[7] = (mx[1]*mx[6] - mx[0]*mx[7]) / det;
+    inv[8] = (mx[0]*mx[4] - mx[1]*mx[3]) / det;
+
+    vout[0] = inv[0]*vin[0] + inv[1]*vin[1] + inv[2]*vin[2];
+    vout[1] = inv[3]*vin[0] + inv[4]*vin[1] + inv[5]*vin[2];
+    vout[2] = inv[6]*vin[0] + inv[7]*vin[1] + inv[8]*vin[2];
+
+    return TRUE;
+}
+
+void grid_find_incentre(grid_face *f)
+{
+    double xbest, ybest, bestdist;
+    int i, j, k, m;
+    grid_dot *edgedot1[3], *edgedot2[3];
+    grid_dot *dots[3];
+    int nedges, ndots;
+
+    if (f->has_incentre)
+        return;
+
+    /*
+     * Find the point in the polygon with the maximum distance to any
+     * edge or corner.
+     *
+     * Such a point must exist which is in contact with at least three
+     * edges and/or vertices. (Proof: if it's only in contact with two
+     * edges and/or vertices, it can't even be at a _local_ maximum -
+     * any such circle can always be expanded in some direction.) So
+     * we iterate through all 3-subsets of the combined set of edges
+     * and vertices; for each subset we generate one or two candidate
+     * points that might be the incentre, and then we vet each one to
+     * see if it's inside the polygon and what its maximum radius is.
+     *
+     * (There's one case which this algorithm will get noticeably
+     * wrong, and that's when a continuum of equally good answers
+     * exists due to parallel edges. Consider a long thin rectangle,
+     * for instance, or a parallelogram. This algorithm will pick a
+     * point near one end, and choose the end arbitrarily; obviously a
+     * nicer point to choose would be in the centre. To fix this I
+     * would have to introduce a special-case system which detected
+     * parallel edges in advance, set aside all candidate points
+     * generated using both edges in a parallel pair, and generated
+     * some additional candidate points half way between them. Also,
+     * of course, I'd have to cope with rounding error making such a
+     * point look worse than one of its endpoints. So I haven't done
+     * this for the moment, and will cross it if necessary when I come
+     * to it.)
+     *
+     * We don't actually iterate literally over _edges_, in the sense
+     * of grid_edge structures. Instead, we fill in edgedot1[] and
+     * edgedot2[] with a pair of dots adjacent in the face's list of
+     * vertices. This ensures that we get the edges in consistent
+     * orientation, which we could not do from the grid structure
+     * alone. (A moment's consideration of an order-3 vertex should
+     * make it clear that if a notional arrow was written on each
+     * edge, _at least one_ of the three faces bordering that vertex
+     * would have to have the two arrows tip-to-tip or tail-to-tail
+     * rather than tip-to-tail.)
+     */
+    nedges = ndots = 0;
+    bestdist = 0;
+    xbest = ybest = 0;
+
+    for (i = 0; i+2 < 2*f->order; i++) {
+        if (i < f->order) {
+            edgedot1[nedges] = f->dots[i];
+            edgedot2[nedges++] = f->dots[(i+1)%f->order];
+        } else
+            dots[ndots++] = f->dots[i - f->order];
+
+        for (j = i+1; j+1 < 2*f->order; j++) {
+            if (j < f->order) {
+                edgedot1[nedges] = f->dots[j];
+                edgedot2[nedges++] = f->dots[(j+1)%f->order];
+            } else
+                dots[ndots++] = f->dots[j - f->order];
+
+            for (k = j+1; k < 2*f->order; k++) {
+                double cx[2], cy[2];   /* candidate positions */
+                int cn = 0;            /* number of candidates */
+
+                if (k < f->order) {
+                    edgedot1[nedges] = f->dots[k];
+                    edgedot2[nedges++] = f->dots[(k+1)%f->order];
+                } else
+                    dots[ndots++] = f->dots[k - f->order];
+
+                /*
+                 * Find a point, or pair of points, equidistant from
+                 * all the specified edges and/or vertices.
+                 */
+                if (nedges == 3) {
+                    /*
+                     * Three edges. This is a linear matrix equation:
+                     * each row of the matrix represents the fact that
+                     * the point (x,y) we seek is at distance r from
+                     * that edge, and we solve three of those
+                     * simultaneously to obtain x,y,r. (We ignore r.)
+                     */
+                    double matrix[9], vector[3], vector2[3];
+                    int m;
+
+                    for (m = 0; m < 3; m++) {
+                        int x1 = edgedot1[m]->x, x2 = edgedot2[m]->x;
+                        int y1 = edgedot1[m]->y, y2 = edgedot2[m]->y;
+                        int dx = x2-x1, dy = y2-y1;
+
+                        /*
+                         * ((x,y) - (x1,y1)) . (dy,-dx) = r |(dx,dy)|
+                         *
+                         * => x dy - y dx - r |(dx,dy)| = (x1 dy - y1 dx)
+                         */
+                        matrix[3*m+0] = dy;
+                        matrix[3*m+1] = -dx;
+                        matrix[3*m+2] = -sqrt((double)dx*dx+(double)dy*dy);
+                        vector[m] = (double)x1*dy - (double)y1*dx;
+                    }
+
+                    if (solve_3x3_matrix(matrix, vector, vector2)) {
+                        cx[cn] = vector2[0];
+                        cy[cn] = vector2[1];
+                        cn++;
+                    }
+                } else if (nedges == 2) {
+                    /*
+                     * Two edges and a dot. This will end up in a
+                     * quadratic equation.
+                     *
+                     * First, look at the two edges. Having our point
+                     * be some distance r from both of them gives rise
+                     * to a pair of linear equations in x,y,r of the
+                     * form
+                     *
+                     *   (x-x1) dy - (y-y1) dx = r sqrt(dx^2+dy^2)
+                     *
+                     * We eliminate r between those equations to give
+                     * us a single linear equation in x,y describing
+                     * the locus of points equidistant from both lines
+                     * - i.e. the angle bisector. 
+                     *
+                     * We then choose one of x,y to be a parameter t,
+                     * and derive linear formulae for x,y,r in terms
+                     * of t. This enables us to write down the
+                     * circular equation (x-xd)^2+(y-yd)^2=r^2 as a
+                     * quadratic in t; solving that and substituting
+                     * in for x,y gives us two candidate points.
+                     */
+                    double eqs[2][4];  /* a,b,c,d : ax+by+cr=d */
+                    double eq[3];      /* a,b,c: ax+by=c */
+                    double xt[2], yt[2], rt[2]; /* a,b: {x,y,r}=at+b */
+                    double q[3];                /* a,b,c: at^2+bt+c=0 */
+                    double disc;
+
+                    /* Find equations of the two input lines. */
+                    for (m = 0; m < 2; m++) {
+                        int x1 = edgedot1[m]->x, x2 = edgedot2[m]->x;
+                        int y1 = edgedot1[m]->y, y2 = edgedot2[m]->y;
+                        int dx = x2-x1, dy = y2-y1;
+
+                        eqs[m][0] = dy;
+                        eqs[m][1] = -dx;
+                        eqs[m][2] = -sqrt(dx*dx+dy*dy);
+                        eqs[m][3] = x1*dy - y1*dx;
+                    }
+
+                    /* Derive the angle bisector by eliminating r. */
+                    eq[0] = eqs[0][0]*eqs[1][2] - eqs[1][0]*eqs[0][2];
+                    eq[1] = eqs[0][1]*eqs[1][2] - eqs[1][1]*eqs[0][2];
+                    eq[2] = eqs[0][3]*eqs[1][2] - eqs[1][3]*eqs[0][2];
+
+                    /* Parametrise x and y in terms of some t. */
+                    if (fabs(eq[0]) < fabs(eq[1])) {
+                        /* Parameter is x. */
+                        xt[0] = 1; xt[1] = 0;
+                        yt[0] = -eq[0]/eq[1]; yt[1] = eq[2]/eq[1];
+                    } else {
+                        /* Parameter is y. */
+                        yt[0] = 1; yt[1] = 0;
+                        xt[0] = -eq[1]/eq[0]; xt[1] = eq[2]/eq[0];
+                    }
+
+                    /* Find a linear representation of r using eqs[0]. */
+                    rt[0] = -(eqs[0][0]*xt[0] + eqs[0][1]*yt[0])/eqs[0][2];
+                    rt[1] = (eqs[0][3] - eqs[0][0]*xt[1] -
+                             eqs[0][1]*yt[1])/eqs[0][2];
+
+                    /* Construct the quadratic equation. */
+                    q[0] = -rt[0]*rt[0];
+                    q[1] = -2*rt[0]*rt[1];
+                    q[2] = -rt[1]*rt[1];
+                    q[0] += xt[0]*xt[0];
+                    q[1] += 2*xt[0]*(xt[1]-dots[0]->x);
+                    q[2] += (xt[1]-dots[0]->x)*(xt[1]-dots[0]->x);
+                    q[0] += yt[0]*yt[0];
+                    q[1] += 2*yt[0]*(yt[1]-dots[0]->y);
+                    q[2] += (yt[1]-dots[0]->y)*(yt[1]-dots[0]->y);
+
+                    /* And solve it. */
+                    disc = q[1]*q[1] - 4*q[0]*q[2];
+                    if (disc >= 0) {
+                        double t;
+
+                        disc = sqrt(disc);
+
+                        t = (-q[1] + disc) / (2*q[0]);
+                        cx[cn] = xt[0]*t + xt[1];
+                        cy[cn] = yt[0]*t + yt[1];
+                        cn++;
+
+                        t = (-q[1] - disc) / (2*q[0]);
+                        cx[cn] = xt[0]*t + xt[1];
+                        cy[cn] = yt[0]*t + yt[1];
+                        cn++;
+                    }
+                } else if (nedges == 1) {
+                    /*
+                     * Two dots and an edge. This one's another
+                     * quadratic equation.
+                     *
+                     * The point we want must lie on the perpendicular
+                     * bisector of the two dots; that much is obvious.
+                     * So we can construct a parametrisation of that
+                     * bisecting line, giving linear formulae for x,y
+                     * in terms of t. We can also express the distance
+                     * from the edge as such a linear formula.
+                     *
+                     * Then we set that equal to the radius of the
+                     * circle passing through the two points, which is
+                     * a Pythagoras exercise; that gives rise to a
+                     * quadratic in t, which we solve.
+                     */
+                    double xt[2], yt[2], rt[2]; /* a,b: {x,y,r}=at+b */
+                    double q[3];                /* a,b,c: at^2+bt+c=0 */
+                    double disc;
+                    double halfsep;
+
+                    /* Find parametric formulae for x,y. */
+                    {
+                        int x1 = dots[0]->x, x2 = dots[1]->x;
+                        int y1 = dots[0]->y, y2 = dots[1]->y;
+                        int dx = x2-x1, dy = y2-y1;
+                        double d = sqrt((double)dx*dx + (double)dy*dy);
+
+                        xt[1] = (x1+x2)/2.0;
+                        yt[1] = (y1+y2)/2.0;
+                        /* It's convenient if we have t at standard scale. */
+                        xt[0] = -dy/d;
+                        yt[0] = dx/d;
+
+                        /* Also note down half the separation between
+                         * the dots, for use in computing the circle radius. */
+                        halfsep = 0.5*d;
+                    }
+
+                    /* Find a parametric formula for r. */
+                    {
+                        int x1 = edgedot1[0]->x, x2 = edgedot2[0]->x;
+                        int y1 = edgedot1[0]->y, y2 = edgedot2[0]->y;
+                        int dx = x2-x1, dy = y2-y1;
+                        double d = sqrt((double)dx*dx + (double)dy*dy);
+                        rt[0] = (xt[0]*dy - yt[0]*dx) / d;
+                        rt[1] = ((xt[1]-x1)*dy - (yt[1]-y1)*dx) / d;
+                    }
+
+                    /* Construct the quadratic equation. */
+                    q[0] = rt[0]*rt[0];
+                    q[1] = 2*rt[0]*rt[1];
+                    q[2] = rt[1]*rt[1];
+                    q[0] -= 1;
+                    q[2] -= halfsep*halfsep;
+
+                    /* And solve it. */
+                    disc = q[1]*q[1] - 4*q[0]*q[2];
+                    if (disc >= 0) {
+                        double t;
+
+                        disc = sqrt(disc);
+
+                        t = (-q[1] + disc) / (2*q[0]);
+                        cx[cn] = xt[0]*t + xt[1];
+                        cy[cn] = yt[0]*t + yt[1];
+                        cn++;
+
+                        t = (-q[1] - disc) / (2*q[0]);
+                        cx[cn] = xt[0]*t + xt[1];
+                        cy[cn] = yt[0]*t + yt[1];
+                        cn++;
+                    }
+                } else if (nedges == 0) {
+                    /*
+                     * Three dots. This is another linear matrix
+                     * equation, this time with each row of the matrix
+                     * representing the perpendicular bisector between
+                     * two of the points. Of course we only need two
+                     * such lines to find their intersection, so we
+                     * need only solve a 2x2 matrix equation.
+                     */
+
+                    double matrix[4], vector[2], vector2[2];
+                    int m;
+
+                    for (m = 0; m < 2; m++) {
+                        int x1 = dots[m]->x, x2 = dots[m+1]->x;
+                        int y1 = dots[m]->y, y2 = dots[m+1]->y;
+                        int dx = x2-x1, dy = y2-y1;
+
+                        /*
+                         * ((x,y) - (x1,y1)) . (dx,dy) = 1/2 |(dx,dy)|^2
+                         *
+                         * => 2x dx + 2y dy = dx^2+dy^2 + (2 x1 dx + 2 y1 dy)
+                         */
+                        matrix[2*m+0] = 2*dx;
+                        matrix[2*m+1] = 2*dy;
+                        vector[m] = ((double)dx*dx + (double)dy*dy +
+                                     2.0*x1*dx + 2.0*y1*dy);
+                    }
+
+                    if (solve_2x2_matrix(matrix, vector, vector2)) {
+                        cx[cn] = vector2[0];
+                        cy[cn] = vector2[1];
+                        cn++;
+                    }
+                }
+
+                /*
+                 * Now go through our candidate points and see if any
+                 * of them are better than what we've got so far.
+                 */
+                for (m = 0; m < cn; m++) {
+                    double x = cx[m], y = cy[m];
+
+                    /*
+                     * First, disqualify the point if it's not inside
+                     * the polygon, which we work out by counting the
+                     * edges to the right of the point. (For
+                     * tiebreaking purposes when edges start or end on
+                     * our y-coordinate or go right through it, we
+                     * consider our point to be offset by a small
+                     * _positive_ epsilon in both the x- and
+                     * y-direction.)
+                     */
+                    int e, in = 0;
+                    for (e = 0; e < f->order; e++) {
+                        int xs = f->edges[e]->dot1->x;
+                        int xe = f->edges[e]->dot2->x;
+                        int ys = f->edges[e]->dot1->y;
+                        int ye = f->edges[e]->dot2->y;
+                        if ((y >= ys && y < ye) || (y >= ye && y < ys)) {
+                            /*
+                             * The line goes past our y-position. Now we need
+                             * to know if its x-coordinate when it does so is
+                             * to our right.
+                             *
+                             * The x-coordinate in question is mathematically
+                             * (y - ys) * (xe - xs) / (ye - ys), and we want
+                             * to know whether (x - xs) >= that. Of course we
+                             * avoid the division, so we can work in integers;
+                             * to do this we must multiply both sides of the
+                             * inequality by ye - ys, which means we must
+                             * first check that's not negative.
+                             */
+                            int num = xe - xs, denom = ye - ys;
+                            if (denom < 0) {
+                                num = -num;
+                                denom = -denom;
+                            }
+                            if ((x - xs) * denom >= (y - ys) * num)
+                                in ^= 1;
+                        }
+                    }
+
+                    if (in) {
+#ifdef HUGE_VAL
+                        double mindist = HUGE_VAL;
+#else
+#ifdef DBL_MAX
+                        double mindist = DBL_MAX;
+#else
+#error No way to get maximum floating-point number.
+#endif
+#endif
+                        int e, d;
+
+                        /*
+                         * This point is inside the polygon, so now we check
+                         * its minimum distance to every edge and corner.
+                         * First the corners ...
+                         */
+                        for (d = 0; d < f->order; d++) {
+                            int xp = f->dots[d]->x;
+                            int yp = f->dots[d]->y;
+                            double dx = x - xp, dy = y - yp;
+                            double dist = dx*dx + dy*dy;
+                            if (mindist > dist)
+                                mindist = dist;
+                        }
+
+                        /*
+                         * ... and now also check the perpendicular distance
+                         * to every edge, if the perpendicular lies between
+                         * the edge's endpoints.
+                         */
+                        for (e = 0; e < f->order; e++) {
+                            int xs = f->edges[e]->dot1->x;
+                            int xe = f->edges[e]->dot2->x;
+                            int ys = f->edges[e]->dot1->y;
+                            int ye = f->edges[e]->dot2->y;
+
+                            /*
+                             * If s and e are our endpoints, and p our
+                             * candidate circle centre, the foot of a
+                             * perpendicular from p to the line se lies
+                             * between s and e if and only if (p-s).(e-s) lies
+                             * strictly between 0 and (e-s).(e-s).
+                             */
+                            int edx = xe - xs, edy = ye - ys;
+                            double pdx = x - xs, pdy = y - ys;
+                            double pde = pdx * edx + pdy * edy;
+                            long ede = (long)edx * edx + (long)edy * edy;
+                            if (0 < pde && pde < ede) {
+                                /*
+                                 * Yes, the nearest point on this edge is
+                                 * closer than either endpoint, so we must
+                                 * take it into account by measuring the
+                                 * perpendicular distance to the edge and
+                                 * checking its square against mindist.
+                                 */
+
+                                double pdre = pdx * edy - pdy * edx;
+                                double sqlen = pdre * pdre / ede;
+
+                                if (mindist > sqlen)
+                                    mindist = sqlen;
+                            }
+                        }
+
+                        /*
+                         * Right. Now we know the biggest circle around this
+                         * point, so we can check it against bestdist.
+                         */
+                        if (bestdist < mindist) {
+                            bestdist = mindist;
+                            xbest = x;
+                            ybest = y;
+                        }
+                    }
+                }
+
+                if (k < f->order)
+                    nedges--;
+                else
+                    ndots--;
+            }
+            if (j < f->order)
+                nedges--;
+            else
+                ndots--;
+        }
+        if (i < f->order)
+            nedges--;
+        else
+            ndots--;
+    }
+
+    assert(bestdist > 0);
+
+    f->has_incentre = TRUE;
+    f->ix = xbest + 0.5;               /* round to nearest */
+    f->iy = ybest + 0.5;
+}
+
+/* ------ Generate various types of grid ------ */
+
+/* General method is to generate faces, by calculating their dot coordinates.
+ * As new faces are added, we keep track of all the dots so we can tell when
+ * a new face reuses an existing dot.  For example, two squares touching at an
+ * edge would generate six unique dots: four dots from the first face, then
+ * two additional dots for the second face, because we detect the other two
+ * dots have already been taken up.  This list is stored in a tree234
+ * called "points".  No extra memory-allocation needed here - we store the
+ * actual grid_dot* pointers, which all point into the g->dots list.
+ * For this reason, we have to calculate coordinates in such a way as to
+ * eliminate any rounding errors, so we can detect when a dot on one
+ * face precisely lands on a dot of a different face.  No floating-point
+ * arithmetic here!
+ */
+
+#define SQUARE_TILESIZE 20
+
+static void grid_size_square(int width, int height,
+                      int *tilesize, int *xextent, int *yextent)
+{
+    int a = SQUARE_TILESIZE;
+
+    *tilesize = a;
+    *xextent = width * a;
+    *yextent = height * a;
+}
+
+static grid *grid_new_square(int width, int height, const char *desc)
+{
+    int x, y;
+    /* Side length */
+    int a = SQUARE_TILESIZE;
+
+    /* Upper bounds - don't have to be exact */
+    int max_faces = width * height;
+    int max_dots = (width + 1) * (height + 1);
+
+    tree234 *points;
+
+    grid *g = grid_empty();
+    g->tilesize = a;
+    g->faces = snewn(max_faces, grid_face);
+    g->dots = snewn(max_dots, grid_dot);
+
+    points = newtree234(grid_point_cmp_fn);
+
+    /* generate square faces */
+    for (y = 0; y < height; y++) {
+        for (x = 0; x < width; x++) {
+            grid_dot *d;
+            /* face position */
+            int px = a * x;
+            int py = a * y;
+
+            grid_face_add_new(g, 4);
+            d = grid_get_dot(g, points, px, py);
+            grid_face_set_dot(g, d, 0);
+            d = grid_get_dot(g, points, px + a, py);
+            grid_face_set_dot(g, d, 1);
+            d = grid_get_dot(g, points, px + a, py + a);
+            grid_face_set_dot(g, d, 2);
+            d = grid_get_dot(g, points, px, py + a);
+            grid_face_set_dot(g, d, 3);
+        }
+    }
+
+    freetree234(points);
+    assert(g->num_faces <= max_faces);
+    assert(g->num_dots <= max_dots);
+
+    grid_make_consistent(g);
+    return g;
+}
+
+#define HONEY_TILESIZE 45
+/* Vector for side of hexagon - ratio is close to sqrt(3) */
+#define HONEY_A 15
+#define HONEY_B 26
+
+static void grid_size_honeycomb(int width, int height,
+                         int *tilesize, int *xextent, int *yextent)
+{
+    int a = HONEY_A;
+    int b = HONEY_B;
+
+    *tilesize = HONEY_TILESIZE;
+    *xextent = (3 * a * (width-1)) + 4*a;
+    *yextent = (2 * b * (height-1)) + 3*b;
+}
+
+static grid *grid_new_honeycomb(int width, int height, const char *desc)
+{
+    int x, y;
+    int a = HONEY_A;
+    int b = HONEY_B;
+
+    /* Upper bounds - don't have to be exact */
+    int max_faces = width * height;
+    int max_dots = 2 * (width + 1) * (height + 1);
+
+    tree234 *points;
+
+    grid *g = grid_empty();
+    g->tilesize = HONEY_TILESIZE;
+    g->faces = snewn(max_faces, grid_face);
+    g->dots = snewn(max_dots, grid_dot);
+
+    points = newtree234(grid_point_cmp_fn);
+
+    /* generate hexagonal faces */
+    for (y = 0; y < height; y++) {
+        for (x = 0; x < width; x++) {
+            grid_dot *d;
+            /* face centre */
+            int cx = 3 * a * x;
+            int cy = 2 * b * y;
+            if (x % 2)
+                cy += b;
+            grid_face_add_new(g, 6);
+
+            d = grid_get_dot(g, points, cx - a, cy - b);
+            grid_face_set_dot(g, d, 0);
+            d = grid_get_dot(g, points, cx + a, cy - b);
+            grid_face_set_dot(g, d, 1);
+            d = grid_get_dot(g, points, cx + 2*a, cy);
+            grid_face_set_dot(g, d, 2);
+            d = grid_get_dot(g, points, cx + a, cy + b);
+            grid_face_set_dot(g, d, 3);
+            d = grid_get_dot(g, points, cx - a, cy + b);
+            grid_face_set_dot(g, d, 4);
+            d = grid_get_dot(g, points, cx - 2*a, cy);
+            grid_face_set_dot(g, d, 5);
+        }
+    }
+
+    freetree234(points);
+    assert(g->num_faces <= max_faces);
+    assert(g->num_dots <= max_dots);
+
+    grid_make_consistent(g);
+    return g;
+}
+
+#define TRIANGLE_TILESIZE 18
+/* Vector for side of triangle - ratio is close to sqrt(3) */
+#define TRIANGLE_VEC_X 15
+#define TRIANGLE_VEC_Y 26
+
+static void grid_size_triangular(int width, int height,
+                          int *tilesize, int *xextent, int *yextent)
+{
+    int vec_x = TRIANGLE_VEC_X;
+    int vec_y = TRIANGLE_VEC_Y;
+
+    *tilesize = TRIANGLE_TILESIZE;
+    *xextent = (width+1) * 2 * vec_x;
+    *yextent = height * vec_y;
+}
+
+static char *grid_validate_desc_triangular(grid_type type, int width,
+                                           int height, const char *desc)
+{
+    /*
+     * Triangular grids: an absent description is valid (indicating
+     * the old-style approach which had 'ears', i.e. triangles
+     * connected to only one other face, at some grid corners), and so
+     * is a description reading just "0" (indicating the new-style
+     * approach in which those ears are trimmed off). Anything else is
+     * illegal.
+     */
+
+    if (!desc || !strcmp(desc, "0"))
+        return NULL;
+
+    return "Unrecognised grid description.";
+}
+
+/* Doesn't use the previous method of generation, it pre-dates it!
+ * A triangular grid is just about simple enough to do by "brute force" */
+static grid *grid_new_triangular(int width, int height, const char *desc)
+{
+    int x,y;
+    int version = (desc == NULL ? -1 : atoi(desc));
+    
+    /* Vector for side of triangle - ratio is close to sqrt(3) */
+    int vec_x = TRIANGLE_VEC_X;
+    int vec_y = TRIANGLE_VEC_Y;
+    
+    int index;
+
+    /* convenient alias */
+    int w = width + 1;
+
+    grid *g = grid_empty();
+    g->tilesize = TRIANGLE_TILESIZE;
+
+    if (version == -1) {
+        /*
+         * Old-style triangular grid generation, preserved as-is for
+         * backwards compatibility with old game ids, in which it's
+         * just a little asymmetric and there are 'ears' (faces linked
+         * to only one other face) at two grid corners.
+         *
+         * Example old-style game ids, which should still work, and in
+         * which you should see the ears in the TL/BR corners on the
+         * first one and in the TL/BL corners on the second:
+         *
+         *   5x5t1:2c2a1a2a201a1a1a1112a1a2b1211f0b21a2a2a0a
+         *   5x6t1:a022a212h1a1d1a12c2b11a012b1a20d1a0a12e
+         */
+
+        g->num_faces = width * height * 2;
+        g->num_dots = (width + 1) * (height + 1);
+        g->faces = snewn(g->num_faces, grid_face);
+        g->dots = snewn(g->num_dots, grid_dot);
+
+        /* generate dots */
+        index = 0;
+        for (y = 0; y <= height; y++) {
+            for (x = 0; x <= width; x++) {
+                grid_dot *d = g->dots + index;
+                /* odd rows are offset to the right */
+                d->order = 0;
+                d->edges = NULL;
+                d->faces = NULL;
+                d->x = x * 2 * vec_x + ((y % 2) ? vec_x : 0);
+                d->y = y * vec_y;
+                index++;
+            }
+        }
+    
+        /* generate faces */
+        index = 0;
+        for (y = 0; y < height; y++) {
+            for (x = 0; x < width; x++) {
+                /* initialise two faces for this (x,y) */
+                grid_face *f1 = g->faces + index;
+                grid_face *f2 = f1 + 1;
+                f1->edges = NULL;
+                f1->order = 3;
+                f1->dots = snewn(f1->order, grid_dot*);
+                f1->has_incentre = FALSE;
+                f2->edges = NULL;
+                f2->order = 3;
+                f2->dots = snewn(f2->order, grid_dot*);
+                f2->has_incentre = FALSE;
+
+                /* face descriptions depend on whether the row-number is
+                 * odd or even */
+                if (y % 2) {
+                    f1->dots[0] = g->dots + y       * w + x;
+                    f1->dots[1] = g->dots + (y + 1) * w + x + 1;
+                    f1->dots[2] = g->dots + (y + 1) * w + x;
+                    f2->dots[0] = g->dots + y       * w + x;
+                    f2->dots[1] = g->dots + y       * w + x + 1;
+                    f2->dots[2] = g->dots + (y + 1) * w + x + 1;
+                } else {
+                    f1->dots[0] = g->dots + y       * w + x;
+                    f1->dots[1] = g->dots + y       * w + x + 1;
+                    f1->dots[2] = g->dots + (y + 1) * w + x;
+                    f2->dots[0] = g->dots + y       * w + x + 1;
+                    f2->dots[1] = g->dots + (y + 1) * w + x + 1;
+                    f2->dots[2] = g->dots + (y + 1) * w + x;
+                }
+                index += 2;
+            }
+        }
+    } else {
+        /*
+         * New-style approach, in which there are never any 'ears',
+         * and if height is even then the grid is nicely 4-way
+         * symmetric.
+         *
+         * Example new-style grids:
+         *
+         *   5x5t1:0_21120b11a1a01a1a00c1a0b211021c1h1a2a1a0a
+         *   5x6t1:0_a1212c22c2a02a2f22a0c12a110d0e1c0c0a101121a1
+         */
+        tree234 *points = newtree234(grid_point_cmp_fn);
+        /* Upper bounds - don't have to be exact */
+        int max_faces = height * (2*width+1);
+        int max_dots = (height+1) * (width+1) * 4;
+
+        g->faces = snewn(max_faces, grid_face);
+        g->dots = snewn(max_dots, grid_dot);
+
+        for (y = 0; y < height; y++) {
+            /*
+             * Each row contains (width+1) triangles one way up, and
+             * (width) triangles the other way up. Which way up is
+             * which varies with parity of y. Also, the dots around
+             * each face will flip direction with parity of y, so we
+             * set up n1 and n2 to cope with that easily.
+             */
+            int y0, y1, n1, n2;
+            y0 = y1 = y * vec_y;
+            if (y % 2) {
+                y1 += vec_y;
+                n1 = 2; n2 = 1;
+            } else {
+                y0 += vec_y;
+                n1 = 1; n2 = 2;
+            }
+
+            for (x = 0; x <= width; x++) {
+                int x0 = 2*x * vec_x, x1 = x0 + vec_x, x2 = x1 + vec_x;
+
+                /*
+                 * If the grid has odd height, then we skip the first
+                 * and last triangles on this row, otherwise they'll
+                 * end up as ears.
+                 */
+                if (height % 2 == 1 && y == height-1 && (x == 0 || x == width))
+                    continue;
+
+                grid_face_add_new(g, 3);
+                grid_face_set_dot(g, grid_get_dot(g, points, x0, y0), 0);
+                grid_face_set_dot(g, grid_get_dot(g, points, x1, y1), n1);
+                grid_face_set_dot(g, grid_get_dot(g, points, x2, y0), n2);
+            }
+
+            for (x = 0; x < width; x++) {
+                int x0 = (2*x+1) * vec_x, x1 = x0 + vec_x, x2 = x1 + vec_x;
+
+                grid_face_add_new(g, 3);
+                grid_face_set_dot(g, grid_get_dot(g, points, x0, y1), 0);
+                grid_face_set_dot(g, grid_get_dot(g, points, x1, y0), n2);
+                grid_face_set_dot(g, grid_get_dot(g, points, x2, y1), n1);
+            }
+        }
+
+        freetree234(points);
+        assert(g->num_faces <= max_faces);
+        assert(g->num_dots <= max_dots);
+    }
+
+    grid_make_consistent(g);
+    return g;
+}
+
+#define SNUBSQUARE_TILESIZE 18
+/* Vector for side of triangle - ratio is close to sqrt(3) */
+#define SNUBSQUARE_A 15
+#define SNUBSQUARE_B 26
+
+static void grid_size_snubsquare(int width, int height,
+                          int *tilesize, int *xextent, int *yextent)
+{
+    int a = SNUBSQUARE_A;
+    int b = SNUBSQUARE_B;
+
+    *tilesize = SNUBSQUARE_TILESIZE;
+    *xextent = (a+b) * (width-1) + a + b;
+    *yextent = (a+b) * (height-1) + a + b;
+}
+
+static grid *grid_new_snubsquare(int width, int height, const char *desc)
+{
+    int x, y;
+    int a = SNUBSQUARE_A;
+    int b = SNUBSQUARE_B;
+
+    /* Upper bounds - don't have to be exact */
+    int max_faces = 3 * width * height;
+    int max_dots = 2 * (width + 1) * (height + 1);
+
+    tree234 *points;
+
+    grid *g = grid_empty();
+    g->tilesize = SNUBSQUARE_TILESIZE;
+    g->faces = snewn(max_faces, grid_face);
+    g->dots = snewn(max_dots, grid_dot);
+
+    points = newtree234(grid_point_cmp_fn);
+
+    for (y = 0; y < height; y++) {
+        for (x = 0; x < width; x++) {
+            grid_dot *d;
+            /* face position */
+            int px = (a + b) * x;
+            int py = (a + b) * y;
+
+            /* generate square faces */
+            grid_face_add_new(g, 4);
+            if ((x + y) % 2) {
+                d = grid_get_dot(g, points, px + a, py);
+                grid_face_set_dot(g, d, 0);
+                d = grid_get_dot(g, points, px + a + b, py + a);
+                grid_face_set_dot(g, d, 1);
+                d = grid_get_dot(g, points, px + b, py + a + b);
+                grid_face_set_dot(g, d, 2);
+                d = grid_get_dot(g, points, px, py + b);
+                grid_face_set_dot(g, d, 3);
+            } else {
+                d = grid_get_dot(g, points, px + b, py);
+                grid_face_set_dot(g, d, 0);
+                d = grid_get_dot(g, points, px + a + b, py + b);
+                grid_face_set_dot(g, d, 1);
+                d = grid_get_dot(g, points, px + a, py + a + b);
+                grid_face_set_dot(g, d, 2);
+                d = grid_get_dot(g, points, px, py + a);
+                grid_face_set_dot(g, d, 3);
+            }
+
+            /* generate up/down triangles */
+            if (x > 0) {
+                grid_face_add_new(g, 3);
+                if ((x + y) % 2) {
+                    d = grid_get_dot(g, points, px + a, py);
+                    grid_face_set_dot(g, d, 0);
+                    d = grid_get_dot(g, points, px, py + b);
+                    grid_face_set_dot(g, d, 1);
+                    d = grid_get_dot(g, points, px - a, py);
+                    grid_face_set_dot(g, d, 2);
+                } else {
+                    d = grid_get_dot(g, points, px, py + a);
+                    grid_face_set_dot(g, d, 0);
+                    d = grid_get_dot(g, points, px + a, py + a + b);
+                    grid_face_set_dot(g, d, 1);
+                    d = grid_get_dot(g, points, px - a, py + a + b);
+                    grid_face_set_dot(g, d, 2);
+                }
+            }
+
+            /* generate left/right triangles */
+            if (y > 0) {
+                grid_face_add_new(g, 3);
+                if ((x + y) % 2) {
+                    d = grid_get_dot(g, points, px + a, py);
+                    grid_face_set_dot(g, d, 0);
+                    d = grid_get_dot(g, points, px + a + b, py - a);
+                    grid_face_set_dot(g, d, 1);
+                    d = grid_get_dot(g, points, px + a + b, py + a);
+                    grid_face_set_dot(g, d, 2);
+                } else {
+                    d = grid_get_dot(g, points, px, py - a);
+                    grid_face_set_dot(g, d, 0);
+                    d = grid_get_dot(g, points, px + b, py);
+                    grid_face_set_dot(g, d, 1);
+                    d = grid_get_dot(g, points, px, py + a);
+                    grid_face_set_dot(g, d, 2);
+                }
+            }
+        }
+    }
+
+    freetree234(points);
+    assert(g->num_faces <= max_faces);
+    assert(g->num_dots <= max_dots);
+
+    grid_make_consistent(g);
+    return g;
+}
+
+#define CAIRO_TILESIZE 40
+/* Vector for side of pentagon - ratio is close to (4+sqrt(7))/3 */
+#define CAIRO_A 14
+#define CAIRO_B 31
+
+static void grid_size_cairo(int width, int height,
+                          int *tilesize, int *xextent, int *yextent)
+{
+    int b = CAIRO_B; /* a unused in determining grid size. */
+
+    *tilesize = CAIRO_TILESIZE;
+    *xextent = 2*b*(width-1) + 2*b;
+    *yextent = 2*b*(height-1) + 2*b;
+}
+
+static grid *grid_new_cairo(int width, int height, const char *desc)
+{
+    int x, y;
+    int a = CAIRO_A;
+    int b = CAIRO_B;
+
+    /* Upper bounds - don't have to be exact */
+    int max_faces = 2 * width * height;
+    int max_dots = 3 * (width + 1) * (height + 1);
+
+    tree234 *points;
+
+    grid *g = grid_empty();
+    g->tilesize = CAIRO_TILESIZE;
+    g->faces = snewn(max_faces, grid_face);
+    g->dots = snewn(max_dots, grid_dot);
+
+    points = newtree234(grid_point_cmp_fn);
+
+    for (y = 0; y < height; y++) {
+        for (x = 0; x < width; x++) {
+            grid_dot *d;
+            /* cell position */
+            int px = 2 * b * x;
+            int py = 2 * b * y;
+
+            /* horizontal pentagons */
+            if (y > 0) {
+                grid_face_add_new(g, 5);
+                if ((x + y) % 2) {
+                    d = grid_get_dot(g, points, px + a, py - b);
+                    grid_face_set_dot(g, d, 0);
+                    d = grid_get_dot(g, points, px + 2*b - a, py - b);
+                    grid_face_set_dot(g, d, 1);
+                    d = grid_get_dot(g, points, px + 2*b, py);
+                    grid_face_set_dot(g, d, 2);
+                    d = grid_get_dot(g, points, px + b, py + a);
+                    grid_face_set_dot(g, d, 3);
+                    d = grid_get_dot(g, points, px, py);
+                    grid_face_set_dot(g, d, 4);
+                } else {
+                    d = grid_get_dot(g, points, px, py);
+                    grid_face_set_dot(g, d, 0);
+                    d = grid_get_dot(g, points, px + b, py - a);
+                    grid_face_set_dot(g, d, 1);
+                    d = grid_get_dot(g, points, px + 2*b, py);
+                    grid_face_set_dot(g, d, 2);
+                    d = grid_get_dot(g, points, px + 2*b - a, py + b);
+                    grid_face_set_dot(g, d, 3);
+                    d = grid_get_dot(g, points, px + a, py + b);
+                    grid_face_set_dot(g, d, 4);
+                }
+            }
+            /* vertical pentagons */
+            if (x > 0) {
+                grid_face_add_new(g, 5);
+                if ((x + y) % 2) {
+                    d = grid_get_dot(g, points, px, py);
+                    grid_face_set_dot(g, d, 0);
+                    d = grid_get_dot(g, points, px + b, py + a);
+                    grid_face_set_dot(g, d, 1);
+                    d = grid_get_dot(g, points, px + b, py + 2*b - a);
+                    grid_face_set_dot(g, d, 2);
+                    d = grid_get_dot(g, points, px, py + 2*b);
+                    grid_face_set_dot(g, d, 3);
+                    d = grid_get_dot(g, points, px - a, py + b);
+                    grid_face_set_dot(g, d, 4);
+                } else {
+                    d = grid_get_dot(g, points, px, py);
+                    grid_face_set_dot(g, d, 0);
+                    d = grid_get_dot(g, points, px + a, py + b);
+                    grid_face_set_dot(g, d, 1);
+                    d = grid_get_dot(g, points, px, py + 2*b);
+                    grid_face_set_dot(g, d, 2);
+                    d = grid_get_dot(g, points, px - b, py + 2*b - a);
+                    grid_face_set_dot(g, d, 3);
+                    d = grid_get_dot(g, points, px - b, py + a);
+                    grid_face_set_dot(g, d, 4);
+                }
+            }
+        }
+    }
+
+    freetree234(points);
+    assert(g->num_faces <= max_faces);
+    assert(g->num_dots <= max_dots);
+
+    grid_make_consistent(g);
+    return g;
+}
+
+#define GREATHEX_TILESIZE 18
+/* Vector for side of triangle - ratio is close to sqrt(3) */
+#define GREATHEX_A 15
+#define GREATHEX_B 26
+
+static void grid_size_greathexagonal(int width, int height,
+                          int *tilesize, int *xextent, int *yextent)
+{
+    int a = GREATHEX_A;
+    int b = GREATHEX_B;
+
+    *tilesize = GREATHEX_TILESIZE;
+    *xextent = (3*a + b) * (width-1) + 4*a;
+    *yextent = (2*a + 2*b) * (height-1) + 3*b + a;
+}
+
+static grid *grid_new_greathexagonal(int width, int height, const char *desc)
+{
+    int x, y;
+    int a = GREATHEX_A;
+    int b = GREATHEX_B;
+
+    /* Upper bounds - don't have to be exact */
+    int max_faces = 6 * (width + 1) * (height + 1);
+    int max_dots = 6 * width * height;
+
+    tree234 *points;
+
+    grid *g = grid_empty();
+    g->tilesize = GREATHEX_TILESIZE;
+    g->faces = snewn(max_faces, grid_face);
+    g->dots = snewn(max_dots, grid_dot);
+
+    points = newtree234(grid_point_cmp_fn);
+
+    for (y = 0; y < height; y++) {
+        for (x = 0; x < width; x++) {
+            grid_dot *d;
+            /* centre of hexagon */
+            int px = (3*a + b) * x;
+            int py = (2*a + 2*b) * y;
+            if (x % 2)
+                py += a + b;
+
+            /* hexagon */
+            grid_face_add_new(g, 6);
+            d = grid_get_dot(g, points, px - a, py - b);
+            grid_face_set_dot(g, d, 0);
+            d = grid_get_dot(g, points, px + a, py - b);
+            grid_face_set_dot(g, d, 1);
+            d = grid_get_dot(g, points, px + 2*a, py);
+            grid_face_set_dot(g, d, 2);
+            d = grid_get_dot(g, points, px + a, py + b);
+            grid_face_set_dot(g, d, 3);
+            d = grid_get_dot(g, points, px - a, py + b);
+            grid_face_set_dot(g, d, 4);
+            d = grid_get_dot(g, points, px - 2*a, py);
+            grid_face_set_dot(g, d, 5);
+
+            /* square below hexagon */
+            if (y < height - 1) {
+                grid_face_add_new(g, 4);
+                d = grid_get_dot(g, points, px - a, py + b);
+                grid_face_set_dot(g, d, 0);
+                d = grid_get_dot(g, points, px + a, py + b);
+                grid_face_set_dot(g, d, 1);
+                d = grid_get_dot(g, points, px + a, py + 2*a + b);
+                grid_face_set_dot(g, d, 2);
+                d = grid_get_dot(g, points, px - a, py + 2*a + b);
+                grid_face_set_dot(g, d, 3);
+            }
+
+            /* square below right */
+            if ((x < width - 1) && (((x % 2) == 0) || (y < height - 1))) {
+                grid_face_add_new(g, 4);
+                d = grid_get_dot(g, points, px + 2*a, py);
+                grid_face_set_dot(g, d, 0);
+                d = grid_get_dot(g, points, px + 2*a + b, py + a);
+                grid_face_set_dot(g, d, 1);
+                d = grid_get_dot(g, points, px + a + b, py + a + b);
+                grid_face_set_dot(g, d, 2);
+                d = grid_get_dot(g, points, px + a, py + b);
+                grid_face_set_dot(g, d, 3);
+            }
+
+            /* square below left */
+            if ((x > 0) && (((x % 2) == 0) || (y < height - 1))) {
+                grid_face_add_new(g, 4);
+                d = grid_get_dot(g, points, px - 2*a, py);
+                grid_face_set_dot(g, d, 0);
+                d = grid_get_dot(g, points, px - a, py + b);
+                grid_face_set_dot(g, d, 1);
+                d = grid_get_dot(g, points, px - a - b, py + a + b);
+                grid_face_set_dot(g, d, 2);
+                d = grid_get_dot(g, points, px - 2*a - b, py + a);
+                grid_face_set_dot(g, d, 3);
+            }
+           
+            /* Triangle below right */
+            if ((x < width - 1) && (y < height - 1)) {
+                grid_face_add_new(g, 3);
+                d = grid_get_dot(g, points, px + a, py + b);
+                grid_face_set_dot(g, d, 0);
+                d = grid_get_dot(g, points, px + a + b, py + a + b);
+                grid_face_set_dot(g, d, 1);
+                d = grid_get_dot(g, points, px + a, py + 2*a + b);
+                grid_face_set_dot(g, d, 2);
+            }
+
+            /* Triangle below left */
+            if ((x > 0) && (y < height - 1)) {
+                grid_face_add_new(g, 3);
+                d = grid_get_dot(g, points, px - a, py + b);
+                grid_face_set_dot(g, d, 0);
+                d = grid_get_dot(g, points, px - a, py + 2*a + b);
+                grid_face_set_dot(g, d, 1);
+                d = grid_get_dot(g, points, px - a - b, py + a + b);
+                grid_face_set_dot(g, d, 2);
+            }
+        }
+    }
+
+    freetree234(points);
+    assert(g->num_faces <= max_faces);
+    assert(g->num_dots <= max_dots);
+
+    grid_make_consistent(g);
+    return g;
+}
+
+#define OCTAGONAL_TILESIZE 40
+/* b/a approx sqrt(2) */
+#define OCTAGONAL_A 29
+#define OCTAGONAL_B 41
+
+static void grid_size_octagonal(int width, int height,
+                          int *tilesize, int *xextent, int *yextent)
+{
+    int a = OCTAGONAL_A;
+    int b = OCTAGONAL_B;
+
+    *tilesize = OCTAGONAL_TILESIZE;
+    *xextent = (2*a + b) * width;
+    *yextent = (2*a + b) * height;
+}
+
+static grid *grid_new_octagonal(int width, int height, const char *desc)
+{
+    int x, y;
+    int a = OCTAGONAL_A;
+    int b = OCTAGONAL_B;
+
+    /* Upper bounds - don't have to be exact */
+    int max_faces = 2 * width * height;
+    int max_dots = 4 * (width + 1) * (height + 1);
+
+    tree234 *points;
+
+    grid *g = grid_empty();
+    g->tilesize = OCTAGONAL_TILESIZE;
+    g->faces = snewn(max_faces, grid_face);
+    g->dots = snewn(max_dots, grid_dot);
+
+    points = newtree234(grid_point_cmp_fn);
+
+    for (y = 0; y < height; y++) {
+        for (x = 0; x < width; x++) {
+            grid_dot *d;
+            /* cell position */
+            int px = (2*a + b) * x;
+            int py = (2*a + b) * y;
+            /* octagon */
+            grid_face_add_new(g, 8);
+            d = grid_get_dot(g, points, px + a, py);
+            grid_face_set_dot(g, d, 0);
+            d = grid_get_dot(g, points, px + a + b, py);
+            grid_face_set_dot(g, d, 1);
+            d = grid_get_dot(g, points, px + 2*a + b, py + a);
+            grid_face_set_dot(g, d, 2);
+            d = grid_get_dot(g, points, px + 2*a + b, py + a + b);
+            grid_face_set_dot(g, d, 3);
+            d = grid_get_dot(g, points, px + a + b, py + 2*a + b);
+            grid_face_set_dot(g, d, 4);
+            d = grid_get_dot(g, points, px + a, py + 2*a + b);
+            grid_face_set_dot(g, d, 5);
+            d = grid_get_dot(g, points, px, py + a + b);
+            grid_face_set_dot(g, d, 6);
+            d = grid_get_dot(g, points, px, py + a);
+            grid_face_set_dot(g, d, 7);
+
+            /* diamond */
+            if ((x > 0) && (y > 0)) {
+                grid_face_add_new(g, 4);
+                d = grid_get_dot(g, points, px, py - a);
+                grid_face_set_dot(g, d, 0);
+                d = grid_get_dot(g, points, px + a, py);
+                grid_face_set_dot(g, d, 1);
+                d = grid_get_dot(g, points, px, py + a);
+                grid_face_set_dot(g, d, 2);
+                d = grid_get_dot(g, points, px - a, py);
+                grid_face_set_dot(g, d, 3);
+            }
+        }
+    }
+
+    freetree234(points);
+    assert(g->num_faces <= max_faces);
+    assert(g->num_dots <= max_dots);
+
+    grid_make_consistent(g);
+    return g;
+}
+
+#define KITE_TILESIZE 40
+/* b/a approx sqrt(3) */
+#define KITE_A 15
+#define KITE_B 26
+
+static void grid_size_kites(int width, int height,
+                     int *tilesize, int *xextent, int *yextent)
+{
+    int a = KITE_A;
+    int b = KITE_B;
+
+    *tilesize = KITE_TILESIZE;
+    *xextent = 4*b * width + 2*b;
+    *yextent = 6*a * (height-1) + 8*a;
+}
+
+static grid *grid_new_kites(int width, int height, const char *desc)
+{
+    int x, y;
+    int a = KITE_A;
+    int b = KITE_B;
+
+    /* Upper bounds - don't have to be exact */
+    int max_faces = 6 * width * height;
+    int max_dots = 6 * (width + 1) * (height + 1);
+
+    tree234 *points;
+
+    grid *g = grid_empty();
+    g->tilesize = KITE_TILESIZE;
+    g->faces = snewn(max_faces, grid_face);
+    g->dots = snewn(max_dots, grid_dot);
+
+    points = newtree234(grid_point_cmp_fn);
+
+    for (y = 0; y < height; y++) {
+        for (x = 0; x < width; x++) {
+            grid_dot *d;
+            /* position of order-6 dot */
+            int px = 4*b * x;
+            int py = 6*a * y;
+            if (y % 2)
+                px += 2*b;
+
+            /* kite pointing up-left */
+            grid_face_add_new(g, 4);
+            d = grid_get_dot(g, points, px, py);
+            grid_face_set_dot(g, d, 0);
+            d = grid_get_dot(g, points, px + 2*b, py);
+            grid_face_set_dot(g, d, 1);
+            d = grid_get_dot(g, points, px + 2*b, py + 2*a);
+            grid_face_set_dot(g, d, 2);
+            d = grid_get_dot(g, points, px + b, py + 3*a);
+            grid_face_set_dot(g, d, 3);
+
+            /* kite pointing up */
+            grid_face_add_new(g, 4);
+            d = grid_get_dot(g, points, px, py);
+            grid_face_set_dot(g, d, 0);
+            d = grid_get_dot(g, points, px + b, py + 3*a);
+            grid_face_set_dot(g, d, 1);
+            d = grid_get_dot(g, points, px, py + 4*a);
+            grid_face_set_dot(g, d, 2);
+            d = grid_get_dot(g, points, px - b, py + 3*a);
+            grid_face_set_dot(g, d, 3);
+
+            /* kite pointing up-right */
+            grid_face_add_new(g, 4);
+            d = grid_get_dot(g, points, px, py);
+            grid_face_set_dot(g, d, 0);
+            d = grid_get_dot(g, points, px - b, py + 3*a);
+            grid_face_set_dot(g, d, 1);
+            d = grid_get_dot(g, points, px - 2*b, py + 2*a);
+            grid_face_set_dot(g, d, 2);
+            d = grid_get_dot(g, points, px - 2*b, py);
+            grid_face_set_dot(g, d, 3);
+
+            /* kite pointing down-right */
+            grid_face_add_new(g, 4);
+            d = grid_get_dot(g, points, px, py);
+            grid_face_set_dot(g, d, 0);
+            d = grid_get_dot(g, points, px - 2*b, py);
+            grid_face_set_dot(g, d, 1);
+            d = grid_get_dot(g, points, px - 2*b, py - 2*a);
+            grid_face_set_dot(g, d, 2);
+            d = grid_get_dot(g, points, px - b, py - 3*a);
+            grid_face_set_dot(g, d, 3);
+
+            /* kite pointing down */
+            grid_face_add_new(g, 4);
+            d = grid_get_dot(g, points, px, py);
+            grid_face_set_dot(g, d, 0);
+            d = grid_get_dot(g, points, px - b, py - 3*a);
+            grid_face_set_dot(g, d, 1);
+            d = grid_get_dot(g, points, px, py - 4*a);
+            grid_face_set_dot(g, d, 2);
+            d = grid_get_dot(g, points, px + b, py - 3*a);
+            grid_face_set_dot(g, d, 3);
+
+            /* kite pointing down-left */
+            grid_face_add_new(g, 4);
+            d = grid_get_dot(g, points, px, py);
+            grid_face_set_dot(g, d, 0);
+            d = grid_get_dot(g, points, px + b, py - 3*a);
+            grid_face_set_dot(g, d, 1);
+            d = grid_get_dot(g, points, px + 2*b, py - 2*a);
+            grid_face_set_dot(g, d, 2);
+            d = grid_get_dot(g, points, px + 2*b, py);
+            grid_face_set_dot(g, d, 3);
+        }
+    }
+
+    freetree234(points);
+    assert(g->num_faces <= max_faces);
+    assert(g->num_dots <= max_dots);
+
+    grid_make_consistent(g);
+    return g;
+}
+
+#define FLORET_TILESIZE 150
+/* -py/px is close to tan(30 - atan(sqrt(3)/9))
+ * using py=26 makes everything lean to the left, rather than right
+ */
+#define FLORET_PX 75
+#define FLORET_PY -26
+
+static void grid_size_floret(int width, int height,
+                          int *tilesize, int *xextent, int *yextent)
+{
+    int px = FLORET_PX, py = FLORET_PY;         /* |( 75, -26)| = 79.43 */
+    int qx = 4*px/5, qy = -py*2;                /* |( 60,  52)| = 79.40 */
+    int ry = qy-py;
+    /* rx unused in determining grid size. */
+
+    *tilesize = FLORET_TILESIZE;
+    *xextent = (6*px+3*qx)/2 * (width-1) + 4*qx + 2*px;
+    *yextent = (5*qy-4*py) * (height-1) + 4*qy + 2*ry;
+}
+
+static grid *grid_new_floret(int width, int height, const char *desc)
+{
+    int x, y;
+    /* Vectors for sides; weird numbers needed to keep puzzle aligned with window
+     * -py/px is close to tan(30 - atan(sqrt(3)/9))
+     * using py=26 makes everything lean to the left, rather than right
+     */
+    int px = FLORET_PX, py = FLORET_PY;         /* |( 75, -26)| = 79.43 */
+    int qx = 4*px/5, qy = -py*2;                /* |( 60,  52)| = 79.40 */
+    int rx = qx-px, ry = qy-py;                 /* |(-15,  78)| = 79.38 */
+
+    /* Upper bounds - don't have to be exact */
+    int max_faces = 6 * width * height;
+    int max_dots = 9 * (width + 1) * (height + 1);
+
+    tree234 *points;
+
+    grid *g = grid_empty();
+    g->tilesize = FLORET_TILESIZE;
+    g->faces = snewn(max_faces, grid_face);
+    g->dots = snewn(max_dots, grid_dot);
+
+    points = newtree234(grid_point_cmp_fn);
+
+    /* generate pentagonal faces */
+    for (y = 0; y < height; y++) {
+        for (x = 0; x < width; x++) {
+            grid_dot *d;
+            /* face centre */
+            int cx = (6*px+3*qx)/2 * x;
+            int cy = (4*py-5*qy) * y;
+            if (x % 2)
+                cy -= (4*py-5*qy)/2;
+            else if (y && y == height-1)
+                continue; /* make better looking grids?  try 3x3 for instance */
+
+            grid_face_add_new(g, 5);
+            d = grid_get_dot(g, points, cx        , cy        ); grid_face_set_dot(g, d, 0);
+            d = grid_get_dot(g, points, cx+2*rx   , cy+2*ry   ); grid_face_set_dot(g, d, 1);
+            d = grid_get_dot(g, points, cx+2*rx+qx, cy+2*ry+qy); grid_face_set_dot(g, d, 2);
+            d = grid_get_dot(g, points, cx+2*qx+rx, cy+2*qy+ry); grid_face_set_dot(g, d, 3);
+            d = grid_get_dot(g, points, cx+2*qx   , cy+2*qy   ); grid_face_set_dot(g, d, 4);
+
+            grid_face_add_new(g, 5);
+            d = grid_get_dot(g, points, cx        , cy        ); grid_face_set_dot(g, d, 0);
+            d = grid_get_dot(g, points, cx+2*qx   , cy+2*qy   ); grid_face_set_dot(g, d, 1);
+            d = grid_get_dot(g, points, cx+2*qx+px, cy+2*qy+py); grid_face_set_dot(g, d, 2);
+            d = grid_get_dot(g, points, cx+2*px+qx, cy+2*py+qy); grid_face_set_dot(g, d, 3);
+            d = grid_get_dot(g, points, cx+2*px   , cy+2*py   ); grid_face_set_dot(g, d, 4);
+
+            grid_face_add_new(g, 5);
+            d = grid_get_dot(g, points, cx        , cy        ); grid_face_set_dot(g, d, 0);
+            d = grid_get_dot(g, points, cx+2*px   , cy+2*py   ); grid_face_set_dot(g, d, 1);
+            d = grid_get_dot(g, points, cx+2*px-rx, cy+2*py-ry); grid_face_set_dot(g, d, 2);
+            d = grid_get_dot(g, points, cx-2*rx+px, cy-2*ry+py); grid_face_set_dot(g, d, 3);
+            d = grid_get_dot(g, points, cx-2*rx   , cy-2*ry   ); grid_face_set_dot(g, d, 4);
+
+            grid_face_add_new(g, 5);
+            d = grid_get_dot(g, points, cx        , cy        ); grid_face_set_dot(g, d, 0);
+            d = grid_get_dot(g, points, cx-2*rx   , cy-2*ry   ); grid_face_set_dot(g, d, 1);
+            d = grid_get_dot(g, points, cx-2*rx-qx, cy-2*ry-qy); grid_face_set_dot(g, d, 2);
+            d = grid_get_dot(g, points, cx-2*qx-rx, cy-2*qy-ry); grid_face_set_dot(g, d, 3);
+            d = grid_get_dot(g, points, cx-2*qx   , cy-2*qy   ); grid_face_set_dot(g, d, 4);
+
+            grid_face_add_new(g, 5);
+            d = grid_get_dot(g, points, cx        , cy        ); grid_face_set_dot(g, d, 0);
+            d = grid_get_dot(g, points, cx-2*qx   , cy-2*qy   ); grid_face_set_dot(g, d, 1);
+            d = grid_get_dot(g, points, cx-2*qx-px, cy-2*qy-py); grid_face_set_dot(g, d, 2);
+            d = grid_get_dot(g, points, cx-2*px-qx, cy-2*py-qy); grid_face_set_dot(g, d, 3);
+            d = grid_get_dot(g, points, cx-2*px   , cy-2*py   ); grid_face_set_dot(g, d, 4);
+
+            grid_face_add_new(g, 5);
+            d = grid_get_dot(g, points, cx        , cy        ); grid_face_set_dot(g, d, 0);
+            d = grid_get_dot(g, points, cx-2*px   , cy-2*py   ); grid_face_set_dot(g, d, 1);
+            d = grid_get_dot(g, points, cx-2*px+rx, cy-2*py+ry); grid_face_set_dot(g, d, 2);
+            d = grid_get_dot(g, points, cx+2*rx-px, cy+2*ry-py); grid_face_set_dot(g, d, 3);
+            d = grid_get_dot(g, points, cx+2*rx   , cy+2*ry   ); grid_face_set_dot(g, d, 4);
+        }
+    }
+
+    freetree234(points);
+    assert(g->num_faces <= max_faces);
+    assert(g->num_dots <= max_dots);
+
+    grid_make_consistent(g);
+    return g;
+}
+
+/* DODEC_* are used for dodecagonal and great-dodecagonal grids. */
+#define DODEC_TILESIZE 26
+/* Vector for side of triangle - ratio is close to sqrt(3) */
+#define DODEC_A 15
+#define DODEC_B 26
+
+static void grid_size_dodecagonal(int width, int height,
+                          int *tilesize, int *xextent, int *yextent)
+{
+    int a = DODEC_A;
+    int b = DODEC_B;
+
+    *tilesize = DODEC_TILESIZE;
+    *xextent = (4*a + 2*b) * (width-1) + 3*(2*a + b);
+    *yextent = (3*a + 2*b) * (height-1) + 2*(2*a + b);
+}
+
+static grid *grid_new_dodecagonal(int width, int height, const char *desc)
+{
+    int x, y;
+    int a = DODEC_A;
+    int b = DODEC_B;
+
+    /* Upper bounds - don't have to be exact */
+    int max_faces = 3 * width * height;
+    int max_dots = 14 * width * height;
+
+    tree234 *points;
+
+    grid *g = grid_empty();
+    g->tilesize = DODEC_TILESIZE;
+    g->faces = snewn(max_faces, grid_face);
+    g->dots = snewn(max_dots, grid_dot);
+
+    points = newtree234(grid_point_cmp_fn);
+
+    for (y = 0; y < height; y++) {
+        for (x = 0; x < width; x++) {
+            grid_dot *d;
+            /* centre of dodecagon */
+            int px = (4*a + 2*b) * x;
+            int py = (3*a + 2*b) * y;
+            if (y % 2)
+                px += 2*a + b;
+
+            /* dodecagon */
+            grid_face_add_new(g, 12);
+            d = grid_get_dot(g, points, px + (  a    ), py - (2*a + b)); grid_face_set_dot(g, d, 0);
+            d = grid_get_dot(g, points, px + (  a + b), py - (  a + b)); grid_face_set_dot(g, d, 1);
+            d = grid_get_dot(g, points, px + (2*a + b), py - (  a    )); grid_face_set_dot(g, d, 2);
+            d = grid_get_dot(g, points, px + (2*a + b), py + (  a    )); grid_face_set_dot(g, d, 3);
+            d = grid_get_dot(g, points, px + (  a + b), py + (  a + b)); grid_face_set_dot(g, d, 4);
+            d = grid_get_dot(g, points, px + (  a    ), py + (2*a + b)); grid_face_set_dot(g, d, 5);
+            d = grid_get_dot(g, points, px - (  a    ), py + (2*a + b)); grid_face_set_dot(g, d, 6);
+            d = grid_get_dot(g, points, px - (  a + b), py + (  a + b)); grid_face_set_dot(g, d, 7);
+            d = grid_get_dot(g, points, px - (2*a + b), py + (  a    )); grid_face_set_dot(g, d, 8);
+            d = grid_get_dot(g, points, px - (2*a + b), py - (  a    )); grid_face_set_dot(g, d, 9);
+            d = grid_get_dot(g, points, px - (  a + b), py - (  a + b)); grid_face_set_dot(g, d, 10);
+            d = grid_get_dot(g, points, px - (  a    ), py - (2*a + b)); grid_face_set_dot(g, d, 11);
+
+            /* triangle below dodecagon */
+           if ((y < height - 1 && (x < width - 1 || !(y % 2)) && (x > 0 || (y % 2)))) {
+               grid_face_add_new(g, 3);
+               d = grid_get_dot(g, points, px + a, py + (2*a +   b)); grid_face_set_dot(g, d, 0);
+               d = grid_get_dot(g, points, px    , py + (2*a + 2*b)); grid_face_set_dot(g, d, 1);
+               d = grid_get_dot(g, points, px - a, py + (2*a +   b)); grid_face_set_dot(g, d, 2);
+           }
+
+            /* triangle above dodecagon */
+           if ((y && (x < width - 1 || !(y % 2)) && (x > 0 || (y % 2)))) {
+               grid_face_add_new(g, 3);
+               d = grid_get_dot(g, points, px - a, py - (2*a +   b)); grid_face_set_dot(g, d, 0);
+               d = grid_get_dot(g, points, px    , py - (2*a + 2*b)); grid_face_set_dot(g, d, 1);
+               d = grid_get_dot(g, points, px + a, py - (2*a +   b)); grid_face_set_dot(g, d, 2);
+           }
+       }
+    }
+
+    freetree234(points);
+    assert(g->num_faces <= max_faces);
+    assert(g->num_dots <= max_dots);
+
+    grid_make_consistent(g);
+    return g;
+}
+
+static void grid_size_greatdodecagonal(int width, int height,
+                          int *tilesize, int *xextent, int *yextent)
+{
+    int a = DODEC_A;
+    int b = DODEC_B;
+
+    *tilesize = DODEC_TILESIZE;
+    *xextent = (6*a + 2*b) * (width-1) + 2*(2*a + b) + 3*a + b;
+    *yextent = (3*a + 3*b) * (height-1) + 2*(2*a + b);
+}
+
+static grid *grid_new_greatdodecagonal(int width, int height, const char *desc)
+{
+    int x, y;
+    /* Vector for side of triangle - ratio is close to sqrt(3) */
+    int a = DODEC_A;
+    int b = DODEC_B;
+
+    /* Upper bounds - don't have to be exact */
+    int max_faces = 30 * width * height;
+    int max_dots = 200 * width * height;
+
+    tree234 *points;
+
+    grid *g = grid_empty();
+    g->tilesize = DODEC_TILESIZE;
+    g->faces = snewn(max_faces, grid_face);
+    g->dots = snewn(max_dots, grid_dot);
+
+    points = newtree234(grid_point_cmp_fn);
+
+    for (y = 0; y < height; y++) {
+        for (x = 0; x < width; x++) {
+            grid_dot *d;
+            /* centre of dodecagon */
+            int px = (6*a + 2*b) * x;
+            int py = (3*a + 3*b) * y;
+            if (y % 2)
+                px += 3*a + b;
+
+            /* dodecagon */
+            grid_face_add_new(g, 12);
+            d = grid_get_dot(g, points, px + (  a    ), py - (2*a + b)); grid_face_set_dot(g, d, 0);
+            d = grid_get_dot(g, points, px + (  a + b), py - (  a + b)); grid_face_set_dot(g, d, 1);
+            d = grid_get_dot(g, points, px + (2*a + b), py - (  a    )); grid_face_set_dot(g, d, 2);
+            d = grid_get_dot(g, points, px + (2*a + b), py + (  a    )); grid_face_set_dot(g, d, 3);
+            d = grid_get_dot(g, points, px + (  a + b), py + (  a + b)); grid_face_set_dot(g, d, 4);
+            d = grid_get_dot(g, points, px + (  a    ), py + (2*a + b)); grid_face_set_dot(g, d, 5);
+            d = grid_get_dot(g, points, px - (  a    ), py + (2*a + b)); grid_face_set_dot(g, d, 6);
+            d = grid_get_dot(g, points, px - (  a + b), py + (  a + b)); grid_face_set_dot(g, d, 7);
+            d = grid_get_dot(g, points, px - (2*a + b), py + (  a    )); grid_face_set_dot(g, d, 8);
+            d = grid_get_dot(g, points, px - (2*a + b), py - (  a    )); grid_face_set_dot(g, d, 9);
+            d = grid_get_dot(g, points, px - (  a + b), py - (  a + b)); grid_face_set_dot(g, d, 10);
+            d = grid_get_dot(g, points, px - (  a    ), py - (2*a + b)); grid_face_set_dot(g, d, 11);
+
+            /* hexagon below dodecagon */
+           if (y < height - 1 && (x < width - 1 || !(y % 2)) && (x > 0 || (y % 2))) {
+               grid_face_add_new(g, 6);
+               d = grid_get_dot(g, points, px +   a, py + (2*a +   b)); grid_face_set_dot(g, d, 0);
+               d = grid_get_dot(g, points, px + 2*a, py + (2*a + 2*b)); grid_face_set_dot(g, d, 1);
+               d = grid_get_dot(g, points, px +   a, py + (2*a + 3*b)); grid_face_set_dot(g, d, 2);
+               d = grid_get_dot(g, points, px -   a, py + (2*a + 3*b)); grid_face_set_dot(g, d, 3);
+               d = grid_get_dot(g, points, px - 2*a, py + (2*a + 2*b)); grid_face_set_dot(g, d, 4);
+               d = grid_get_dot(g, points, px -   a, py + (2*a +   b)); grid_face_set_dot(g, d, 5);
+           }
+
+            /* hexagon above dodecagon */
+           if (y && (x < width - 1 || !(y % 2)) && (x > 0 || (y % 2))) {
+               grid_face_add_new(g, 6);
+               d = grid_get_dot(g, points, px -   a, py - (2*a +   b)); grid_face_set_dot(g, d, 0);
+               d = grid_get_dot(g, points, px - 2*a, py - (2*a + 2*b)); grid_face_set_dot(g, d, 1);
+               d = grid_get_dot(g, points, px -   a, py - (2*a + 3*b)); grid_face_set_dot(g, d, 2);
+               d = grid_get_dot(g, points, px +   a, py - (2*a + 3*b)); grid_face_set_dot(g, d, 3);
+               d = grid_get_dot(g, points, px + 2*a, py - (2*a + 2*b)); grid_face_set_dot(g, d, 4);
+               d = grid_get_dot(g, points, px +   a, py - (2*a +   b)); grid_face_set_dot(g, d, 5);
+           }
+
+            /* square on right of dodecagon */
+           if (x < width - 1) {
+               grid_face_add_new(g, 4);
+               d = grid_get_dot(g, points, px + 2*a + b, py - a); grid_face_set_dot(g, d, 0);
+               d = grid_get_dot(g, points, px + 4*a + b, py - a); grid_face_set_dot(g, d, 1);
+               d = grid_get_dot(g, points, px + 4*a + b, py + a); grid_face_set_dot(g, d, 2);
+               d = grid_get_dot(g, points, px + 2*a + b, py + a); grid_face_set_dot(g, d, 3);
+           }
+
+            /* square on top right of dodecagon */
+           if (y && (x < width - 1 || !(y % 2))) {
+               grid_face_add_new(g, 4);
+               d = grid_get_dot(g, points, px + (  a    ), py - (2*a +   b)); grid_face_set_dot(g, d, 0);
+               d = grid_get_dot(g, points, px + (2*a    ), py - (2*a + 2*b)); grid_face_set_dot(g, d, 1);
+               d = grid_get_dot(g, points, px + (2*a + b), py - (  a + 2*b)); grid_face_set_dot(g, d, 2);
+               d = grid_get_dot(g, points, px + (  a + b), py - (  a +   b)); grid_face_set_dot(g, d, 3);
+           }
+
+            /* square on top left of dodecagon */
+           if (y && (x || (y % 2))) {
+               grid_face_add_new(g, 4);
+               d = grid_get_dot(g, points, px - (  a + b), py - (  a +   b)); grid_face_set_dot(g, d, 0);
+               d = grid_get_dot(g, points, px - (2*a + b), py - (  a + 2*b)); grid_face_set_dot(g, d, 1);
+               d = grid_get_dot(g, points, px - (2*a    ), py - (2*a + 2*b)); grid_face_set_dot(g, d, 2);
+               d = grid_get_dot(g, points, px - (  a    ), py - (2*a +   b)); grid_face_set_dot(g, d, 3);
+           }
+       }
+    }
+
+    freetree234(points);
+    assert(g->num_faces <= max_faces);
+    assert(g->num_dots <= max_dots);
+
+    grid_make_consistent(g);
+    return g;
+}
+
+typedef struct setface_ctx
+{
+    int xmin, xmax, ymin, ymax;
+
+    grid *g;
+    tree234 *points;
+} setface_ctx;
+
+static double round_int_nearest_away(double r)
+{
+    return (r > 0.0) ? floor(r + 0.5) : ceil(r - 0.5);
+}
+
+static int set_faces(penrose_state *state, vector *vs, int n, int depth)
+{
+    setface_ctx *sf_ctx = (setface_ctx *)state->ctx;
+    int i;
+    int xs[4], ys[4];
+
+    if (depth < state->max_depth) return 0;
+#ifdef DEBUG_PENROSE
+    if (n != 4) return 0; /* triangles are sent as debugging. */
+#endif
+
+    for (i = 0; i < n; i++) {
+        double tx = v_x(vs, i), ty = v_y(vs, i);
+
+        xs[i] = (int)round_int_nearest_away(tx);
+        ys[i] = (int)round_int_nearest_away(ty);
+
+        if (xs[i] < sf_ctx->xmin || xs[i] > sf_ctx->xmax) return 0;
+        if (ys[i] < sf_ctx->ymin || ys[i] > sf_ctx->ymax) return 0;
+    }
+
+    grid_face_add_new(sf_ctx->g, n);
+    debug(("penrose: new face l=%f gen=%d...",
+           penrose_side_length(state->start_size, depth), depth));
+    for (i = 0; i < n; i++) {
+        grid_dot *d = grid_get_dot(sf_ctx->g, sf_ctx->points,
+                                   xs[i], ys[i]);
+        grid_face_set_dot(sf_ctx->g, d, i);
+        debug((" ... dot 0x%x (%d,%d) (was %2.2f,%2.2f)",
+               d, d->x, d->y, v_x(vs, i), v_y(vs, i)));
+    }
+
+    return 0;
+}
+
+#define PENROSE_TILESIZE 100
+
+static void grid_size_penrose(int width, int height,
+                       int *tilesize, int *xextent, int *yextent)
+{
+    int l = PENROSE_TILESIZE;
+
+    *tilesize = l;
+    *xextent = l * width;
+    *yextent = l * height;
+}
+
+static grid *grid_new_penrose(int width, int height, int which, const char *desc); /* forward reference */
+
+static char *grid_new_desc_penrose(grid_type type, int width, int height, random_state *rs)
+{
+    int tilesize = PENROSE_TILESIZE, startsz, depth, xoff, yoff, aoff;
+    double outer_radius;
+    int inner_radius;
+    char gd[255];
+    int which = (type == GRID_PENROSE_P2 ? PENROSE_P2 : PENROSE_P3);
+    grid *g;
+
+    while (1) {
+        /* We want to produce a random bit of penrose tiling, so we
+         * calculate a random offset (within the patch that penrose.c
+         * calculates for us) and an angle (multiple of 36) to rotate
+         * the patch. */
+
+        penrose_calculate_size(which, tilesize, width, height,
+                               &outer_radius, &startsz, &depth);
+
+        /* Calculate radius of (circumcircle of) patch, subtract from
+         * radius calculated. */
+        inner_radius = (int)(outer_radius - sqrt(width*width + height*height));
+
+        /* Pick a random offset (the easy way: choose within outer
+         * square, discarding while it's outside the circle) */
+        do {
+            xoff = random_upto(rs, 2*inner_radius) - inner_radius;
+            yoff = random_upto(rs, 2*inner_radius) - inner_radius;
+        } while (sqrt(xoff*xoff+yoff*yoff) > inner_radius);
+
+        aoff = random_upto(rs, 360/36) * 36;
+
+        debug(("grid_desc: ts %d, %dx%d patch, orad %2.2f irad %d",
+               tilesize, width, height, outer_radius, inner_radius));
+        debug(("    -> xoff %d yoff %d aoff %d", xoff, yoff, aoff));
+
+        sprintf(gd, "G%d,%d,%d", xoff, yoff, aoff);
+
+        /*
+         * Now test-generate our grid, to make sure it actually
+         * produces something.
+         */
+        g = grid_new_penrose(width, height, which, gd);
+        if (g) {
+            grid_free(g);
+            break;
+        }
+        /* If not, go back to the top of this while loop and try again
+         * with a different random offset. */
+    }
+
+    return dupstr(gd);
+}
+
+static char *grid_validate_desc_penrose(grid_type type, int width, int height,
+                                        const char *desc)
+{
+    int tilesize = PENROSE_TILESIZE, startsz, depth, xoff, yoff, aoff, inner_radius;
+    double outer_radius;
+    int which = (type == GRID_PENROSE_P2 ? PENROSE_P2 : PENROSE_P3);
+    grid *g;
+
+    if (!desc)
+        return "Missing grid description string.";
+
+    penrose_calculate_size(which, tilesize, width, height,
+                           &outer_radius, &startsz, &depth);
+    inner_radius = (int)(outer_radius - sqrt(width*width + height*height));
+
+    if (sscanf(desc, "G%d,%d,%d", &xoff, &yoff, &aoff) != 3)
+        return "Invalid format grid description string.";
+
+    if (sqrt(xoff*xoff + yoff*yoff) > inner_radius)
+        return "Patch offset out of bounds.";
+    if ((aoff % 36) != 0 || aoff < 0 || aoff >= 360)
+        return "Angle offset out of bounds.";
+
+    /*
+     * Test-generate to ensure these parameters don't end us up with
+     * no grid at all.
+     */
+    g = grid_new_penrose(width, height, which, desc);
+    if (!g)
+        return "Patch coordinates do not identify a usable grid fragment";
+    grid_free(g);
+
+    return NULL;
+}
+
+/*
+ * We're asked for a grid of a particular size, and we generate enough
+ * of the tiling so we can be sure to have enough random grid from which
+ * to pick.
+ */
+
+static grid *grid_new_penrose(int width, int height, int which, const char *desc)
+{
+    int max_faces, max_dots, tilesize = PENROSE_TILESIZE;
+    int xsz, ysz, xoff, yoff, aoff;
+    double rradius;
+
+    tree234 *points;
+    grid *g;
+
+    penrose_state ps;
+    setface_ctx sf_ctx;
+
+    penrose_calculate_size(which, tilesize, width, height,
+                           &rradius, &ps.start_size, &ps.max_depth);
+
+    debug(("penrose: w%d h%d, tile size %d, start size %d, depth %d",
+           width, height, tilesize, ps.start_size, ps.max_depth));
+
+    ps.new_tile = set_faces;
+    ps.ctx = &sf_ctx;
+
+    max_faces = (width*3) * (height*3); /* somewhat paranoid... */
+    max_dots = max_faces * 4; /* ditto... */
+
+    g = grid_empty();
+    g->tilesize = tilesize;
+    g->faces = snewn(max_faces, grid_face);
+    g->dots = snewn(max_dots, grid_dot);
+
+    points = newtree234(grid_point_cmp_fn);
+
+    memset(&sf_ctx, 0, sizeof(sf_ctx));
+    sf_ctx.g = g;
+    sf_ctx.points = points;
+
+    if (desc != NULL) {
+        if (sscanf(desc, "G%d,%d,%d", &xoff, &yoff, &aoff) != 3)
+            assert(!"Invalid grid description.");
+    } else {
+        xoff = yoff = aoff = 0;
+    }
+
+    xsz = width * tilesize;
+    ysz = height * tilesize;
+
+    sf_ctx.xmin = xoff - xsz/2;
+    sf_ctx.xmax = xoff + xsz/2;
+    sf_ctx.ymin = yoff - ysz/2;
+    sf_ctx.ymax = yoff + ysz/2;
+
+    debug(("penrose: centre (%f, %f) xsz %f ysz %f",
+           0.0, 0.0, xsz, ysz));
+    debug(("penrose: x range (%f --> %f), y range (%f --> %f)",
+           sf_ctx.xmin, sf_ctx.xmax, sf_ctx.ymin, sf_ctx.ymax));
+
+    penrose(&ps, which, aoff);
+
+    freetree234(points);
+    assert(g->num_faces <= max_faces);
+    assert(g->num_dots <= max_dots);
+
+    debug(("penrose: %d faces total (equivalent to %d wide by %d high)",
+           g->num_faces, g->num_faces/height, g->num_faces/width));
+
+    /*
+     * Return NULL if we ended up with an empty grid, either because
+     * the initial generation was over too small a rectangle to
+     * encompass any face or because grid_trim_vigorously ended up
+     * removing absolutely everything.
+     */
+    if (g->num_faces == 0 || g->num_dots == 0) {
+        grid_free(g);
+        return NULL;
+    }
+    grid_trim_vigorously(g);
+    if (g->num_faces == 0 || g->num_dots == 0) {
+        grid_free(g);
+        return NULL;
+    }
+
+    grid_make_consistent(g);
+
+    /*
+     * Centre the grid in its originally promised rectangle.
+     */
+    g->lowest_x -= ((sf_ctx.xmax - sf_ctx.xmin) -
+                    (g->highest_x - g->lowest_x)) / 2;
+    g->highest_x = g->lowest_x + (sf_ctx.xmax - sf_ctx.xmin);
+    g->lowest_y -= ((sf_ctx.ymax - sf_ctx.ymin) -
+                    (g->highest_y - g->lowest_y)) / 2;
+    g->highest_y = g->lowest_y + (sf_ctx.ymax - sf_ctx.ymin);
+
+    return g;
+}
+
+static void grid_size_penrose_p2_kite(int width, int height,
+                       int *tilesize, int *xextent, int *yextent)
+{
+    grid_size_penrose(width, height, tilesize, xextent, yextent);
+}
+
+static void grid_size_penrose_p3_thick(int width, int height,
+                       int *tilesize, int *xextent, int *yextent)
+{
+    grid_size_penrose(width, height, tilesize, xextent, yextent);
+}
+
+static grid *grid_new_penrose_p2_kite(int width, int height, const char *desc)
+{
+    return grid_new_penrose(width, height, PENROSE_P2, desc);
+}
+
+static grid *grid_new_penrose_p3_thick(int width, int height, const char *desc)
+{
+    return grid_new_penrose(width, height, PENROSE_P3, desc);
+}
+
+/* ----------- End of grid generators ------------- */
+
+#define FNNEW(upper,lower) &grid_new_ ## lower,
+#define FNSZ(upper,lower) &grid_size_ ## lower,
+
+static grid *(*(grid_news[]))(int, int, const char*) = { GRIDGEN_LIST(FNNEW) };
+static void(*(grid_sizes[]))(int, int, int*, int*, int*) = { GRIDGEN_LIST(FNSZ) };
+
+char *grid_new_desc(grid_type type, int width, int height, random_state *rs)
+{
+    if (type == GRID_PENROSE_P2 || type == GRID_PENROSE_P3) {
+        return grid_new_desc_penrose(type, width, height, rs);
+    } else if (type == GRID_TRIANGULAR) {
+        return dupstr("0"); /* up-to-date version of triangular grid */
+    } else {
+        return NULL;
+    }
+}
+
+char *grid_validate_desc(grid_type type, int width, int height,
+                         const char *desc)
+{
+    if (type == GRID_PENROSE_P2 || type == GRID_PENROSE_P3) {
+        return grid_validate_desc_penrose(type, width, height, desc);
+    } else if (type == GRID_TRIANGULAR) {
+        return grid_validate_desc_triangular(type, width, height, desc);
+    } else {
+        if (desc != NULL)
+            return "Grid description strings not used with this grid type";
+        return NULL;
+    }
+}
+
+grid *grid_new(grid_type type, int width, int height, const char *desc)
+{
+    char *err = grid_validate_desc(type, width, height, desc);
+    if (err) assert(!"Invalid grid description.");
+
+    return grid_news[type](width, height, desc);
+}
+
+void grid_compute_size(grid_type type, int width, int height,
+                       int *tilesize, int *xextent, int *yextent)
+{
+    grid_sizes[type](width, height, tilesize, xextent, yextent);
+}
+
+/* ----------- End of grid helpers ------------- */
+
+/* vim: set shiftwidth=4 tabstop=8: */
diff --git a/grid.h b/grid.h
new file mode 100644 (file)
index 0000000..17d0aa1
--- /dev/null
+++ b/grid.h
@@ -0,0 +1,132 @@
+/*
+ * (c) Lambros Lambrou 2008
+ *
+ * Code for working with general grids, which can be any planar graph
+ * with faces, edges and vertices (dots).  Includes generators for a few
+ * types of grid, including square, hexagonal, triangular and others.
+ */
+
+#ifndef PUZZLES_GRID_H
+#define PUZZLES_GRID_H
+
+#include "puzzles.h" /* for random_state */
+
+/* Useful macros */
+#define SQ(x) ( (x) * (x) )
+
+/* ----------------------------------------------------------------------
+ * Grid structures:
+ * A grid is made up of faces, edges and dots.  These structures hold
+ * the incidence relationships between these types.  For example, an
+ * edge always joins two dots, and is adjacent to two faces.
+ * The "grid_xxx **" members are lists of pointers which are dynamically
+ * allocated during grid generation.
+ * A pointer to a face/edge/dot will always point somewhere inside one of the
+ * three lists of the main "grid" structure: faces, edges, dots.
+ * Could have used integer offsets into these lists, but using actual
+ * pointers instead gives us type-safety.
+ */
+
+/* Need forward declarations */
+typedef struct grid_face grid_face;
+typedef struct grid_edge grid_edge;
+typedef struct grid_dot grid_dot;
+
+struct grid_face {
+  int order; /* Number of edges, also the number of dots */
+  grid_edge **edges; /* edges around this face */
+  grid_dot **dots; /* corners of this face */
+  /*
+   * For each face, we optionally compute and store its 'incentre'.
+   * The incentre of a triangle is the centre of a circle tangent to
+   * all three edges; I generalise the concept to arbitrary polygons
+   * by defining it to be the centre of the largest circle you can fit
+   * anywhere in the polygon. It's a useful thing to know because if
+   * you want to draw any symbol or text in the face (e.g. clue
+   * numbers in Loopy), that's the place it will most easily fit.
+   *
+   * When a grid is first generated, no face has this information
+   * computed, because it's fiddly to do. You can call
+   * grid_find_incentre() on a face, and it will fill in ix,iy below
+   * and set has_incentre to indicate that it's done so.
+   */
+  int has_incentre;
+  int ix, iy;      /* incentre (centre of largest inscribed circle) */
+};
+struct grid_edge {
+  grid_dot *dot1, *dot2;
+  grid_face *face1, *face2; /* Use NULL for the infinite outside face */
+};
+struct grid_dot {
+  int order;
+  grid_edge **edges;
+  grid_face **faces; /* A NULL grid_face* means infinite outside face */
+
+  /* Position in some fairly arbitrary (Cartesian) coordinate system.
+   * Use large enough values such that we can get away with
+   * integer arithmetic, but small enough such that arithmetic
+   * won't overflow. */
+  int x, y;
+};
+typedef struct grid {
+  /* These are (dynamically allocated) arrays of all the
+   * faces, edges, dots that are in the grid. */
+  int num_faces; grid_face *faces;
+  int num_edges; grid_edge *edges;
+  int num_dots;  grid_dot *dots;
+
+  /* Cache the bounding-box of the grid, so the drawing-code can quickly
+   * figure out the proper scaling to draw onto a given area. */
+  int lowest_x, lowest_y, highest_x, highest_y;
+
+  /* A measure of tile size for this grid (in grid coordinates), to help
+   * the renderer decide how large to draw the grid.
+   * Roughly the size of a single tile - for example the side-length
+   * of a square cell. */
+  int tilesize;
+
+  /* We really don't want to copy this monstrosity!
+   * A grid is immutable once generated.
+   */
+  int refcount;
+} grid;
+
+/* Grids are specified by type: GRID_SQUARE, GRID_KITE, etc. */
+
+#define GRIDGEN_LIST(A) \
+  A(SQUARE,square) \
+  A(HONEYCOMB,honeycomb) \
+  A(TRIANGULAR,triangular) \
+  A(SNUBSQUARE,snubsquare) \
+  A(CAIRO,cairo) \
+  A(GREATHEXAGONAL,greathexagonal) \
+  A(OCTAGONAL,octagonal) \
+  A(KITE,kites) \
+  A(FLORET,floret) \
+  A(DODECAGONAL,dodecagonal) \
+  A(GREATDODECAGONAL,greatdodecagonal) \
+  A(PENROSE_P2,penrose_p2_kite) \
+  A(PENROSE_P3,penrose_p3_thick)
+
+#define ENUM(upper,lower) GRID_ ## upper,
+typedef enum grid_type { GRIDGEN_LIST(ENUM) GRID_TYPE_MAX } grid_type;
+#undef ENUM
+
+/* Free directly after use if non-NULL. Will never contain an underscore
+ * (so clients can safely use that as a separator). */
+char *grid_new_desc(grid_type type, int width, int height, random_state *rs);
+char *grid_validate_desc(grid_type type, int width, int height,
+                         const char *desc);
+
+grid *grid_new(grid_type type, int width, int height, const char *desc);
+
+void grid_free(grid *g);
+
+grid_edge *grid_nearest_edge(grid *g, int x, int y);
+
+void grid_compute_size(grid_type type, int width, int height,
+                       int *tilesize, int *xextent, int *yextent);
+
+void grid_find_incentre(grid_face *f);
+
+#endif /* PUZZLES_GRID_H */
diff --git a/gtk.c b/gtk.c
new file mode 100644 (file)
index 0000000..53f5d22
--- /dev/null
+++ b/gtk.c
@@ -0,0 +1,3244 @@
+/*
+ * gtk.c: GTK front end for my puzzle collection.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <time.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <math.h>
+
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include <gdk/gdkx.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+
+#include "puzzles.h"
+
+#if GTK_CHECK_VERSION(2,0,0)
+# define USE_PANGO
+# ifdef PANGO_VERSION_CHECK
+#  if PANGO_VERSION_CHECK(1,8,0)
+#   define HAVE_SENSIBLE_ABSOLUTE_SIZE_FUNCTION
+#  endif
+# endif
+#endif
+#if !GTK_CHECK_VERSION(2,4,0)
+# define OLD_FILESEL
+#endif
+#if GTK_CHECK_VERSION(2,8,0)
+# define USE_CAIRO
+# if GTK_CHECK_VERSION(3,0,0) || defined(GDK_DISABLE_DEPRECATED)
+#  define USE_CAIRO_WITHOUT_PIXMAP
+# endif
+#endif
+
+#if GTK_CHECK_VERSION(3,0,0)
+/* The old names are still more concise! */
+#define gtk_hbox_new(x,y) gtk_box_new(GTK_ORIENTATION_HORIZONTAL,y)
+#define gtk_vbox_new(x,y) gtk_box_new(GTK_ORIENTATION_VERTICAL,y)
+/* GTK 3 has retired stock button labels */
+#define LABEL_OK "_OK"
+#define LABEL_CANCEL "_Cancel"
+#define LABEL_NO "_No"
+#define LABEL_YES "_Yes"
+#define LABEL_SAVE "_Save"
+#define LABEL_OPEN "_Open"
+#define gtk_button_new_with_our_label gtk_button_new_with_mnemonic
+#else
+#define LABEL_OK GTK_STOCK_OK
+#define LABEL_CANCEL GTK_STOCK_CANCEL
+#define LABEL_NO GTK_STOCK_NO
+#define LABEL_YES GTK_STOCK_YES
+#define LABEL_SAVE GTK_STOCK_SAVE
+#define LABEL_OPEN GTK_STOCK_OPEN
+#define gtk_button_new_with_our_label gtk_button_new_from_stock
+#endif
+
+/* #undef USE_CAIRO */
+/* #define NO_THICK_LINE */
+#ifdef DEBUGGING
+static FILE *debug_fp = NULL;
+
+void dputs(char *buf)
+{
+    if (!debug_fp) {
+        debug_fp = fopen("debug.log", "w");
+    }
+
+    fputs(buf, stderr);
+
+    if (debug_fp) {
+        fputs(buf, debug_fp);
+        fflush(debug_fp);
+    }
+}
+
+void debug_printf(char *fmt, ...)
+{
+    char buf[4096];
+    va_list ap;
+
+    va_start(ap, fmt);
+    vsprintf(buf, fmt, ap);
+    dputs(buf);
+    va_end(ap);
+}
+#endif
+
+/* ----------------------------------------------------------------------
+ * Error reporting functions used elsewhere.
+ */
+
+void fatal(char *fmt, ...)
+{
+    va_list ap;
+
+    fprintf(stderr, "fatal error: ");
+
+    va_start(ap, fmt);
+    vfprintf(stderr, fmt, ap);
+    va_end(ap);
+
+    fprintf(stderr, "\n");
+    exit(1);
+}
+
+/* ----------------------------------------------------------------------
+ * GTK front end to puzzles.
+ */
+
+static void changed_preset(frontend *fe);
+
+struct font {
+#ifdef USE_PANGO
+    PangoFontDescription *desc;
+#else
+    GdkFont *font;
+#endif
+    int type;
+    int size;
+};
+
+/*
+ * This structure holds all the data relevant to a single window.
+ * In principle this would allow us to open multiple independent
+ * puzzle windows, although I can't currently see any real point in
+ * doing so. I'm just coding cleanly because there's no
+ * particularly good reason not to.
+ */
+struct frontend {
+    GtkWidget *window;
+    GtkAccelGroup *accelgroup;
+    GtkWidget *area;
+    GtkWidget *statusbar;
+    GtkWidget *menubar;
+    guint statusctx;
+    int w, h;
+    midend *me;
+#ifdef USE_CAIRO
+    const float *colours;
+    cairo_t *cr;
+    cairo_surface_t *image;
+#ifndef USE_CAIRO_WITHOUT_PIXMAP
+    GdkPixmap *pixmap;
+#endif
+    GdkColor background;              /* for painting outside puzzle area */
+#else
+    GdkPixmap *pixmap;
+    GdkGC *gc;
+    GdkColor *colours;
+    GdkColormap *colmap;
+    int backgroundindex;              /* which of colours[] is background */
+#endif
+    int ncolours;
+    int bbox_l, bbox_r, bbox_u, bbox_d;
+    int timer_active, timer_id;
+    struct timeval last_time;
+    struct font *fonts;
+    int nfonts, fontsize;
+    config_item *cfg;
+    int cfg_which, cfgret;
+    GtkWidget *cfgbox;
+    void *paste_data;
+    int paste_data_len;
+    int pw, ph;                        /* pixmap size (w, h are area size) */
+    int ox, oy;                        /* offset of pixmap in drawing area */
+#ifdef OLD_FILESEL
+    char *filesel_name;
+#endif
+    GSList *preset_radio;
+    int n_preset_menu_items;
+    int preset_threaded;
+    GtkWidget *preset_custom;
+    GtkWidget *copy_menu_item;
+#if !GTK_CHECK_VERSION(3,0,0)
+    int drawing_area_shrink_pending;
+    int menubar_is_local;
+#endif
+};
+
+struct blitter {
+#ifdef USE_CAIRO
+    cairo_surface_t *image;
+#else
+    GdkPixmap *pixmap;
+#endif
+    int w, h, x, y;
+};
+
+void get_random_seed(void **randseed, int *randseedsize)
+{
+    struct timeval *tvp = snew(struct timeval);
+    gettimeofday(tvp, NULL);
+    *randseed = (void *)tvp;
+    *randseedsize = sizeof(struct timeval);
+}
+
+void frontend_default_colour(frontend *fe, float *output)
+{
+#if !GTK_CHECK_VERSION(3,0,0)
+    /*
+     * Use the widget style's default background colour as the
+     * background for the puzzle drawing area.
+     */
+    GdkColor col = gtk_widget_get_style(fe->window)->bg[GTK_STATE_NORMAL];
+    output[0] = col.red / 65535.0;
+    output[1] = col.green / 65535.0;
+    output[2] = col.blue / 65535.0;
+#else
+    /*
+     * GTK 3 has decided that there's no such thing as a 'default
+     * background colour' any more, because widget styles might set
+     * the background to something more complicated like a background
+     * image. We don't want to get into overlaying our entire puzzle
+     * on an arbitrary background image, so we'll just make up a
+     * reasonable shade of grey.
+     */
+    output[0] = output[1] = output[2] = 0.9F;
+#endif
+}
+
+void gtk_status_bar(void *handle, char *text)
+{
+    frontend *fe = (frontend *)handle;
+
+    assert(fe->statusbar);
+
+    gtk_statusbar_pop(GTK_STATUSBAR(fe->statusbar), fe->statusctx);
+    gtk_statusbar_push(GTK_STATUSBAR(fe->statusbar), fe->statusctx, text);
+}
+
+/* ----------------------------------------------------------------------
+ * Cairo drawing functions.
+ */
+
+#ifdef USE_CAIRO
+
+static void setup_drawing(frontend *fe)
+{
+    fe->cr = cairo_create(fe->image);
+    cairo_set_antialias(fe->cr, CAIRO_ANTIALIAS_GRAY);
+    cairo_set_line_width(fe->cr, 1.0);
+    cairo_set_line_cap(fe->cr, CAIRO_LINE_CAP_SQUARE);
+    cairo_set_line_join(fe->cr, CAIRO_LINE_JOIN_ROUND);
+}
+
+static void teardown_drawing(frontend *fe)
+{
+    cairo_destroy(fe->cr);
+    fe->cr = NULL;
+
+#ifndef USE_CAIRO_WITHOUT_PIXMAP
+    {
+        cairo_t *cr = gdk_cairo_create(fe->pixmap);
+        cairo_set_source_surface(cr, fe->image, 0, 0);
+        cairo_rectangle(cr,
+                        fe->bbox_l - 1,
+                        fe->bbox_u - 1,
+                        fe->bbox_r - fe->bbox_l + 2,
+                        fe->bbox_d - fe->bbox_u + 2);
+        cairo_fill(cr);
+        cairo_destroy(cr);
+    }
+#endif
+}
+
+static void snaffle_colours(frontend *fe)
+{
+    fe->colours = midend_colours(fe->me, &fe->ncolours);
+}
+
+static void set_colour(frontend *fe, int colour)
+{
+    cairo_set_source_rgb(fe->cr,
+                        fe->colours[3*colour + 0],
+                        fe->colours[3*colour + 1],
+                        fe->colours[3*colour + 2]);
+}
+
+static void set_window_background(frontend *fe, int colour)
+{
+#if GTK_CHECK_VERSION(3,0,0)
+    GdkRGBA rgba;
+    rgba.red = fe->colours[3*colour + 0];
+    rgba.green = fe->colours[3*colour + 1];
+    rgba.blue = fe->colours[3*colour + 2];
+    rgba.alpha = 1.0;
+    gdk_window_set_background_rgba(gtk_widget_get_window(fe->area), &rgba);
+    gdk_window_set_background_rgba(gtk_widget_get_window(fe->window), &rgba);
+#else
+    GdkColormap *colmap;
+
+    colmap = gdk_colormap_get_system();
+    fe->background.red = fe->colours[3*colour + 0] * 65535;
+    fe->background.green = fe->colours[3*colour + 1] * 65535;
+    fe->background.blue = fe->colours[3*colour + 2] * 65535;
+    if (!gdk_colormap_alloc_color(colmap, &fe->background, FALSE, FALSE)) {
+       g_error("couldn't allocate background (#%02x%02x%02x)\n",
+               fe->background.red >> 8, fe->background.green >> 8,
+               fe->background.blue >> 8);
+    }
+    gdk_window_set_background(gtk_widget_get_window(fe->area),
+                              &fe->background);
+    gdk_window_set_background(gtk_widget_get_window(fe->window),
+                              &fe->background);
+#endif
+}
+
+static PangoLayout *make_pango_layout(frontend *fe)
+{
+    return (pango_cairo_create_layout(fe->cr));
+}
+
+static void draw_pango_layout(frontend *fe, PangoLayout *layout,
+                             int x, int y)
+{
+    cairo_move_to(fe->cr, x, y);
+    pango_cairo_show_layout(fe->cr, layout);
+}
+
+static void save_screenshot_png(frontend *fe, const char *screenshot_file)
+{
+    cairo_surface_write_to_png(fe->image, screenshot_file);
+}
+
+static void do_clip(frontend *fe, int x, int y, int w, int h)
+{
+    cairo_new_path(fe->cr);
+    cairo_rectangle(fe->cr, x, y, w, h);
+    cairo_clip(fe->cr);
+}
+
+static void do_unclip(frontend *fe)
+{
+    cairo_reset_clip(fe->cr);
+}
+
+static void do_draw_rect(frontend *fe, int x, int y, int w, int h)
+{
+    cairo_save(fe->cr);
+    cairo_new_path(fe->cr);
+    cairo_set_antialias(fe->cr, CAIRO_ANTIALIAS_NONE);
+    cairo_rectangle(fe->cr, x, y, w, h);
+    cairo_fill(fe->cr);
+    cairo_restore(fe->cr);
+}
+
+static void do_draw_line(frontend *fe, int x1, int y1, int x2, int y2)
+{
+    cairo_new_path(fe->cr);
+    cairo_move_to(fe->cr, x1 + 0.5, y1 + 0.5);
+    cairo_line_to(fe->cr, x2 + 0.5, y2 + 0.5);
+    cairo_stroke(fe->cr);
+}
+
+static void do_draw_thick_line(frontend *fe, float thickness,
+                              float x1, float y1, float x2, float y2)
+{
+    cairo_save(fe->cr);
+    cairo_set_line_width(fe->cr, thickness);
+    cairo_new_path(fe->cr);
+    cairo_move_to(fe->cr, x1, y1);
+    cairo_line_to(fe->cr, x2, y2);
+    cairo_stroke(fe->cr);
+    cairo_restore(fe->cr);
+}
+
+static void do_draw_poly(frontend *fe, int *coords, int npoints,
+                        int fillcolour, int outlinecolour)
+{
+    int i;
+
+    cairo_new_path(fe->cr);
+    for (i = 0; i < npoints; i++)
+       cairo_line_to(fe->cr, coords[i*2] + 0.5, coords[i*2 + 1] + 0.5);
+    cairo_close_path(fe->cr);
+    if (fillcolour >= 0) {
+        set_colour(fe, fillcolour);
+       cairo_fill_preserve(fe->cr);
+    }
+    assert(outlinecolour >= 0);
+    set_colour(fe, outlinecolour);
+    cairo_stroke(fe->cr);
+}
+
+static void do_draw_circle(frontend *fe, int cx, int cy, int radius,
+                          int fillcolour, int outlinecolour)
+{
+    cairo_new_path(fe->cr);
+    cairo_arc(fe->cr, cx + 0.5, cy + 0.5, radius, 0, 2*PI);
+    cairo_close_path(fe->cr);          /* Just in case... */
+    if (fillcolour >= 0) {
+       set_colour(fe, fillcolour);
+       cairo_fill_preserve(fe->cr);
+    }
+    assert(outlinecolour >= 0);
+    set_colour(fe, outlinecolour);
+    cairo_stroke(fe->cr);
+}
+
+static void setup_blitter(blitter *bl, int w, int h)
+{
+    bl->image = cairo_image_surface_create(CAIRO_FORMAT_RGB24, w, h);
+}
+
+static void teardown_blitter(blitter *bl)
+{
+    cairo_surface_destroy(bl->image);
+}
+
+static void do_blitter_save(frontend *fe, blitter *bl, int x, int y)
+{
+    cairo_t *cr = cairo_create(bl->image);
+
+    cairo_set_source_surface(cr, fe->image, -x, -y);
+    cairo_paint(cr);
+    cairo_destroy(cr);
+}
+
+static void do_blitter_load(frontend *fe, blitter *bl, int x, int y)
+{
+    cairo_set_source_surface(fe->cr, bl->image, x, y);
+    cairo_paint(fe->cr);
+}
+
+static void clear_backing_store(frontend *fe)
+{
+    fe->image = NULL;
+}
+
+static void wipe_and_destroy_cairo(frontend *fe, cairo_t *cr)
+{
+    cairo_set_source_rgb(cr, fe->colours[0], fe->colours[1], fe->colours[2]);
+    cairo_paint(cr);
+    cairo_destroy(cr);
+}
+
+static void setup_backing_store(frontend *fe)
+{
+#ifndef USE_CAIRO_WITHOUT_PIXMAP
+    fe->pixmap = gdk_pixmap_new(gtk_widget_get_window(fe->area),
+                                fe->pw, fe->ph, -1);
+#endif
+    fe->image = cairo_image_surface_create(CAIRO_FORMAT_RGB24,
+                                          fe->pw, fe->ph);
+
+    wipe_and_destroy_cairo(fe, cairo_create(fe->image));
+#ifndef USE_CAIRO_WITHOUT_PIXMAP
+    wipe_and_destroy_cairo(fe, gdk_cairo_create(fe->pixmap));
+#endif
+    wipe_and_destroy_cairo(fe, gdk_cairo_create
+                           (gtk_widget_get_window(fe->area)));
+}
+
+static int backing_store_ok(frontend *fe)
+{
+    return (!!fe->image);
+}
+
+static void teardown_backing_store(frontend *fe)
+{
+    cairo_surface_destroy(fe->image);
+#ifndef USE_CAIRO_WITHOUT_PIXMAP
+    gdk_pixmap_unref(fe->pixmap);
+#endif
+    fe->image = NULL;
+}
+
+#endif
+
+/* ----------------------------------------------------------------------
+ * GDK drawing functions.
+ */
+
+#ifndef USE_CAIRO
+
+static void setup_drawing(frontend *fe)
+{
+    fe->gc = gdk_gc_new(fe->area->window);
+}
+
+static void teardown_drawing(frontend *fe)
+{
+    gdk_gc_unref(fe->gc);
+    fe->gc = NULL;
+}
+
+static void snaffle_colours(frontend *fe)
+{
+    int i, ncolours;
+    float *colours;
+    gboolean *success;
+
+    fe->colmap = gdk_colormap_get_system();
+    colours = midend_colours(fe->me, &ncolours);
+    fe->ncolours = ncolours;
+    fe->colours = snewn(ncolours, GdkColor);
+    for (i = 0; i < ncolours; i++) {
+       fe->colours[i].red = colours[i*3] * 0xFFFF;
+       fe->colours[i].green = colours[i*3+1] * 0xFFFF;
+       fe->colours[i].blue = colours[i*3+2] * 0xFFFF;
+    }
+    success = snewn(ncolours, gboolean);
+    gdk_colormap_alloc_colors(fe->colmap, fe->colours, ncolours,
+                             FALSE, FALSE, success);
+    for (i = 0; i < ncolours; i++) {
+       if (!success[i]) {
+           g_error("couldn't allocate colour %d (#%02x%02x%02x)\n",
+                   i, fe->colours[i].red >> 8,
+                   fe->colours[i].green >> 8,
+                   fe->colours[i].blue >> 8);
+       }
+    }
+}
+
+static void set_window_background(frontend *fe, int colour)
+{
+    fe->backgroundindex = colour;
+    gdk_window_set_background(fe->area->window, &fe->colours[colour]);
+    gdk_window_set_background(fe->window->window, &fe->colours[colour]);
+}
+
+static void set_colour(frontend *fe, int colour)
+{
+    gdk_gc_set_foreground(fe->gc, &fe->colours[colour]);
+}
+
+#ifdef USE_PANGO
+static PangoLayout *make_pango_layout(frontend *fe)
+{
+    return (pango_layout_new(gtk_widget_get_pango_context(fe->area)));
+}
+
+static void draw_pango_layout(frontend *fe, PangoLayout *layout,
+                             int x, int y)
+{
+    gdk_draw_layout(fe->pixmap, fe->gc, x, y, layout);
+}
+#endif
+
+static void save_screenshot_png(frontend *fe, const char *screenshot_file)
+{
+    GdkPixbuf *pb;
+    GError *gerror = NULL;
+
+    midend_redraw(fe->me);
+
+    pb = gdk_pixbuf_get_from_drawable(NULL, fe->pixmap,
+                                     NULL, 0, 0, 0, 0, -1, -1);
+    gdk_pixbuf_save(pb, screenshot_file, "png", &gerror, NULL);
+}
+
+static void do_clip(frontend *fe, int x, int y, int w, int h)
+{
+    GdkRectangle rect;
+
+    rect.x = x;
+    rect.y = y;
+    rect.width = w;
+    rect.height = h;
+    gdk_gc_set_clip_rectangle(fe->gc, &rect);
+}
+
+static void do_unclip(frontend *fe)
+{
+    GdkRectangle rect;
+
+    rect.x = 0;
+    rect.y = 0;
+    rect.width = fe->w;
+    rect.height = fe->h;
+    gdk_gc_set_clip_rectangle(fe->gc, &rect);
+}
+
+static void do_draw_rect(frontend *fe, int x, int y, int w, int h)
+{
+    gdk_draw_rectangle(fe->pixmap, fe->gc, 1, x, y, w, h);
+}
+
+static void do_draw_line(frontend *fe, int x1, int y1, int x2, int y2)
+{
+    gdk_draw_line(fe->pixmap, fe->gc, x1, y1, x2, y2);
+}
+
+static void do_draw_thick_line(frontend *fe, float thickness,
+                              float x1, float y1, float x2, float y2)
+{
+    GdkGCValues save;
+
+    gdk_gc_get_values(fe->gc, &save);
+    gdk_gc_set_line_attributes(fe->gc,
+                              thickness,
+                              GDK_LINE_SOLID,
+                              GDK_CAP_BUTT,
+                              GDK_JOIN_BEVEL);
+    gdk_draw_line(fe->pixmap, fe->gc, x1, y1, x2, y2);
+    gdk_gc_set_line_attributes(fe->gc,
+                              save.line_width,
+                              save.line_style,
+                              save.cap_style,
+                              save.join_style);
+}
+
+static void do_draw_poly(frontend *fe, int *coords, int npoints,
+                        int fillcolour, int outlinecolour)
+{
+    GdkPoint *points = snewn(npoints, GdkPoint);
+    int i;
+
+    for (i = 0; i < npoints; i++) {
+        points[i].x = coords[i*2];
+        points[i].y = coords[i*2+1];
+    }
+
+    if (fillcolour >= 0) {
+       set_colour(fe, fillcolour);
+       gdk_draw_polygon(fe->pixmap, fe->gc, TRUE, points, npoints);
+    }
+    assert(outlinecolour >= 0);
+    set_colour(fe, outlinecolour);
+
+    /*
+     * In principle we ought to be able to use gdk_draw_polygon for
+     * the outline as well. In fact, it turns out to interact badly
+     * with a clipping region, for no terribly obvious reason, so I
+     * draw the outline as a sequence of lines instead.
+     */
+    for (i = 0; i < npoints; i++)
+       gdk_draw_line(fe->pixmap, fe->gc,
+                     points[i].x, points[i].y,
+                     points[(i+1)%npoints].x, points[(i+1)%npoints].y);
+
+    sfree(points);
+}
+
+static void do_draw_circle(frontend *fe, int cx, int cy, int radius,
+                          int fillcolour, int outlinecolour)
+{
+    if (fillcolour >= 0) {
+       set_colour(fe, fillcolour);
+       gdk_draw_arc(fe->pixmap, fe->gc, TRUE,
+                    cx - radius, cy - radius,
+                    2 * radius, 2 * radius, 0, 360 * 64);
+    }
+
+    assert(outlinecolour >= 0);
+    set_colour(fe, outlinecolour);
+    gdk_draw_arc(fe->pixmap, fe->gc, FALSE,
+                cx - radius, cy - radius,
+                2 * radius, 2 * radius, 0, 360 * 64);
+}
+
+static void setup_blitter(blitter *bl, int w, int h)
+{
+    /*
+     * We can't create the pixmap right now, because fe->window
+     * might not yet exist. So we just cache w and h and create it
+     * during the firs call to blitter_save.
+     */
+    bl->pixmap = NULL;
+}
+
+static void teardown_blitter(blitter *bl)
+{
+    if (bl->pixmap)
+       gdk_pixmap_unref(bl->pixmap);
+}
+
+static void do_blitter_save(frontend *fe, blitter *bl, int x, int y)
+{
+    if (!bl->pixmap)
+        bl->pixmap = gdk_pixmap_new(fe->area->window, bl->w, bl->h, -1);
+    gdk_draw_pixmap(bl->pixmap,
+                    fe->area->style->fg_gc[GTK_WIDGET_STATE(fe->area)],
+                    fe->pixmap,
+                    x, y, 0, 0, bl->w, bl->h);
+}
+
+static void do_blitter_load(frontend *fe, blitter *bl, int x, int y)
+{
+    assert(bl->pixmap);
+    gdk_draw_pixmap(fe->pixmap,
+                    fe->area->style->fg_gc[GTK_WIDGET_STATE(fe->area)],
+                    bl->pixmap,
+                    0, 0, x, y, bl->w, bl->h);
+}
+
+static void clear_backing_store(frontend *fe)
+{
+    fe->pixmap = NULL;
+}
+
+static void setup_backing_store(frontend *fe)
+{
+    GdkGC *gc;
+
+    fe->pixmap = gdk_pixmap_new(fe->area->window, fe->pw, fe->ph, -1);
+
+    gc = gdk_gc_new(fe->area->window);
+    gdk_gc_set_foreground(gc, &fe->colours[0]);
+    gdk_draw_rectangle(fe->pixmap, gc, 1, 0, 0, fe->pw, fe->ph);
+    gdk_draw_rectangle(fe->area->window, gc, 1, 0, 0, fe->w, fe->h);
+    gdk_gc_unref(gc);
+}
+
+static int backing_store_ok(frontend *fe)
+{
+    return (!!fe->pixmap);
+}
+
+static void teardown_backing_store(frontend *fe)
+{
+    gdk_pixmap_unref(fe->pixmap);
+    fe->pixmap = NULL;
+}
+
+#endif
+
+#ifndef USE_CAIRO_WITHOUT_PIXMAP
+static void repaint_rectangle(frontend *fe, GtkWidget *widget,
+                             int x, int y, int w, int h)
+{
+    GdkGC *gc = gdk_gc_new(gtk_widget_get_window(widget));
+#ifdef USE_CAIRO
+    gdk_gc_set_foreground(gc, &fe->background);
+#else
+    gdk_gc_set_foreground(gc, &fe->colours[fe->backgroundindex]);
+#endif
+    if (x < fe->ox) {
+       gdk_draw_rectangle(gtk_widget_get_window(widget), gc,
+                          TRUE, x, y, fe->ox - x, h);
+       w -= (fe->ox - x);
+       x = fe->ox;
+    }
+    if (y < fe->oy) {
+       gdk_draw_rectangle(gtk_widget_get_window(widget), gc,
+                          TRUE, x, y, w, fe->oy - y);
+       h -= (fe->oy - y);
+       y = fe->oy;
+    }
+    if (w > fe->pw) {
+       gdk_draw_rectangle(gtk_widget_get_window(widget), gc,
+                          TRUE, x + fe->pw, y, w - fe->pw, h);
+       w = fe->pw;
+    }
+    if (h > fe->ph) {
+       gdk_draw_rectangle(gtk_widget_get_window(widget), gc,
+                          TRUE, x, y + fe->ph, w, h - fe->ph);
+       h = fe->ph;
+    }
+    gdk_draw_pixmap(gtk_widget_get_window(widget), gc, fe->pixmap,
+                   x - fe->ox, y - fe->oy, x, y, w, h);
+    gdk_gc_unref(gc);
+}
+#endif
+
+/* ----------------------------------------------------------------------
+ * Pango font functions.
+ */
+
+#ifdef USE_PANGO
+
+static void add_font(frontend *fe, int index, int fonttype, int fontsize)
+{
+    /*
+     * Use Pango to find the closest match to the requested
+     * font.
+     */
+    PangoFontDescription *fd;
+
+    fd = pango_font_description_new();
+    /* `Monospace' and `Sans' are meta-families guaranteed to exist */
+    pango_font_description_set_family(fd, fonttype == FONT_FIXED ?
+                                     "Monospace" : "Sans");
+    pango_font_description_set_weight(fd, PANGO_WEIGHT_BOLD);
+    /*
+     * I found some online Pango documentation which
+     * described a function called
+     * pango_font_description_set_absolute_size(), which is
+     * _exactly_ what I want here. Unfortunately, none of
+     * my local Pango installations have it (presumably
+     * they're too old), so I'm going to have to hack round
+     * it by figuring out the point size myself. This
+     * limits me to X and probably also breaks in later
+     * Pango installations, so ideally I should add another
+     * CHECK_VERSION type ifdef and use set_absolute_size
+     * where available. All very annoying.
+     */
+#ifdef HAVE_SENSIBLE_ABSOLUTE_SIZE_FUNCTION
+    pango_font_description_set_absolute_size(fd, PANGO_SCALE*fontsize);
+#else
+    {
+       Display *d = GDK_DISPLAY();
+       int s = DefaultScreen(d);
+       double resolution =
+           (PANGO_SCALE * 72.27 / 25.4) *
+           ((double) DisplayWidthMM(d, s) / DisplayWidth (d, s));
+       pango_font_description_set_size(fd, resolution * fontsize);
+    }
+#endif
+    fe->fonts[index].desc = fd;
+}
+
+static void align_and_draw_text(frontend *fe,
+                               int index, int align, int x, int y,
+                               const char *text)
+{
+    PangoLayout *layout;
+    PangoRectangle rect;
+
+    layout = make_pango_layout(fe);
+
+    /*
+     * Create a layout.
+     */
+    pango_layout_set_font_description(layout, fe->fonts[index].desc);
+    pango_layout_set_text(layout, text, strlen(text));
+    pango_layout_get_pixel_extents(layout, NULL, &rect);
+
+    if (align & ALIGN_VCENTRE)
+       rect.y -= rect.height / 2;
+    else
+       rect.y -= rect.height;
+
+    if (align & ALIGN_HCENTRE)
+       rect.x -= rect.width / 2;
+    else if (align & ALIGN_HRIGHT)
+       rect.x -= rect.width;
+
+    draw_pango_layout(fe, layout, rect.x + x, rect.y + y);
+
+    g_object_unref(layout);
+}
+
+#endif
+
+/* ----------------------------------------------------------------------
+ * Old-fashioned font functions.
+ */
+
+#ifndef USE_PANGO
+
+static void add_font(int index, int fonttype, int fontsize)
+{
+    /*
+     * In GTK 1.2, I don't know of any plausible way to
+     * pick a suitable font, so I'm just going to be
+     * tedious.
+     */
+    fe->fonts[i].font = gdk_font_load(fonttype == FONT_FIXED ?
+                                     "fixed" : "variable");
+}
+
+static void align_and_draw_text(int index, int align, int x, int y,
+                               const char *text)
+{
+    int lb, rb, wid, asc, desc;
+
+    /*
+     * Measure vertical string extents with respect to the same
+     * string always...
+     */
+    gdk_string_extents(fe->fonts[i].font,
+                      "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
+                      &lb, &rb, &wid, &asc, &desc);
+    if (align & ALIGN_VCENTRE)
+       y += asc - (asc+desc)/2;
+    else
+       y += asc;
+
+    /*
+     * ... but horizontal extents with respect to the provided
+     * string. This means that multiple pieces of text centred
+     * on the same y-coordinate don't have different baselines.
+     */
+    gdk_string_extents(fe->fonts[i].font, text,
+                      &lb, &rb, &wid, &asc, &desc);
+
+    if (align & ALIGN_HCENTRE)
+       x -= wid / 2;
+    else if (align & ALIGN_HRIGHT)
+       x -= wid;
+
+    /*
+     * Actually draw the text.
+     */
+    gdk_draw_string(fe->pixmap, fe->fonts[i].font, fe->gc, x, y, text);
+}
+
+#endif
+
+/* ----------------------------------------------------------------------
+ * The exported drawing functions.
+ */
+
+void gtk_start_draw(void *handle)
+{
+    frontend *fe = (frontend *)handle;
+    fe->bbox_l = fe->w;
+    fe->bbox_r = 0;
+    fe->bbox_u = fe->h;
+    fe->bbox_d = 0;
+    setup_drawing(fe);
+}
+
+void gtk_clip(void *handle, int x, int y, int w, int h)
+{
+    frontend *fe = (frontend *)handle;
+    do_clip(fe, x, y, w, h);
+}
+
+void gtk_unclip(void *handle)
+{
+    frontend *fe = (frontend *)handle;
+    do_unclip(fe);
+}
+
+void gtk_draw_text(void *handle, int x, int y, int fonttype, int fontsize,
+                  int align, int colour, char *text)
+{
+    frontend *fe = (frontend *)handle;
+    int i;
+
+    /*
+     * Find or create the font.
+     */
+    for (i = 0; i < fe->nfonts; i++)
+        if (fe->fonts[i].type == fonttype && fe->fonts[i].size == fontsize)
+            break;
+
+    if (i == fe->nfonts) {
+        if (fe->fontsize <= fe->nfonts) {
+            fe->fontsize = fe->nfonts + 10;
+            fe->fonts = sresize(fe->fonts, fe->fontsize, struct font);
+        }
+
+        fe->nfonts++;
+
+        fe->fonts[i].type = fonttype;
+        fe->fonts[i].size = fontsize;
+       add_font(fe, i, fonttype, fontsize);
+    }
+
+    /*
+     * Do the job.
+     */
+    set_colour(fe, colour);
+    align_and_draw_text(fe, i, align, x, y, text);
+}
+
+void gtk_draw_rect(void *handle, int x, int y, int w, int h, int colour)
+{
+    frontend *fe = (frontend *)handle;
+    set_colour(fe, colour);
+    do_draw_rect(fe, x, y, w, h);
+}
+
+void gtk_draw_line(void *handle, int x1, int y1, int x2, int y2, int colour)
+{
+    frontend *fe = (frontend *)handle;
+    set_colour(fe, colour);
+    do_draw_line(fe, x1, y1, x2, y2);
+}
+
+void gtk_draw_thick_line(void *handle, float thickness,
+                        float x1, float y1, float x2, float y2, int colour)
+{
+    frontend *fe = (frontend *)handle;
+    set_colour(fe, colour);
+    do_draw_thick_line(fe, thickness, x1, y1, x2, y2);
+}
+
+void gtk_draw_poly(void *handle, int *coords, int npoints,
+                  int fillcolour, int outlinecolour)
+{
+    frontend *fe = (frontend *)handle;
+    do_draw_poly(fe, coords, npoints, fillcolour, outlinecolour);
+}
+
+void gtk_draw_circle(void *handle, int cx, int cy, int radius,
+                    int fillcolour, int outlinecolour)
+{
+    frontend *fe = (frontend *)handle;
+    do_draw_circle(fe, cx, cy, radius, fillcolour, outlinecolour);
+}
+
+blitter *gtk_blitter_new(void *handle, int w, int h)
+{
+    blitter *bl = snew(blitter);
+    setup_blitter(bl, w, h);
+    bl->w = w;
+    bl->h = h;
+    return bl;
+}
+
+void gtk_blitter_free(void *handle, blitter *bl)
+{
+    teardown_blitter(bl);
+    sfree(bl);
+}
+
+void gtk_blitter_save(void *handle, blitter *bl, int x, int y)
+{
+    frontend *fe = (frontend *)handle;
+    do_blitter_save(fe, bl, x, y);
+    bl->x = x;
+    bl->y = y;
+}
+
+void gtk_blitter_load(void *handle, blitter *bl, int x, int y)
+{
+    frontend *fe = (frontend *)handle;
+    if (x == BLITTER_FROMSAVED && y == BLITTER_FROMSAVED) {
+        x = bl->x;
+        y = bl->y;
+    }
+    do_blitter_load(fe, bl, x, y);
+}
+
+void gtk_draw_update(void *handle, int x, int y, int w, int h)
+{
+    frontend *fe = (frontend *)handle;
+    if (fe->bbox_l > x  ) fe->bbox_l = x  ;
+    if (fe->bbox_r < x+w) fe->bbox_r = x+w;
+    if (fe->bbox_u > y  ) fe->bbox_u = y  ;
+    if (fe->bbox_d < y+h) fe->bbox_d = y+h;
+}
+
+void gtk_end_draw(void *handle)
+{
+    frontend *fe = (frontend *)handle;
+
+    teardown_drawing(fe);
+
+    if (fe->bbox_l < fe->bbox_r && fe->bbox_u < fe->bbox_d) {
+#ifdef USE_CAIRO_WITHOUT_PIXMAP
+        gtk_widget_queue_draw_area(fe->area,
+                                   fe->bbox_l - 1 + fe->ox,
+                                   fe->bbox_u - 1 + fe->oy,
+                                   fe->bbox_r - fe->bbox_l + 2,
+                                   fe->bbox_d - fe->bbox_u + 2);
+#else
+       repaint_rectangle(fe, fe->area,
+                         fe->bbox_l - 1 + fe->ox,
+                         fe->bbox_u - 1 + fe->oy,
+                         fe->bbox_r - fe->bbox_l + 2,
+                         fe->bbox_d - fe->bbox_u + 2);
+#endif
+    }
+}
+
+#ifdef USE_PANGO
+char *gtk_text_fallback(void *handle, const char *const *strings, int nstrings)
+{
+    /*
+     * We assume Pango can cope with any UTF-8 likely to be emitted
+     * by a puzzle.
+     */
+    return dupstr(strings[0]);
+}
+#endif
+
+const struct drawing_api gtk_drawing = {
+    gtk_draw_text,
+    gtk_draw_rect,
+    gtk_draw_line,
+    gtk_draw_poly,
+    gtk_draw_circle,
+    gtk_draw_update,
+    gtk_clip,
+    gtk_unclip,
+    gtk_start_draw,
+    gtk_end_draw,
+    gtk_status_bar,
+    gtk_blitter_new,
+    gtk_blitter_free,
+    gtk_blitter_save,
+    gtk_blitter_load,
+    NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */
+    NULL, NULL,                               /* line_width, line_dotted */
+#ifdef USE_PANGO
+    gtk_text_fallback,
+#else
+    NULL,
+#endif
+#ifdef NO_THICK_LINE
+    NULL,
+#else
+    gtk_draw_thick_line,
+#endif
+};
+
+static void destroy(GtkWidget *widget, gpointer data)
+{
+    frontend *fe = (frontend *)data;
+    deactivate_timer(fe);
+    midend_free(fe->me);
+    gtk_main_quit();
+}
+
+static gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
+{
+    frontend *fe = (frontend *)data;
+    int keyval;
+    int shift = (event->state & GDK_SHIFT_MASK) ? MOD_SHFT : 0;
+    int ctrl = (event->state & GDK_CONTROL_MASK) ? MOD_CTRL : 0;
+
+    if (!backing_store_ok(fe))
+        return TRUE;
+
+#if !GTK_CHECK_VERSION(2,0,0)
+    /* Gtk 1.2 passes a key event to this function even if it's also
+     * defined as an accelerator.
+     * Gtk 2 doesn't do this, and this function appears not to exist there. */
+    if (fe->accelgroup &&
+        gtk_accel_group_get_entry(fe->accelgroup,
+        event->keyval, event->state))
+        return TRUE;
+#endif
+
+    /* Handle mnemonics. */
+    if (gtk_window_activate_key(GTK_WINDOW(fe->window), event))
+        return TRUE;
+
+    if (event->keyval == GDK_KEY_Up)
+        keyval = shift | ctrl | CURSOR_UP;
+    else if (event->keyval == GDK_KEY_KP_Up ||
+             event->keyval == GDK_KEY_KP_8)
+       keyval = MOD_NUM_KEYPAD | '8';
+    else if (event->keyval == GDK_KEY_Down)
+        keyval = shift | ctrl | CURSOR_DOWN;
+    else if (event->keyval == GDK_KEY_KP_Down ||
+             event->keyval == GDK_KEY_KP_2)
+       keyval = MOD_NUM_KEYPAD | '2';
+    else if (event->keyval == GDK_KEY_Left)
+        keyval = shift | ctrl | CURSOR_LEFT;
+    else if (event->keyval == GDK_KEY_KP_Left ||
+             event->keyval == GDK_KEY_KP_4)
+       keyval = MOD_NUM_KEYPAD | '4';
+    else if (event->keyval == GDK_KEY_Right)
+        keyval = shift | ctrl | CURSOR_RIGHT;
+    else if (event->keyval == GDK_KEY_KP_Right ||
+             event->keyval == GDK_KEY_KP_6)
+       keyval = MOD_NUM_KEYPAD | '6';
+    else if (event->keyval == GDK_KEY_KP_Home ||
+             event->keyval == GDK_KEY_KP_7)
+        keyval = MOD_NUM_KEYPAD | '7';
+    else if (event->keyval == GDK_KEY_KP_End ||
+             event->keyval == GDK_KEY_KP_1)
+        keyval = MOD_NUM_KEYPAD | '1';
+    else if (event->keyval == GDK_KEY_KP_Page_Up ||
+             event->keyval == GDK_KEY_KP_9)
+        keyval = MOD_NUM_KEYPAD | '9';
+    else if (event->keyval == GDK_KEY_KP_Page_Down ||
+             event->keyval == GDK_KEY_KP_3)
+        keyval = MOD_NUM_KEYPAD | '3';
+    else if (event->keyval == GDK_KEY_KP_Insert ||
+             event->keyval == GDK_KEY_KP_0)
+        keyval = MOD_NUM_KEYPAD | '0';
+    else if (event->keyval == GDK_KEY_KP_Begin ||
+             event->keyval == GDK_KEY_KP_5)
+        keyval = MOD_NUM_KEYPAD | '5';
+    else if (event->keyval == GDK_KEY_BackSpace ||
+            event->keyval == GDK_KEY_Delete ||
+            event->keyval == GDK_KEY_KP_Delete)
+        keyval = '\177';
+    else if (event->string[0] && !event->string[1])
+        keyval = (unsigned char)event->string[0];
+    else
+        keyval = -1;
+
+    if (keyval >= 0 &&
+        !midend_process_key(fe->me, 0, 0, keyval))
+       gtk_widget_destroy(fe->window);
+
+    return TRUE;
+}
+
+static gint button_event(GtkWidget *widget, GdkEventButton *event,
+                         gpointer data)
+{
+    frontend *fe = (frontend *)data;
+    int button;
+
+    if (!backing_store_ok(fe))
+        return TRUE;
+
+    if (event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE)
+        return TRUE;
+
+    if (event->button == 2 || (event->state & GDK_SHIFT_MASK))
+       button = MIDDLE_BUTTON;
+    else if (event->button == 3 || (event->state & GDK_MOD1_MASK))
+       button = RIGHT_BUTTON;
+    else if (event->button == 1)
+       button = LEFT_BUTTON;
+    else if (event->button == 8 && event->type == GDK_BUTTON_PRESS)
+        button = 'u';
+    else if (event->button == 9 && event->type == GDK_BUTTON_PRESS)
+        button = 'r';
+    else
+       return FALSE;                  /* don't even know what button! */
+
+    if (event->type == GDK_BUTTON_RELEASE && button >= LEFT_BUTTON)
+        button += LEFT_RELEASE - LEFT_BUTTON;
+
+    if (!midend_process_key(fe->me, event->x - fe->ox,
+                            event->y - fe->oy, button))
+       gtk_widget_destroy(fe->window);
+
+    return TRUE;
+}
+
+static gint motion_event(GtkWidget *widget, GdkEventMotion *event,
+                         gpointer data)
+{
+    frontend *fe = (frontend *)data;
+    int button;
+
+    if (!backing_store_ok(fe))
+        return TRUE;
+
+    if (event->state & (GDK_BUTTON2_MASK | GDK_SHIFT_MASK))
+       button = MIDDLE_DRAG;
+    else if (event->state & GDK_BUTTON1_MASK)
+       button = LEFT_DRAG;
+    else if (event->state & GDK_BUTTON3_MASK)
+       button = RIGHT_DRAG;
+    else
+       return FALSE;                  /* don't even know what button! */
+
+    if (!midend_process_key(fe->me, event->x - fe->ox,
+                            event->y - fe->oy, button))
+       gtk_widget_destroy(fe->window);
+#if GTK_CHECK_VERSION(2,12,0)
+    gdk_event_request_motions(event);
+#else
+    gdk_window_get_pointer(gtk_widget_get_window(widget), NULL, NULL, NULL);
+#endif
+
+    return TRUE;
+}
+
+#if GTK_CHECK_VERSION(3,0,0)
+static gint draw_area(GtkWidget *widget, cairo_t *cr, gpointer data)
+{
+    frontend *fe = (frontend *)data;
+    GdkRectangle dirtyrect;
+
+    gdk_cairo_get_clip_rectangle(cr, &dirtyrect);
+    cairo_set_source_surface(cr, fe->image, fe->ox, fe->oy);
+    cairo_rectangle(cr, dirtyrect.x, dirtyrect.y,
+                    dirtyrect.width, dirtyrect.height);
+    cairo_fill(cr);
+
+    return TRUE;
+}
+#else
+static gint expose_area(GtkWidget *widget, GdkEventExpose *event,
+                        gpointer data)
+{
+    frontend *fe = (frontend *)data;
+
+    if (backing_store_ok(fe)) {
+#ifdef USE_CAIRO_WITHOUT_PIXMAP
+        cairo_t *cr = gdk_cairo_create(gtk_widget_get_window(widget));
+        cairo_set_source_surface(cr, fe->image, fe->ox, fe->oy);
+        cairo_rectangle(cr, event->area.x, event->area.y,
+                        event->area.width, event->area.height);
+        cairo_fill(cr);
+        cairo_destroy(cr);
+#else
+       repaint_rectangle(fe, widget,
+                         event->area.x, event->area.y,
+                         event->area.width, event->area.height);
+#endif
+    }
+    return TRUE;
+}
+#endif
+
+static gint map_window(GtkWidget *widget, GdkEvent *event,
+                      gpointer data)
+{
+    frontend *fe = (frontend *)data;
+
+    /*
+     * Apparently we need to do this because otherwise the status
+     * bar will fail to update immediately. Annoying, but there we
+     * go.
+     */
+    gtk_widget_queue_draw(fe->window);
+
+    return TRUE;
+}
+
+static gint configure_area(GtkWidget *widget,
+                           GdkEventConfigure *event, gpointer data)
+{
+    frontend *fe = (frontend *)data;
+    int x, y;
+    int oldw = fe->w, oldpw = fe->pw, oldh = fe->h, oldph = fe->ph;
+
+    x = event->width;
+    y = event->height;
+    fe->w = x;
+    fe->h = y;
+    midend_size(fe->me, &x, &y, TRUE);
+    fe->pw = x;
+    fe->ph = y;
+    fe->ox = (fe->w - fe->pw) / 2;
+    fe->oy = (fe->h - fe->ph) / 2;
+
+    if (oldw != fe->w || oldpw != fe->pw ||
+        oldh != fe->h || oldph != fe->ph || !backing_store_ok(fe)) {
+        if (backing_store_ok(fe))
+            teardown_backing_store(fe);
+        setup_backing_store(fe);
+    }
+
+    midend_force_redraw(fe->me);
+
+    return TRUE;
+}
+
+static gint timer_func(gpointer data)
+{
+    frontend *fe = (frontend *)data;
+
+    if (fe->timer_active) {
+       struct timeval now;
+       float elapsed;
+       gettimeofday(&now, NULL);
+       elapsed = ((now.tv_usec - fe->last_time.tv_usec) * 0.000001F +
+                  (now.tv_sec - fe->last_time.tv_sec));
+        midend_timer(fe->me, elapsed); /* may clear timer_active */
+       fe->last_time = now;
+    }
+
+    return fe->timer_active;
+}
+
+void deactivate_timer(frontend *fe)
+{
+    if (!fe)
+       return;                        /* can happen due to --generate */
+    if (fe->timer_active)
+        g_source_remove(fe->timer_id);
+    fe->timer_active = FALSE;
+}
+
+void activate_timer(frontend *fe)
+{
+    if (!fe)
+       return;                        /* can happen due to --generate */
+    if (!fe->timer_active) {
+        fe->timer_id = g_timeout_add(20, timer_func, fe);
+       gettimeofday(&fe->last_time, NULL);
+    }
+    fe->timer_active = TRUE;
+}
+
+static void window_destroy(GtkWidget *widget, gpointer data)
+{
+    gtk_main_quit();
+}
+
+static int win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
+{
+    GObject *cancelbutton = G_OBJECT(data);
+
+    /*
+     * `Escape' effectively clicks the cancel button
+     */
+    if (event->keyval == GDK_KEY_Escape) {
+       g_signal_emit_by_name(cancelbutton, "clicked");
+       return TRUE;
+    }
+
+    return FALSE;
+}
+
+enum { MB_OK, MB_YESNO };
+
+static void align_label(GtkLabel *label, double x, double y)
+{
+#if GTK_CHECK_VERSION(3,16,0)
+    gtk_label_set_xalign(label, x);
+    gtk_label_set_yalign(label, y);
+#elif GTK_CHECK_VERSION(3,14,0)
+    gtk_widget_set_halign(GTK_WIDGET(label),
+                          x == 0 ? GTK_ALIGN_START :
+                          x == 1 ? GTK_ALIGN_END : GTK_ALIGN_CENTER);
+    gtk_widget_set_valign(GTK_WIDGET(label),
+                          y == 0 ? GTK_ALIGN_START :
+                          y == 1 ? GTK_ALIGN_END : GTK_ALIGN_CENTER);
+#else
+    gtk_misc_set_alignment(GTK_MISC(label), x, y);
+#endif
+}
+
+#if GTK_CHECK_VERSION(3,0,0)
+int message_box(GtkWidget *parent, char *title, char *msg, int centre,
+               int type)
+{
+    GtkWidget *window;
+    gint ret;
+
+    window = gtk_message_dialog_new
+        (GTK_WINDOW(parent),
+         (GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
+         (type == MB_OK ? GTK_MESSAGE_INFO : GTK_MESSAGE_QUESTION),
+         (type == MB_OK ? GTK_BUTTONS_OK   : GTK_BUTTONS_YES_NO),
+         "%s", msg);
+    gtk_window_set_title(GTK_WINDOW(window), title);
+    ret = gtk_dialog_run(GTK_DIALOG(window));
+    gtk_widget_destroy(window);
+    return (type == MB_OK ? TRUE : (ret == GTK_RESPONSE_YES));
+}
+#else /* GTK_CHECK_VERSION(3,0,0) */
+static void msgbox_button_clicked(GtkButton *button, gpointer data)
+{
+    GtkWidget *window = GTK_WIDGET(data);
+    int v, *ip;
+
+    ip = (int *)g_object_get_data(G_OBJECT(window), "user-data");
+    v = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button), "user-data"));
+    *ip = v;
+
+    gtk_widget_destroy(GTK_WIDGET(data));
+}
+
+int message_box(GtkWidget *parent, char *title, char *msg, int centre,
+               int type)
+{
+    GtkWidget *window, *hbox, *text, *button;
+    char *titles;
+    int i, def, cancel;
+
+    window = gtk_dialog_new();
+    text = gtk_label_new(msg);
+    align_label(GTK_LABEL(text), 0.0, 0.0);
+    hbox = gtk_hbox_new(FALSE, 0);
+    gtk_box_pack_start(GTK_BOX(hbox), text, FALSE, FALSE, 20);
+    gtk_box_pack_start
+        (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(window))),
+         hbox, FALSE, FALSE, 20);
+    gtk_widget_show(text);
+    gtk_widget_show(hbox);
+    gtk_window_set_title(GTK_WINDOW(window), title);
+    gtk_label_set_line_wrap(GTK_LABEL(text), TRUE);
+
+    if (type == MB_OK) {
+       titles = LABEL_OK "\0";
+       def = cancel = 0;
+    } else {
+       assert(type == MB_YESNO);
+       titles = LABEL_NO "\0" LABEL_YES "\0";
+       def = 1;
+       cancel = 0;
+    }
+    i = 0;
+    
+    while (*titles) {
+       button = gtk_button_new_with_our_label(titles);
+       gtk_box_pack_end
+            (GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(window))),
+             button, FALSE, FALSE, 0);
+       gtk_widget_show(button);
+       if (i == def) {
+           gtk_widget_set_can_default(button, TRUE);
+           gtk_window_set_default(GTK_WINDOW(window), button);
+       }
+       if (i == cancel) {
+           g_signal_connect(G_OBJECT(window), "key_press_event",
+                             G_CALLBACK(win_key_press), button);
+       }
+       g_signal_connect(G_OBJECT(button), "clicked",
+                         G_CALLBACK(msgbox_button_clicked), window);
+       g_object_set_data(G_OBJECT(button), "user-data",
+                          GINT_TO_POINTER(i));
+       titles += strlen(titles)+1;
+       i++;
+    }
+    g_object_set_data(G_OBJECT(window), "user-data", &i);
+    g_signal_connect(G_OBJECT(window), "destroy",
+                     G_CALLBACK(window_destroy), NULL);
+    gtk_window_set_modal(GTK_WINDOW(window), TRUE);
+    gtk_window_set_transient_for(GTK_WINDOW(window), GTK_WINDOW(parent));
+    /* set_transient_window_pos(parent, window); */
+    gtk_widget_show(window);
+    i = -1;
+    gtk_main();
+    return (type == MB_YESNO ? i == 1 : TRUE);
+}
+#endif /* GTK_CHECK_VERSION(3,0,0) */
+
+void error_box(GtkWidget *parent, char *msg)
+{
+    message_box(parent, "Error", msg, FALSE, MB_OK);
+}
+
+static void config_ok_button_clicked(GtkButton *button, gpointer data)
+{
+    frontend *fe = (frontend *)data;
+    char *err;
+
+    err = midend_set_config(fe->me, fe->cfg_which, fe->cfg);
+
+    if (err)
+       error_box(fe->cfgbox, err);
+    else {
+       fe->cfgret = TRUE;
+       gtk_widget_destroy(fe->cfgbox);
+       changed_preset(fe);
+    }
+}
+
+static void config_cancel_button_clicked(GtkButton *button, gpointer data)
+{
+    frontend *fe = (frontend *)data;
+
+    gtk_widget_destroy(fe->cfgbox);
+}
+
+static int editbox_key(GtkWidget *widget, GdkEventKey *event, gpointer data)
+{
+    /*
+     * GtkEntry has a nasty habit of eating the Return key, which
+     * is unhelpful since it doesn't actually _do_ anything with it
+     * (it calls gtk_widget_activate, but our edit boxes never need
+     * activating). So I catch Return before GtkEntry sees it, and
+     * pass it straight on to the parent widget. Effect: hitting
+     * Return in an edit box will now activate the default button
+     * in the dialog just like it will everywhere else.
+     */
+    if (event->keyval == GDK_KEY_Return &&
+        gtk_widget_get_parent(widget) != NULL) {
+       gint return_val;
+       g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
+       g_signal_emit_by_name(G_OBJECT(gtk_widget_get_parent(widget)),
+                              "key_press_event", event, &return_val);
+       return return_val;
+    }
+    return FALSE;
+}
+
+static void editbox_changed(GtkEditable *ed, gpointer data)
+{
+    config_item *i = (config_item *)data;
+
+    sfree(i->sval);
+    i->sval = dupstr(gtk_entry_get_text(GTK_ENTRY(ed)));
+}
+
+static void button_toggled(GtkToggleButton *tb, gpointer data)
+{
+    config_item *i = (config_item *)data;
+
+    i->ival = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(tb));
+}
+
+static void droplist_sel(GtkComboBox *combo, gpointer data)
+{
+    config_item *i = (config_item *)data;
+
+    i->ival = gtk_combo_box_get_active(combo);
+}
+
+static int get_config(frontend *fe, int which)
+{
+    GtkWidget *w, *table, *cancel;
+    GtkBox *content_box, *button_box;
+    char *title;
+    config_item *i;
+    int y;
+
+    fe->cfg = midend_get_config(fe->me, which, &title);
+    fe->cfg_which = which;
+    fe->cfgret = FALSE;
+
+#if GTK_CHECK_VERSION(3,0,0)
+    /* GtkDialog isn't quite flexible enough */
+    fe->cfgbox = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+    content_box = GTK_BOX(gtk_vbox_new(FALSE, 8));
+    g_object_set(G_OBJECT(content_box), "margin", 8, (const char *)NULL);
+    gtk_widget_show(GTK_WIDGET(content_box));
+    gtk_container_add(GTK_CONTAINER(fe->cfgbox), GTK_WIDGET(content_box));
+    button_box = GTK_BOX(gtk_hbox_new(FALSE, 8));
+    gtk_widget_show(GTK_WIDGET(button_box));
+    gtk_box_pack_end(content_box, GTK_WIDGET(button_box), FALSE, FALSE, 0);
+    {
+        GtkWidget *sep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
+        gtk_widget_show(sep);
+        gtk_box_pack_end(content_box, sep, FALSE, FALSE, 0);
+    }
+#else
+    fe->cfgbox = gtk_dialog_new();
+    content_box = GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(fe->cfgbox)));
+    button_box = GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(fe->cfgbox)));
+#endif
+    gtk_window_set_title(GTK_WINDOW(fe->cfgbox), title);
+    sfree(title);
+
+    w = gtk_button_new_with_our_label(LABEL_CANCEL);
+    gtk_box_pack_end(button_box, w, FALSE, FALSE, 0);
+    gtk_widget_show(w);
+    g_signal_connect(G_OBJECT(w), "clicked",
+                     G_CALLBACK(config_cancel_button_clicked), fe);
+    cancel = w;
+
+    w = gtk_button_new_with_our_label(LABEL_OK);
+    gtk_box_pack_end(button_box, w, FALSE, FALSE, 0);
+    gtk_widget_show(w);
+    gtk_widget_set_can_default(w, TRUE);
+    gtk_window_set_default(GTK_WINDOW(fe->cfgbox), w);
+    g_signal_connect(G_OBJECT(w), "clicked",
+                     G_CALLBACK(config_ok_button_clicked), fe);
+
+#if GTK_CHECK_VERSION(3,0,0)
+    table = gtk_grid_new();
+#else
+    table = gtk_table_new(1, 2, FALSE);
+#endif
+    y = 0;
+    gtk_box_pack_start(content_box, table, FALSE, FALSE, 0);
+    gtk_widget_show(table);
+
+    for (i = fe->cfg; i->type != C_END; i++) {
+#if !GTK_CHECK_VERSION(3,0,0)
+       gtk_table_resize(GTK_TABLE(table), y+1, 2);
+#endif
+
+       switch (i->type) {
+         case C_STRING:
+           /*
+            * Edit box with a label beside it.
+            */
+
+           w = gtk_label_new(i->name);
+           align_label(GTK_LABEL(w), 0.0, 0.5);
+#if GTK_CHECK_VERSION(3,0,0)
+            gtk_grid_attach(GTK_GRID(table), w, 0, y, 1, 1);
+#else
+           gtk_table_attach(GTK_TABLE(table), w, 0, 1, y, y+1,
+                            GTK_SHRINK | GTK_FILL,
+                            GTK_EXPAND | GTK_SHRINK | GTK_FILL,
+                            3, 3);
+#endif
+           gtk_widget_show(w);
+
+           w = gtk_entry_new();
+#if GTK_CHECK_VERSION(3,0,0)
+            gtk_grid_attach(GTK_GRID(table), w, 1, y, 1, 1);
+            g_object_set(G_OBJECT(w), "hexpand", TRUE, (const char *)NULL);
+#else
+           gtk_table_attach(GTK_TABLE(table), w, 1, 2, y, y+1,
+                            GTK_EXPAND | GTK_SHRINK | GTK_FILL,
+                            GTK_EXPAND | GTK_SHRINK | GTK_FILL,
+                            3, 3);
+#endif
+           gtk_entry_set_text(GTK_ENTRY(w), i->sval);
+           g_signal_connect(G_OBJECT(w), "changed",
+                             G_CALLBACK(editbox_changed), i);
+           g_signal_connect(G_OBJECT(w), "key_press_event",
+                             G_CALLBACK(editbox_key), NULL);
+           gtk_widget_show(w);
+
+           break;
+
+         case C_BOOLEAN:
+           /*
+            * Simple checkbox.
+            */
+            w = gtk_check_button_new_with_label(i->name);
+           g_signal_connect(G_OBJECT(w), "toggled",
+                             G_CALLBACK(button_toggled), i);
+#if GTK_CHECK_VERSION(3,0,0)
+            gtk_grid_attach(GTK_GRID(table), w, 0, y, 2, 1);
+            g_object_set(G_OBJECT(w), "hexpand", TRUE, (const char *)NULL);
+#else
+           gtk_table_attach(GTK_TABLE(table), w, 0, 2, y, y+1,
+                            GTK_EXPAND | GTK_SHRINK | GTK_FILL,
+                            GTK_EXPAND | GTK_SHRINK | GTK_FILL,
+                            3, 3);
+#endif
+           gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), i->ival);
+           gtk_widget_show(w);
+           break;
+
+         case C_CHOICES:
+           /*
+            * Drop-down list (GtkComboBox).
+            */
+
+           w = gtk_label_new(i->name);
+           align_label(GTK_LABEL(w), 0.0, 0.5);
+#if GTK_CHECK_VERSION(3,0,0)
+            gtk_grid_attach(GTK_GRID(table), w, 0, y, 1, 1);
+#else
+           gtk_table_attach(GTK_TABLE(table), w, 0, 1, y, y+1,
+                            GTK_SHRINK | GTK_FILL,
+                            GTK_EXPAND | GTK_SHRINK | GTK_FILL ,
+                            3, 3);
+#endif
+           gtk_widget_show(w);
+
+            {
+               int c;
+               char *p, *q, *name;
+                GtkListStore *model;
+               GtkCellRenderer *cr;
+                GtkTreeIter iter;
+
+                model = gtk_list_store_new(1, G_TYPE_STRING);
+
+               c = *i->sval;
+               p = i->sval+1;
+
+               while (*p) {
+                   q = p;
+                   while (*q && *q != c)
+                       q++;
+
+                   name = snewn(q-p+1, char);
+                   strncpy(name, p, q-p);
+                   name[q-p] = '\0';
+
+                   if (*q) q++;       /* eat delimiter */
+
+                    gtk_list_store_append(model, &iter);
+                    gtk_list_store_set(model, &iter, 0, name, -1);
+
+                   p = q;
+               }
+
+                w = gtk_combo_box_new_with_model(GTK_TREE_MODEL(model));
+
+               gtk_combo_box_set_active(GTK_COMBO_BOX(w), i->ival);
+
+               cr = gtk_cell_renderer_text_new();
+               gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(w), cr, TRUE);
+               gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(w), cr,
+                                              "text", 0, NULL);
+
+               g_signal_connect(G_OBJECT(w), "changed",
+                                G_CALLBACK(droplist_sel), i);
+            }
+
+#if GTK_CHECK_VERSION(3,0,0)
+            gtk_grid_attach(GTK_GRID(table), w, 1, y, 1, 1);
+            g_object_set(G_OBJECT(w), "hexpand", TRUE, (const char *)NULL);
+#else
+           gtk_table_attach(GTK_TABLE(table), w, 1, 2, y, y+1,
+                            GTK_EXPAND | GTK_SHRINK | GTK_FILL,
+                            GTK_EXPAND | GTK_SHRINK | GTK_FILL,
+                            3, 3);
+#endif
+           gtk_widget_show(w);
+           break;
+       }
+
+       y++;
+    }
+
+    g_signal_connect(G_OBJECT(fe->cfgbox), "destroy",
+                     G_CALLBACK(window_destroy), NULL);
+    g_signal_connect(G_OBJECT(fe->cfgbox), "key_press_event",
+                     G_CALLBACK(win_key_press), cancel);
+    gtk_window_set_modal(GTK_WINDOW(fe->cfgbox), TRUE);
+    gtk_window_set_transient_for(GTK_WINDOW(fe->cfgbox),
+                                GTK_WINDOW(fe->window));
+    /* set_transient_window_pos(fe->window, fe->cfgbox); */
+    gtk_widget_show(fe->cfgbox);
+    gtk_main();
+
+    free_cfg(fe->cfg);
+
+    return fe->cfgret;
+}
+
+static void menu_key_event(GtkMenuItem *menuitem, gpointer data)
+{
+    frontend *fe = (frontend *)data;
+    int key = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menuitem),
+                                                "user-data"));
+    if (!midend_process_key(fe->me, 0, 0, key))
+       gtk_widget_destroy(fe->window);
+}
+
+static void get_size(frontend *fe, int *px, int *py)
+{
+    int x, y;
+
+    /*
+     * Currently I don't want to make the GTK port scale large
+     * puzzles to fit on the screen. This is because X does permit
+     * extremely large windows and many window managers provide a
+     * means of navigating round them, and the users I consulted
+     * before deciding said that they'd rather have enormous puzzle
+     * windows spanning multiple screen pages than have them
+     * shrunk. I could change my mind later or introduce
+     * configurability; this would be the place to do so, by
+     * replacing the initial values of x and y with the screen
+     * dimensions.
+     */
+    x = INT_MAX;
+    y = INT_MAX;
+    midend_size(fe->me, &x, &y, FALSE);
+    *px = x;
+    *py = y;
+}
+
+#if !GTK_CHECK_VERSION(2,0,0)
+#define gtk_window_resize(win, x, y) \
+       gdk_window_resize(GTK_WIDGET(win)->window, x, y)
+#endif
+
+/*
+ * Called when any other code in this file has changed the
+ * selected game parameters.
+ */
+static void changed_preset(frontend *fe)
+{
+    int n = midend_which_preset(fe->me);
+
+    fe->preset_threaded = TRUE;
+    if (n < 0 && fe->preset_custom) {
+       gtk_check_menu_item_set_active(
+           GTK_CHECK_MENU_ITEM(fe->preset_custom),
+           TRUE);
+    } else {
+       GSList *gs = fe->preset_radio;
+       int i = fe->n_preset_menu_items - 1 - n;
+       if (fe->preset_custom)
+           gs = gs->next;
+       while (i && gs) {
+           i--;
+           gs = gs->next;
+       }
+       if (gs) {
+           gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gs->data),
+                                          TRUE);
+       } else for (gs = fe->preset_radio; gs; gs = gs->next) {
+           gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gs->data),
+                                          FALSE);
+       }
+    }
+    fe->preset_threaded = FALSE;
+
+    /*
+     * Update the greying on the Copy menu option.
+     */
+    if (fe->copy_menu_item) {
+       int enabled = midend_can_format_as_text_now(fe->me);
+       gtk_widget_set_sensitive(fe->copy_menu_item, enabled);
+    }
+}
+
+#if !GTK_CHECK_VERSION(3,0,0)
+static gboolean not_size_allocated_yet(GtkWidget *w)
+{
+    /*
+     * This function tests whether a widget has not yet taken up space
+     * on the screen which it will occupy in future. (Therefore, it
+     * returns true only if the widget does exist but does not have a
+     * size allocation. A null widget is already taking up all the
+     * space it ever will.)
+     */
+    if (!w)
+        return FALSE;        /* nonexistent widgets aren't a problem */
+
+#if GTK_CHECK_VERSION(2,18,0)  /* skip if no gtk_widget_get_allocation */
+    {
+        GtkAllocation a;
+        gtk_widget_get_allocation(w, &a);
+        if (a.height == 0 || a.width == 0)
+            return TRUE;       /* widget exists but has no size yet */
+    }
+#endif
+
+    return FALSE;
+}
+
+static void try_shrink_drawing_area(frontend *fe)
+{
+    if (fe->drawing_area_shrink_pending &&
+        (!fe->menubar_is_local || !not_size_allocated_yet(fe->menubar)) &&
+        !not_size_allocated_yet(fe->statusbar)) {
+        /*
+         * In order to permit the user to resize the window smaller as
+         * well as bigger, we call this function after the window size
+         * has ended up where we want it. This shouldn't shrink the
+         * window immediately; it just arranges that the next time the
+         * user tries to shrink it, they can.
+         *
+         * However, at puzzle creation time, we defer the first of
+         * these operations until after the menu bar and status bar
+         * are actually visible. On Ubuntu 12.04 I've found that these
+         * can take a while to be displayed, and that it's a mistake
+         * to reduce the drawing area's size allocation before they've
+         * turned up or else the drawing area makes room for them by
+         * shrinking to less than the size we intended.
+         */
+        gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), 1, 1);
+        fe->drawing_area_shrink_pending = FALSE;
+    }
+}
+#endif /* !GTK_CHECK_VERSION(3,0,0) */
+
+static gint configure_window(GtkWidget *widget,
+                             GdkEventConfigure *event, gpointer data)
+{
+#if !GTK_CHECK_VERSION(3,0,0)
+    /*
+     * When the main puzzle window changes size, it might be because
+     * the menu bar or status bar has turned up after starting off
+     * absent, in which case we should have another go at enacting a
+     * pending shrink of the drawing area.
+     */
+    frontend *fe = (frontend *)data;
+    try_shrink_drawing_area(fe);
+#endif
+    return FALSE;
+}
+
+#if GTK_CHECK_VERSION(3,0,0)
+static int window_extra_height(frontend *fe)
+{
+    int ret = 0;
+    if (fe->menubar) {
+        GtkRequisition req;
+        gtk_widget_get_preferred_size(fe->menubar, &req, NULL);
+        ret += req.height;
+    }
+    if (fe->statusbar) {
+        GtkRequisition req;
+        gtk_widget_get_preferred_size(fe->statusbar, &req, NULL);
+        ret += req.height;
+    }
+    return ret;
+}
+#endif
+
+static void resize_fe(frontend *fe)
+{
+    int x, y;
+
+    get_size(fe, &x, &y);
+
+#if GTK_CHECK_VERSION(3,0,0)
+    gtk_window_resize(GTK_WINDOW(fe->window), x, y + window_extra_height(fe));
+#else
+    fe->drawing_area_shrink_pending = FALSE;
+    gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), x, y);
+    {
+        GtkRequisition req;
+        gtk_widget_size_request(GTK_WIDGET(fe->window), &req);
+        gtk_window_resize(GTK_WINDOW(fe->window), req.width, req.height);
+    }
+    fe->drawing_area_shrink_pending = TRUE;
+    try_shrink_drawing_area(fe);
+#endif
+}
+
+static void menu_preset_event(GtkMenuItem *menuitem, gpointer data)
+{
+    frontend *fe = (frontend *)data;
+    game_params *params =
+        (game_params *)g_object_get_data(G_OBJECT(menuitem), "user-data");
+
+    if (fe->preset_threaded ||
+       (GTK_IS_CHECK_MENU_ITEM(menuitem) &&
+        !gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem))))
+       return;
+    midend_set_params(fe->me, params);
+    midend_new_game(fe->me);
+    changed_preset(fe);
+    resize_fe(fe);
+    midend_redraw(fe->me);
+}
+
+GdkAtom compound_text_atom, utf8_string_atom;
+int paste_initialised = FALSE;
+
+static void set_selection(frontend *fe, GdkAtom selection)
+{
+    if (!paste_initialised) {
+       compound_text_atom = gdk_atom_intern("COMPOUND_TEXT", FALSE);
+       utf8_string_atom = gdk_atom_intern("UTF8_STRING", FALSE);
+       paste_initialised = TRUE;
+    }
+
+    /*
+     * For this simple application we can safely assume that the
+     * data passed to this function is pure ASCII, which means we
+     * can return precisely the same stuff for types STRING,
+     * COMPOUND_TEXT or UTF8_STRING.
+     */
+
+    if (gtk_selection_owner_set(fe->area, selection, CurrentTime)) {
+       gtk_selection_clear_targets(fe->area, selection);
+       gtk_selection_add_target(fe->area, selection,
+                                GDK_SELECTION_TYPE_STRING, 1);
+       gtk_selection_add_target(fe->area, selection, compound_text_atom, 1);
+       gtk_selection_add_target(fe->area, selection, utf8_string_atom, 1);
+    }
+}
+
+void write_clip(frontend *fe, char *data)
+{
+    if (fe->paste_data)
+       sfree(fe->paste_data);
+
+    fe->paste_data = data;
+    fe->paste_data_len = strlen(data);
+
+    set_selection(fe, GDK_SELECTION_PRIMARY);
+    set_selection(fe, GDK_SELECTION_CLIPBOARD);
+}
+
+void selection_get(GtkWidget *widget, GtkSelectionData *seldata,
+                  guint info, guint time_stamp, gpointer data)
+{
+    frontend *fe = (frontend *)data;
+    gtk_selection_data_set(seldata, gtk_selection_data_get_target(seldata), 8,
+                          fe->paste_data, fe->paste_data_len);
+}
+
+gint selection_clear(GtkWidget *widget, GdkEventSelection *seldata,
+                    gpointer data)
+{
+    frontend *fe = (frontend *)data;
+
+    if (fe->paste_data)
+       sfree(fe->paste_data);
+    fe->paste_data = NULL;
+    fe->paste_data_len = 0;
+    return TRUE;
+}
+
+static void menu_copy_event(GtkMenuItem *menuitem, gpointer data)
+{
+    frontend *fe = (frontend *)data;
+    char *text;
+
+    text = midend_text_format(fe->me);
+
+    if (text) {
+       write_clip(fe, text);
+    } else {
+       gdk_beep();
+    }
+}
+
+#ifdef OLD_FILESEL
+
+static void filesel_ok(GtkButton *button, gpointer data)
+{
+    frontend *fe = (frontend *)data;
+
+    gpointer filesel = g_object_get_data(G_OBJECT(button), "user-data");
+
+    const char *name =
+        gtk_file_selection_get_filename(GTK_FILE_SELECTION(filesel));
+
+    fe->filesel_name = dupstr(name);
+}
+
+static char *file_selector(frontend *fe, char *title, int save)
+{
+    GtkWidget *filesel =
+        gtk_file_selection_new(title);
+
+    fe->filesel_name = NULL;
+
+    gtk_window_set_modal(GTK_WINDOW(filesel), TRUE);
+    g_object_set_data
+        (G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "user-data",
+         (gpointer)filesel);
+    g_signal_connect
+        (G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked",
+         G_CALLBACK(filesel_ok), fe);
+    g_signal_connect_swapped
+        (G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked",
+         G_CALLBACK(gtk_widget_destroy), (gpointer)filesel);
+    g_signal_connect_object
+        (G_OBJECT(GTK_FILE_SELECTION(filesel)->cancel_button), "clicked",
+         G_CALLBACK(gtk_widget_destroy), (gpointer)filesel);
+    g_signal_connect(G_OBJECT(filesel), "destroy",
+                     G_CALLBACK(window_destroy), NULL);
+    gtk_widget_show(filesel);
+    gtk_window_set_transient_for(GTK_WINDOW(filesel), GTK_WINDOW(fe->window));
+    gtk_main();
+
+    return fe->filesel_name;
+}
+
+#else
+
+static char *file_selector(frontend *fe, char *title, int save)
+{
+    char *filesel_name = NULL;
+
+    GtkWidget *filesel =
+        gtk_file_chooser_dialog_new(title,
+                                   GTK_WINDOW(fe->window),
+                                   save ? GTK_FILE_CHOOSER_ACTION_SAVE :
+                                   GTK_FILE_CHOOSER_ACTION_OPEN,
+                                   LABEL_CANCEL, GTK_RESPONSE_CANCEL,
+                                   save ? LABEL_SAVE : LABEL_OPEN,
+                                   GTK_RESPONSE_ACCEPT,
+                                   NULL);
+
+    if (gtk_dialog_run(GTK_DIALOG(filesel)) == GTK_RESPONSE_ACCEPT) {
+        char *name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filesel));
+        filesel_name = dupstr(name);
+        g_free(name);
+    }
+
+    gtk_widget_destroy(filesel);
+
+    return filesel_name;
+}
+
+#endif
+
+struct savefile_write_ctx {
+    FILE *fp;
+    int error;
+};
+
+static void savefile_write(void *wctx, void *buf, int len)
+{
+    struct savefile_write_ctx *ctx = (struct savefile_write_ctx *)wctx;
+    if (fwrite(buf, 1, len, ctx->fp) < len)
+       ctx->error = errno;
+}
+
+static int savefile_read(void *wctx, void *buf, int len)
+{
+    FILE *fp = (FILE *)wctx;
+    int ret;
+
+    ret = fread(buf, 1, len, fp);
+    return (ret == len);
+}
+
+static void menu_save_event(GtkMenuItem *menuitem, gpointer data)
+{
+    frontend *fe = (frontend *)data;
+    char *name;
+
+    name = file_selector(fe, "Enter name of game file to save", TRUE);
+
+    if (name) {
+        FILE *fp;
+
+       if ((fp = fopen(name, "r")) != NULL) {
+           char buf[256 + FILENAME_MAX];
+           fclose(fp);
+           /* file exists */
+
+           sprintf(buf, "Are you sure you want to overwrite the"
+                   " file \"%.*s\"?",
+                   FILENAME_MAX, name);
+           if (!message_box(fe->window, "Question", buf, TRUE, MB_YESNO))
+                goto free_and_return;
+       }
+
+       fp = fopen(name, "w");
+
+        if (!fp) {
+            error_box(fe->window, "Unable to open save file");
+            goto free_and_return;
+        }
+
+       {
+           struct savefile_write_ctx ctx;
+           ctx.fp = fp;
+           ctx.error = 0;
+           midend_serialise(fe->me, savefile_write, &ctx);
+           fclose(fp);
+           if (ctx.error) {
+               char boxmsg[512];
+               sprintf(boxmsg, "Error writing save file: %.400s",
+                       strerror(errno));
+               error_box(fe->window, boxmsg);
+               goto free_and_return;
+           }
+       }
+    free_and_return:
+        sfree(name);
+    }
+}
+
+static void menu_load_event(GtkMenuItem *menuitem, gpointer data)
+{
+    frontend *fe = (frontend *)data;
+    char *name, *err;
+
+    name = file_selector(fe, "Enter name of saved game file to load", FALSE);
+
+    if (name) {
+        FILE *fp = fopen(name, "r");
+        sfree(name);
+
+        if (!fp) {
+            error_box(fe->window, "Unable to open saved game file");
+            return;
+        }
+
+        err = midend_deserialise(fe->me, savefile_read, fp);
+
+        fclose(fp);
+
+        if (err) {
+            error_box(fe->window, err);
+            return;
+        }
+
+       changed_preset(fe);
+        resize_fe(fe);
+        midend_redraw(fe->me);
+    }
+}
+
+static void menu_solve_event(GtkMenuItem *menuitem, gpointer data)
+{
+    frontend *fe = (frontend *)data;
+    char *msg;
+
+    msg = midend_solve(fe->me);
+
+    if (msg)
+       error_box(fe->window, msg);
+}
+
+static void menu_restart_event(GtkMenuItem *menuitem, gpointer data)
+{
+    frontend *fe = (frontend *)data;
+
+    midend_restart_game(fe->me);
+}
+
+static void menu_config_event(GtkMenuItem *menuitem, gpointer data)
+{
+    frontend *fe = (frontend *)data;
+    int which = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menuitem),
+                                                  "user-data"));
+
+    if (fe->preset_threaded ||
+       (GTK_IS_CHECK_MENU_ITEM(menuitem) &&
+        !gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem))))
+       return;
+    changed_preset(fe);                /* Put the old preset back! */
+    if (!get_config(fe, which))
+       return;
+
+    midend_new_game(fe->me);
+    resize_fe(fe);
+    midend_redraw(fe->me);
+}
+
+static void menu_about_event(GtkMenuItem *menuitem, gpointer data)
+{
+    frontend *fe = (frontend *)data;
+
+#if GTK_CHECK_VERSION(3,0,0)
+    extern char *const *const xpm_icons[];
+    extern const int n_xpm_icons;
+    GdkPixbuf *icon = gdk_pixbuf_new_from_xpm_data
+        ((const gchar **)xpm_icons[n_xpm_icons-1]);
+    gtk_show_about_dialog
+        (GTK_WINDOW(fe->window),
+         "program-name", thegame.name,
+         "version", ver,
+         "comments", "Part of Simon Tatham's Portable Puzzle Collection",
+         "logo", icon,
+         (const gchar *)NULL);
+    g_object_unref(G_OBJECT(icon));
+#else
+    char titlebuf[256];
+    char textbuf[1024];
+
+    sprintf(titlebuf, "About %.200s", thegame.name);
+    sprintf(textbuf,
+           "%.200s\n\n"
+           "from Simon Tatham's Portable Puzzle Collection\n\n"
+           "%.500s", thegame.name, ver);
+
+    message_box(fe->window, titlebuf, textbuf, TRUE, MB_OK);
+#endif
+}
+
+static GtkWidget *add_menu_item_with_key(frontend *fe, GtkContainer *cont,
+                                         char *text, int key)
+{
+    GtkWidget *menuitem = gtk_menu_item_new_with_label(text);
+    int keyqual;
+    gtk_container_add(cont, menuitem);
+    g_object_set_data(G_OBJECT(menuitem), "user-data", GINT_TO_POINTER(key));
+    g_signal_connect(G_OBJECT(menuitem), "activate",
+                     G_CALLBACK(menu_key_event), fe);
+    switch (key & ~0x1F) {
+      case 0x00:
+       key += 0x60;
+       keyqual = GDK_CONTROL_MASK;
+       break;
+      case 0x40:
+       key += 0x20;
+       keyqual = GDK_SHIFT_MASK;
+       break;
+      default:
+       keyqual = 0;
+       break;
+    }
+    gtk_widget_add_accelerator(menuitem,
+                              "activate", fe->accelgroup,
+                              key, keyqual,
+                              GTK_ACCEL_VISIBLE);
+    gtk_widget_show(menuitem);
+    return menuitem;
+}
+
+static void add_menu_separator(GtkContainer *cont)
+{
+    GtkWidget *menuitem = gtk_menu_item_new();
+    gtk_container_add(cont, menuitem);
+    gtk_widget_show(menuitem);
+}
+
+enum { ARG_EITHER, ARG_SAVE, ARG_ID }; /* for argtype */
+
+static frontend *new_window(char *arg, int argtype, char **error)
+{
+    frontend *fe;
+    GtkBox *vbox, *hbox;
+    GtkWidget *menu, *menuitem;
+    GList *iconlist;
+    int x, y, n;
+    char errbuf[1024];
+    extern char *const *const xpm_icons[];
+    extern const int n_xpm_icons;
+
+    fe = snew(frontend);
+
+    fe->timer_active = FALSE;
+    fe->timer_id = -1;
+
+    fe->me = midend_new(fe, &thegame, &gtk_drawing, fe);
+
+    if (arg) {
+       char *err;
+       FILE *fp;
+
+       errbuf[0] = '\0';
+
+       switch (argtype) {
+         case ARG_ID:
+           err = midend_game_id(fe->me, arg);
+           if (!err)
+               midend_new_game(fe->me);
+           else
+               sprintf(errbuf, "Invalid game ID: %.800s", err);
+           break;
+         case ARG_SAVE:
+           fp = fopen(arg, "r");
+           if (!fp) {
+               sprintf(errbuf, "Error opening file: %.800s", strerror(errno));
+           } else {
+               err = midend_deserialise(fe->me, savefile_read, fp);
+                if (err)
+                    sprintf(errbuf, "Invalid save file: %.800s", err);
+                fclose(fp);
+           }
+           break;
+         default /*case ARG_EITHER*/:
+           /*
+            * First try treating the argument as a game ID.
+            */
+           err = midend_game_id(fe->me, arg);
+           if (!err) {
+               /*
+                * It's a valid game ID.
+                */
+               midend_new_game(fe->me);
+           } else {
+               FILE *fp = fopen(arg, "r");
+               if (!fp) {
+                   sprintf(errbuf, "Supplied argument is neither a game ID (%.400s)"
+                           " nor a save file (%.400s)", err, strerror(errno));
+               } else {
+                   err = midend_deserialise(fe->me, savefile_read, fp);
+                   if (err)
+                       sprintf(errbuf, "%.800s", err);
+                   fclose(fp);
+               }
+           }
+           break;
+       }
+       if (*errbuf) {
+           *error = dupstr(errbuf);
+           midend_free(fe->me);
+           sfree(fe);
+           return NULL;
+       }
+
+    } else {
+       midend_new_game(fe->me);
+    }
+
+#if !GTK_CHECK_VERSION(3,0,0)
+    {
+        /*
+         * try_shrink_drawing_area() will do some fiddling with the
+         * window size request (see comment in that function) after
+         * all the bits and pieces such as the menu bar and status bar
+         * have appeared in the puzzle window.
+         *
+         * However, on Unity systems, the menu bar _doesn't_ appear in
+         * the puzzle window, because the Unity shell hijacks it into
+         * the menu bar at the very top of the screen. We therefore
+         * try to detect that situation here, so that we don't sit
+         * here forever waiting for a menu bar.
+         */
+        const char prop[] = "gtk-shell-shows-menubar";
+        GtkSettings *settings = gtk_settings_get_default();
+        if (!g_object_class_find_property(G_OBJECT_GET_CLASS(settings),
+                                          prop)) {
+            fe->menubar_is_local = TRUE;
+        } else {
+            int unity_mode;
+            g_object_get(gtk_settings_get_default(),
+                         prop, &unity_mode,
+                         (const gchar *)NULL);
+            fe->menubar_is_local = !unity_mode;
+        }
+    }
+#endif
+
+    fe->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+    gtk_window_set_title(GTK_WINDOW(fe->window), thegame.name);
+
+    vbox = GTK_BOX(gtk_vbox_new(FALSE, 0));
+    gtk_container_add(GTK_CONTAINER(fe->window), GTK_WIDGET(vbox));
+    gtk_widget_show(GTK_WIDGET(vbox));
+
+    fe->accelgroup = gtk_accel_group_new();
+    gtk_window_add_accel_group(GTK_WINDOW(fe->window), fe->accelgroup);
+
+    hbox = GTK_BOX(gtk_hbox_new(FALSE, 0));
+    gtk_box_pack_start(vbox, GTK_WIDGET(hbox), FALSE, FALSE, 0);
+    gtk_widget_show(GTK_WIDGET(hbox));
+
+    fe->menubar = gtk_menu_bar_new();
+    gtk_box_pack_start(hbox, fe->menubar, TRUE, TRUE, 0);
+    gtk_widget_show(fe->menubar);
+
+    menuitem = gtk_menu_item_new_with_mnemonic("_Game");
+    gtk_container_add(GTK_CONTAINER(fe->menubar), menuitem);
+    gtk_widget_show(menuitem);
+
+    menu = gtk_menu_new();
+    gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
+
+    add_menu_item_with_key(fe, GTK_CONTAINER(menu), "New", 'n');
+
+    menuitem = gtk_menu_item_new_with_label("Restart");
+    gtk_container_add(GTK_CONTAINER(menu), menuitem);
+    g_signal_connect(G_OBJECT(menuitem), "activate",
+                     G_CALLBACK(menu_restart_event), fe);
+    gtk_widget_show(menuitem);
+
+    menuitem = gtk_menu_item_new_with_label("Specific...");
+    g_object_set_data(G_OBJECT(menuitem), "user-data",
+                      GINT_TO_POINTER(CFG_DESC));
+    gtk_container_add(GTK_CONTAINER(menu), menuitem);
+    g_signal_connect(G_OBJECT(menuitem), "activate",
+                     G_CALLBACK(menu_config_event), fe);
+    gtk_widget_show(menuitem);
+
+    menuitem = gtk_menu_item_new_with_label("Random Seed...");
+    g_object_set_data(G_OBJECT(menuitem), "user-data",
+                      GINT_TO_POINTER(CFG_SEED));
+    gtk_container_add(GTK_CONTAINER(menu), menuitem);
+    g_signal_connect(G_OBJECT(menuitem), "activate",
+                     G_CALLBACK(menu_config_event), fe);
+    gtk_widget_show(menuitem);
+
+    fe->preset_radio = NULL;
+    fe->preset_custom = NULL;
+    fe->n_preset_menu_items = 0;
+    fe->preset_threaded = FALSE;
+    if ((n = midend_num_presets(fe->me)) > 0 || thegame.can_configure) {
+        GtkWidget *submenu;
+        int i;
+
+        menuitem = gtk_menu_item_new_with_mnemonic("_Type");
+        gtk_container_add(GTK_CONTAINER(fe->menubar), menuitem);
+        gtk_widget_show(menuitem);
+
+        submenu = gtk_menu_new();
+        gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
+
+        for (i = 0; i < n; i++) {
+            char *name;
+            game_params *params;
+
+            midend_fetch_preset(fe->me, i, &name, &params);
+
+           menuitem =
+               gtk_radio_menu_item_new_with_label(fe->preset_radio, name);
+           fe->preset_radio =
+               gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(menuitem));
+           fe->n_preset_menu_items++;
+            gtk_container_add(GTK_CONTAINER(submenu), menuitem);
+            g_object_set_data(G_OBJECT(menuitem), "user-data", params);
+            g_signal_connect(G_OBJECT(menuitem), "activate",
+                             G_CALLBACK(menu_preset_event), fe);
+            gtk_widget_show(menuitem);
+        }
+
+       if (thegame.can_configure) {
+           menuitem = fe->preset_custom =
+               gtk_radio_menu_item_new_with_label(fe->preset_radio,
+                                                  "Custom...");
+           fe->preset_radio =
+               gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(menuitem));
+            gtk_container_add(GTK_CONTAINER(submenu), menuitem);
+            g_object_set_data(G_OBJECT(menuitem), "user-data",
+                              GINT_TO_POINTER(CFG_SETTINGS));
+            g_signal_connect(G_OBJECT(menuitem), "activate",
+                             G_CALLBACK(menu_config_event), fe);
+            gtk_widget_show(menuitem);
+       }
+
+    }
+
+    add_menu_separator(GTK_CONTAINER(menu));
+    menuitem = gtk_menu_item_new_with_label("Load...");
+    gtk_container_add(GTK_CONTAINER(menu), menuitem);
+    g_signal_connect(G_OBJECT(menuitem), "activate",
+                     G_CALLBACK(menu_load_event), fe);
+    gtk_widget_show(menuitem);
+    menuitem = gtk_menu_item_new_with_label("Save...");
+    gtk_container_add(GTK_CONTAINER(menu), menuitem);
+    g_signal_connect(G_OBJECT(menuitem), "activate",
+                     G_CALLBACK(menu_save_event), fe);
+    gtk_widget_show(menuitem);
+#ifndef STYLUS_BASED
+    add_menu_separator(GTK_CONTAINER(menu));
+    add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Undo", 'u');
+    add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Redo", 'r');
+#endif
+    if (thegame.can_format_as_text_ever) {
+       add_menu_separator(GTK_CONTAINER(menu));
+       menuitem = gtk_menu_item_new_with_label("Copy");
+       gtk_container_add(GTK_CONTAINER(menu), menuitem);
+       g_signal_connect(G_OBJECT(menuitem), "activate",
+                         G_CALLBACK(menu_copy_event), fe);
+       gtk_widget_show(menuitem);
+       fe->copy_menu_item = menuitem;
+    } else {
+       fe->copy_menu_item = NULL;
+    }
+    if (thegame.can_solve) {
+       add_menu_separator(GTK_CONTAINER(menu));
+       menuitem = gtk_menu_item_new_with_label("Solve");
+       gtk_container_add(GTK_CONTAINER(menu), menuitem);
+       g_signal_connect(G_OBJECT(menuitem), "activate",
+                         G_CALLBACK(menu_solve_event), fe);
+       gtk_widget_show(menuitem);
+    }
+    add_menu_separator(GTK_CONTAINER(menu));
+    add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Exit", 'q');
+
+    menuitem = gtk_menu_item_new_with_mnemonic("_Help");
+    gtk_container_add(GTK_CONTAINER(fe->menubar), menuitem);
+    gtk_widget_show(menuitem);
+
+    menu = gtk_menu_new();
+    gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
+
+    menuitem = gtk_menu_item_new_with_label("About");
+    gtk_container_add(GTK_CONTAINER(menu), menuitem);
+    g_signal_connect(G_OBJECT(menuitem), "activate",
+                     G_CALLBACK(menu_about_event), fe);
+    gtk_widget_show(menuitem);
+
+#ifdef STYLUS_BASED
+    menuitem=gtk_button_new_with_mnemonic("_Redo");
+    g_object_set_data(G_OBJECT(menuitem), "user-data",
+                      GINT_TO_POINTER((int)('r')));
+    g_signal_connect(G_OBJECT(menuitem), "clicked",
+                     G_CALLBACK(menu_key_event), fe);
+    gtk_box_pack_end(hbox, menuitem, FALSE, FALSE, 0);
+    gtk_widget_show(menuitem);
+
+    menuitem=gtk_button_new_with_mnemonic("_Undo");
+    g_object_set_data(G_OBJECT(menuitem), "user-data",
+                      GINT_TO_POINTER((int)('u')));
+    g_signal_connect(G_OBJECT(menuitem), "clicked",
+                     G_CALLBACK(menu_key_event), fe);
+    gtk_box_pack_end(hbox, menuitem, FALSE, FALSE, 0);
+    gtk_widget_show(menuitem);
+
+    if (thegame.flags & REQUIRE_NUMPAD) {
+       hbox = GTK_BOX(gtk_hbox_new(FALSE, 0));
+       gtk_box_pack_start(vbox, GTK_WIDGET(hbox), FALSE, FALSE, 0);
+       gtk_widget_show(GTK_WIDGET(hbox));
+
+       *((int*)errbuf)=0;
+       errbuf[1]='\0';
+       for(errbuf[0]='0';errbuf[0]<='9';errbuf[0]++) {
+           menuitem=gtk_button_new_with_label(errbuf);
+           g_object_set_data(G_OBJECT(menuitem), "user-data",
+                              GINT_TO_POINTER((int)(errbuf[0])));
+           g_signal_connect(G_OBJECT(menuitem), "clicked",
+                             G_CALLBACK(menu_key_event), fe);
+           gtk_box_pack_start(hbox, menuitem, TRUE, TRUE, 0);
+           gtk_widget_show(menuitem);
+       }
+    }
+#endif /* STYLUS_BASED */
+
+    changed_preset(fe);
+
+    snaffle_colours(fe);
+
+    if (midend_wants_statusbar(fe->me)) {
+       GtkWidget *viewport;
+       GtkRequisition req;
+
+       viewport = gtk_viewport_new(NULL, NULL);
+       gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_NONE);
+       fe->statusbar = gtk_statusbar_new();
+       gtk_container_add(GTK_CONTAINER(viewport), fe->statusbar);
+       gtk_widget_show(viewport);
+       gtk_box_pack_end(vbox, viewport, FALSE, FALSE, 0);
+       gtk_widget_show(fe->statusbar);
+       fe->statusctx = gtk_statusbar_get_context_id
+           (GTK_STATUSBAR(fe->statusbar), "game");
+       gtk_statusbar_push(GTK_STATUSBAR(fe->statusbar), fe->statusctx,
+                          DEFAULT_STATUSBAR_TEXT);
+#if GTK_CHECK_VERSION(3,0,0)
+       gtk_widget_get_preferred_size(fe->statusbar, &req, NULL);
+#else
+       gtk_widget_size_request(fe->statusbar, &req);
+#endif
+       gtk_widget_set_size_request(viewport, -1, req.height);
+    } else
+       fe->statusbar = NULL;
+
+    fe->area = gtk_drawing_area_new();
+#if GTK_CHECK_VERSION(2,0,0) && !GTK_CHECK_VERSION(3,0,0)
+    gtk_widget_set_double_buffered(fe->area, FALSE);
+#endif
+    {
+        GdkGeometry geom;
+        geom.base_width = 0;
+#if GTK_CHECK_VERSION(3,0,0)
+        geom.base_height = window_extra_height(fe);
+        gtk_window_set_geometry_hints(GTK_WINDOW(fe->window), NULL,
+                                      &geom, GDK_HINT_BASE_SIZE);
+#else
+        geom.base_height = 0;
+        gtk_window_set_geometry_hints(GTK_WINDOW(fe->window), fe->area,
+                                      &geom, GDK_HINT_BASE_SIZE);
+#endif
+    }
+    fe->w = -1;
+    fe->h = -1;
+    get_size(fe, &x, &y);
+#if GTK_CHECK_VERSION(3,0,0)
+    gtk_window_set_default_size(GTK_WINDOW(fe->window),
+                                x, y + window_extra_height(fe));
+#else
+    fe->drawing_area_shrink_pending = FALSE;
+    gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), x, y);
+#endif
+
+    gtk_box_pack_end(vbox, fe->area, TRUE, TRUE, 0);
+
+    clear_backing_store(fe);
+    fe->fonts = NULL;
+    fe->nfonts = fe->fontsize = 0;
+
+    fe->paste_data = NULL;
+    fe->paste_data_len = 0;
+
+    g_signal_connect(G_OBJECT(fe->window), "destroy",
+                     G_CALLBACK(destroy), fe);
+    g_signal_connect(G_OBJECT(fe->window), "key_press_event",
+                     G_CALLBACK(key_event), fe);
+    g_signal_connect(G_OBJECT(fe->area), "button_press_event",
+                     G_CALLBACK(button_event), fe);
+    g_signal_connect(G_OBJECT(fe->area), "button_release_event",
+                     G_CALLBACK(button_event), fe);
+    g_signal_connect(G_OBJECT(fe->area), "motion_notify_event",
+                     G_CALLBACK(motion_event), fe);
+    g_signal_connect(G_OBJECT(fe->area), "selection_get",
+                     G_CALLBACK(selection_get), fe);
+    g_signal_connect(G_OBJECT(fe->area), "selection_clear_event",
+                     G_CALLBACK(selection_clear), fe);
+#if GTK_CHECK_VERSION(3,0,0)
+    g_signal_connect(G_OBJECT(fe->area), "draw",
+                     G_CALLBACK(draw_area), fe);
+#else
+    g_signal_connect(G_OBJECT(fe->area), "expose_event",
+                     G_CALLBACK(expose_area), fe);
+#endif
+    g_signal_connect(G_OBJECT(fe->window), "map_event",
+                     G_CALLBACK(map_window), fe);
+    g_signal_connect(G_OBJECT(fe->area), "configure_event",
+                     G_CALLBACK(configure_area), fe);
+    g_signal_connect(G_OBJECT(fe->window), "configure_event",
+                     G_CALLBACK(configure_window), fe);
+
+    gtk_widget_add_events(GTK_WIDGET(fe->area),
+                          GDK_BUTTON_PRESS_MASK |
+                          GDK_BUTTON_RELEASE_MASK |
+                         GDK_BUTTON_MOTION_MASK |
+                         GDK_POINTER_MOTION_HINT_MASK);
+
+    if (n_xpm_icons) {
+        gtk_window_set_icon(GTK_WINDOW(fe->window),
+                            gdk_pixbuf_new_from_xpm_data
+                            ((const gchar **)xpm_icons[0]));
+
+       iconlist = NULL;
+       for (n = 0; n < n_xpm_icons; n++) {
+           iconlist =
+               g_list_append(iconlist,
+                             gdk_pixbuf_new_from_xpm_data((const gchar **)
+                                                          xpm_icons[n]));
+       }
+       gtk_window_set_icon_list(GTK_WINDOW(fe->window), iconlist);
+    }
+
+    gtk_widget_show(fe->area);
+    gtk_widget_show(fe->window);
+
+#if !GTK_CHECK_VERSION(3,0,0)
+    fe->drawing_area_shrink_pending = TRUE;
+    try_shrink_drawing_area(fe);
+#endif
+
+    set_window_background(fe, 0);
+
+    return fe;
+}
+
+char *fgetline(FILE *fp)
+{
+    char *ret = snewn(512, char);
+    int size = 512, len = 0;
+    while (fgets(ret + len, size - len, fp)) {
+       len += strlen(ret + len);
+       if (ret[len-1] == '\n')
+           break;                     /* got a newline, we're done */
+       size = len + 512;
+       ret = sresize(ret, size, char);
+    }
+    if (len == 0) {                   /* first fgets returned NULL */
+       sfree(ret);
+       return NULL;
+    }
+    ret[len] = '\0';
+    return ret;
+}
+
+int main(int argc, char **argv)
+{
+    char *pname = argv[0];
+    char *error;
+    int ngenerate = 0, print = FALSE, px = 1, py = 1;
+    int time_generation = FALSE, test_solve = FALSE, list_presets = FALSE;
+    int soln = FALSE, colour = FALSE;
+    float scale = 1.0F;
+    float redo_proportion = 0.0F;
+    char *savefile = NULL, *savesuffix = NULL;
+    char *arg = NULL;
+    int argtype = ARG_EITHER;
+    char *screenshot_file = NULL;
+    int doing_opts = TRUE;
+    int ac = argc;
+    char **av = argv;
+    char errbuf[500];
+
+    /*
+     * Command line parsing in this function is rather fiddly,
+     * because GTK wants to have a go at argc/argv _first_ - and
+     * yet we can't let it, because gtk_init() will bomb out if it
+     * can't open an X display, whereas in fact we want to permit
+     * our --generate and --print modes to run without an X
+     * display.
+     * 
+     * So what we do is:
+     *         - we parse the command line ourselves, without modifying
+     *           argc/argv
+     *         - if we encounter an error which might plausibly be the
+     *           result of a GTK command line (i.e. not detailed errors in
+     *           particular options of ours) we store the error message
+     *           and terminate parsing.
+     *         - if we got enough out of the command line to know it
+     *           specifies a non-X mode of operation, we either display
+     *           the stored error and return failure, or if there is no
+     *           stored error we do the non-X operation and return
+     *           success.
+     *  - otherwise, we go straight to gtk_init().
+     */
+
+    errbuf[0] = '\0';
+    while (--ac > 0) {
+       char *p = *++av;
+       if (doing_opts && !strcmp(p, "--version")) {
+           printf("%s, from Simon Tatham's Portable Puzzle Collection\n%s\n",
+                  thegame.name, ver);
+           return 0;
+       } else if (doing_opts && !strcmp(p, "--generate")) {
+           if (--ac > 0) {
+               ngenerate = atoi(*++av);
+               if (!ngenerate) {
+                   fprintf(stderr, "%s: '--generate' expected a number\n",
+                           pname);
+                   return 1;
+               }
+           } else
+               ngenerate = 1;
+       } else if (doing_opts && !strcmp(p, "--time-generation")) {
+            time_generation = TRUE;
+       } else if (doing_opts && !strcmp(p, "--test-solve")) {
+            test_solve = TRUE;
+       } else if (doing_opts && !strcmp(p, "--list-presets")) {
+            list_presets = TRUE;
+       } else if (doing_opts && !strcmp(p, "--save")) {
+           if (--ac > 0) {
+               savefile = *++av;
+           } else {
+               fprintf(stderr, "%s: '--save' expected a filename\n",
+                       pname);
+               return 1;
+           }
+       } else if (doing_opts && (!strcmp(p, "--save-suffix") ||
+                                 !strcmp(p, "--savesuffix"))) {
+           if (--ac > 0) {
+               savesuffix = *++av;
+           } else {
+               fprintf(stderr, "%s: '--save-suffix' expected a filename\n",
+                       pname);
+               return 1;
+           }
+       } else if (doing_opts && !strcmp(p, "--print")) {
+           if (!thegame.can_print) {
+               fprintf(stderr, "%s: this game does not support printing\n",
+                       pname);
+               return 1;
+           }
+           print = TRUE;
+           if (--ac > 0) {
+               char *dim = *++av;
+               if (sscanf(dim, "%dx%d", &px, &py) != 2) {
+                   fprintf(stderr, "%s: unable to parse argument '%s' to "
+                           "'--print'\n", pname, dim);
+                   return 1;
+               }
+           } else {
+               px = py = 1;
+           }
+       } else if (doing_opts && !strcmp(p, "--scale")) {
+           if (--ac > 0) {
+               scale = atof(*++av);
+           } else {
+               fprintf(stderr, "%s: no argument supplied to '--scale'\n",
+                       pname);
+               return 1;
+           }
+       } else if (doing_opts && !strcmp(p, "--redo")) {
+           /*
+            * This is an internal option which I don't expect
+            * users to have any particular use for. The effect of
+            * --redo is that once the game has been loaded and
+            * initialised, the next move in the redo chain is
+            * replayed, and the game screen is redrawn part way
+            * through the making of the move. This is only
+            * meaningful if there _is_ a next move in the redo
+            * chain, which means in turn that this option is only
+            * useful if you're also passing a save file on the
+            * command line.
+            *
+            * This option is used by the script which generates
+            * the puzzle icons and website screenshots, and I
+            * don't imagine it's useful for anything else.
+            * (Unless, I suppose, users don't like my screenshots
+            * and want to generate their own in the same way for
+            * some repackaged version of the puzzles.)
+            */
+           if (--ac > 0) {
+               redo_proportion = atof(*++av);
+           } else {
+               fprintf(stderr, "%s: no argument supplied to '--redo'\n",
+                       pname);
+               return 1;
+           }
+       } else if (doing_opts && !strcmp(p, "--screenshot")) {
+           /*
+            * Another internal option for the icon building
+            * script. This causes a screenshot of the central
+            * drawing area (i.e. not including the menu bar or
+            * status bar) to be saved to a PNG file once the
+            * window has been drawn, and then the application
+            * quits immediately.
+            */
+           if (--ac > 0) {
+               screenshot_file = *++av;
+           } else {
+               fprintf(stderr, "%s: no argument supplied to '--screenshot'\n",
+                       pname);
+               return 1;
+           }
+       } else if (doing_opts && (!strcmp(p, "--with-solutions") ||
+                                 !strcmp(p, "--with-solution") ||
+                                 !strcmp(p, "--with-solns") ||
+                                 !strcmp(p, "--with-soln") ||
+                                 !strcmp(p, "--solutions") ||
+                                 !strcmp(p, "--solution") ||
+                                 !strcmp(p, "--solns") ||
+                                 !strcmp(p, "--soln"))) {
+           soln = TRUE;
+       } else if (doing_opts && !strcmp(p, "--colour")) {
+           if (!thegame.can_print_in_colour) {
+               fprintf(stderr, "%s: this game does not support colour"
+                       " printing\n", pname);
+               return 1;
+           }
+           colour = TRUE;
+       } else if (doing_opts && !strcmp(p, "--load")) {
+           argtype = ARG_SAVE;
+       } else if (doing_opts && !strcmp(p, "--game")) {
+           argtype = ARG_ID;
+       } else if (doing_opts && !strcmp(p, "--")) {
+           doing_opts = FALSE;
+       } else if (!doing_opts || p[0] != '-') {
+           if (arg) {
+               fprintf(stderr, "%s: more than one argument supplied\n",
+                       pname);
+               return 1;
+           }
+           arg = p;
+       } else {
+           sprintf(errbuf, "%.100s: unrecognised option '%.100s'\n",
+                   pname, p);
+           break;
+       }
+    }
+
+    /*
+     * Special standalone mode for generating puzzle IDs on the
+     * command line. Useful for generating puzzles to be printed
+     * out and solved offline (for puzzles where that even makes
+     * sense - Solo, for example, is a lot more pencil-and-paper
+     * friendly than Twiddle!)
+     * 
+     * Usage:
+     * 
+     *   <puzzle-name> --generate [<n> [<params>]]
+     * 
+     * <n>, if present, is the number of puzzle IDs to generate.
+     * <params>, if present, is the same type of parameter string
+     * you would pass to the puzzle when running it in GUI mode,
+     * including optional extras such as the expansion factor in
+     * Rectangles and the difficulty level in Solo.
+     * 
+     * If you specify <params>, you must also specify <n> (although
+     * you may specify it to be 1). Sorry; that was the
+     * simplest-to-parse command-line syntax I came up with.
+     */
+    if (ngenerate > 0 || print || savefile || savesuffix) {
+       int i, n = 1;
+       midend *me;
+       char *id;
+       document *doc = NULL;
+
+        /*
+         * If we're in this branch, we should display any pending
+         * error message from the command line, since GTK isn't going
+         * to take another crack at making sense of it.
+         */
+        if (*errbuf) {
+            fputs(errbuf, stderr);
+            return 1;
+        }
+
+       n = ngenerate;
+
+       me = midend_new(NULL, &thegame, NULL, NULL);
+       i = 0;
+
+       if (savefile && !savesuffix)
+           savesuffix = "";
+       if (!savefile && savesuffix)
+           savefile = "";
+
+       if (print)
+           doc = document_new(px, py, scale);
+
+       /*
+        * In this loop, we either generate a game ID or read one
+        * from stdin depending on whether we're in generate mode;
+        * then we either write it to stdout or print it, depending
+        * on whether we're in print mode. Thus, this loop handles
+        * generate-to-stdout, print-from-stdin and generate-and-
+        * immediately-print modes.
+        * 
+        * (It could also handle a copy-stdin-to-stdout mode,
+        * although there's currently no combination of options
+        * which will cause this loop to be activated in that mode.
+        * It wouldn't be _entirely_ pointless, though, because
+        * stdin could contain bare params strings or random-seed
+        * IDs, and stdout would contain nothing but fully
+        * generated descriptive game IDs.)
+        */
+       while (ngenerate == 0 || i < n) {
+           char *pstr, *err, *seed;
+            struct rusage before, after;
+
+           if (ngenerate == 0) {
+               pstr = fgetline(stdin);
+               if (!pstr)
+                   break;
+               pstr[strcspn(pstr, "\r\n")] = '\0';
+           } else {
+               if (arg) {
+                   pstr = snewn(strlen(arg) + 40, char);
+
+                   strcpy(pstr, arg);
+                   if (i > 0 && strchr(arg, '#'))
+                       sprintf(pstr + strlen(pstr), "-%d", i);
+               } else
+                   pstr = NULL;
+           }
+
+           if (pstr) {
+               err = midend_game_id(me, pstr);
+               if (err) {
+                   fprintf(stderr, "%s: error parsing '%s': %s\n",
+                           pname, pstr, err);
+                   return 1;
+               }
+           }
+
+            if (time_generation)
+                getrusage(RUSAGE_SELF, &before);
+
+            midend_new_game(me);
+
+            seed = midend_get_random_seed(me);
+
+            if (time_generation) {
+                double elapsed;
+
+                getrusage(RUSAGE_SELF, &after);
+
+                elapsed = (after.ru_utime.tv_sec -
+                           before.ru_utime.tv_sec);
+                elapsed += (after.ru_utime.tv_usec -
+                            before.ru_utime.tv_usec) / 1000000.0;
+
+                printf("%s %s: %.6f\n", thegame.name, seed, elapsed);
+            }
+
+            if (test_solve && thegame.can_solve) {
+                /*
+                 * Now destroy the aux_info in the midend, by means of
+                 * re-entering the same game id, and then try to solve
+                 * it.
+                 */
+                char *game_id, *err;
+
+                game_id = midend_get_game_id(me);
+                err = midend_game_id(me, game_id);
+                if (err) {
+                    fprintf(stderr, "%s %s: game id re-entry error: %s\n",
+                            thegame.name, seed, err);
+                    return 1;
+                }
+                midend_new_game(me);
+                sfree(game_id);
+
+                err = midend_solve(me);
+                /*
+                 * If the solve operation returned the error "Solution
+                 * not known for this puzzle", that's OK, because that
+                 * just means it's a puzzle for which we don't have an
+                 * algorithmic solver and hence can't solve it without
+                 * the aux_info, e.g. Netslide. Any other error is a
+                 * problem, though.
+                 */
+                if (err && strcmp(err, "Solution not known for this puzzle")) {
+                    fprintf(stderr, "%s %s: solve error: %s\n",
+                            thegame.name, seed, err);
+                    return 1;
+                }
+            }
+
+           sfree(pstr);
+            sfree(seed);
+
+           if (doc) {
+               err = midend_print_puzzle(me, doc, soln);
+               if (err) {
+                   fprintf(stderr, "%s: error in printing: %s\n", pname, err);
+                   return 1;
+               }
+           }
+           if (savefile) {
+               struct savefile_write_ctx ctx;
+               char *realname = snewn(40 + strlen(savefile) +
+                                      strlen(savesuffix), char);
+               sprintf(realname, "%s%d%s", savefile, i, savesuffix);
+
+                if (soln) {
+                    char *err = midend_solve(me);
+                    if (err) {
+                        fprintf(stderr, "%s: unable to show solution: %s\n",
+                                realname, err);
+                        return 1;
+                    }
+                }
+
+               ctx.fp = fopen(realname, "w");
+               if (!ctx.fp) {
+                   fprintf(stderr, "%s: open: %s\n", realname,
+                           strerror(errno));
+                   return 1;
+               }
+                ctx.error = 0;
+               midend_serialise(me, savefile_write, &ctx);
+               if (ctx.error) {
+                   fprintf(stderr, "%s: write: %s\n", realname,
+                           strerror(ctx.error));
+                   return 1;
+               }
+               if (fclose(ctx.fp)) {
+                   fprintf(stderr, "%s: close: %s\n", realname,
+                           strerror(errno));
+                   return 1;
+               }
+               sfree(realname);
+           }
+           if (!doc && !savefile && !time_generation) {
+               id = midend_get_game_id(me);
+               puts(id);
+               sfree(id);
+           }
+
+           i++;
+       }
+
+       if (doc) {
+           psdata *ps = ps_init(stdout, colour);
+           document_print(doc, ps_drawing_api(ps));
+           document_free(doc);
+           ps_free(ps);
+       }
+
+       midend_free(me);
+
+       return 0;
+    } else if (list_presets) {
+        /*
+         * Another specialist mode which causes the puzzle to list the
+         * game_params strings for all its preset configurations.
+         */
+        int i, npresets;
+        midend *me;
+
+       me = midend_new(NULL, &thegame, NULL, NULL);
+        npresets = midend_num_presets(me);
+
+        for (i = 0; i < npresets; i++) {
+            game_params *params;
+            char *name, *paramstr;
+
+            midend_fetch_preset(me, i, &name, &params);
+            paramstr = thegame.encode_params(params, TRUE);
+
+            printf("%s %s\n", paramstr, name);
+            sfree(paramstr);
+        }
+
+       midend_free(me);
+        return 0;
+    } else {
+       frontend *fe;
+
+       gtk_init(&argc, &argv);
+
+       fe = new_window(arg, argtype, &error);
+
+       if (!fe) {
+           fprintf(stderr, "%s: %s\n", pname, error);
+           return 1;
+       }
+
+       if (screenshot_file) {
+           /*
+            * Some puzzles will not redraw their entire area if
+            * given a partially completed animation, which means
+            * we must redraw now and _then_ redraw again after
+            * freezing the move timer.
+            */
+           midend_force_redraw(fe->me);
+       }
+
+       if (redo_proportion) {
+           /* Start a redo. */
+           midend_process_key(fe->me, 0, 0, 'r');
+           /* And freeze the timer at the specified position. */
+           midend_freeze_timer(fe->me, redo_proportion);
+       }
+
+       if (screenshot_file) {
+           save_screenshot_png(fe, screenshot_file);
+           exit(0);
+       }
+
+       gtk_main();
+    }
+
+    return 0;
+}
diff --git a/guess.R b/guess.R
new file mode 100644 (file)
index 0000000..0e1a00c
--- /dev/null
+++ b/guess.R
@@ -0,0 +1,19 @@
+# -*- makefile -*-
+
+guess    : [X] GTK COMMON guess guess-icon|no-icon
+
+guess    : [G] WINDOWS COMMON guess guess.res|noicon.res
+
+ALL += guess[COMBINED]
+
+!begin am gtk
+GAMES += guess
+!end
+
+!begin >list.c
+    A(guess) \
+!end
+
+!begin >gamedesc.txt
+guess:guess.exe:Guess:Combination-guessing puzzle:Guess the hidden combination of colours.
+!end
diff --git a/guess.c b/guess.c
new file mode 100644 (file)
index 0000000..df9b7f6
--- /dev/null
+++ b/guess.c
@@ -0,0 +1,1518 @@
+/*
+ * guess.c: Mastermind clone.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+
+#define FLASH_FRAME 0.5F
+
+enum {
+    COL_BACKGROUND,
+    COL_FRAME, COL_CURSOR, COL_FLASH, COL_HOLD,
+    COL_EMPTY, /* must be COL_1 - 1 */
+    COL_1, COL_2, COL_3, COL_4, COL_5, COL_6, COL_7, COL_8, COL_9, COL_10,
+    COL_CORRECTPLACE, COL_CORRECTCOLOUR,
+    NCOLOURS
+};
+
+struct game_params {
+    int ncolours, npegs, nguesses;
+    int allow_blank, allow_multiple;
+};
+
+#define FEEDBACK_CORRECTPLACE  1
+#define FEEDBACK_CORRECTCOLOUR 2
+
+typedef struct pegrow {
+    int npegs;
+    int *pegs;          /* 0 is 'empty' */
+    int *feedback;      /* may well be unused */
+} *pegrow;
+
+struct game_state {
+    game_params params;
+    pegrow *guesses;  /* length params->nguesses */
+    int *holds;
+    pegrow solution;
+    int next_go; /* from 0 to nguesses-1;
+                    if next_go == nguesses then they've lost. */
+    int solved;   /* +1 = win, -1 = lose, 0 = still playing */
+};
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+
+    /* AFAIK this is the canonical Mastermind ruleset. */
+    ret->ncolours = 6;
+    ret->npegs = 4;
+    ret->nguesses = 10;
+
+    ret->allow_blank = 0;
+    ret->allow_multiple = 1;
+
+    return ret;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+static const struct {
+    char *name;
+    game_params params;
+} guess_presets[] = {
+    {"Standard", {6, 4, 10, FALSE, TRUE}},
+    {"Super", {8, 5, 12, FALSE, TRUE}},
+};
+
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    if (i < 0 || i >= lenof(guess_presets))
+        return FALSE;
+
+    *name = dupstr(guess_presets[i].name);
+    /*
+     * get round annoying const issues
+     */
+    {
+        game_params tmp = guess_presets[i].params;
+        *params = dup_params(&tmp);
+    }
+
+    return TRUE;
+}
+
+static void decode_params(game_params *params, char const *string)
+{
+    char const *p = string;
+    game_params *defs = default_params();
+
+    *params = *defs; free_params(defs);
+
+    while (*p) {
+       switch (*p++) {
+       case 'c':
+           params->ncolours = atoi(p);
+           while (*p && isdigit((unsigned char)*p)) p++;
+           break;
+
+       case 'p':
+           params->npegs = atoi(p);
+           while (*p && isdigit((unsigned char)*p)) p++;
+           break;
+
+       case 'g':
+           params->nguesses = atoi(p);
+           while (*p && isdigit((unsigned char)*p)) p++;
+           break;
+
+        case 'b':
+            params->allow_blank = 1;
+            break;
+
+        case 'B':
+            params->allow_blank = 0;
+            break;
+
+        case 'm':
+            params->allow_multiple = 1;
+            break;
+
+        case 'M':
+            params->allow_multiple = 0;
+            break;
+
+       default:
+            ;
+       }
+    }
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char data[256];
+
+    sprintf(data, "c%dp%dg%d%s%s",
+            params->ncolours, params->npegs, params->nguesses,
+            params->allow_blank ? "b" : "B", params->allow_multiple ? "m" : "M");
+
+    return dupstr(data);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+    char buf[80];
+
+    ret = snewn(6, config_item);
+
+    ret[0].name = "Colours";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->ncolours);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Pegs per guess";
+    ret[1].type = C_STRING;
+    sprintf(buf, "%d", params->npegs);
+    ret[1].sval = dupstr(buf);
+    ret[1].ival = 0;
+
+    ret[2].name = "Guesses";
+    ret[2].type = C_STRING;
+    sprintf(buf, "%d", params->nguesses);
+    ret[2].sval = dupstr(buf);
+    ret[2].ival = 0;
+
+    ret[3].name = "Allow blanks";
+    ret[3].type = C_BOOLEAN;
+    ret[3].sval = NULL;
+    ret[3].ival = params->allow_blank;
+
+    ret[4].name = "Allow duplicates";
+    ret[4].type = C_BOOLEAN;
+    ret[4].sval = NULL;
+    ret[4].ival = params->allow_multiple;
+
+    ret[5].name = NULL;
+    ret[5].type = C_END;
+    ret[5].sval = NULL;
+    ret[5].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->ncolours = atoi(cfg[0].sval);
+    ret->npegs = atoi(cfg[1].sval);
+    ret->nguesses = atoi(cfg[2].sval);
+
+    ret->allow_blank = cfg[3].ival;
+    ret->allow_multiple = cfg[4].ival;
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    if (params->ncolours < 2 || params->npegs < 2)
+       return "Trivial solutions are uninteresting";
+    /* NB as well as the no. of colours we define, max(ncolours) must
+     * also fit in an unsigned char; see new_game_desc. */
+    if (params->ncolours > 10)
+       return "Too many colours";
+    if (params->nguesses < 1)
+       return "Must have at least one guess";
+    if (!params->allow_multiple && params->ncolours < params->npegs)
+        return "Disallowing multiple colours requires at least as many colours as pegs";
+    return NULL;
+}
+
+static pegrow new_pegrow(int npegs)
+{
+    pegrow pegs = snew(struct pegrow);
+
+    pegs->npegs = npegs;
+    pegs->pegs = snewn(pegs->npegs, int);
+    memset(pegs->pegs, 0, pegs->npegs * sizeof(int));
+    pegs->feedback = snewn(pegs->npegs, int);
+    memset(pegs->feedback, 0, pegs->npegs * sizeof(int));
+
+    return pegs;
+}
+
+static pegrow dup_pegrow(pegrow pegs)
+{
+    pegrow newpegs = new_pegrow(pegs->npegs);
+
+    memcpy(newpegs->pegs, pegs->pegs, newpegs->npegs * sizeof(int));
+    memcpy(newpegs->feedback, pegs->feedback, newpegs->npegs * sizeof(int));
+
+    return newpegs;
+}
+
+static void invalidate_pegrow(pegrow pegs)
+{
+    memset(pegs->pegs, -1, pegs->npegs * sizeof(int));
+    memset(pegs->feedback, -1, pegs->npegs * sizeof(int));
+}
+
+static void free_pegrow(pegrow pegs)
+{
+    sfree(pegs->pegs);
+    sfree(pegs->feedback);
+    sfree(pegs);
+}
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                          char **aux, int interactive)
+{
+    unsigned char *bmp = snewn(params->npegs, unsigned char);
+    char *ret;
+    int i, c;
+    pegrow colcount = new_pegrow(params->ncolours);
+
+    for (i = 0; i < params->npegs; i++) {
+newcol:
+        c = random_upto(rs, params->ncolours);
+        if (!params->allow_multiple && colcount->pegs[c]) goto newcol;
+        colcount->pegs[c]++;
+        bmp[i] = (unsigned char)(c+1);
+    }
+    obfuscate_bitmap(bmp, params->npegs*8, FALSE);
+
+    ret = bin2hex(bmp, params->npegs);
+    sfree(bmp);
+    free_pegrow(colcount);
+    return ret;
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    unsigned char *bmp;
+    int i;
+
+    /* desc is just an (obfuscated) bitmap of the solution; check that
+     * it's the correct length and (when unobfuscated) contains only
+     * sensible colours. */
+    if (strlen(desc) != params->npegs * 2)
+        return "Game description is wrong length";
+    bmp = hex2bin(desc, params->npegs);
+    obfuscate_bitmap(bmp, params->npegs*8, TRUE);
+    for (i = 0; i < params->npegs; i++) {
+        if (bmp[i] < 1 || bmp[i] > params->ncolours) {
+            sfree(bmp);
+            return "Game description is corrupted";
+        }
+    }
+    sfree(bmp);
+
+    return NULL;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    game_state *state = snew(game_state);
+    unsigned char *bmp;
+    int i;
+
+    state->params = *params;
+    state->guesses = snewn(params->nguesses, pegrow);
+    for (i = 0; i < params->nguesses; i++)
+       state->guesses[i] = new_pegrow(params->npegs);
+    state->holds = snewn(params->npegs, int);
+    state->solution = new_pegrow(params->npegs);
+
+    bmp = hex2bin(desc, params->npegs);
+    obfuscate_bitmap(bmp, params->npegs*8, TRUE);
+    for (i = 0; i < params->npegs; i++)
+       state->solution->pegs[i] = (int)bmp[i];
+    sfree(bmp);
+
+    memset(state->holds, 0, sizeof(int) * params->npegs);
+    state->next_go = state->solved = 0;
+
+    return state;
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    game_state *ret = snew(game_state);
+    int i;
+
+    *ret = *state;
+
+    ret->guesses = snewn(state->params.nguesses, pegrow);
+    for (i = 0; i < state->params.nguesses; i++)
+       ret->guesses[i] = dup_pegrow(state->guesses[i]);
+    ret->holds = snewn(state->params.npegs, int);
+    memcpy(ret->holds, state->holds, sizeof(int) * state->params.npegs);
+    ret->solution = dup_pegrow(state->solution);
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    int i;
+
+    free_pegrow(state->solution);
+    for (i = 0; i < state->params.nguesses; i++)
+       free_pegrow(state->guesses[i]);
+    sfree(state->holds);
+    sfree(state->guesses);
+
+    sfree(state);
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    return dupstr("S");
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    return NULL;
+}
+
+static int is_markable(const game_params *params, pegrow pegs)
+{
+    int i, nset = 0, nrequired, ret = 0;
+    pegrow colcount = new_pegrow(params->ncolours);
+
+    nrequired = params->allow_blank ? 1 : params->npegs;
+
+    for (i = 0; i < params->npegs; i++) {
+        int c = pegs->pegs[i];
+        if (c > 0) {
+            colcount->pegs[c-1]++;
+            nset++;
+        }
+    }
+    if (nset < nrequired) goto done;
+
+    if (!params->allow_multiple) {
+        for (i = 0; i < params->ncolours; i++) {
+            if (colcount->pegs[i] > 1) goto done;
+        }
+    }
+    ret = 1;
+done:
+    free_pegrow(colcount);
+    return ret;
+}
+
+struct game_ui {
+    game_params params;
+    pegrow curr_pegs; /* half-finished current move */
+    int *holds;
+    int colour_cur;   /* position of up-down colour picker cursor */
+    int peg_cur;      /* position of left-right peg picker cursor */
+    int display_cur, markable;
+
+    int drag_col, drag_x, drag_y; /* x and y are *center* of peg! */
+    int drag_opeg; /* peg index, if dragged from a peg (from current guess), otherwise -1 */
+
+    int show_labels;                   /* label the colours with letters */
+    pegrow hint;
+};
+
+static game_ui *new_ui(const game_state *state)
+{
+    game_ui *ui = snew(game_ui);
+    memset(ui, 0, sizeof(game_ui));
+    ui->params = state->params;        /* structure copy */
+    ui->curr_pegs = new_pegrow(state->params.npegs);
+    ui->holds = snewn(state->params.npegs, int);
+    memset(ui->holds, 0, sizeof(int)*state->params.npegs);
+    ui->drag_opeg = -1;
+    return ui;
+}
+
+static void free_ui(game_ui *ui)
+{
+    if (ui->hint)
+        free_pegrow(ui->hint);
+    free_pegrow(ui->curr_pegs);
+    sfree(ui->holds);
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    char *ret, *p, *sep;
+    int i;
+
+    /*
+     * For this game it's worth storing the contents of the current
+     * guess, and the current set of holds.
+     */
+    ret = snewn(40 * ui->curr_pegs->npegs, char);
+    p = ret;
+    sep = "";
+    for (i = 0; i < ui->curr_pegs->npegs; i++) {
+        p += sprintf(p, "%s%d%s", sep, ui->curr_pegs->pegs[i],
+                     ui->holds[i] ? "_" : "");
+        sep = ",";
+    }
+    *p++ = '\0';
+    assert(p - ret < 40 * ui->curr_pegs->npegs);
+    return sresize(ret, p - ret, char);
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+    int i;
+    const char *p = encoding;
+    for (i = 0; i < ui->curr_pegs->npegs; i++) {
+        ui->curr_pegs->pegs[i] = atoi(p);
+        while (*p && isdigit((unsigned char)*p)) p++;
+        if (*p == '_') {
+            /* NB: old versions didn't store holds */
+            ui->holds[i] = 1;
+            p++;
+        } else
+            ui->holds[i] = 0;
+        if (*p == ',') p++;
+    }
+    ui->markable = is_markable(&ui->params, ui->curr_pegs);
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+    int i;
+
+    if (newstate->next_go < oldstate->next_go) {
+        sfree(ui->hint);
+        ui->hint = NULL;
+    }
+
+    /* Implement holds, clear other pegs.
+     * This does something that is arguably the Right Thing even
+     * for undo. */
+    for (i = 0; i < newstate->solution->npegs; i++) {
+        if (newstate->solved)
+            ui->holds[i] = 0;
+        else
+            ui->holds[i] = newstate->holds[i];
+       if (newstate->solved || (newstate->next_go == 0) || !ui->holds[i]) {
+           ui->curr_pegs->pegs[i] = 0;
+       } else
+            ui->curr_pegs->pegs[i] =
+                newstate->guesses[newstate->next_go-1]->pegs[i];
+    }
+    ui->markable = is_markable(&newstate->params, ui->curr_pegs);
+    /* Clean up cursor position */
+    if (!ui->markable && ui->peg_cur == newstate->solution->npegs)
+       ui->peg_cur--;
+}
+
+#define PEGSZ   (ds->pegsz)
+#define PEGOFF  (ds->pegsz + ds->gapsz)
+#define HINTSZ  (ds->hintsz)
+#define HINTOFF (ds->hintsz + ds->gapsz)
+
+#define GAP     (ds->gapsz)
+#define CGAP    (ds->gapsz / 2)
+
+#define PEGRAD  (ds->pegrad)
+#define HINTRAD (ds->hintrad)
+
+#define COL_OX          (ds->colx)
+#define COL_OY          (ds->coly)
+#define COL_X(c)        (COL_OX)
+#define COL_Y(c)        (COL_OY + (c)*PEGOFF)
+#define COL_W           PEGOFF
+#define COL_H           (ds->colours->npegs*PEGOFF)
+
+#define GUESS_OX        (ds->guessx)
+#define GUESS_OY        (ds->guessy)
+#define GUESS_X(g,p)    (GUESS_OX + (p)*PEGOFF)
+#define GUESS_Y(g,p)    (GUESS_OY + (g)*PEGOFF)
+#define GUESS_W         (ds->solution->npegs*PEGOFF)
+#define GUESS_H         (ds->nguesses*PEGOFF)
+
+#define HINT_OX         (GUESS_OX + GUESS_W + ds->gapsz)
+#define HINT_OY         (GUESS_OY + (PEGSZ - HINTOFF - HINTSZ) / 2)
+#define HINT_X(g)       HINT_OX
+#define HINT_Y(g)       (HINT_OY + (g)*PEGOFF)
+#define HINT_W          ((ds->hintw*HINTOFF) - GAP)
+#define HINT_H          GUESS_H
+
+#define SOLN_OX         GUESS_OX
+#define SOLN_OY         (GUESS_OY + GUESS_H + ds->gapsz + 2)
+#define SOLN_W          GUESS_W
+#define SOLN_H          PEGOFF
+
+struct game_drawstate {
+    int nguesses;
+    pegrow *guesses;    /* same size as state->guesses */
+    pegrow solution;    /* only displayed if state->solved */
+    pegrow colours;     /* length ncolours, not npegs */
+
+    int pegsz, hintsz, gapsz; /* peg size (diameter), etc. */
+    int pegrad, hintrad;      /* radius of peg, hint */
+    int border;
+    int colx, coly;     /* origin of colours vertical bar */
+    int guessx, guessy; /* origin of guesses */
+    int solnx, solny;   /* origin of solution */
+    int hintw;          /* no. of hint tiles we're wide per row */
+    int w, h, started, solved;
+
+    int next_go;
+
+    blitter *blit_peg;
+    int drag_col, blit_ox, blit_oy;
+};
+
+static void set_peg(const game_params *params, game_ui *ui, int peg, int col)
+{
+    ui->curr_pegs->pegs[peg] = col;
+    ui->markable = is_markable(params, ui->curr_pegs);
+}
+
+static int mark_pegs(pegrow guess, const pegrow solution, int ncols)
+{
+    int nc_place = 0, nc_colour = 0, i, j;
+
+    assert(guess && solution && (guess->npegs == solution->npegs));
+
+    for (i = 0; i < guess->npegs; i++) {
+        if (guess->pegs[i] == solution->pegs[i]) nc_place++;
+    }
+
+    /* slight bit of cleverness: we have the following formula, from
+     * http://mathworld.wolfram.com/Mastermind.html that gives:
+     *
+     * nc_colour = sum(colours, min(#solution, #guess)) - nc_place
+     *
+     * I think this is due to Knuth.
+     */
+    for (i = 1; i <= ncols; i++) {
+        int n_guess = 0, n_solution = 0;
+        for (j = 0; j < guess->npegs; j++) {
+            if (guess->pegs[j] == i) n_guess++;
+            if (solution->pegs[j] == i) n_solution++;
+        }
+        nc_colour += min(n_guess, n_solution);
+    }
+    nc_colour -= nc_place;
+
+    debug(("mark_pegs, %d pegs, %d right place, %d right colour",
+           guess->npegs, nc_place, nc_colour));
+    assert((nc_colour + nc_place) <= guess->npegs);
+
+    memset(guess->feedback, 0, guess->npegs*sizeof(int));
+    for (i = 0, j = 0; i < nc_place; i++)
+        guess->feedback[j++] = FEEDBACK_CORRECTPLACE;
+    for (i = 0; i < nc_colour; i++)
+        guess->feedback[j++] = FEEDBACK_CORRECTCOLOUR;
+
+    return nc_place;
+}
+
+static char *encode_move(const game_state *from, game_ui *ui)
+{
+    char *buf, *p, *sep;
+    int len, i;
+
+    len = ui->curr_pegs->npegs * 20 + 2;
+    buf = snewn(len, char);
+    p = buf;
+    *p++ = 'G';
+    sep = "";
+    for (i = 0; i < ui->curr_pegs->npegs; i++) {
+       p += sprintf(p, "%s%d%s", sep, ui->curr_pegs->pegs[i],
+                     ui->holds[i] ? "_" : "");
+       sep = ",";
+    }
+    *p++ = '\0';
+    assert(p - buf <= len);
+    buf = sresize(buf, len, char);
+
+    return buf;
+}
+
+static void compute_hint(const game_state *state, game_ui *ui)
+{
+    /* Suggest the lexicographically first row consistent with all
+     * previous feedback.  This is not only a useful hint, but also
+     * a reasonable strategy if applied consistently.  If the user
+     * uses hints in every turn, they may be able to intuit this
+     * strategy, or one similar to it.  I (Jonas Kölker) came up
+     * with something close to it without seeing it in action. */
+
+    /* Some performance characteristics: I want to ask for each n,
+     * how many solutions are guessed in exactly n guesses if you
+     * use the hint in each turn.
+     *
+     * With 4 pegs and 6 colours you get the following histogram:
+     *
+     *  1 guesses:     1 solution
+     *  2 guesses:     4 solutions
+     *  3 guesses:    25 solutions
+     *  4 guesses:   108 solutions
+     *  5 guesses:   305 solutions
+     *  6 guesses:   602 solutions
+     *  7 guesses:   196 solutions
+     *  8 guesses:    49 solutions
+     *  9 guesses:     6 solutions
+     * (note: the tenth guess is never necessary.)
+     *
+     * With 5 pegs and 8 colours you get the following histogram:
+     *
+     *  1 guesses:     1 solution
+     *  2 guesses:     5 solutions
+     *  3 guesses:    43 solutions
+     *  4 guesses:   278 solutions
+     *  5 guesses:  1240 solutions
+     *  6 guesses:  3515 solutions
+     *  7 guesses:  7564 solutions
+     *  8 guesses: 14086 solutions
+     *  9 guesses:  4614 solutions
+     * 10 guesses:  1239 solutions
+     * 11 guesses:   175 solutions
+     * 12 guesses:     7 solutions
+     * 13 guesses:     1 solution
+     *
+     * The solution which takes too many guesses is {8, 8, 5, 6, 7}.
+     * The game ID is c8p5g12Bm:4991e5e41a. */
+
+    int mincolour = 1, maxcolour = 0, i, j;
+
+    /* For large values of npegs and ncolours, the lexicographically
+     * next guess make take a while to find.  Finding upper and
+     * lower limits on which colours we have to consider will speed
+     * this up, as will caching our progress from one invocation to
+     * the next.  The latter strategy works, since if we have ruled
+     * out a candidate we will never reverse this judgment in the
+     * light of new information.  Removing information, i.e. undo,
+     * will require us to backtrack somehow.  We backtrack by fully
+     * forgetting our progress (and recomputing it if required). */
+
+    for (i = 0; i < state->next_go; ++i)
+        for (j = 0; j < state->params.npegs; ++j)
+            if (state->guesses[i]->pegs[j] > maxcolour)
+                maxcolour = state->guesses[i]->pegs[j];
+    maxcolour = min(maxcolour + 1, state->params.ncolours);
+
+increase_mincolour:
+    for (i = 0; i < state->next_go; ++i) {
+        if (state->guesses[i]->feedback[0])
+            goto next_iteration;
+        for (j = 0; j < state->params.npegs; ++j)
+            if (state->guesses[i]->pegs[j] != mincolour)
+                goto next_iteration;
+        ++mincolour;
+        goto increase_mincolour;
+    next_iteration:
+        ;
+    }
+
+    if (!ui->hint) {
+        ui->hint = new_pegrow(state->params.npegs);
+        for (i = 0; i < state->params.npegs; ++i)
+            ui->hint->pegs[i] = 1;
+    }
+
+    while (ui->hint->pegs[0] <= state->params.ncolours) {
+        for (i = 0; i < state->next_go; ++i) {
+            mark_pegs(ui->hint, state->guesses[i], maxcolour);
+            for (j = 0; j < state->params.npegs; ++j)
+                if (ui->hint->feedback[j] != state->guesses[i]->feedback[j])
+                    goto increment_pegrow;
+        }
+        /* a valid guess was found; install it and return */
+        for (i = 0; i < state->params.npegs; ++i)
+            ui->curr_pegs->pegs[i] = ui->hint->pegs[i];
+
+        ui->markable = TRUE;
+        ui->peg_cur = state->params.npegs;
+        ui->display_cur = 1;
+        return;
+
+    increment_pegrow:
+        for (i = ui->hint->npegs;
+             ++ui->hint->pegs[--i], i && ui->hint->pegs[i] > maxcolour;
+             ui->hint->pegs[i] = mincolour);
+    }
+    /* No solution is compatible with the given hints.  Impossible! */
+    /* (hack new_game_desc to create invalid solutions to get here) */
+
+    /* For some values of npegs and ncolours, the hinting function takes a
+     * long time to complete.  To visually indicate completion with failure,
+     * should it ever happen, update the ui in some trivial way.  This gives
+     * the user a sense of broken(ish)ness and futility. */
+    if (!ui->display_cur) {
+        ui->display_cur = 1;
+    } else if (state->params.npegs == 1) {
+        ui->display_cur = 0;
+    } else {
+        ui->peg_cur = (ui->peg_cur + 1) % state->params.npegs;
+    }
+}
+
+static char *interpret_move(const game_state *from, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    int over_col = 0;           /* one-indexed */
+    int over_guess = -1;        /* zero-indexed */
+    int over_past_guess_y = -1; /* zero-indexed */
+    int over_past_guess_x = -1; /* zero-indexed */
+    int over_hint = 0;          /* zero or one */
+    char *ret = NULL;
+
+    int guess_ox = GUESS_X(from->next_go, 0);
+    int guess_oy = GUESS_Y(from->next_go, 0);
+
+    /*
+     * Enable or disable labels on colours.
+     */
+    if (button == 'l' || button == 'L') {
+        ui->show_labels = !ui->show_labels;
+        return "";
+    }
+
+    if (from->solved) return NULL;
+
+    if (x >= COL_OX && x < (COL_OX + COL_W) &&
+        y >= COL_OY && y < (COL_OY + COL_H)) {
+        over_col = ((y - COL_OY) / PEGOFF) + 1;
+        assert(over_col >= 1 && over_col <= ds->colours->npegs);
+    } else if (x >= guess_ox &&
+               y >= guess_oy && y < (guess_oy + GUESS_H)) {
+        if (x < (guess_ox + GUESS_W)) {
+            over_guess = (x - guess_ox) / PEGOFF;
+            assert(over_guess >= 0 && over_guess < ds->solution->npegs);
+        } else {
+            over_hint = 1;
+        }
+    } else if (x >= guess_ox && x < (guess_ox + GUESS_W) &&
+               y >= GUESS_OY && y < guess_oy) {
+        over_past_guess_y = (y - GUESS_OY) / PEGOFF;
+        over_past_guess_x = (x - guess_ox) / PEGOFF;
+        assert(over_past_guess_y >= 0 && over_past_guess_y < from->next_go);
+        assert(over_past_guess_x >= 0 && over_past_guess_x < ds->solution->npegs);
+    }
+    debug(("make_move: over_col %d, over_guess %d, over_hint %d,"
+           " over_past_guess (%d,%d)", over_col, over_guess, over_hint,
+           over_past_guess_x, over_past_guess_y));
+
+    assert(ds->blit_peg);
+
+    /* mouse input */
+    if (button == LEFT_BUTTON) {
+        if (over_col > 0) {
+            ui->drag_col = over_col;
+            ui->drag_opeg = -1;
+            debug(("Start dragging from colours"));
+        } else if (over_guess > -1) {
+            int col = ui->curr_pegs->pegs[over_guess];
+            if (col) {
+                ui->drag_col = col;
+                ui->drag_opeg = over_guess;
+                debug(("Start dragging from a guess"));
+            }
+        } else if (over_past_guess_y > -1) {
+            int col =
+                from->guesses[over_past_guess_y]->pegs[over_past_guess_x];
+            if (col) {
+                ui->drag_col = col;
+                ui->drag_opeg = -1;
+                debug(("Start dragging from a past guess"));
+            }
+        }
+        if (ui->drag_col) {
+            ui->drag_x = x;
+            ui->drag_y = y;
+            debug(("Start dragging, col = %d, (%d,%d)",
+                   ui->drag_col, ui->drag_x, ui->drag_y));
+            ret = "";
+        }
+    } else if (button == LEFT_DRAG && ui->drag_col) {
+        ui->drag_x = x;
+        ui->drag_y = y;
+        debug(("Keep dragging, (%d,%d)", ui->drag_x, ui->drag_y));
+        ret = "";
+    } else if (button == LEFT_RELEASE && ui->drag_col) {
+        if (over_guess > -1) {
+            debug(("Dropping colour %d onto guess peg %d",
+                   ui->drag_col, over_guess));
+            set_peg(&from->params, ui, over_guess, ui->drag_col);
+        } else {
+            if (ui->drag_opeg > -1) {
+                debug(("Removing colour %d from peg %d",
+                       ui->drag_col, ui->drag_opeg));
+                set_peg(&from->params, ui, ui->drag_opeg, 0);
+            }
+        }
+        ui->drag_col = 0;
+        ui->drag_opeg = -1;
+        ui->display_cur = 0;
+        debug(("Stop dragging."));
+        ret = "";
+    } else if (button == RIGHT_BUTTON) {
+        if (over_guess > -1) {
+            /* we use ths feedback in the game_ui to signify
+             * 'carry this peg to the next guess as well'. */
+            ui->holds[over_guess] = 1 - ui->holds[over_guess];
+            ret = "";
+        }
+    } else if (button == LEFT_RELEASE && over_hint && ui->markable) {
+        /* NB this won't trigger if on the end of a drag; that's on
+         * purpose, in case you drop by mistake... */
+        ret = encode_move(from, ui);
+    }
+
+    /* keyboard input */
+    if (button == CURSOR_UP || button == CURSOR_DOWN) {
+        ui->display_cur = 1;
+        if (button == CURSOR_DOWN && (ui->colour_cur+1) < from->params.ncolours)
+            ui->colour_cur++;
+        if (button == CURSOR_UP && ui->colour_cur > 0)
+            ui->colour_cur--;
+        ret = "";
+    } else if (button == 'h' || button == 'H' || button == '?') {
+        compute_hint(from, ui);
+        ret = "";
+    } else if (button == CURSOR_LEFT || button == CURSOR_RIGHT) {
+        int maxcur = from->params.npegs;
+        if (ui->markable) maxcur++;
+
+        ui->display_cur = 1;
+        if (button == CURSOR_RIGHT && (ui->peg_cur+1) < maxcur)
+            ui->peg_cur++;
+        if (button == CURSOR_LEFT && ui->peg_cur > 0)
+            ui->peg_cur--;
+        ret = "";
+    } else if (IS_CURSOR_SELECT(button)) {
+        ui->display_cur = 1;
+        if (ui->peg_cur == from->params.npegs) {
+            ret = encode_move(from, ui);
+        } else {
+            set_peg(&from->params, ui, ui->peg_cur, ui->colour_cur+1);
+            ret = "";
+        }
+    } else if (button == 'D' || button == 'd' || button == '\b') {
+        ui->display_cur = 1;
+        set_peg(&from->params, ui, ui->peg_cur, 0);
+        ret = "";
+    } else if (button == CURSOR_SELECT2) {
+        if (ui->peg_cur == from->params.npegs)
+            return NULL;
+        ui->display_cur = 1;
+        ui->holds[ui->peg_cur] = 1 - ui->holds[ui->peg_cur];
+        ret = "";
+    }
+    return ret;
+}
+
+static game_state *execute_move(const game_state *from, const char *move)
+{
+    int i, nc_place;
+    game_state *ret;
+    const char *p;
+
+    if (!strcmp(move, "S")) {
+       ret = dup_game(from);
+       ret->solved = -1;
+       return ret;
+    } else if (move[0] == 'G') {
+       p = move+1;
+
+       ret = dup_game(from);
+
+       for (i = 0; i < from->solution->npegs; i++) {
+           int val = atoi(p);
+           int min_colour = from->params.allow_blank? 0 : 1;
+           if (val < min_colour || val > from->params.ncolours) {
+               free_game(ret);
+               return NULL;
+           }
+           ret->guesses[from->next_go]->pegs[i] = atoi(p);
+           while (*p && isdigit((unsigned char)*p)) p++;
+            if (*p == '_') {
+                ret->holds[i] = 1;
+                p++;
+            } else
+                ret->holds[i] = 0;
+           if (*p == ',') p++;
+       }
+
+       nc_place = mark_pegs(ret->guesses[from->next_go], ret->solution, ret->params.ncolours);
+
+       if (nc_place == ret->solution->npegs) {
+           ret->solved = +1; /* win! */
+       } else {
+           ret->next_go = from->next_go + 1;
+           if (ret->next_go >= ret->params.nguesses)
+               ret->solved = -1; /* lose, meaning we show the pegs. */
+       }
+
+       return ret;
+    } else
+       return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+#define PEG_PREFER_SZ 32
+
+/* next three are multipliers for pegsz. It will look much nicer if
+ * (2*PEG_HINT) + PEG_GAP = 1.0 as the hints are formatted like that. */
+#define PEG_GAP   0.10
+#define PEG_HINT  0.35
+
+#define BORDER    0.5
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    double hmul, vmul_c, vmul_g, vmul;
+    int hintw = (params->npegs+1)/2;
+
+    hmul = BORDER   * 2.0 +               /* border */
+           1.0      * 2.0 +               /* vertical colour bar */
+           1.0      * params->npegs +     /* guess pegs */
+           PEG_GAP  * params->npegs +     /* guess gaps */
+           PEG_HINT * hintw +             /* hint pegs */
+           PEG_GAP  * (hintw - 1);        /* hint gaps */
+
+    vmul_c = BORDER  * 2.0 +                    /* border */
+             1.0     * params->ncolours +       /* colour pegs */
+             PEG_GAP * (params->ncolours - 1);  /* colour gaps */
+
+    vmul_g = BORDER  * 2.0 +                    /* border */
+             1.0     * (params->nguesses + 1) + /* guesses plus solution */
+             PEG_GAP * (params->nguesses + 1);  /* gaps plus gap above soln */
+
+    vmul = max(vmul_c, vmul_g);
+
+    *x = (int)ceil((double)tilesize * hmul);
+    *y = (int)ceil((double)tilesize * vmul);
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    int colh, guessh;
+
+    ds->pegsz = tilesize;
+
+    ds->hintsz = (int)((double)ds->pegsz * PEG_HINT);
+    ds->gapsz  = (int)((double)ds->pegsz * PEG_GAP);
+    ds->border = (int)((double)ds->pegsz * BORDER);
+
+    ds->pegrad  = (ds->pegsz -1)/2; /* radius of peg to fit in pegsz (which is 2r+1) */
+    ds->hintrad = (ds->hintsz-1)/2;
+
+    colh = ((ds->pegsz + ds->gapsz) * params->ncolours) - ds->gapsz;
+    guessh = ((ds->pegsz + ds->gapsz) * params->nguesses);      /* guesses */
+    guessh += ds->gapsz + ds->pegsz;                            /* solution */
+
+    game_compute_size(params, tilesize, &ds->w, &ds->h);
+    ds->colx = ds->border;
+    ds->coly = (ds->h - colh) / 2;
+
+    ds->guessx = ds->solnx = ds->border + ds->pegsz * 2;     /* border + colours */
+    ds->guessy = (ds->h - guessh) / 2;
+    ds->solny = ds->guessy + ((ds->pegsz + ds->gapsz) * params->nguesses) + ds->gapsz;
+
+    assert(ds->pegsz > 0);
+    assert(!ds->blit_peg);             /* set_size is never called twice */
+    ds->blit_peg = blitter_new(dr, ds->pegsz+2, ds->pegsz+2);
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float), max;
+    int i;
+
+    frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
+
+    /* red */
+    ret[COL_1 * 3 + 0] = 1.0F;
+    ret[COL_1 * 3 + 1] = 0.0F;
+    ret[COL_1 * 3 + 2] = 0.0F;
+
+    /* yellow */
+    ret[COL_2 * 3 + 0] = 1.0F;
+    ret[COL_2 * 3 + 1] = 1.0F;
+    ret[COL_2 * 3 + 2] = 0.0F;
+
+    /* green */
+    ret[COL_3 * 3 + 0] = 0.0F;
+    ret[COL_3 * 3 + 1] = 1.0F;
+    ret[COL_3 * 3 + 2] = 0.0F;
+
+    /* blue */
+    ret[COL_4 * 3 + 0] = 0.2F;
+    ret[COL_4 * 3 + 1] = 0.3F;
+    ret[COL_4 * 3 + 2] = 1.0F;
+
+    /* orange */
+    ret[COL_5 * 3 + 0] = 1.0F;
+    ret[COL_5 * 3 + 1] = 0.5F;
+    ret[COL_5 * 3 + 2] = 0.0F;
+
+    /* purple */
+    ret[COL_6 * 3 + 0] = 0.5F;
+    ret[COL_6 * 3 + 1] = 0.0F;
+    ret[COL_6 * 3 + 2] = 0.7F;
+
+    /* brown */
+    ret[COL_7 * 3 + 0] = 0.5F;
+    ret[COL_7 * 3 + 1] = 0.3F;
+    ret[COL_7 * 3 + 2] = 0.3F;
+
+    /* light blue */
+    ret[COL_8 * 3 + 0] = 0.4F;
+    ret[COL_8 * 3 + 1] = 0.8F;
+    ret[COL_8 * 3 + 2] = 1.0F;
+
+    /* light green */
+    ret[COL_9 * 3 + 0] = 0.7F;
+    ret[COL_9 * 3 + 1] = 1.0F;
+    ret[COL_9 * 3 + 2] = 0.7F;
+
+    /* pink */
+    ret[COL_10 * 3 + 0] = 1.0F;
+    ret[COL_10 * 3 + 1] = 0.6F;
+    ret[COL_10 * 3 + 2] = 1.0F;
+
+    ret[COL_FRAME * 3 + 0] = 0.0F;
+    ret[COL_FRAME * 3 + 1] = 0.0F;
+    ret[COL_FRAME * 3 + 2] = 0.0F;
+
+    ret[COL_CURSOR * 3 + 0] = 0.0F;
+    ret[COL_CURSOR * 3 + 1] = 0.0F;
+    ret[COL_CURSOR * 3 + 2] = 0.0F;
+
+    ret[COL_FLASH * 3 + 0] = 0.5F;
+    ret[COL_FLASH * 3 + 1] = 1.0F;
+    ret[COL_FLASH * 3 + 2] = 1.0F;
+
+    ret[COL_HOLD * 3 + 0] = 1.0F;
+    ret[COL_HOLD * 3 + 1] = 0.5F;
+    ret[COL_HOLD * 3 + 2] = 0.5F;
+
+    ret[COL_CORRECTPLACE*3 + 0] = 0.0F;
+    ret[COL_CORRECTPLACE*3 + 1] = 0.0F;
+    ret[COL_CORRECTPLACE*3 + 2] = 0.0F;
+
+    ret[COL_CORRECTCOLOUR*3 + 0] = 1.0F;
+    ret[COL_CORRECTCOLOUR*3 + 1] = 1.0F;
+    ret[COL_CORRECTCOLOUR*3 + 2] = 1.0F;
+
+    /* We want to make sure we can distinguish COL_CORRECTCOLOUR
+     * (which we hard-code as white) from COL_BACKGROUND (which
+     * could default to white on some platforms).
+     * Code borrowed from fifteen.c. */
+    max = ret[COL_BACKGROUND*3];
+    for (i = 1; i < 3; i++)
+        if (ret[COL_BACKGROUND*3+i] > max)
+            max = ret[COL_BACKGROUND*3+i];
+    if (max * 1.2F > 1.0F) {
+        for (i = 0; i < 3; i++)
+            ret[COL_BACKGROUND*3+i] /= (max * 1.2F);
+    }
+
+    /* We also want to be able to tell the difference between BACKGROUND
+     * and EMPTY, for similar distinguishing-hint reasons. */
+    ret[COL_EMPTY * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 2.0F / 3.0F;
+    ret[COL_EMPTY * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 2.0F / 3.0F;
+    ret[COL_EMPTY * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] * 2.0F / 3.0F;
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    struct game_drawstate *ds = snew(struct game_drawstate);
+    int i;
+
+    memset(ds, 0, sizeof(struct game_drawstate));
+
+    ds->guesses = snewn(state->params.nguesses, pegrow);
+    ds->nguesses = state->params.nguesses;
+    for (i = 0; i < state->params.nguesses; i++) {
+        ds->guesses[i] = new_pegrow(state->params.npegs);
+        invalidate_pegrow(ds->guesses[i]);
+    }
+    ds->solution = new_pegrow(state->params.npegs);
+    invalidate_pegrow(ds->solution);
+    ds->colours = new_pegrow(state->params.ncolours);
+    invalidate_pegrow(ds->colours);
+
+    ds->hintw = (state->params.npegs+1)/2; /* must round up */
+
+    ds->blit_peg = NULL;
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    int i;
+
+    if (ds->blit_peg) blitter_free(dr, ds->blit_peg);
+    free_pegrow(ds->colours);
+    free_pegrow(ds->solution);
+    for (i = 0; i < ds->nguesses; i++)
+        free_pegrow(ds->guesses[i]);
+    sfree(ds->guesses);
+    sfree(ds);
+}
+
+static void draw_peg(drawing *dr, game_drawstate *ds, int cx, int cy,
+                    int moving, int labelled, int col)
+{
+    /*
+     * Some platforms antialias circles, which means we shouldn't
+     * overwrite a circle of one colour with a circle of another
+     * colour without erasing the background first. However, if the
+     * peg is the one being dragged, we don't erase the background
+     * because we _want_ it to alpha-blend nicely into whatever's
+     * behind it.
+     */
+    if (!moving)
+        draw_rect(dr, cx-CGAP, cy-CGAP, PEGSZ+CGAP*2, PEGSZ+CGAP*2,
+                  COL_BACKGROUND);
+    if (PEGRAD > 0) {
+        draw_circle(dr, cx+PEGRAD, cy+PEGRAD, PEGRAD,
+                   COL_EMPTY + col, (col ? COL_FRAME : COL_EMPTY));
+    } else
+        draw_rect(dr, cx, cy, PEGSZ, PEGSZ, COL_EMPTY + col);
+
+    if (labelled && col) {
+        char buf[2];
+        buf[0] = 'a'-1 + col;
+        buf[1] = '\0';
+        draw_text(dr, cx+PEGRAD, cy+PEGRAD, FONT_VARIABLE, PEGRAD,
+                  ALIGN_HCENTRE|ALIGN_VCENTRE, COL_FRAME, buf);
+    }
+
+    draw_update(dr, cx-CGAP, cy-CGAP, PEGSZ+CGAP*2, PEGSZ+CGAP*2);
+}
+
+static void draw_cursor(drawing *dr, game_drawstate *ds, int x, int y)
+{
+    draw_circle(dr, x+PEGRAD, y+PEGRAD, PEGRAD+CGAP, -1, COL_CURSOR);
+
+    draw_update(dr, x-CGAP, y-CGAP, PEGSZ+CGAP*2, PEGSZ+CGAP*2);
+}
+
+static void guess_redraw(drawing *dr, game_drawstate *ds, int guess,
+                         pegrow src, int *holds, int cur_col, int force,
+                         int labelled)
+{
+    pegrow dest;
+    int rowx, rowy, i, scol;
+
+    if (guess == -1) {
+        dest = ds->solution;
+        rowx = SOLN_OX;
+        rowy = SOLN_OY;
+    } else {
+        dest = ds->guesses[guess];
+        rowx = GUESS_X(guess,0);
+        rowy = GUESS_Y(guess,0);
+    }
+    if (src) assert(src->npegs == dest->npegs);
+
+    for (i = 0; i < dest->npegs; i++) {
+        scol = src ? src->pegs[i] : 0;
+        if (i == cur_col)
+            scol |= 0x1000;
+        if (holds && holds[i])
+            scol |= 0x2000;
+        if (labelled)
+            scol |= 0x4000;
+        if ((dest->pegs[i] != scol) || force) {
+           draw_peg(dr, ds, rowx + PEGOFF * i, rowy, FALSE, labelled,
+                     scol &~ 0x7000);
+            /*
+             * Hold marker.
+             */
+            draw_rect(dr, rowx + PEGOFF * i, rowy + PEGSZ + ds->gapsz/2,
+                      PEGSZ, 2, (scol & 0x2000 ? COL_HOLD : COL_BACKGROUND));
+            draw_update(dr, rowx + PEGOFF * i, rowy + PEGSZ + ds->gapsz/2,
+                        PEGSZ, 2);
+            if (scol & 0x1000)
+                draw_cursor(dr, ds, rowx + PEGOFF * i, rowy);
+        }
+        dest->pegs[i] = scol;
+    }
+}
+
+static void hint_redraw(drawing *dr, game_drawstate *ds, int guess,
+                        pegrow src, int force, int cursor, int markable)
+{
+    pegrow dest = ds->guesses[guess];
+    int rowx, rowy, i, scol, col, hintlen;
+    int need_redraw;
+    int emptycol = (markable ? COL_FLASH : COL_EMPTY);
+
+    if (src) assert(src->npegs == dest->npegs);
+
+    hintlen = (dest->npegs + 1)/2;
+
+    /*
+     * Because of the possible presence of the cursor around this
+     * entire section, we redraw all or none of it but never part.
+     */
+    need_redraw = FALSE;
+
+    for (i = 0; i < dest->npegs; i++) {
+        scol = src ? src->feedback[i] : 0;
+        if (i == 0 && cursor)
+            scol |= 0x1000;
+        if (i == 0 && markable)
+            scol |= 0x2000;
+        if ((scol != dest->feedback[i]) || force) {
+            need_redraw = TRUE;
+        }
+        dest->feedback[i] = scol;
+    }
+
+    if (need_redraw) {
+        int hinth = HINTSZ + GAP + HINTSZ;
+        int hx,hy,hw,hh;
+
+        hx = HINT_X(guess)-GAP; hy = HINT_Y(guess)-GAP;
+        hw = HINT_W+GAP*2; hh = hinth+GAP*2;
+
+        /* erase a large background rectangle */
+        draw_rect(dr, hx, hy, hw, hh, COL_BACKGROUND);
+
+        for (i = 0; i < dest->npegs; i++) {
+            scol = src ? src->feedback[i] : 0;
+            col = ((scol == FEEDBACK_CORRECTPLACE) ? COL_CORRECTPLACE :
+                   (scol == FEEDBACK_CORRECTCOLOUR) ? COL_CORRECTCOLOUR :
+                   emptycol);
+
+            rowx = HINT_X(guess);
+            rowy = HINT_Y(guess);
+            if (i < hintlen) {
+                rowx += HINTOFF * i;
+            } else {
+                rowx += HINTOFF * (i - hintlen);
+                rowy += HINTOFF;
+            }
+            if (HINTRAD > 0) {
+                draw_circle(dr, rowx+HINTRAD, rowy+HINTRAD, HINTRAD, col,
+                            (col == emptycol ? emptycol : COL_FRAME));
+            } else {
+                draw_rect(dr, rowx, rowy, HINTSZ, HINTSZ, col);
+            }
+        }
+        if (cursor) {
+            int x1,y1,x2,y2;
+            x1 = hx + CGAP; y1 = hy + CGAP;
+            x2 = hx + hw - CGAP; y2 = hy + hh - CGAP;
+            draw_line(dr, x1, y1, x2, y1, COL_CURSOR);
+            draw_line(dr, x2, y1, x2, y2, COL_CURSOR);
+            draw_line(dr, x2, y2, x1, y2, COL_CURSOR);
+            draw_line(dr, x1, y2, x1, y1, COL_CURSOR);
+        }
+
+        draw_update(dr, hx, hy, hw, hh);
+    }
+}
+
+static void currmove_redraw(drawing *dr, game_drawstate *ds, int guess, int col)
+{
+    int ox = GUESS_X(guess, 0), oy = GUESS_Y(guess, 0), off = PEGSZ/4;
+
+    draw_rect(dr, ox-off-1, oy, 2, PEGSZ, col);
+    draw_update(dr, ox-off-1, oy, 2, PEGSZ);
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int i, new_move;
+
+    new_move = (state->next_go != ds->next_go) || !ds->started;
+
+    if (!ds->started) {
+      draw_rect(dr, 0, 0, ds->w, ds->h, COL_BACKGROUND);
+      draw_rect(dr, SOLN_OX, SOLN_OY - ds->gapsz - 1, SOLN_W, 2, COL_FRAME);
+      draw_update(dr, 0, 0, ds->w, ds->h);
+    }
+
+    if (ds->drag_col != 0) {
+        debug(("Loading from blitter."));
+        blitter_load(dr, ds->blit_peg, ds->blit_ox, ds->blit_oy);
+        draw_update(dr, ds->blit_ox, ds->blit_oy, PEGSZ, PEGSZ);
+    }
+
+    /* draw the colours */
+    for (i = 0; i < state->params.ncolours; i++) {
+        int val = i+1;
+        if (ui->display_cur && ui->colour_cur == i)
+            val |= 0x1000;
+        if (ui->show_labels)
+            val |= 0x2000;
+        if (ds->colours->pegs[i] != val) {
+           draw_peg(dr, ds, COL_X(i), COL_Y(i), FALSE, ui->show_labels, i+1);
+            if (val & 0x1000)
+                draw_cursor(dr, ds, COL_X(i), COL_Y(i));
+            ds->colours->pegs[i] = val;
+        }
+    }
+
+    /* draw the guesses (so far) and the hints
+     * (in reverse order to avoid trampling holds, and postponing the
+     * next_go'th to not overrender the top of the circular cursor) */
+    for (i = state->params.nguesses - 1; i >= 0; i--) {
+        if (i < state->next_go || state->solved) {
+            /* this info is stored in the game_state already */
+            guess_redraw(dr, ds, i, state->guesses[i], NULL, -1, 0,
+                         ui->show_labels);
+            hint_redraw(dr, ds, i, state->guesses[i],
+                        i == (state->next_go-1) ? 1 : 0, FALSE, FALSE);
+        } else if (i > state->next_go) {
+            /* we've not got here yet; it's blank. */
+            guess_redraw(dr, ds, i, NULL, NULL, -1, 0, ui->show_labels);
+            hint_redraw(dr, ds, i, NULL, 0, FALSE, FALSE);
+        }
+    }
+    if (!state->solved) {
+       /* this is the one we're on; the (incomplete) guess is stored in
+        * the game_ui. */
+       guess_redraw(dr, ds, state->next_go, ui->curr_pegs,
+                    ui->holds, ui->display_cur ? ui->peg_cur : -1, 0,
+                    ui->show_labels);
+       hint_redraw(dr, ds, state->next_go, NULL, 1,
+                   ui->display_cur && ui->peg_cur == state->params.npegs,
+                   ui->markable);
+    }
+
+    /* draw the 'current move' and 'able to mark' sign. */
+    if (new_move)
+        currmove_redraw(dr, ds, ds->next_go, COL_BACKGROUND);
+    if (!state->solved)
+        currmove_redraw(dr, ds, state->next_go, COL_HOLD);
+
+    /* draw the solution (or the big rectangle) */
+    if ((!state->solved ^ !ds->solved) || !ds->started) {
+        draw_rect(dr, SOLN_OX, SOLN_OY, SOLN_W, SOLN_H,
+                  state->solved ? COL_BACKGROUND : COL_EMPTY);
+        draw_update(dr, SOLN_OX, SOLN_OY, SOLN_W, SOLN_H);
+    }
+    if (state->solved)
+        guess_redraw(dr, ds, -1, state->solution, NULL, -1, !ds->solved,
+                     ui->show_labels);
+    ds->solved = state->solved;
+
+    ds->next_go = state->next_go;
+
+    /* if ui->drag_col != 0, save the screen to the blitter,
+     * draw the peg where we saved, and set ds->drag_* == ui->drag_*. */
+    if (ui->drag_col != 0) {
+        int ox = ui->drag_x - (PEGSZ/2);
+        int oy = ui->drag_y - (PEGSZ/2);
+        ds->blit_ox = ox - 1; ds->blit_oy = oy - 1;
+        debug(("Saving to blitter at (%d,%d)", ds->blit_ox, ds->blit_oy));
+        blitter_save(dr, ds->blit_peg, ds->blit_ox, ds->blit_oy);
+        draw_peg(dr, ds, ox, oy, TRUE, ui->show_labels, ui->drag_col);
+    }
+    ds->drag_col = ui->drag_col;
+
+    ds->started = 1;
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return 0.0F;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    /*
+     * We return nonzero whenever the solution has been revealed, even
+     * (on spoiler grounds) if it wasn't guessed correctly. The
+     * correct return value from this function is already in
+     * state->solved.
+     */
+    return state->solved;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+}
+
+#ifdef COMBINED
+#define thegame guess
+#endif
+
+const struct game thegame = {
+    "Guess", "games.guess", "guess",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    FALSE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PEG_PREFER_SZ, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    FALSE, FALSE, game_print_size, game_print,
+    FALSE,                            /* wants_statusbar */
+    FALSE, game_timing_state,
+    0,                                /* flags */
+};
+
+/* vim: set shiftwidth=4 tabstop=8: */
diff --git a/icons/Makefile b/icons/Makefile
new file mode 100644 (file)
index 0000000..00dae1f
--- /dev/null
@@ -0,0 +1,153 @@
+# Makefile for Puzzles icons.
+
+PUZZLES = blackbox bridges cube dominosa fifteen filling flip flood    \
+         galaxies guess inertia keen lightup loopy magnets map mines   \
+         net netslide palisade pattern pearl pegs range rect           \
+         samegame signpost singles sixteen slant solo tents towers     \
+         twiddle tracks undead unequal unruly untangle
+
+BASE = $(patsubst %,%-base.png,$(PUZZLES))
+WEB = $(patsubst %,%-web.png,$(PUZZLES))
+
+IBASE = $(patsubst %,%-ibase.png,$(PUZZLES))
+IBASE4 = $(patsubst %,%-ibase4.png,$(PUZZLES))
+P48D24 = $(patsubst %,%-48d24.png,$(PUZZLES))
+P48D8 = $(patsubst %,%-48d8.png,$(PUZZLES))
+P48D4 = $(patsubst %,%-48d4.png,$(PUZZLES))
+P32D24 = $(patsubst %,%-32d24.png,$(PUZZLES))
+P32D8 = $(patsubst %,%-32d8.png,$(PUZZLES))
+P32D4 = $(patsubst %,%-32d4.png,$(PUZZLES))
+P16D24 = $(patsubst %,%-16d24.png,$(PUZZLES))
+P16D8 = $(patsubst %,%-16d8.png,$(PUZZLES))
+P16D4 = $(patsubst %,%-16d4.png,$(PUZZLES))
+ICONS = $(patsubst %,%.ico,$(PUZZLES))
+CICONS = $(patsubst %,%-icon.c,$(PUZZLES))
+RC = $(patsubst %,%.rc,$(PUZZLES))
+
+BIN = ../
+PIC = ./
+
+# Work around newer ImageMagick unilaterally distorting colours when
+# converting to PNG.
+CSP = -set colorspace RGB
+
+base: $(BASE)
+web: $(WEB)
+pngicons: $(P48D24) $(P32D24) $(P16D24)
+winicons: $(ICONS) $(RC)
+gtkicons: $(CICONS)
+all: base web pngicons winicons gtkicons
+
+# Build the base puzzle screenshots from which all the other images
+# are derived. Some of them involve showing a move animation
+# part-way through.
+fifteen-base.png : override REDO=0.3
+flip-base.png : override REDO=0.3
+netslide-base.png : override REDO=0.3
+sixteen-base.png : override REDO=0.3
+twiddle-base.png : override REDO=0.3
+$(BASE): %-base.png: $(BIN)% $(PIC)%.sav
+       $(PIC)screenshot.sh $(BIN)$* $(PIC)$*.sav $@ $(REDO)
+
+# Build the screenshots for the web, by scaling the original base
+# images to a uniform size.
+$(WEB): %-web.png: %-base.png
+       $(PIC)square.pl 150 5 $^ $@
+
+# Build the base _icon_ images, by careful cropping of the base
+# images: icons are very small so it's often necessary to zoom in
+# on a smaller portion of the screenshot.
+blackbox-ibase.png : override CROP=352x352 144x144+0+208
+bridges-ibase.png : override CROP=264x264 107x107+157+157
+dominosa-ibase.png : override CROP=304x272 152x152+152+0
+fifteen-ibase.png : override CROP=240x240 120x120+0+120
+filling-ibase.png : override CROP=256x256 133x133+14+78
+flip-ibase.png : override CROP=288x288 145x145+120+72
+galaxies-ibase.png : override CROP=288x288 165x165+0+0
+guess-ibase.png : override CROP=263x420 178x178+75+17
+inertia-ibase.png : override CROP=321x321 128x128+193+0
+keen-ibase.png : override CROP=288x288 96x96+24+120
+lightup-ibase.png : override CROP=256x256 112x112+144+0
+loopy-ibase.png : override CROP=257x257 113x113+0+0
+magnets-ibase.png : override CROP=264x232 96x96+36+100
+mines-ibase.png : override CROP=240x240 110x110+130+130
+net-ibase.png : override CROP=193x193 113x113+0+80
+netslide-ibase.png : override CROP=289x289 144x144+0+0
+palisade-ibase.png : override CROP=288x288 192x192+0+0
+pattern-ibase.png : override CROP=384x384 223x223+0+0
+pearl-ibase.png : override CROP=216x216 94x94+108+15
+pegs-ibase.png : override CROP=263x263 147x147+116+0
+range-ibase.png : override CROP=256x256 98x98+111+15
+rect-ibase.png : override CROP=205x205 115x115+90+0
+signpost-ibase.png : override CROP=240x240 98x98+23+23
+singles-ibase.png : override CROP=224x224 98x98+15+15
+sixteen-ibase.png : override CROP=288x288 144x144+144+144
+slant-ibase.png : override CROP=321x321 160x160+160+160
+solo-ibase.png : override CROP=481x481 145x145+24+24
+tents-ibase.png : override CROP=320x320 165x165+142+0
+towers-ibase.png : override CROP=300x300 102x102+151+6
+tracks-ibase.png : override CROP=246x246 118x118+6+6
+twiddle-ibase.png : override CROP=192x192 102x102+69+21
+undead-ibase.png : override CROP=416x480 192x192+16+80
+unequal-ibase.png : override CROP=208x208 104x104+104+104
+untangle-ibase.png : override CROP=320x320 164x164+3+116
+$(IBASE): %-ibase.png: %-base.png
+       $(PIC)crop.sh $^ $@ $(CROP)
+
+# Convert the full-size icon images to 4-bit colour, because that
+# seems to work better than reducing it in 24 bits and then
+# dithering.
+$(IBASE4): %-ibase4.png: %-ibase.png
+       convert -colors 16 +dither $(CSP) -map $(PIC)win16pal.xpm $^ $@
+
+# Build the 24-bit PNGs for the icons, at three sizes.
+$(P48D24): %-48d24.png: %-ibase.png
+       $(PIC)square.pl 48 4 $^ $@
+$(P32D24): %-32d24.png: %-ibase.png
+       $(PIC)square.pl 32 2 $^ $@
+$(P16D24): %-16d24.png: %-ibase.png
+       $(PIC)square.pl 16 1 $^ $@
+
+# The 8-bit icon PNGs are just custom-paletted quantisations of the
+# 24-bit ones.
+$(P48D8) $(P32D8) $(P16D8): %d8.png: %d24.png
+       convert -colors 256 $^ $@
+
+# But the depth-4 images work better if we re-shrink from the
+# ibase4 versions of the images, and then normalise the colours
+# again afterwards. (They're still not very good, but my hope is
+# that on most modern Windows machines this won't matter too
+# much...)
+$(P48D4): %-48d4.png: %-ibase4.png
+       $(PIC)square.pl 48 1 $^ $@-tmp2.png
+       convert -colors 16 $(CSP) -map $(PIC)win16pal.xpm $@-tmp2.png $@
+       rm -f $@-tmp2.png
+$(P32D4): %-32d4.png: %-ibase.png
+       $(PIC)square.pl 32 1 $^ $@-tmp2.png
+       convert -colors 16 $(CSP) -map $(PIC)win16pal.xpm $@-tmp2.png $@
+       rm -f $@-tmp2.png
+$(P16D4): %-16d4.png: %-ibase.png
+       $(PIC)square.pl 16 1 $^ $@-tmp2.png
+       convert -colors 16 $(CSP) -map $(PIC)win16pal.xpm $@-tmp2.png $@
+       rm -f $@-tmp2.png
+
+# Build the actual Windows icons themselves, by feeding all those
+# PNGs to my icon builder script.
+$(ICONS): %.ico: %-48d24.png %-48d8.png %-48d4.png \
+                 %-32d24.png %-32d8.png %-32d4.png \
+                 %-16d24.png %-16d8.png %-16d4.png
+       $(PIC)icon.pl -24 $*-48d24.png $*-32d24.png $*-16d24.png \
+                     -8  $*-48d8.png  $*-32d8.png  $*-16d8.png  \
+                     -4  $*-48d4.png  $*-32d4.png  $*-16d4.png  > $@
+
+# Build the .RC files which bind the icons into the applications.
+$(RC): %.rc:
+       echo '#include "puzzles.rc2"' > $@
+       echo '200 ICON "$*.ico"' >> $@
+
+# Build the GTK icon source files.
+$(CICONS): %-icon.c: %-16d24.png %-32d24.png %-48d24.png
+       $(PIC)cicon.pl $^ > $@  
+
+clean:
+       rm -f *.png *.ico *.rc *-icon.c
diff --git a/icons/blackbox-16d24.png b/icons/blackbox-16d24.png
new file mode 100644 (file)
index 0000000..45a0d2d
Binary files /dev/null and b/icons/blackbox-16d24.png differ
diff --git a/icons/blackbox-16d4.png b/icons/blackbox-16d4.png
new file mode 100644 (file)
index 0000000..d378aad
Binary files /dev/null and b/icons/blackbox-16d4.png differ
diff --git a/icons/blackbox-16d8.png b/icons/blackbox-16d8.png
new file mode 100644 (file)
index 0000000..45a0d2d
Binary files /dev/null and b/icons/blackbox-16d8.png differ
diff --git a/icons/blackbox-32d24.png b/icons/blackbox-32d24.png
new file mode 100644 (file)
index 0000000..00c38e9
Binary files /dev/null and b/icons/blackbox-32d24.png differ
diff --git a/icons/blackbox-32d4.png b/icons/blackbox-32d4.png
new file mode 100644 (file)
index 0000000..47f42ab
Binary files /dev/null and b/icons/blackbox-32d4.png differ
diff --git a/icons/blackbox-32d8.png b/icons/blackbox-32d8.png
new file mode 100644 (file)
index 0000000..00c38e9
Binary files /dev/null and b/icons/blackbox-32d8.png differ
diff --git a/icons/blackbox-48d24.png b/icons/blackbox-48d24.png
new file mode 100644 (file)
index 0000000..976e278
Binary files /dev/null and b/icons/blackbox-48d24.png differ
diff --git a/icons/blackbox-48d4.png b/icons/blackbox-48d4.png
new file mode 100644 (file)
index 0000000..ba442ee
Binary files /dev/null and b/icons/blackbox-48d4.png differ
diff --git a/icons/blackbox-48d8.png b/icons/blackbox-48d8.png
new file mode 100644 (file)
index 0000000..976e278
Binary files /dev/null and b/icons/blackbox-48d8.png differ
diff --git a/icons/blackbox-base.png b/icons/blackbox-base.png
new file mode 100644 (file)
index 0000000..e13f6d0
Binary files /dev/null and b/icons/blackbox-base.png differ
diff --git a/icons/blackbox-ibase.png b/icons/blackbox-ibase.png
new file mode 100644 (file)
index 0000000..268f0bb
Binary files /dev/null and b/icons/blackbox-ibase.png differ
diff --git a/icons/blackbox-ibase4.png b/icons/blackbox-ibase4.png
new file mode 100644 (file)
index 0000000..0b0e142
Binary files /dev/null and b/icons/blackbox-ibase4.png differ
diff --git a/icons/blackbox-icon.c b/icons/blackbox-icon.c
new file mode 100644 (file)
index 0000000..d09227e
--- /dev/null
@@ -0,0 +1,891 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"qX0XqX0X9X7X7X8X8X7X7X8X7X7X8XqX",
+"eXqX4XtX'.4.9.i.a.7.0.h.q.7.s.8X",
+"qXsX@.T. X..$.2.6.$.&.w.=.#.7.7X",
+"wXtXW.&X'.*.3.8.9.-.:.t.,.=.q.7X",
+"eX7XsXdX].,.| p.j.w.r.z.t.w.h.8X",
+"wXtXI..X!.#   k q.*.;.r.:.&.0.7X",
+"wXiX0.G.U.    6 5.%.*.w.-.$.7.7X",
+"eX9X0XyX'.A 0 ..g.5.9.g.0.5.a.8X",
+"eX0X6XrX_.6.9.p.u.4.6.d.7.3.i.8X",
+"eXeX0XpX(.@.=.4.8.*.-.r.:.&.9.7X",
+"eXwX9XiX~...#.<.3.+.$.0.%.o.4.7X",
+"wX6X3X6X*X^.(._._./.(.`.).'.`.9X",
+"qXqXqXqXyXiXiXrXeXpXuXrX7X[.aX9X",
+"qXqXqXqXeX0XqX8X7XqX0X9X:Xg.eX9X",
+"qXqXqXqXqX6X7X4X4X7X6X3X5X4X5X0X",
+"qXqXqXqX0X7X7X7X7X7X7X7X7X8X7XqX"
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"qXqXqXwXwXwXwXwXwXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqX0X8X8X8X8X9X8X0XqXqXqXqXqX0X0XqXqXqXqXqX0X0XqXqXqXqXqX0XqXqX",
+"0XtXqX7X8X9X7X7X0XB.8.r.e.e.0.k.m.9.r.e.r.0.l.n.9.r.e.r.0.k.0XqX",
+"0XtXeXqXwX5XyXqXtXh.O.*.&.*.@.0.s.+.*.&.*.@.w.p.+.*.&.*.@.0.qXqX",
+"0XtX0XsXD.H d.pXwXl.%.>.:.>.*.r.g.&.>.:.>.*.t.d.&.>.:.>.*.r.qXqX",
+"0XtX0XgXc.i 2.dXqXk.$.:.;.:.&.e.f.%.:.;.:.&.t.d.%.:.;.:.&.e.qXqX",
+"0XtXqXsXH.y.p.9XrXl.%.>.:.>.*.r.g.&.>.:.>.*.y.d.&.>.:.>.*.r.qXqX",
+"0XrXrXqXtXsXyXqXyXg.X.%.#.$.O.9.a.o.&.%.&.O.q.i.O.&.%.&.+.9.qXqX",
+"0XtX9X3X4X3X3X4X6XS.i.z.z.x.d.v.C.i.d.d.d.p.m.V.i.d.d.d.p.n.0XqX",
+"0XtX0X5X7X6X6X6X8XC.w.] V L ,.v.m.q.y.t.t.q.z.m.q.y.t.t.w.l.0XqX",
+"0XtXrXwXqXrXwXqXtXl.w   .     $.h.o.*.&.&.+.q.p.O.*.&.*.@.0.qXqX",
+"0XtX0XfXb.j.1.$XfX1.  O o O   b z.%.>.:.:.&.t.d.&.>.:.>.*.e.qXqX",
+"0XtX0XhXf.F D %XjX .  o   .   t d.%.:.;.:.&.t.d.%.:.;.:.&.e.qXqX",
+"0XtXqXsXD.U.j.&XfX<.  O o +   v z.%.>.:.>.*.y.d.&.>.:.>.*.r.qXqX",
+"0XrXrXqXuXtXiXeXtXk.9   .     @.g.X.&.%.&.O.q.i.o.&.%.&.+.9.qXqX",
+"0XtX9X3X3X3X3X3X6XF.a. .D T 5.V.C.a.g.f.g.a.M.C.a.g.f.g.s.m.0XqX",
+"0XtX0X6X7X7X6X6X9XN.9.s.a.s.e.h.m.9.r.e.r.0.l.b.9.r.e.r.0.k.0XqX",
+"0XtXrXqXwXwXwXqXtXh.O.&.%.%.+.0.s.+.*.&.*.@.w.p.+.*.&.*.@.0.qXqX",
+"0XtXeX0XqXqXqX0XrXl.%.>.:.:.*.r.g.&.>.:.:.&.t.d.&.>.:.>.*.e.qXqX",
+"0XtXeX0XqXqXqX0XrXk.$.:.;.:.&.e.f.%.:.;.:.&.t.d.%.:.;.:.&.e.qXqX",
+"0XtXeX0XqXqX0X0XrXl.&.,.:.>.*.r.g.&.>.:.>.*.y.f.&.>.:.>.*.r.qXqX",
+"0XrXrXwXeXeXeXwXyXg.X.&.$.%.O.8.p.o.%.$.%.O.0.i.o.%.$.%.O.8.qXqX",
+"0XtX6X,X1X1X1X<X3XH.g.l.k.l.h.B.D.g.l.k.l.h.V.S.g.l.k.l.h.B.0XqX",
+"qXwX7X4X5X4X5X4X8XeXrXrXrXrXtX9X6XyXrXrXrXtX9X6XtXtXiXrXtX0X8XwX",
+"qXqXwXeXeXeXeXwXyXwX0X0X0X0XqX6X4XwX0X0X0XqX6X4XwX7X;X9XqX7X9XwX",
+"qXqXqXqXqXqXqX0XtXeX0XqXqXqXwX6X4XwXqXqXqXwX6X3XrX1XE |.pX6X9XwX",
+"qXqXqXqXqXqXqX0XtXeX0XqXqXqXwX7X4XwXqXqXqXwX6X4X0XfX$.R.dX6X9XwX",
+"qXqXqXqXqXqXqX0XtXwX0XqXqX0XwX6X4XwX0XqX0XwX6X3XtX<X| b.pX6X9XwX",
+"qXqXqXqXqXqXqX0XtXrXwXeXeXeXrX8X5XrXeXeXeXrX8X6XrXeXdXuXeX9X9XwX",
+"qXqXqXqXqXqXqX0XtX5X,X<X<X<X1X:X;X1X<X<X<X1X:X;X1X<X>X,X<X>X9XwX",
+"qXqXqXqXqXqXqXqXwX7X5X5X5X5X5X5X6X5X5X5X5X5X6X6X5X5X5X5X5X6XqXqX",
+"qXqXqXqXqXqXqXqXqXwXeXwXeXwXeXwXwXeXwXeXwXeXwXwXeXwXeXwXeXwXqXqX"
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqX0X0X0X0X0X0X0X0XqXqX0X0X0X0X0X0X0X0XqXqX0X0X0X0X0X0X0X0XqXqXqXqXqX",
+"qXqXqXqXwXwXwXwXwXwXwXwXwXwXeXeXeXeXeXeXeXeXeXeXeXeXeXeXeXeXeXeXeXeXeXeXeXeXeXeXeXeXeXeXqXqXqXqX",
+"qXqXqXeX8X5X6X6X6X6X6X6X6X6X3X2X2X2X2X2X2X2X2X3X3X2X2X2X2X2X2X2X2X3X3X2X2X2X2X2X2X2X2X4XqXqXqXqX",
+"qXqX0XpX9X7X8X7X6X6X8X8X8X7Xl.>.4.3.3.3.3.4.:.v.v.:.4.3.3.3.3.4.:.b.x.:.4.3.3.3.3.4.:.b.tX0XqXqX",
+"qXqX0XiXwXqXqXtXdXaXqXwXwXqXd.#.-.=.=.=.=.-.@.k.k.@.-.=.=.=.=.-.+.l.h.@.-.=.=.=.=.;.@.l.yX0XqXqX",
+"qXqX0XiXqX0XeX,XR.'.tX0XqX0Xg.%.>.;.;.;.;.>.$.l.l.$.>.;.;.;.;.>.$.x.j.$.>.;.;.;.;.>.$.z.yX0XqXqX",
+"qXqX0XiXqX7XjXe.j n f.gX8X0Xf.%.:.;.;.;.;.:.$.l.l.$.:.;.;.;.;.:.#.z.j.$.:.;.;.;.;.:.$.z.yX0XqXqX",
+"qXqX0XiXqX8XjXw.G U n.fX9X0Xf.%.:.;.;.;.;.:.$.l.l.$.:.;.;.;.;.:.#.z.j.$.:.;.;.;.;.:.$.z.yX0XqXqX",
+"qXqX0XiXqX7XjXq.l n s.fX9X0Xf.%.:.;.;.;.;.:.$.l.l.$.:.;.;.;.;.:.#.z.j.$.:.;.;.;.;.:.$.z.yX0XqXqX",
+"qXqX0XiXqX9XsXS.Z.oX>.uX0X0Xf.%.:.;.;.;.;.:.$.l.l.$.:.;.;.;.;.:.#.z.j.$.:.;.;.;.;.:.$.z.yX0XqXqX",
+"qXqX0XiXqX0X9XiXiXtXfX9XqX0Xg.&.>.:.:.:.:.>.%.l.l.%.>.:.:.:.:.>.%.x.k.%.>.:.:.:.:.,.%.x.yX0XqXqX",
+"qXqX0XiXwXwXeXqX0XqX0XwXwXwXa.o.&.#.#.#.#.&.X.h.h.X.&.%.%.%.%.&.X.j.f.X.&.%.%.%.%.&.X.j.uX0XqXqX",
+"qXqX9XpX4X>X,X,X,X,X,X,X,X>XT.C.S.K.L.L.L.F.V.R.R.C.S.S.S.S.S.S.C.E.T.C.S.S.S.S.S.D.C.W.eXqXqXqX",
+"qXqX0XpX9X7X8X7X6X8X6X7X8X7Xc.6.q._ M v U 2.8.n.M.3.0.9.9.9.9.0.3.N.n.4.0.9.9.9.9.0.3.N.tX0XqXqX",
+"qXqX0XiXwXwXqXyXuXwXpXwXwX0Xf...9   .     $ Y c.h.O.=.*.*.*.*.=.O.l.g.O.=.*.*.*.*.=.O.k.yX0XqXqX",
+"qXqX0XiXqX0XrX*X+X7X[.0XqX9Xl.t   o X X X   + p.x.#.>.;.;.;.;.>.$.x.j.$.>.;.;.;.;.>.$.z.yX0XqXqX",
+"qXqX0XiXqX7XlX6.{ 5Xu 7XwXeX6.  X         o   } m.@.:.;.;.;.;.:.#.z.j.$.:.;.;.;.;.:.$.z.yX0XqXqX",
+"qXqX0XiXqX8XhXp.0 U i 9X0XuX+.  o         o   J n.@.:.;.;.;.;.:.#.z.j.$.:.;.;.;.;.:.$.z.yX0XqXqX",
+"qXqX0XiXqX7XkX7.Y (.e 6XqXyX%.  X         o   U n.@.:.;.;.;.;.:.#.z.j.$.:.;.;.;.;.:.$.z.yX0XqXqX",
+"qXqX0XiXqX9XsXJ.n.qX2.9XqXwXr.o   .       o   %.n.@.:.;.;.;.;.:.#.z.j.$.:.;.;.;.;.:.$.z.yX0XqXqX",
+"qXqX0XiXqX0X9XiXaXqXgXqXqX9Xx.m   . X o X   3 l.z.$.>.:.:.:.:.>.$.x.k.%.>.:.:.:.:.>.%.x.yX0XqXqX",
+"qXqX0XiXwXwXwXqX0XwX9XwXwXqXa.#.m X   .   w [ k.g.o.*.%.%.%.%.*.X.k.f.o.*.%.%.%.%.*.o.j.yX0XqXqX",
+"qXqX9XpX4X:X,X,X,X,X,X,X,X>XT.Z.L.n.9.5.g.J.S.R.E.Z.D.D.D.D.D.D.Z.E.R.Z.D.D.D.D.D.D.Z.Q.eXqXqXqX",
+"qXqX0XpX9X7X8X8X8X8X8X8X8X7Xc.3.7.0.r.r.q.7.1.m.m.2.8.7.7.7.7.8.2.M.b.2.8.7.7.7.7.8.2.M.tX0XqXqX",
+"qXqX0XiXwXqXwXwXwXwXwXqXwXqXd.@.-.*.&.&.&.=.+.j.j.+.=.*.*.*.*.=.O.l.g.+.=.*.*.*.*.-.+.k.yX0XqXqX",
+"qXqX0XiXqX0XqXqXqXqXqXqXqX0Xg.%.>.;.;.;.;.>.$.l.l.$.>.;.;.;.;.>.$.x.j.$.:.;.;.;.;.>.$.z.yX0XqXqX",
+"qXqX0XiXqXqXqXqXqXqXqXqXqX0Xf.%.:.;.;.;.;.:.$.l.l.$.:.;.;.;.;.:.#.z.j.$.:.;.;.;.;.:.$.z.yX0XqXqX",
+"qXqX0XiXqXqXqXqXqXqXqXqXqX0Xf.%.:.;.;.;.;.:.$.l.l.$.:.;.;.;.;.:.#.z.j.$.:.;.;.;.;.:.$.z.yX0XqXqX",
+"qXqX0XiXqXqXqXqXqXqXqXqXqX0Xf.%.:.;.;.;.;.:.$.l.l.$.:.;.;.;.;.:.#.z.j.$.:.;.;.;.;.:.$.z.yX0XqXqX",
+"qXqX0XiXqXqXqXqXqXqXqXqXqX0Xf.%.:.;.;.;.;.:.$.l.l.$.:.;.;.;.;.:.#.z.j.$.:.;.;.;.;.:.$.z.yX0XqXqX",
+"qXqX0XiXqX0XqXqXqXqXqX0XqX0Xg.&.,.:.:.:.:.,.%.z.z.%.>.:.:.:.:.,.%.x.k.%.>.:.:.:.:.,.%.x.yX0XqXqX",
+"qXqX0XiXeXeXeXeXeXeXeXeXeXwXp.X.%.$.$.$.$.%...g.g...%.$.$.$.$.%...j.d...%.$.$.$.$.%...h.uX0XqXqX",
+"qXqX0XpX>X&X*X*X*X*X*X*X*X=X^.P.U.U.U.U.U.U.P././.P.U.U.U.U.U.U.P./.^.P.U.U.U.U.U.Y.P.).wXqXqXqX",
+"qXqXqXqX7X6X6X6X6X6X6X6X5XrXrXyXyXyXyXyXyXtXiX6X6XiXtXyXyXyXyXtXiX5X6XiXtXyXrXrXyXtXuX8X8XwXqXqX",
+"qXqXqXqXwXwXwXwXwXwXwXeXqXaX0X0X0X0X0X0X0X0XwX2X2XwX0X0X0X0X0X0XwX2X3XwX0XqXsXiX9X0XqX5X9XwXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqX0XiXqXqXqXqXqXqXqXqXeX3X3XeXqXqXqXqXqXqXeX2X3XwXqX9XU.`.eX0XwX5X9XwXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqX0XpXqXqXqXqXqXqXqXqXeX3X3XeXqXqXqXqXqXqXeX2X3XwXwX7XY D aX9XwX5X9XwXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqX0XpXqXqXqXqXqXqXqXqXeX3X3XeXqXqXqXqXqXqXeX2X3XeX8XjX(.Y kX8XwX5X9XwXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqX0XpXqXqXqXqXqXqXqXqXeX3X3XeXqXqXqXqXqXqXeX2X3XwX0XyX0.g &XeXqX5X9XwXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqX0XpXqXqXqXqXqXqXqXqXeX3X3XeXqXqXqXqXqXqXeX2X3XwXeX2X1. .F.pX0X5X9XwXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqX0XpXqX0XqXqXqXqXqX0XwX3X3XwX0XqXqXqXqX0XwX2X3XwX0XwXhXlXpX9XwX5X9XwXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqX0XpXwXeXeXeXeXeXeXwXtX4X4XtXwXeXeXeXeXwXtX4X5XrXeXeX0X9XqXeXrX6X9XwXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqX0XiX:X%X*X*X*X*X*X*X*X$X$X*X*X*X*X*X*X*X*X$X$X*X*X*X*X*X*X*X*X*X0XqXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqX8X7X7X7X7X7X7X7X7X7X7X7X7X7X7X7X7X7X7X7X7X7X7X7X7X7X7X7X7X8XqXqXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXqXqXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX"
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/blackbox-web.png b/icons/blackbox-web.png
new file mode 100644 (file)
index 0000000..3e2446d
Binary files /dev/null and b/icons/blackbox-web.png differ
diff --git a/icons/blackbox.ico b/icons/blackbox.ico
new file mode 100644 (file)
index 0000000..6a2cb1c
Binary files /dev/null and b/icons/blackbox.ico differ
diff --git a/icons/blackbox.rc b/icons/blackbox.rc
new file mode 100644 (file)
index 0000000..d698666
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "blackbox.ico"
diff --git a/icons/blackbox.sav b/icons/blackbox.sav
new file mode 100644 (file)
index 0000000..4483f3c
--- /dev/null
@@ -0,0 +1,27 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :9:Black Box
+PARAMS  :8:w8h8m5M5
+CPARAMS :8:w8h8m5M5
+SEED    :15:999785320716678
+DESC    :24:c8b9f8528193756b9a2fd24d
+UI      :2:E0
+NSTATES :2:18
+STATEPOS:2:13
+MOVE    :2:F2
+MOVE    :2:F4
+MOVE    :2:F5
+MOVE    :3:F25
+MOVE    :3:F26
+MOVE    :3:F11
+MOVE    :3:F12
+MOVE    :3:F15
+MOVE    :4:T7,7
+MOVE    :4:T7,4
+MOVE    :4:T1,7
+MOVE    :2:F3
+MOVE    :2:F9
+MOVE    :3:F27
+MOVE    :4:T5,7
+MOVE    :4:T1,8
+MOVE    :1:R
diff --git a/icons/bridges-16d24.png b/icons/bridges-16d24.png
new file mode 100644 (file)
index 0000000..49c140a
Binary files /dev/null and b/icons/bridges-16d24.png differ
diff --git a/icons/bridges-16d4.png b/icons/bridges-16d4.png
new file mode 100644 (file)
index 0000000..ee265c9
Binary files /dev/null and b/icons/bridges-16d4.png differ
diff --git a/icons/bridges-16d8.png b/icons/bridges-16d8.png
new file mode 100644 (file)
index 0000000..49c140a
Binary files /dev/null and b/icons/bridges-16d8.png differ
diff --git a/icons/bridges-32d24.png b/icons/bridges-32d24.png
new file mode 100644 (file)
index 0000000..8608719
Binary files /dev/null and b/icons/bridges-32d24.png differ
diff --git a/icons/bridges-32d4.png b/icons/bridges-32d4.png
new file mode 100644 (file)
index 0000000..aad675c
Binary files /dev/null and b/icons/bridges-32d4.png differ
diff --git a/icons/bridges-32d8.png b/icons/bridges-32d8.png
new file mode 100644 (file)
index 0000000..8608719
Binary files /dev/null and b/icons/bridges-32d8.png differ
diff --git a/icons/bridges-48d24.png b/icons/bridges-48d24.png
new file mode 100644 (file)
index 0000000..973c21f
Binary files /dev/null and b/icons/bridges-48d24.png differ
diff --git a/icons/bridges-48d4.png b/icons/bridges-48d4.png
new file mode 100644 (file)
index 0000000..355cd0c
Binary files /dev/null and b/icons/bridges-48d4.png differ
diff --git a/icons/bridges-48d8.png b/icons/bridges-48d8.png
new file mode 100644 (file)
index 0000000..973c21f
Binary files /dev/null and b/icons/bridges-48d8.png differ
diff --git a/icons/bridges-base.png b/icons/bridges-base.png
new file mode 100644 (file)
index 0000000..577a87f
Binary files /dev/null and b/icons/bridges-base.png differ
diff --git a/icons/bridges-ibase.png b/icons/bridges-ibase.png
new file mode 100644 (file)
index 0000000..fa919d1
Binary files /dev/null and b/icons/bridges-ibase.png differ
diff --git a/icons/bridges-ibase4.png b/icons/bridges-ibase4.png
new file mode 100644 (file)
index 0000000..42382d4
Binary files /dev/null and b/icons/bridges-ibase4.png differ
diff --git a/icons/bridges-icon.c b/icons/bridges-icon.c
new file mode 100644 (file)
index 0000000..d0ddbed
--- /dev/null
@@ -0,0 +1,891 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"qXqXqXqXqXqXqXwX3X+X8X<X9X5X3XtX",
+"qXqXqXqXqX0X0XwX4Xy.;Xd.2X@Xg.wX",
+"qXqXqXqX9XtXuX8XyXL.~.|._.E.Q.'.",
+"qXqXqX0XaX=X#XiXtXS.*Xj.OX=Xf.1X",
+"wXeX9XaXD.5.r.z.fXm.gX~.u.t. XiX",
+"&X|.sX%Xp.!.W.l.[.G.9XxX^.OXjX8X",
+"#X).dXXXg.'.N.N.XXJ.dXaX~.|.iX0X",
+"qXiXyXjXN.a.l.f.K.1.F.jX!.|.aX9X",
+"0X-X*X*X<XP.T.Z.J.#XB.E.].{.aX0X",
+"3X8.>.<.,.8.8.) CX7.=XS.'. XpX9X",
+"0X*X%X&X&X#X,XM.A.1XA.R.Z.D.gX0X",
+"wXjXkXkXkXkXkXkXU.M.R.j.C.B.b.pX",
+"&X7.f.s.s.s.s.d.v.m.<.(.$XT.P.|.",
+"-Xh.V.N.N.N.N.N.n.A.7.L.>XoXV.+X",
+"wXdXfXfXfXfXfXfXfXdXlXb.N.m.F.aX",
+"qX8X8X8X8X8X8X8X8X8X8XpX.X@XaX0X"
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXuXuXuXuXqX9XtXiXqXwXpXwX9XqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXwX#X@X@X$X0XpX>XXX9X3X|.9XyX0XqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX9XXXE E  XsXG.g %XUXKXv.l >XtX0X",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX5XPXp.i.IX2Xk >XkX3.Y.UXy.( jX8X",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXwX4XDXi.t.UXm.^ UXjX;Xl bXNXn <XrX",
+"qXqXqXqXqXqXqXqXqXqXqX0X0XqXqXwX4XGXp.y.UXy.#.UXUXw.n.UXHXB @XuX",
+"qXqXqXqXqXqXqXqXqX9X9XtXyX0X8XwX4XGXp.u.UXW.N UX Xq 4.GX4Xn rXqX",
+"qXqXqXqXqXqXqXqX9XiXhX:X%XpXfX9X4XGXp.p.DXiXT ,.UXPXUXAXM a.jX8X",
+"qXqX0XqXqXqXqX9XaXoXL l x m Z.gX2XGXp.p.FX7X8XK Z l.e.d ,.dX9XqX",
+"qXwXrXqXqXqX9XiXXXw e.qXtXK.e z.iXSXp.p.GX3XtXuX}.b ..-XfX0XqXqX",
+"tX-X].uX0XqX9XhXI t.ZX#.:.VX|.d -XKXi.p.GX5XqX3XUX$.T.AX4XqXqXqX",
+"iX.X8.fX9X0XtX:Xv 1XaXm.Y uXhXT c.UXy.p.GX4XwX4XJX+.L.mX6XqXqXqX",
+"pX.X>.fX8X0XyX%Xv 6XsXK.G 0XkX^ d.UXt.i.FX4XwX4XPX@.I.NX6XqXqXqX",
+"uX#XJ.pX9XqX0XiXN P.cX#.N !.iXg `.IXp.s.FX2XwX4XPX@.I.NX6XqXqXqX",
+"qX0XqXwXqXqX9XgXA.r |.bXNXqXF X.bXNX-.;.CXpX0X4XPX@.I.NX6XqXqXqX",
+"qXqX7X6X6X6X6X4XiXf.r H Y u [ lXv.j Y P j v.gX1XPX@.I.NX6XqXqXqX",
+"qXeXmXBXNXNXNXNXmXLXlX/.T.8XUXN.a 8XIXUX0Xs b.uXJX@.I.NX6XqXqXqX",
+"wX8XI.J.K.K.K.K.K.G.I./.(.Y.U.z 5XZX9._ ZX5Xz 3XIX@.I.NX6XqXqXqX",
+"eX5Xe.5.7.7.7.7.7.7.6.5.3.e._ H UXUX}.z pXUXK I.UX+.I.NX6XqXqXqX",
+"eX5Xe.5.7.7.7.7.7.7.7.7.6.r._ G UXbXQ.g *XUXJ I.UXo.P.MX6XqXqXqX",
+"wX8XI.J.K.K.K.K.K.K.K.K.K.J.R.z 6XbXt.M.UX1Xz 1XUX$.Y.VX4XqXqXqX",
+"qXeXMXBXBXBXBXBXBXBXBXBXBXmXLXB.a wXUXUX0Xa n.nX8XG 5.aXsX9XqXqX",
+"qXqX2X,X,X,X,X,X,X,X,X,X,X,X:XyXj.i C C i k.zX{ z <.| p k.gX9XqX",
+"wX9XnXKXGXGXGXGXGXGXGXGXGXGXHXDXUXSX2X2XAXUX../ HXBXUXaXd C.fX8X",
+"tX-X5.p.y.u.u.u.u.u.u.u.u.u.u.u.r.u.k.k.a.5.n JXiX-.Y UX@XN iX0X",
+"uX@XT N.z.x.x.x.x.x.x.x.x.x.x.x.x.x.z.k.A.( @.UXUXG.A DXKXN $XuX",
+"yX%XN @.X.o.o.o.o.o.o.o.o.o.o.o.o.o.o.X.=.n ' UXwXc.g hXDXn ;XtX",
+"tX>X/.*X@X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X$X+Xk uXkXv.@XUXN.P hX8X",
+"qXwXhXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXgXVXl.p ,XUXKXC.r oXiX9X",
+"qXqX8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X6XfXC.v b c J XXaX9XqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX9XfXpX$X;XhXiX9XqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX8X0XuXtX8X9XqXqXqX"
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX0X8X8X8X8X9XqXqXqX8X8X0XqXqX0X8X9XqXqXqXqXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX0XuXkXjXhXkXpXqXqX0XgXlXuX9X9XpXlXdX9XqXqXqXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX0XrX}.k.c.n.g.~.wXqXyXF.f.].iXyX/.s.I.pX0XqXqXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXeX$X!.M 5 L.XX9XfX-.  h.NXUXIXhX8.  y.gX8XqXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX0XUX2.M UXeXsXI.  ).UXfX/.3XIXUXZ.  {.pX9XqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXwX4XUX,.b UX6XgXS E UXLX&.! 1 z.UXUXb _ kX8XqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX4XUX1.n UXqX6X- R.UXJXAXUXP } UXUXl.8 uXqXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX4XUX1.n UXwX1XX @XUXKXUXn.N bXUXUXU.= rXqXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX4XUX1.n UXqX8X1 S.UXUXq.  n.8XLXUXt.y pX0XqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqX0X8X8X8X8X9XqXqXqXqX4XUX1.n UX6XjXQ N UXUX<.( R y.UXLXy %.kX8XqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqX8XtXjXlXlXkXdX0X0XqXqX4XUX1.n UX7XiX{.. c.UXPXUXUXPXUX5.& =XyX0XqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqX9XfX:X0.S b ) L.aXtX9XwX4XUX1.n UX9X8XfXk.  o.0XJXGX3X~   G.dX8XqXqXqX",
+"qXqXqX0XqXqXqXqXqXqXqXqX9XdXx.# 1 U / b   Z <XyX0X4XUX1.n UX9XqX9XfXP.e + r w o f (.dX8XqXqXqXqX",
+"qXqX0XrXwXqXqXqXqXqXqX8XfXx.  :.rXSXDXgX'.e f 8XeX3XUX1.n UX9XqXqX9XsXyX0Xf D tXuXaX8XqXqXqXqXqX",
+"qX0XyX@X,XrXqXqXqXqX0XtX;X= ,.mX;Xw.u.<XhX1X, &.jX1XUX1.n UX9XqXqXqX8X7XUXK +.UX4X9XqXqXqXqXqXqX",
+"qX8XhXb.B.gX9XqXqXqX8XjX0.2 9XiX'.C > +XqXkXt.< 6X6XUX1.n UX9XqXqXqXqX7XUXH X.UX5XqXqXqXqXqXqXqX",
+"qX8XlXf.$.xX7XqXqXqX8XlXA R kX5XdX{.4 OXrXtXXXO /.rXUX1.n UX9XqXqXqXqX7XUXJ O.UX5XqXqXqXqXqXqXqX",
+"qX8XlXd. .cX7XqXqXqX8XlXb _ xX5XgX|.5 4XiXeX-Xo E.tXUX1.n UX9XqXqXqXqX7XUXJ O.UX5XqXqXqXqXqXqXqX",
+"qX8XkXk.0.kX8XqXqXqX8XkX` n sXqX-XX.% <.,XfXI.% OXqXUX1.n UX9XqXqXqXqX7XUXJ O.UX5XqXqXqXqXqXqXqX",
+"qX9XsXI.`.pX0XqXqXqX9XdXP.. `.zX`.] &.{ $XnXI c dX<XUX1.n UX6X0XqXqXqX7XUXJ O.UX5XqXqXqXqXqXqXqX",
+"qXqXqX9X0XqXqXqXqXqXqX0XpXF t <XbXMXvXCXbXu.  I.pXrXUX2.N UXdX0X0XqXqX7XUXJ O.UX5XqXqXqXqXqXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqX0XtX<Xd - w.}.%XK.L   y.gXaXK.` , @ Y v.uXtX9XqX7XUXJ O.UX5XqXqXqXqXqXqXqX",
+"qXqXqXqX0X0X0X0X0X0X0XqX9XtX9X#., O X # k I.fXwX(   H x.B.` X c <XyX9X7XUXJ O.UX5XqXqXqXqXqXqXqX",
+"qXqXqXqX0X0X0X0X0X0X0X0X0X8XqXhX5X~.T.XXsXiXsX` - -XUXPXIXUXdXg k wXwX7XUXJ O.UX5XqXqXqXqXqXqXqX",
+"qXwX8XgXUXIXUXUXUXUXUXUXUXUXUXUXUXUXUXUXLXUXOX  %XUX@X{ >.qXUXlX, 6.jX4XUXJ O.UX5XqXqXqXqXqXqXqX",
+"qX9XdXP.r l h h h h h h h h h h g f f g g b , ^ UXUXXX`.A H UXUXw.t yX7XUXJ O.UX5XqXqXqXqXqXqXqX",
+"qX0XyXOXi.g.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.x.+ n.UXKXIXV.y c.UXUX&XO -XwXUXJ O.UX5XqXqXqXqXqXqXqX",
+"qXqXwX7X:X>X>X>X>X>X>X>X>X>X>X>X>X>X>X>X>X7X1 M.UXKXPX,X! A UXUX:XX =XwXUXJ O.UX5XqXqXqXqXqXqXqX",
+"qX8XgXV.  # O O O O O O O O O O O O O O o $   $.UXUXp.r.M P UXUXc.7 0X8XUXJ o.UX5XqXqXqXqXqXqXqX",
+"qXwX9XfXUXLXLXLXLXLXLXLXLXLXLXLXLXLXLXLXHXUXT.# iXUX-Xl.Y.BXUXHXi O.jX1XUXJ O.UX2XqXqXqXqXqXqXqX",
+"qXqX0XrXdXsXsXsXsXsXsXsXsXsXsXsXsXsXsXsXsXsXhXN h cXUXIXIXUXKX) 4 :XyXdXUXH X.UXaXwX9XqXqXqXqXqX",
+"qXqXqX0X9X9X9X9X9X9X9X9X9X9X9X9X9X9X9X9X9X7XrX<Xf = q.@X-Xv.e > _.jX6X<.d o + k q.rXyX9XqXqXqXqX",
+"qXqXqX0X9X0X9X9X9X9X9X9X9X9X9X9X9X9X9X9X9X0X7XrXqX4.0 X . 2 | ;XhXoX0 < w.XX}.1.& g ,XtX0XqXqXqX",
+"qXqX0XtXaXuXiXiXiXiXiXiXiXiXiXiXiXiXiXiXiXiXiXyXiXnXfX2X1XuXnXgXqXy F CXUXIXIXUXzXh n tXqXqXqXqX",
+"qX0XiX`.fXUXIXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXLXUXd.4 NXUXB.Z _ 4XUXaX* h.hX8XqXqX",
+"qX8XgXV.o ; * * * * * * * * * * * * * * * * * * * * * & & * & <   d.UXLX=X0Xg %.UXUX+.m dX9XqXqX",
+"qX8XlXg.! <X{. X X X X X X X X X X X X X X X X X X X X X X XoX_.. oXUXUXVX+.3 |.UXUXJ.: tXqXqXqX",
+"qX8XkXk.A T.B.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.C.S.n.  .XUXLXAXdXG R UXUXF., tXqXqXqX",
+"qX8XgXV.2 y e e e e e e e e e e e e e e e e e e e e e e e e w s O q.UXHX+.| r s.UXUX] Z gX9XqXqX",
+"qX0XuX XzXUXIXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXKXUXm.= kXUXqX_.7XIXUX8X+ M.gX8XqXqX",
+"qXqX0XtXtXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXeXyXx g gXUXIXPXUXqX7 L pX0XqXqXqX",
+"qXqXqX0X9X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X8XtX,Xx O o.G.S.'   G 8XrX0XqXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXwX9XtXyXh.v : > V M.aXeX9XqXqXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX0XqXhXfXtXtXgXgX0X0XqXqXqXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX8X9XqXqX9X8XqXqXqXqXqXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX"
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/bridges-web.png b/icons/bridges-web.png
new file mode 100644 (file)
index 0000000..7bf435c
Binary files /dev/null and b/icons/bridges-web.png differ
diff --git a/icons/bridges.ico b/icons/bridges.ico
new file mode 100644 (file)
index 0000000..3c17b94
Binary files /dev/null and b/icons/bridges.ico differ
diff --git a/icons/bridges.rc b/icons/bridges.rc
new file mode 100644 (file)
index 0000000..c5c7f27
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "bridges.ico"
diff --git a/icons/bridges.sav b/icons/bridges.sav
new file mode 100644 (file)
index 0000000..ddae708
--- /dev/null
@@ -0,0 +1,72 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :7:Bridges
+PARAMS  :15:10x10i30e10m2d2
+CPARAMS :15:10x10i30e10m2d2
+SEED    :15:944199396008454
+DESC    :49:a1a4a4b4k4b6b2a4b2b2b1e2e1a4b4c3j25d2a1f2c3a4d4c3
+AUXINFO :838:bd75eb5f7b129109b5cdcff0925c77ca5c0a135365002b93b44c5013c7a307b9504affcfb8ad934263196fc3e6d0b023abe48d254d46d29520e50a5e423c0fb1bc01ccc51cad61045c439e7c2bb8e5788bc7f3622aaa3a8125ebde11c9cd69b6f2393246fd094ad91e81ae58cd557b73bd1c9839cfad5835c8519e44298204eaca58dfd79289546959bfbabdc5f3cb7a27b8d3fb2d0b062bd5c2e469493c19f8c89989df73d8a3ab02d9afcbfedf245306d15881a01d153122f8374c7526abecc90919f99ff62e9789cabc402249af095ceb14c8c59c0d9ffbcdd731d50114e7c30c31ef0638f4d352abbfd04b4315d368d65bbfe005b6586245bc5244e5050098cf4c1b6986120f40d5ce038c10a3f309286f950cdc287e495aa13c70ab0c1f113a135556d7ce895fd8244afcbad43fe51f275837f223a1cb95151de8a158cb0add7fa8c9f1fa0e09a1ce842136c1679144cead56b164c4ef1a09ed36fd9704ba191b5957bc3d5bb97d8a1f7451d357a6638ac320b0beb0cd35aa404c8f1621c6d400960aa97bf6ce3a944339d7e401c4d98c31773b2a8881352d5653fdb5e8f7c04b
+NSTATES :2:63
+STATEPOS:2:41
+MOVE    :10:L8,0,5,0,1
+MOVE    :10:L8,0,5,0,2
+MOVE    :10:L8,0,8,2,1
+MOVE    :10:L8,0,8,2,2
+MOVE    :4:M8,0
+MOVE    :10:L0,2,3,2,1
+MOVE    :10:L0,2,3,2,2
+MOVE    :10:L0,2,0,7,1
+MOVE    :10:L0,2,0,7,2
+MOVE    :4:M0,2
+MOVE    :10:L1,0,3,0,1
+MOVE    :4:M1,0
+MOVE    :10:L3,0,5,0,1
+MOVE    :10:L3,0,3,2,1
+MOVE    :10:L1,3,1,5,1
+MOVE    :10:L0,7,5,7,1
+MOVE    :10:L0,7,0,9,1
+MOVE    :10:L0,9,5,9,1
+MOVE    :10:L0,9,5,9,2
+MOVE    :10:L0,9,0,7,2
+MOVE    :4:M0,9
+MOVE    :4:M0,7
+MOVE    :10:L4,8,8,8,1
+MOVE    :10:L4,8,8,8,2
+MOVE    :4:M4,8
+MOVE    :10:L5,9,9,9,1
+MOVE    :10:L5,9,9,9,2
+MOVE    :4:M5,9
+MOVE    :10:L9,9,9,6,1
+MOVE    :4:M9,9
+MOVE    :10:L8,8,8,5,1
+MOVE    :4:M8,8
+MOVE    :10:L9,6,9,4,1
+MOVE    :4:M9,6
+MOVE    :4:M9,4
+MOVE    :10:L1,5,4,5,1
+MOVE    :10:L1,5,4,5,2
+MOVE    :10:L1,5,1,3,2
+MOVE    :4:M1,3
+MOVE    :4:M1,5
+MOVE    :10:L3,4,3,2,1
+MOVE    :10:L3,4,3,2,2
+MOVE    :4:M3,4
+MOVE    :10:L4,5,8,5,1
+MOVE    :10:L7,7,5,7,1
+MOVE    :4:M5,7
+MOVE    :4:M7,7
+MOVE    :10:L7,3,4,3,1
+MOVE    :4:M7,3
+MOVE    :10:L5,0,3,0,2
+MOVE    :4:M5,0
+MOVE    :4:M3,0
+MOVE    :10:L3,2,6,2,1
+MOVE    :4:M3,2
+MOVE    :10:L6,2,8,2,1
+MOVE    :4:M6,2
+MOVE    :10:L8,2,8,5,1
+MOVE    :4:M8,2
+MOVE    :4:M8,5
+MOVE    :10:L4,5,4,3,1
+MOVE    :4:M4,3
+MOVE    :4:M4,5
diff --git a/icons/cicon.pl b/icons/cicon.pl
new file mode 100755 (executable)
index 0000000..3578bd3
--- /dev/null
@@ -0,0 +1,27 @@
+#!/usr/bin/perl 
+
+# Given a list of input PNGs, create a C source file file
+# containing a const array of XPMs, under the name `xpm_icon'.
+
+$k = 0;
+@xpms = ();
+foreach $f (@ARGV) {
+  # XPM format is generated directly by ImageMagick, so that's easy
+  # enough. We just have to adjust the declaration line so that it
+  # has the right name, linkage and storage class.
+  @lines = ();
+  open XPM, "convert $f xpm:- |";
+  push @lines, $_ while <XPM>;
+  close XPM;
+  die "XPM from $f in unexpected format\n" unless $lines[1] =~ /^static.*\{$/;
+  $lines[1] = "static const char *const xpm_icon_$k"."[] = {\n";
+  $k++;
+  push @xpms, @lines, "\n";
+}
+
+# Now output.
+foreach $line (@xpms) { print $line; }
+print "const char *const *const xpm_icons[] = {\n";
+for ($i = 0; $i < $k; $i++) { print "    xpm_icon_$i,\n"; }
+print "};\n";
+print "const int n_xpm_icons = $k;\n";
diff --git a/icons/crop.sh b/icons/crop.sh
new file mode 100755 (executable)
index 0000000..0d15d3c
--- /dev/null
@@ -0,0 +1,37 @@
+#!/bin/sh 
+
+# Crop one image into another, after first checking that the source
+# image has the expected size in pixels.
+#
+# This is used in the Puzzles icon build scripts to construct icons
+# which are zoomed in on a particular sub-area of the puzzle's
+# basic screenshot. This way I can define crop areas in pixels,
+# while not having to worry too much that if I adjust the source
+# puzzle so as to alter the layout the crop area might start
+# hitting the wrong bit of picture. Most layout changes I can
+# conveniently imagine will also alter the overall image size, so
+# this script will give a build error and alert me to the fact that
+# I need to fiddle with the icon makefile.
+
+infile="$1"
+outfile="$2"
+insize="$3"
+crop="$4"
+
+# Special case: if no input size or crop parameter was specified at
+# all, we just copy the input to the output file.
+
+if test $# -lt 3; then
+  cp "$infile" "$outfile"
+  exit 0
+fi
+
+# Check the input image size.
+realsize=`identify -format %wx%h "$infile"`
+if test "x$insize" != "x$realsize"; then
+  echo "crop.sh: '$infile' has wrong initial size: $realsize != $insize" >&2
+  exit 1
+fi
+
+# And crop.
+convert -crop "$crop" "$infile" "$outfile"
diff --git a/icons/cube-16d24.png b/icons/cube-16d24.png
new file mode 100644 (file)
index 0000000..394c969
Binary files /dev/null and b/icons/cube-16d24.png differ
diff --git a/icons/cube-16d4.png b/icons/cube-16d4.png
new file mode 100644 (file)
index 0000000..732412e
Binary files /dev/null and b/icons/cube-16d4.png differ
diff --git a/icons/cube-16d8.png b/icons/cube-16d8.png
new file mode 100644 (file)
index 0000000..9892a76
Binary files /dev/null and b/icons/cube-16d8.png differ
diff --git a/icons/cube-32d24.png b/icons/cube-32d24.png
new file mode 100644 (file)
index 0000000..592d5a6
Binary files /dev/null and b/icons/cube-32d24.png differ
diff --git a/icons/cube-32d4.png b/icons/cube-32d4.png
new file mode 100644 (file)
index 0000000..cbb82a9
Binary files /dev/null and b/icons/cube-32d4.png differ
diff --git a/icons/cube-32d8.png b/icons/cube-32d8.png
new file mode 100644 (file)
index 0000000..cdc79ea
Binary files /dev/null and b/icons/cube-32d8.png differ
diff --git a/icons/cube-48d24.png b/icons/cube-48d24.png
new file mode 100644 (file)
index 0000000..99179d3
Binary files /dev/null and b/icons/cube-48d24.png differ
diff --git a/icons/cube-48d4.png b/icons/cube-48d4.png
new file mode 100644 (file)
index 0000000..cc59b8d
Binary files /dev/null and b/icons/cube-48d4.png differ
diff --git a/icons/cube-48d8.png b/icons/cube-48d8.png
new file mode 100644 (file)
index 0000000..6fee156
Binary files /dev/null and b/icons/cube-48d8.png differ
diff --git a/icons/cube-base.png b/icons/cube-base.png
new file mode 100644 (file)
index 0000000..6e3f87d
Binary files /dev/null and b/icons/cube-base.png differ
diff --git a/icons/cube-ibase.png b/icons/cube-ibase.png
new file mode 100644 (file)
index 0000000..6e3f87d
Binary files /dev/null and b/icons/cube-ibase.png differ
diff --git a/icons/cube-ibase4.png b/icons/cube-ibase4.png
new file mode 100644 (file)
index 0000000..d4fe125
Binary files /dev/null and b/icons/cube-ibase4.png differ
diff --git a/icons/cube-icon.c b/icons/cube-icon.c
new file mode 100644 (file)
index 0000000..dcc5639
--- /dev/null
@@ -0,0 +1,801 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 236 2 ",
+"   c #E3E3E4",
+".  c #CECEDC",
+"X  c #CCCCDC",
+"o  c #CDCDDC",
+"O  c #CDCDDB",
+"+  c #CDCDDC",
+"@  c #CCCCDC",
+"#  c #CECEDB",
+"$  c #DADADA",
+"%  c gray86",
+"&  c gray86",
+"*  c #DADADA",
+"=  c gray86",
+"-  c #DADADA",
+";  c gray86",
+":  c gray90",
+">  c #1818E9",
+",  c #0909F6",
+"<  c #0D0DF2",
+"1  c #0C0CE2",
+"2  c #0D0DF3",
+"3  c #0909F6",
+"4  c #1A1AE6",
+"5  c #CECED5",
+"6  c #E0E0DE",
+"7  c #DBDBDC",
+"8  c #CDCDCD",
+"9  c #DDDDDD",
+"0  c gray87",
+"q  c LightGray",
+"w  c #DDDDDD",
+"e  c #CDCDDC",
+"r  c #0D0DF3",
+"t  c blue",
+"y  c #0101FE",
+"u  c #0000EC",
+"i  c #0101FF",
+"p  c #1010F1",
+"a  c #D9D9DF",
+"s  c #EDEDEA",
+"d  c #E7E7E7",
+"f  c #D7D7D7",
+"g  c gray91",
+"h  c gray92",
+"j  c #DDDDDD",
+"k  c gainsboro",
+"l  c #CDCDDC",
+"z  c #0C0CF3",
+"x  c #0101FE",
+"c  c #0000EC",
+"v  c #0202FE",
+"b  c #1010F0",
+"n  c #D6D6DD",
+"m  c #E9E9E7",
+"M  c #E4E4E4",
+"N  c #D5D5D5",
+"B  c gray90",
+"V  c #E7E7E7",
+"C  c #DADADA",
+"Z  c gainsboro",
+"A  c #D3D3DA",
+"S  c #7272D7",
+"D  c #6E6EE3",
+"F  c #7979DF",
+"G  c #3131D6",
+"H  c #0000ED",
+"J  c #0000EF",
+"K  c #0F0FE0",
+"L  c #C7C7CD",
+"P  c #D9D9D7",
+"I  c #D3D3D4",
+"U  c #C5C5C5",
+"Y  c #D5D5D5",
+"T  c gray84",
+"R  c gray80",
+"E  c gainsboro",
+"W  c #DCDCDB",
+"Q  c #EBEBDB",
+"!  c #F5F5E7",
+"~  c #FFFFE1",
+"^  c #6B6BE1",
+"/  c #1010F1",
+"(  c #D5D5DD",
+")  c #E9E9E8",
+"_  c #E6E6E7",
+"`  c #D8D8D8",
+"'  c #E9E9E9",
+"]  c #ECECEC",
+"[  c gainsboro",
+"{  c gray86",
+"}  c #DBDBDF",
+"|  c #E3E3EB",
+" . c #F5F5E6",
+".. c #6464E3",
+"X. c #1010F1",
+"o. c #DCDCDF",
+"O. c #F1F1EB",
+"+. c #DCDCDB",
+"@. c #CACACB",
+"#. c #DADADA",
+"$. c gray85",
+"%. c #D7D7D7",
+"&. c gray87",
+"*. c #DADADA",
+"=. c #D2D2D2",
+"-. c #D9D9DD",
+";. c #EAEAD9",
+":. c #6060D7",
+">. c #0000F4",
+",. c #0000F5",
+"<. c #0E0EE6",
+"1. c #BEBED5",
+"2. c #CCCCDC",
+"3. c #C8C8CE",
+"4. c #E1E1E0",
+"5. c #E0E0E1",
+"6. c #D2D2D2",
+"7. c #C3C3C3",
+"8. c gray87",
+"9. c #DADADA",
+"0. c gray83",
+"q. c #DBDBDF",
+"w. c #ECECDA",
+"e. c #6060D8",
+"r. c #0000F6",
+"t. c #0303F7",
+"y. c #0000E9",
+"u. c #0909EA",
+"i. c #0000F5",
+"p. c #8B8BDC",
+"a. c #F4F4E9",
+"s. c #E3E3EA",
+"d. c #DADADF",
+"f. c #D2D2D3",
+"g. c #DDDDDD",
+"h. c gray86",
+"j. c gray87",
+"k. c #E7E7EB",
+"l. c #F8F8E6",
+"z. c #6767E3",
+"x. c #0808FF",
+"c. c #0404F3",
+"v. c #0606F5",
+"b. c #9595DC",
+"n. c #FFFFE2",
+"m. c #F5F5E3",
+"M. c #EDEDD7",
+"N. c #DEDED6",
+"B. c #DDDDDF",
+"V. c gray86",
+"C. c gray86",
+"Z. c #E3E3E8",
+"A. c #F5F5E2",
+"S. c #6464E1",
+"D. c #0404FF",
+"F. c #0000F3",
+"G. c #0101F4",
+"H. c #2323DB",
+"J. c #4040E0",
+"K. c #3B3BE8",
+"L. c #3737E0",
+"P. c #A3A3CD",
+"I. c #E5E5DE",
+"U. c #DADADA",
+"Y. c #CDCDCD",
+"T. c #D6D6D7",
+"R. c #DCDCD4",
+"E. c #9797CB",
+"W. c #6E6EE0",
+"Q. c #7878E2",
+"!. c #7070D5",
+"~. c #7070D6",
+"^. c #7777E3",
+"/. c #6C6CDD",
+"(. c #5E5ED1",
+"). c #6868E4",
+"_. c #6363E6",
+"`. c #8484C8",
+"'. c #E0E0D9",
+"]. c gray86",
+"[. c #E9E9E8",
+"{. c #E4E4E5",
+"}. c #DDDDD5",
+"|. c #F7F7E4",
+" X c #F9F9E7",
+".X c #EBEBD9",
+"XX c #ECECDB",
+"oX c #F9F9E6",
+"OX c #F7F7E4",
+"+X c #E8E8D5",
+"@X c #FAFAE4",
+"#X c #FDFDE6",
+"$X c #E7E7DC",
+"%X c #DBDBDD",
+"&X c gray86",
+"*X c gray87",
+"=X c gray92",
+"-X c #E8E8E7",
+";X c #D6D6D8",
+":X c #E4E4E9",
+">X c #E7E7EB",
+",X c #DADADE",
+"<X c #DBDBDF",
+"1X c #E7E7EB",
+"2X c #E4E4E7",
+"3X c #D4D4D7",
+"4X c #E5E5E9",
+"5X c #E7E7EB",
+"6X c #DADADD",
+"7X c #DDDDDC",
+"8X c #DDDDDD",
+"9X c gray80",
+"0X c gray86",
+"qX c #DDDDDD",
+"wX c gray82",
+"eX c LightGray",
+"rX c #DDDDDD",
+"tX c #DADADA",
+"yX c #CDCDCD",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #D2D2D2",
+"aX c #DDDDDD",
+"sX c gainsboro",
+"dX c gainsboro",
+"fX c gainsboro",
+"gX c gainsboro",
+"hX c gainsboro",
+"jX c gainsboro",
+"kX c gainsboro",
+"lX c gainsboro",
+"zX c gainsboro",
+"xX c gainsboro",
+"cX c #DDDDDD",
+"vX c gray90",
+"bX c white",
+/* pixels */
+"  . X o O + @ # $ % & * = - ; : ",
+". > , < 1 2 3 4 5 6 7 8 9 0 q w ",
+"e r t y u i t p a s d f g h j k ",
+"l z t x c v t b n m M N B V C Z ",
+"A S D F G H J K L P I U Y T R E ",
+"W Q ! ~ ^ t t / ( ) _ ` ' ] [ E ",
+"{ } |  ...t t X.o.O.+.@.#.$.%.&.",
+"*.=.-.;.:.>.,.<.1.2.3.4.5.6.7.8.",
+"9.0.q.w.e.r.t.y.u.i.p.a.s.d.f.g.",
+"h.j.k.l.z.t x.c.v.t b.n.m.M.N.B.",
+"V.C.Z.A.S.t D.F.G.t H.J.K.L.P.I.",
+"U.Y.T.R.E.W.Q.!.~.^./.(.)._.`.'.",
+"].9 [.{.}.|. X.XXXoXOX+X@X#X$X%X",
+"&X*X=X-X;X:X>X,X<X1X2X3X4X5X6X7X",
+"; q 8XC 9X0XqXwXeXrXtXyXuXiXpXaX",
+": w sXZ dXfXgXhXjXgXkXlXzXxXcXvX"
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 234 2 ",
+"   c #1313BC",
+".  c #595999",
+"X  c #0B0BCE",
+"o  c #0D0DCC",
+"O  c #0E0ECD",
+"+  c #1010CB",
+"@  c #0000D1",
+"#  c #0000D3",
+"$  c #0101D3",
+"%  c #0000D4",
+"&  c #0202D7",
+"*  c #0F0FD1",
+"=  c #0D0DD2",
+"-  c #0E0ED2",
+";  c #0101D8",
+":  c #0000D9",
+">  c #0000DA",
+",  c #0000DB",
+"<  c #0404DF",
+"1  c #1111D1",
+"2  c #1616DD",
+"3  c #3131CC",
+"4  c #2121D0",
+"5  c #3131D7",
+"6  c #3131D8",
+"7  c #3434DA",
+"8  c #0000E4",
+"9  c #0101E4",
+"0  c #0000E5",
+"q  c #0101E5",
+"w  c #0000E6",
+"e  c #0D0DE3",
+"r  c #0F0FE2",
+"t  c #0F0FE3",
+"y  c #0D0DE4",
+"u  c #0000E9",
+"i  c #0101E9",
+"p  c #0202E9",
+"a  c #0000EA",
+"s  c #0101EA",
+"d  c #0000EB",
+"f  c #0101EB",
+"g  c #0202EA",
+"h  c #0000EC",
+"j  c #0000EE",
+"k  c #0101EE",
+"l  c #0000EF",
+"z  c #0101EF",
+"x  c #0C0CE9",
+"c  c #0D0DE9",
+"v  c #1212E1",
+"b  c #1010E3",
+"n  c #1212E2",
+"m  c #1414E3",
+"M  c #1616E3",
+"N  c #1010E4",
+"B  c #1212E6",
+"V  c #1313E6",
+"C  c #1212E7",
+"Z  c #1515E4",
+"A  c #1616E4",
+"S  c #1515E7",
+"D  c #0000F0",
+"F  c #0000F1",
+"G  c #0000F5",
+"H  c #0000F8",
+"J  c #0000F9",
+"K  c #0303FB",
+"L  c #0000FD",
+"P  c #0101FD",
+"I  c #0303FC",
+"U  c #0202FD",
+"Y  c #0303FD",
+"T  c #0000FE",
+"R  c #0101FE",
+"E  c blue",
+"W  c #0101FF",
+"Q  c #0202FF",
+"!  c #5D5DCC",
+"~  c #8989B1",
+"^  c #9C9CBD",
+"/  c #ACACAC",
+"(  c #AFAFBA",
+")  c #AEAEBB",
+"_  c #AFAFBB",
+"`  c #B8B8B9",
+"'  c #B9B9B9",
+"]  c gray73",
+"[  c #BBBBBB",
+"{  c #BCBCBC",
+"}  c gray74",
+"|  c gray",
+" . c gray75",
+".. c #C2C2BF",
+"X. c #9797CB",
+"o. c #9797CC",
+"O. c #9898CF",
+"+. c #9797D1",
+"@. c #B3B3C0",
+"#. c #B2B2C1",
+"$. c #B4B4C1",
+"%. c #BABAC1",
+"&. c #BEBECF",
+"*. c #C0C0C0",
+"=. c #C1C1C0",
+"-. c #C1C1C1",
+";. c #C3C3C0",
+":. c #C3C3C3",
+">. c #C5C5C3",
+",. c #C3C3C6",
+"<. c gray77",
+"1. c #C5C5C5",
+"2. c #C6C6C4",
+"3. c #C6C6C6",
+"4. c gray78",
+"5. c #C8C8C5",
+"6. c #CACAC5",
+"7. c #C9C9C6",
+"8. c #C8C8C8",
+"9. c gray79",
+"0. c #CBCBC9",
+"q. c #CBCBCB",
+"w. c gray80",
+"e. c #CCCCCD",
+"r. c #CDCDCD",
+"t. c #CDCDCE",
+"y. c #CECECE",
+"u. c #CECECF",
+"i. c gray81",
+"p. c #D3D3C8",
+"a. c #D2D2CA",
+"s. c #C1C1D0",
+"d. c #C2C2D0",
+"f. c #C2C2D1",
+"g. c #C1C1D3",
+"h. c #C6C6D0",
+"j. c #C7C7D0",
+"k. c #C6C6D1",
+"l. c #C6C6D2",
+"z. c #C2C2D4",
+"x. c #C6C6D4",
+"c. c #C6C6D5",
+"v. c #D0D0D0",
+"b. c gray82",
+"n. c #D2D2D0",
+"m. c #D2D2D2",
+"M. c #D2D2D3",
+"N. c LightGray",
+"B. c #D5D5D2",
+"V. c #D6D6D2",
+"C. c #D3D3D4",
+"Z. c gray83",
+"A. c #D5D5D4",
+"S. c #D4D4D5",
+"D. c #D5D5D5",
+"F. c #D6D6D4",
+"G. c #D7D7D5",
+"H. c gray84",
+"J. c #D7D7D6",
+"K. c #D7D7D7",
+"L. c #D8D8D3",
+"P. c #D8D8D4",
+"I. c #D8D8D5",
+"U. c #D9D9D5",
+"Y. c #D9D9D6",
+"T. c #D7D7D8",
+"R. c #D7D7D9",
+"E. c #D8D8D8",
+"W. c #D8D8D9",
+"Q. c gray85",
+"!. c #DADAD9",
+"~. c #DBDBD9",
+"^. c #D9D9DA",
+"/. c #DADADA",
+"(. c #DCDCD8",
+"). c #DDDDD9",
+"_. c #DFDFD9",
+"`. c #DEDEDA",
+"'. c gainsboro",
+"]. c #DDDDDD",
+"[. c #DEDEDC",
+"{. c #DFDFDC",
+"}. c gray87",
+"|. c #E1E1D7",
+" X c #E9E9DB",
+".X c #EBEBDD",
+"XX c #ECECDC",
+"oX c #ECECDD",
+"OX c #E1E1E1",
+"+X c #E2E2E2",
+"@X c #E3E3E2",
+"#X c gray89",
+"$X c #E2E2E4",
+"%X c #E3E3E4",
+"&X c #E2E2E5",
+"*X c #E3E3E5",
+"=X c #E4E4E4",
+"-X c #E4E4E5",
+";X c gray90",
+":X c #E6E6E6",
+">X c #E7E7E7",
+",X c #E1E1E8",
+"<X c #E5E5E8",
+"1X c #E7E7E8",
+"2X c #E7E7E9",
+"3X c gray91",
+"4X c #E8E8E9",
+"5X c #E9E9E9",
+"6X c #EAEAE9",
+"7X c #E8E8EA",
+"8X c #E9E9EA",
+"9X c #EAEAEA",
+"0X c #EBEBEA",
+"qX c gray92",
+"wX c #EDEDE9",
+"eX c #EDEDEA",
+"rX c #EEEEEA",
+"tX c #EEEEEB",
+"yX c #ECECEC",
+"uX c #EDEDEE",
+"iX c #EEEEEE",
+"pX c #EFEFEE",
+"aX c #EEEEEF",
+"sX c #EFEFEF",
+"dX c #FCFCE1",
+"fX c #F4F4E8",
+"gX c #F5F5E9",
+"hX c #F4F4EB",
+"jX c #F2F2EF",
+"kX c #F3F3EF",
+"lX c #F4F4EF",
+"zX c #FAFAED",
+"xX c gray94",
+"cX c #F3F3F0",
+/* pixels */
+":X:XrXrXtXtXtXtXrXrXrXrXtXtXrXrX3X3XtX9X3X3XtX3XtX9X9X9XtX9X<X:X",
+":X#Xl.l.l.l.l.l.h.l.l.l.l.l.l.l.M.M.a.M.M.M.i.M.M.M.i.M.i.M.:X:X",
+"tXl.& j g g g j & & j q g g j = <.R.C.C.C.E.} >.K.C.C.C.K.} K.3X",
+"tXl.g E E E E E q g E E E E E n E.uX7X9X3XtXM.E.tX3X3X3XtXM.E.3X",
+"rXl.g E P E P E q g E E E E E n K.3X%X:X%X3Xt.K.3X:X:X#X9Xe.R.3X",
+"rXl.g E E E P E q g E E E E E t Y.3X:X:X:X9Xt.K.3X%X:X:XtXt.E.3X",
+"tXl.p E P P P E 8 g E P E P E n K.3X:X:X:X9Xt.K.3X:X:X:X3Xt.E.3X",
+"rXl.q E E E E E q g E E E P E N (.tX3XtX3XxXM.E.xX3XtX3XxXM.E.9X",
+"tXl.= x y y y x X # g q q q g X :.C.i.t.t.B.] :.C.i.t.t.M.} K.3X",
+"tXi.:.E.C.C.M.(.$.: j g g g j = >.E.C.K.C.E.} <.K.K.K.M.Y.} E.3X",
+"9XM.K.tX3XrX3XjXl.g E E E E E N E.tX3X3X3XuXM.E.tX3X3X3XjXt.E.3X",
+"9Xi.C.9X%X:X%XrX&.g E E E E E t K.3X:X:X:X3Xq.K.<X:X:X:X3Xt.R.tX",
+"tXi.M.3X:X:X%XrXf.j E E E P E t K.3X:X:X:XxXM.E.uX3X3X3XtXq.K.3X",
+"9Xi.M.3X=X:X:X3Xs.g E E E P E n B.3X%X3XC.q.] } t.4.q.:.<XM.R.3X",
+"tXC.R.jX3X9X9XjXx.g E E E E E n |.cXrXxX0.B.#X].].].#X] [ q.E.3X",
+"9Xi.%.M.a.i.i.K.) # g q 8 8 q X #.z.&.f.) #X3X3X3X:X9Xe.'./ E.rX",
+"rXt.<.'.K.R.C.(.#.: j g j g D # , D g D 3 .X:X:X:X:XtX<.<Xt.K.3X",
+"9XM.R.tX<X3X3XjXx.g E P E E E q j E E E 7 .X,X:X:X:X3X<.:Xt.R.rX",
+"9XM.M.3X:X:X%XrXf.g E P E P E 8 g E K E 5  X,X%X%X#X<X,.#XM.K.3X",
+"9Xi.B.3X=X:X:XrXf.g E E E E E q j E Y E 7 dXhXgXgXfXzXa.%Xt.E.3X",
+"9Xi.M.3X:X:X#XrXf.g E E E P E 8 g E P E 4 ^ O.X.X.X.O.~ .XM.K.5X",
+"tXM.E.uX3X3X3XcXx.g E E E E E 8 j E E E G @ G E P P E P ! a.K.5X",
+"tXt.} M.t.t.t.B.( 1 N n m n N + 1 N m n Z 2   m m m m Z 9 . |.3X",
+"9XM.<.E.B.K.K.E.:.7.(.Y.Y.Y.(.:.7.(.Y.Y.Y.(.:.7.(.Y.Y.Y.|.%.Y.3X",
+"9XM.K.tX3X3X3XxXC.R.uX3X9X<XxXC.R.uX3X3X3XuXa.R.jX3X3X3XtXM.K.3X",
+"3XM.K.3X<X:X:X3Xi.C.9X:X:X:X3Xt.K.3X:X:X:X3Xt.K.3X:X:X#X9Xt.E.3X",
+"tXi.M.3X:X:X:XtXa.C.3X:X:X:X3Xi.C.3X:X:X:X9Xt.K.3X:X:X:X3Xt.E.3X",
+"9XM.M.3X%X:X:X3Xt.M.3X:X:X%X3Xi.K.3X%X:X%X9Xt.K.3X:X:X#X3Xt.R.rX",
+"3XM.R.tX3X3X3XxXK.R.tX3XrX3XxXM.E.jX3XrX3XxXM.E.uX3XrX3XxXM.K.3X",
+"rXM.} M.t.t.t.M.[ } M.e.t.q.M.] %.M.e.t.q.M.] ..M.e.t.t.M.{ E.3X",
+":X%XR.K.R.R.Y.K.R.K.R.R.K.K.R.R.K.R.K.K.R.R.K.R.K.K.R.K.Y.E.5X:X",
+":X:XtX3X3XtX3X3XtX3X3XtX3X3XtX3X3XrX3X3XtX3X3XtX3X3XtX3X3X3X:X:X"
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 208 2 ",
+"   c #40407A",
+".  c #0505A3",
+"X  c #0000AB",
+"o  c #0404BF",
+"O  c #0808BE",
+"+  c #0909BE",
+"@  c #0A0ABF",
+"#  c #1717BB",
+"$  c #1010BD",
+"%  c #5151AB",
+"&  c #5858BB",
+"*  c #0202C1",
+"=  c #0000C3",
+"-  c #0202C2",
+";  c #0404C0",
+":  c #0505C0",
+">  c #0202C4",
+",  c #0000C6",
+"<  c #0808C1",
+"1  c #0000CE",
+"2  c #1010CE",
+"3  c #0505D2",
+"4  c #0000D7",
+"5  c #0505DA",
+"6  c #0505DB",
+"7  c #0606DB",
+"8  c #0000DC",
+"9  c #0101DC",
+"0  c #0000DD",
+"q  c #0101DD",
+"w  c #0202DD",
+"e  c #0303DD",
+"r  c #0000DE",
+"t  c #0000DF",
+"y  c #0101DF",
+"u  c #0202DE",
+"i  c #0202DF",
+"p  c #0505DC",
+"a  c #0505DD",
+"s  c #0808D9",
+"d  c #0909D9",
+"f  c #0808DA",
+"g  c #1515D2",
+"h  c #1515D3",
+"j  c #1616D5",
+"k  c #0000E0",
+"l  c #0000E1",
+"z  c #0000E3",
+"x  c #0505E1",
+"c  c #0606E0",
+"v  c #0707E0",
+"b  c #0404E3",
+"n  c #0505E3",
+"m  c #0000E5",
+"M  c #0202E5",
+"N  c #0000E6",
+"B  c #0000E7",
+"V  c #0808E0",
+"C  c #0808E1",
+"Z  c #0000E8",
+"A  c #0000E9",
+"S  c #0202EA",
+"D  c #0000EE",
+"F  c #0000F4",
+"G  c #0101F4",
+"H  c #0000F8",
+"J  c #0101F8",
+"K  c #0000FC",
+"L  c #0101FC",
+"P  c #0000FD",
+"I  c #0101FD",
+"U  c #0202FC",
+"Y  c #0000FE",
+"T  c #0101FE",
+"R  c blue",
+"E  c #0101FF",
+"W  c #0202FF",
+"Q  c #939393",
+"!  c gray61",
+"~  c #8F8FA1",
+"^  c #A4A4AD",
+"/  c #A9A9AB",
+"(  c #AAAAAA",
+")  c #A8A8AD",
+"_  c #ACACAC",
+"`  c gray68",
+"'  c #ACACAE",
+"]  c #AEAEAE",
+"[  c #AFAFAF",
+"{  c #B0B0AF",
+"}  c #A7A7B1",
+"|  c #ABABB0",
+" . c #ACACB0",
+".. c #AFAFB1",
+"X. c gray69",
+"o. c #B1B1B1",
+"O. c #B3B3B1",
+"+. c #B2B2B2",
+"@. c #B7B7B7",
+"#. c gray74",
+"$. c #BDBDBE",
+"%. c gray",
+"&. c gray75",
+"*. c #CACABE",
+"=. c #ABABC1",
+"-. c #ABABC2",
+";. c #ACACC3",
+":. c #AEAECB",
+">. c #B0B0C9",
+",. c #C1C1C1",
+"<. c gray76",
+"1. c #C3C3C3",
+"2. c #C1C1C7",
+"3. c gray77",
+"4. c #C4C4C5",
+"5. c #C5C5C5",
+"6. c #C6C6C6",
+"7. c #C7C7C6",
+"8. c gray78",
+"9. c #C9C9C5",
+"0. c #C2C2C8",
+"q. c #C5C5C8",
+"w. c #C6C6C8",
+"e. c #C7C7C9",
+"r. c #C7C7CD",
+"t. c #C8C8C8",
+"y. c gray79",
+"u. c #CACAC8",
+"i. c #CACAC9",
+"p. c #C9C9CA",
+"a. c #C8C8CB",
+"s. c #CACACA",
+"d. c #CBCBCA",
+"f. c #CBCBCB",
+"g. c #CCCCCB",
+"h. c #CDCDCB",
+"j. c #CFCFCB",
+"k. c #C8C8CD",
+"l. c gray80",
+"z. c #CDCDCD",
+"x. c #CFCFCC",
+"c. c #CECECD",
+"v. c #CCCCCE",
+"b. c #CCCCCF",
+"n. c #CDCDCF",
+"m. c #CECECE",
+"M. c #CFCFCE",
+"N. c gray81",
+"B. c #D0D0CE",
+"V. c #D5D5CD",
+"C. c #D8D8CE",
+"Z. c #D0D0D0",
+"A. c #D1D1D0",
+"S. c gray82",
+"D. c #D2D2D0",
+"F. c #D1D1D3",
+"G. c #D2D2D2",
+"H. c LightGray",
+"J. c #D2D2D4",
+"K. c #D5D5D5",
+"L. c #D7D7D7",
+"P. c #D9D9D0",
+"I. c #DCDCD1",
+"U. c #DCDCD3",
+"Y. c #D8D8D8",
+"T. c #DADADA",
+"R. c #DFDFDF",
+"E. c #E8E8D9",
+"W. c #E2E2E3",
+"Q. c gray89",
+"!. c #E2E2E4",
+"~. c #E3E3E4",
+"^. c #E3E3E5",
+"/. c #E4E4E4",
+"(. c #E4E4E5",
+"). c gray90",
+"_. c #E6E6E6",
+"`. c #E7E7E7",
+"'. c #E6E6EA",
+"]. c gray91",
+"[. c #E9E9EB",
+"{. c #EAEAEA",
+"}. c gray92",
+"|. c #ECECEB",
+" X c #EDEDEB",
+".X c #EAEAEC",
+"XX c #EBEBEC",
+"oX c #ECECEC",
+"OX c #EDEDEC",
+"+X c gray93",
+"@X c #EEEEED",
+"#X c #EEEEEE",
+"$X c #F6F6E0",
+"%X c #F1F1EB",
+"&X c #F1F1EC",
+"*X c #F2F2EC",
+"=X c gray94",
+"-X c #F1F1F1",
+";X c #F3F3F2",
+":X c #F3F3F3",
+">X c #F5F5F1",
+",X c #F4F4F3",
+"<X c #F5F5F3",
+"1X c #F3F3F5",
+"2X c #F4F4F4",
+"3X c #F5F5F4",
+"4X c gray96",
+"5X c #FAFAF3",
+/* pixels */
+"_._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._.",
+"_._._._._._.Q._._._._.Q._._._._._.Q._._././._././._._._._.Q._._._._._.~._._._._._././._._._._._.",
+"_._._._.XXoX}.oXoXoX|.}.}.XXXXoXoXXX}.}.|.|.oX|.|.XXXXoXoX|.}.}.XXoXXXXX}.oXoX|.oXoX}.|._._._._.",
+"_._._._.B.j.x.x.x.j.j.j.j.j.x.x.x.x.x.j.B.B.B.B.x.x.k.j.x.k.x.f.x.k.x.j.f.f.x.k.x.x.k.x._._._._.",
+"_._.}.B.$ c f f f f f d V < O f f f f f f d p < ' f.5.5.5.5.q.5.k.' { f.5.5.5.5.5.5.j.` x.XXQ._.",
+"_._.|.j.f R R R R R R R R l i R R R R R R R R p x.<XXXoXoXoXoXoX1Xx.D.<XoXoXoX|.oXXX1Xk.x.XX_._.",
+"_._.|.j.d R U U U R R U R i 8 R U U R U U U R 8 q.XXQ._./._._._.}.5.f.}._._./._._.Q.}.5.k.XX_._.",
+"_.Q.|.B.f R R R R R R R R l i R R R R R R R R i f.oX~._._._._._.oX5.f.oX_._._._._._.XX5.j.XX_._.",
+"_._.}.j.f R U R R R R R R l i R R R R R R R R 8 q.oX~._._._._._.oX5.x.oX_._._._._._.oX9.x.XX~._.",
+"_._.XXj.f R U R R R R R R l 8 R R R R R R R R i f.oX~._._._._._.oXf.k.oX_._._._._._.oX5.k.}._._.",
+"_._.XXx.f R R R R R R R R i i R U R R R R U R i q.oX_._._._._._.oX9.k.oX_._._._._._.oX5.j.XX_._.",
+"_._.oXx.d R U R R U R U R i 8 R U R U R R U R i q.|.~._._.~._.Q.oX5.f.XXQ._./._._._.|.2.x.XX_._.",
+"_._.XXx.f R R R R R R R R l i R R R R R R R R i x.1X}.XX}.}.oX}.-Xx.D.1X}.oXoXoX}.}.-Xf.x.XX_._.",
+"_.Q.XXx.< B i l l i l l B > > Z i i l i l i B -  .x.f.f.f.f.f.f.D.{ o.N.f.f.f.f.f.f.D.' x.oX~._.",
+"_._.}.x.) r.2.0.2.q.0.2.k. .o l 8 i i 8 i 8 l -  .x.5.f.5.f.5.5.k.' o.x.5.5.f.q.9.5.x.) k.oX_._.",
+"_._.}.f.j.1X|.oXoXoXoXoX<XD.p R R R R R R R R l x.1XoXoXoXoXoX|.1Xx.D.1XoXoXoXoXoX|.1Xf.j.|./._.",
+"_._.XXj.5.XX~.~._.~.~.Q.}.f.5 R U R R U U U R 8 5.|.~._._._.Q.Q.XX5.f.|._._._._./.Q.}.5.k.|._._.",
+"_._.XXk.5.oX_._._._._._.|.f.p R U R R R R R R i f.oX~._._._._._.oX5.k.oX/._._._._._.oX5.x.oX_._.",
+"_._.oXx.5.oXQ._._._._.~.|.f.p R R R R R R R R i q.|._._._._._.Q.}.5.f.}.Q._._._./._.oX5.k.oX_._.",
+"_.Q.XXf.f.oX_._._._._._.|.f.p R R R R R R U R i f.|.~._._._._.}.1Xx.D.1X|.oXoXoXoX].oX5.x.XX~._.",
+"_._.}.f.5.oX_._._._._.~.|.f.5 R U R R R R R R i q.oX_._._.}.Y.$.q.( ' 5.1.1.1.1.$.E.-X2.j.}._._.",
+"_._.oXf.5.}._._._.~._.~.}.f.5 R U R U R R U R i q.|.~._.Q.oX$.$.L.D.D.D.N.N.x.L.^ ! }.9.f.XX~._.",
+"_._.oXx.x.1X}.}.oX}.XX}.1XD.5 R R R R R R R R i x.1XoXoXoX1X5.Y.oX}.}.}.XX}.}.=Xq.f.@.$.x.XX_._.",
+"_._.oXk. .x.5.f.f.f.5.f.x.o.o B 8 i i i i 8 l -  .N.q.q.q.x.( H.}.~._._.~._._.oX$.Y._.Q x.XX_._.",
+"_._.XXx.' B.f.f.f.q.f.9.x.o.; B 8 i i i i i l - - B 8 i 5 l # U.'._._._._._._.oX>.D.1X1.f.|._._.",
+"_./.XXj.k.<X}.}.}.}.}.}.1XD.p R R R R R R R R i i R R R R R g U.}.~._._._._._.oX*.H.-X5.x.}./._.",
+"_./.|.k.5.oXQ._._._._.~.}.f.5 R U U R R R U R i i R U R U R g U.}._._._._._._.oX*.N.-X5.k.|./._.",
+"_._.|.x.5.oXQ._._._._.~.oXf.5 R R R R R R R R i i R R R R R j C.'._._._._._._.XX$.D.-X5.x.oX_._.",
+"_._.XXx.5.oX}._._._._.~.|.f.5 R R R R R R U R i i R U R U R g I.'.Q.~.~.~.~.Q.XX$.D.-X5.k.|._._.",
+"_._.XXk.5.oX_._._._._.~.oXj.p R U R R R R R R i i R R R U R j E.<X%X%X*X*X*X%X5X9.J.-X5.x.oX_._.",
+"_._.oXx.5.oX_._._._._.~.oXj.5 R R R R R R R R i i R R R R R 2 ^ :.=.;.;.;.;.;.>.~ V.1X2.f.}._._.",
+"_.~.XXx.5.}.Q.Q._._.~.Q.}.f.5 R U U R R R U R 8 8 R U R U R G X i G Z Z Z Z Z Z B % $Xf.f.}._._.",
+"_._.}.f.j.1X}.oXoXoXoXoX1XD.p R R R R R R R R i i R R R R R R U 1 F R R R R R R R G & *.x.oX_._.",
+"_._.XXx.' x.5.5.q.f.5.5.x.o.O l 5 5 5 p p 5 n ; ; c 5 p 5 p 5 c 3 . 5 p 5 5 p p 5 V 4   B.XX_._.",
+"_._.XXx.' B.f.f.j.f.f.f.D.o.o.D.f.f.f.j.j.f.B.o.o.D.f.f.f.f.f.f.J. .O.D.f.f.f.f.f.f.D.} j.oX_._.",
+"_._.oXk.f.-X}.}.XXXXoX}.1XN.x.<X}.oX}.oXXX}.-XB.x.-X}.|.|.|.}.}.-Xx.D.-X|.|.|.oXoX|.<Xj.k.}.~._.",
+"_._.XXx.2.|./._._.Q._.Q.XXf.5.XXQ.~.~.~.~.Q.}.f.f.}.Q.~.~.~.~.Q.oXq.f.}.~.~.~._._.~.oX5.j.oX_._.",
+"_./.XXj.5.oX_._._._._._.XXf.q.oX_._._._._._.}.5.f.XX_._._._._._.oXf.f.XX_._._._._._.oX5.k.|._._.",
+"_./.}.k.9.oX'._._._._._.XXf.9.oX_._._._._._.}.f.q.|._._._._._._.oXq.x.XX_._._._._._.oX9.x.|._._.",
+"_._.|.x.5.oX_._._._._._.XXf.f.oX~._._._._._.}.f.f.}._._._._._._.oX9.f.XX~._._._._.~.oX5.k.|./._.",
+"_._.XXx.5.XX_._._._._.Q.}.f.5.oX_._._._._._.oXf.f.|._.'._._._._.oX5.f.XX_._._._._._.|.5.x.oX_._.",
+"_._.oXk.5.}.Q._.Q._._._.}.f.5.oX_._._.Q._.Q.oXq.5.}./._._./._./.oX5.f.}.~._._._._.~.|.2.k.|._._.",
+"_._.oXx.k.<XXXoXoXoXoX}.-XD.x.1XoXoXoXoXoXoX1Xx.B.1XoXoX|.oXoXoX1Xx.D.1XXXoXoXoXXXXX1Xj.x.|._._.",
+"_.Q.oXx.) j.5.5.5.5.f.5.x.{ ` k.5.5.q.5.5.5.j.' ' j.5.5.9.5.5.2.f.' ' x.5.5.5.5.5.5.j.) x.|./._.",
+"_._._._.x.k.x.x.j.k.j.f.k.x.x.k.x.k.j.x.k.x.k.x.x.k.x.x.x.k.j.x.x.x.k.x.x.k.j.x.k.x.k.x.~._._._.",
+"_._._._.}.}.XXoXoXXXXX}.|.oXoXoX|.|.XXoX|.oXoX}.XXXXXXXX}.}.XXXXXXXXoXXX|.|.XXoX|.oXoX}.'._._._.",
+"_._._._._._.Q._._._./._._._._._._././._._._._.Q._.~._._.Q._._._.~._._._._././._._._._._._._._._.",
+"_._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._."
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/cube-web.png b/icons/cube-web.png
new file mode 100644 (file)
index 0000000..c52dd4a
Binary files /dev/null and b/icons/cube-web.png differ
diff --git a/icons/cube.ico b/icons/cube.ico
new file mode 100644 (file)
index 0000000..1e306b2
Binary files /dev/null and b/icons/cube.ico differ
diff --git a/icons/cube.rc b/icons/cube.rc
new file mode 100644 (file)
index 0000000..d911fac
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "cube.ico"
diff --git a/icons/cube.sav b/icons/cube.sav
new file mode 100644 (file)
index 0000000..bb123f4
--- /dev/null
@@ -0,0 +1,22 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :4:Cube
+PARAMS  :4:c4x4
+CPARAMS :4:c4x4
+SEED    :15:125146248070163
+DESC    :6:C461,3
+NSTATES :2:14
+STATEPOS:1:5
+MOVE    :1:D
+MOVE    :1:D
+MOVE    :1:D
+MOVE    :1:U
+MOVE    :1:L
+MOVE    :1:L
+MOVE    :1:D
+MOVE    :1:U
+MOVE    :1:D
+MOVE    :1:U
+MOVE    :1:U
+MOVE    :1:U
+MOVE    :1:L
diff --git a/icons/dominosa-16d24.png b/icons/dominosa-16d24.png
new file mode 100644 (file)
index 0000000..71e478d
Binary files /dev/null and b/icons/dominosa-16d24.png differ
diff --git a/icons/dominosa-16d4.png b/icons/dominosa-16d4.png
new file mode 100644 (file)
index 0000000..dbe1765
Binary files /dev/null and b/icons/dominosa-16d4.png differ
diff --git a/icons/dominosa-16d8.png b/icons/dominosa-16d8.png
new file mode 100644 (file)
index 0000000..71e478d
Binary files /dev/null and b/icons/dominosa-16d8.png differ
diff --git a/icons/dominosa-32d24.png b/icons/dominosa-32d24.png
new file mode 100644 (file)
index 0000000..cbc8c2f
Binary files /dev/null and b/icons/dominosa-32d24.png differ
diff --git a/icons/dominosa-32d4.png b/icons/dominosa-32d4.png
new file mode 100644 (file)
index 0000000..a31dbf3
Binary files /dev/null and b/icons/dominosa-32d4.png differ
diff --git a/icons/dominosa-32d8.png b/icons/dominosa-32d8.png
new file mode 100644 (file)
index 0000000..cbc8c2f
Binary files /dev/null and b/icons/dominosa-32d8.png differ
diff --git a/icons/dominosa-48d24.png b/icons/dominosa-48d24.png
new file mode 100644 (file)
index 0000000..565b320
Binary files /dev/null and b/icons/dominosa-48d24.png differ
diff --git a/icons/dominosa-48d4.png b/icons/dominosa-48d4.png
new file mode 100644 (file)
index 0000000..866ec3c
Binary files /dev/null and b/icons/dominosa-48d4.png differ
diff --git a/icons/dominosa-48d8.png b/icons/dominosa-48d8.png
new file mode 100644 (file)
index 0000000..565b320
Binary files /dev/null and b/icons/dominosa-48d8.png differ
diff --git a/icons/dominosa-base.png b/icons/dominosa-base.png
new file mode 100644 (file)
index 0000000..46f1243
Binary files /dev/null and b/icons/dominosa-base.png differ
diff --git a/icons/dominosa-ibase.png b/icons/dominosa-ibase.png
new file mode 100644 (file)
index 0000000..ba9a81b
Binary files /dev/null and b/icons/dominosa-ibase.png differ
diff --git a/icons/dominosa-ibase4.png b/icons/dominosa-ibase4.png
new file mode 100644 (file)
index 0000000..39615bf
Binary files /dev/null and b/icons/dominosa-ibase4.png differ
diff --git a/icons/dominosa-icon.c b/icons/dominosa-icon.c
new file mode 100644 (file)
index 0000000..cea9566
--- /dev/null
@@ -0,0 +1,891 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"zXxXfXhXjXkXgXxXvXgXhXjXjXfXzXxX",
+"NX..y d p i q ' 2.8 g u d s Z pX",
+"VXk G h   3 7.n A [ ~   + ] 4 0X",
+"BXN 2 7   % w m E , e   & B i wX",
+"cX;X(.`.`._.~.*X3X!.'.^.;.Y e.lX",
+"jXVX%XqXGXmX$XnXCX5XeXqX& V 7 qX",
+"kXnXi.W.CX4X,.sXVXI.T.1X> s.t 8X",
+"lXlXzXzXlXxXvXkXlXgXNX-Xo   2 qX",
+"lXlXzXzXlXjXkXzXlXhXNX-XO   1 qX",
+"jXnXi.Q.CXuXi.sXCXR.W.5X- 9.r 8X",
+"lXSX$XqXBXpX/.jXmX@X,X2Xo f 0 0X",
+"gX{.'.'.eXrXjXtXtXaXdXeXI.n.T.lX",
+"wX: 5 $ A.CXoXjXvX1X<XnXBXwXZXlX",
+"rX# =.n n.hX&.fXZXK.T.CXsXs.pXxX",
+"rXn a t /.VXaXkXxXyXsXzXkXrXjXzX",
+"lXjXgXjXcXlXxXlXkXvXxXlXlXvXzXlX"
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"lXlXjXjXjXjXjXjXjXjXjXjXjXjXjXjXkXhXjXjXjXjXjXjXjXjXjXjXjXhXlXlX",
+"lXkXmXBXMXNXMXMXMXMXMXMXNXNXNXNXvXVXMXNXNXMXMXMXMXMXNXNXMXVXzXlX",
+"jXNX~.c n z n m m m n m n n h v.0XS n n z m m m m m x n l %.xXlX",
+"gXFX&.  @ &             O @   L P.  X O %           & O   ; eXvX",
+"gXDX=.  < /.d   O o +   j.i.  R U.  : T.$.  O o O   f.>.  2 eXvX",
+"gXFX&.    j.V   X X   v ;XV.  U Y.  l @XZ.=   . o   Q q.  < eXvX",
+"gXFX*.  4 F.|   @ + . < O.4.  U I.  % ,.<.O O O +   3.c.o : eXvX",
+"gXDX,.  O                 o   R Q.  X                 .   1 yXcX",
+"kXvX6Xa.u.a.p.i.i.i.i.p.s.s.e.#XkXx.y.s.a.i.u.a.b.v.n.M.k.{.bXkX",
+"lXkXvXFXDXGXDXFXFXFXFXFXDXFXGXmXzXDXFXJXFXDXHXmX6.} O. .[ h.cXlX",
+"lXlXkXfXxXpXlXhXgXgXgXgXlXdXgXjXlXhXgXwXjXdXmX(.  X   .   : eXvX",
+"lXlXjXMX%X_ M.NXjXlXhXFXe.i.FXhXlXzXjX9.2.bXBX`.  X j.5.  , eXvX",
+"lXlXhXFXl.7.-.xXkXjXNX@XM &.BXjXlXjXNXA.Y cXVX'.  8 OXF.1 % rXvX",
+"lXlXkXnX1X .J.NXjXkXvX8Xu.7.nXkXkXcXyX9.t.zXVX`.  . <.h.# * rXvX",
+"lXlXlXkXbXbXmXlXlXlXkXbXAXNXzXlXlXlXlXhXMXjXVX`.  . X O   ; eXvX",
+"lXlXlXlXkXkXkXlXlXlXlXkXhXjXlXlXlXlXlXzXjXhXCX`.  X X o   ; eXvX",
+"lXlXlXlXkXkXkXlXlXlXlXlXlXjXlXlXlXlXlXzXjXhXCX`.  X . .   ; eXvX",
+"lXlXlXkXbXbXmXlXlXlXlXlXjXmXlXlXlXlXlXhXmXjXVX`.  X   +   ; eXvX",
+"lXlXkXnX1X..J.NXjXkXcXiX1.x.BXjXlXzXgXe.t.vXBX`.  o K.%.  : eXvX",
+"lXlXhXFXl.7.-.xXkXlXkXBXa.[ ZXjXlXjXBXA.Y vXVX`.  + 9.J.@ = rXvX",
+"lXlXjXMX%X_ M.NXjXkXvX8X-.r.MXjXkXxXyX6.<.lXBX`.  & O.F.% ; rXvX",
+"lXlXhXfXxXpXlXhXjXxXvXzXiXNXvXxXzXvXcXiXmXcXAX`.  . o X   : rXvX",
+"lXzXDXFXDXGXDXFXmXaXqXeXyXwXqXuXfXqXeXyXwXqXuXiX#.T E ! U r.cXkX",
+"xXsXg.y.i.a.a.t.,XdX<X3X<X1X1X0XaX1X3X1X<X2X6XxXSXAXZXZXSXZXlXlX",
+"vXeX;   .   o   _ FXxXcXAXCXvXvXzXbXnXBXDXbXvXkXhXgXbXcXgXhXlXlX",
+"vXeX< X   2./   ^ AXsXDXC.M.CXhXkXxXuXy.k.vXjXlXkXmXy.K.VXjXlXlX",
+"vXeX>   h -X3.  / ZXcX3XF <.ZXhXlXkXnX~.#.mXkXlXjXAXH.f.HXgXlXlX",
+"vXeX1   m W.l.  / ZXvX=X| X.mXkXkXxXaXZ j.nXkXlXjXBXe.] cXlXlXlX",
+"vXeX;   . . +   Q SXfXmXmXfXxXlXlXzXgX7X5XkXlXlXlXlX7X4XhXzXlXlX",
+"cXuXG c N M m l D.BXjXkXkXzXlXlXlXlXzXnXbXlXlXlXlXlXbXnXzXlXlXlX",
+"lXzXMXMXMXNXMXNXNXkXlXlXlXkXlXlXlXlXlXkXkXlXlXlXlXlXkXkXlXlXlXlX",
+"lXlXjXjXjXjXjXjXjXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlX"
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"lXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlX",
+"lXlXlXlXjXkXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXkXkXlXjXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXjXlXlXlXlX",
+"lXlXlXkXmXmXzXxXzXzXzXzXzXzXzXzXzXzXzXzXxXzXmXnXzXBXzXxXzXzXzXzXzXzXzXzXzXzXzXzXxXzXcXNXlXlXlXlX",
+"lXlXjXmX$Xb y p s a i i i i i i i i i s a t n =XcX{ w p p s a i i i i i i i p d p i t n.NXjXlXlX",
+"lXlXgXFX;.  O                             O   e.>X  .                             X   s xXlXlXlX",
+"lXlXgXFX=.  O & [ V   o X X X X X X O ^ 9 .   q.;X  O   : | C   o X X X o   z ..,     p zXlXlXlX",
+"lXlXgXFX=.  . & /./.  X         o   ( UXR     q.;X  X   !.(.K   .       X   I kXA     i xXlXlXlX",
+"lXlXgXFX*.  #   W U.  o       .   p n.~.]     q.;X    6 hXn.c.            X   /.S     i zXlXlXlX",
+"lXlXgXFX=.  O   s.1X5   .     o   I |.jXU.    q.;X    # &Xt.:XO   .     .   u wX2.    i xXlXlXlX",
+"lXlXgXFX=.  . < r.l.H   o . . . . .   { l     q.;X  O   z g.l   X . . . o   E l.0.=   a zXlXlXlX",
+"lXlXgXFX*.  O                             O   q.;X  X                             X   i zXlXlXlX",
+"lXlXjXNX(.2 % * ; : = & & & & & & & & - = $ 3 {.jXV O * = ; = & & & & # # # % = * $ . 5.NXjXlXlX",
+"lXlXlXkXmXpX7X9X9X9X9X9X9X9X9X9X9X9X9X9X9X7XaXnXcXlX7X9X9X9X9X9X9X9X8XaXfXdXfXfXfXdXjXSXlXlXlXlX",
+"lXlXlXlXjXxXbXbXvXcXbXbXbXbXbXbXbXbXbXcXvXbXxXkXkXlXbXbXvXcXvXbXbXvXVXD.U W W Q Q E R !.mXjXlXlX",
+"lXlXlXlXlXkXkXhXbXMXhXkXkXkXkXkXkXkXjXmXxXjXkXlXlXlXkXjXMXBXlXjXjXcXwX,   .       X   k vXkXlXlX",
+"lXlXlXlXlXlXkXMX<X$XMXkXlXlXlXlXlXkXbX*XyXxXlXlXlXlXlXxX#X'.jXzXjXbX9X= . O . b v O   p zXlXlXlX",
+"lXlXlXlXlXjXNXXXC B J.ZXhXlXlXlXhXAX).  T.ZXhXlXlXlXzXgXc.F o.ZXgXbX9X&     1.XXo.X   p xXlXlXlX",
+"lXlXlXlXlXhXJX2.w.Y.! GXhXlXlXkXvX0X2.J L.SXhXlXlXlXjXmXeXD 3.AXgXbX9X&     5XQ.u.$   i zXlXlXlX",
+"lXlXlXlXlXhXFXj.~ =.%.GXhXlXlXhXZXL.M e ' NXjXlXlXlXzXfX{.| D BXhXbX9X&     $X6.3Xa   p zXlXlXlX",
+"lXlXlXlXlXlXzXhX2.o.wXvXkXlXlXlXzXdX0X3.Y.NXjXlXlXkXxXaX4.X.Q.mXhXbX9X&     x K.O.    i zXlXlXlX",
+"lXlXlXlXlXlXlXxXAXSXvXkXlXlXlXlXlXxXnXZXNXkXlXlXlXlXlXxXVXZXBXkXkXbX9X&   .       X   i zXlXlXlX",
+"lXlXlXlXlXlXlXkXhXhXkXlXlXlXlXlXlXlXkXhXjXlXlXlXlXlXlXlXjXhXjXlXkXbX9X&   . X o o X   i zXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXkXbX9X&   .       X   i zXlXlXlX",
+"lXlXlXlXlXlXlXlXkXkXlXlXlXlXlXlXlXlXkXkXlXlXlXlXlXlXlXlXkXkXlXlXkXbX9X&   . . . . X   i zXlXlXlX",
+"lXlXlXlXlXlXlXjXxXcXjXlXlXlXlXlXlXlXbXvXkXlXlXlXlXlXlXlXvXbXkXlXkXbX9X&   .       X   i zXlXlXlX",
+"lXlXlXlXlXlXkXnXdXuXMXkXlXlXlXlXlXzX6XwXMXkXlXlXlXlXlXxXqX6XmXlXkXbX9X&     # q 5 .   i zXlXlXlX",
+"lXlXlXlXlXkXnX1XP Z [.BXjXlXlXkXcXwX6.B m.ZXhXlXlXlXzXhXa.G 5.CXhXbX9X&     V.$X#.    i zXlXlXlX",
+"lXlXlXlXlXhXHX5.,.B./ GXhXlXlXlXjXnX$Xl v.SXhXlXlXlXkXbX7XH ;.SXgXbX9X&     m.OXo.    i zXlXlXlX",
+"lXlXlXlXlXhXGXr.o.p.[ GXhXlXlXlXlXgXoXS &.FXhXlXlXlXlXkX-X' P VXhXbX9X&   o r J gX1   p zXlXlXlX",
+"lXlXlXlXlXkXvXeX` U -XmXjXlXlXkXnX,X+.~ I.BXjXlXlXkXcXuX:.^ l.MXhXbX9X*     1.).q.    p xXlXlXlX",
+"lXlXlXlXlXlXkXvXnXbXnXkXlXlXlXkXkXzXfXlXNXjXkXkXlXkXkXzXhXhXBXkXjXvX9X& . O o #   +   i zXlXlXlX",
+"lXlXlXkXgXhXhXfXfXgXfXhXjXlXxXnXnXbXmXbXvXnXnXcXzXnXnXnXnXnXvXnXbXnXdXp   X       o   K MXkXlXlX",
+"lXlXkXcXDXSXSXSXSXSXSXDXNXzXuX:X<X,X,X,X,X<X:XrXgX>X<X<X,X,X,X<X>X7XmX,Xv.n.n.n.n.b.M.wXvXkXlXlX",
+"lXkXvXwXN.C.C.V.V.C.C.m.[.mXyX;X>X>X>X>X>X>X-XrXgX;X>X>X>X>X>X>X;X7XzXbXFXDXDXDXDXDXDXcXkXlXlXlX",
+"lXjXBX X  .         .     `.ZXvXnXnXbXvXnXnXmXcXzXnXnXnXbXbXvXnXnXvXlXkXgXgXgXfXgXgXgXkXlXlXlXlX",
+"lXjXBX.X  + o O X   o #   V.SXfXkXjXxXNXlXkXkXkXlXkXkXlXcXxXNXjXkXkXlXlXlXlXcXbXxXlXlXlXlXlXlXlX",
+"lXjXBX X  . o   D 3X0     C.SXhXjXmX5Xj `.BXjXlXlXkXxXdX;.K S.NXjXlXlXlXjXNXC.x %XMXjXlXlXlXlXlX",
+"lXjXBX X  X   $ l.rXg     V.SXgXkXbXi.t E.SXhXlXlXlXzXpXjXu.H AXjXlXlXlXkXzXuXM ~.CXjXlXlXlXlXlX",
+"lXjXBX X  +   <.~.7X..    V.SXdXZXY.L M <.VXjXlXlXlXjXNXOXP ,XmXjXlXlXlXlXjXHXP '.AXjXlXlXlXlXlX",
+"lXjXBX X  o   l =.tX}     C.SXfXbX,XM.z 5.BXjXlXlXkXzXjXf h h.cXkXlXlXlXjXNXD.6 ) dXxXkXlXlXlXlX",
+"lXjXBX.X  + O .   : O #   V.SXhXkXbXJXgXcXxXlXlXlXlXlXjXrX3X4XzXlXlXlXlXlXxX0X6X4XhXzXlXlXlXlXlX",
+"lXjXBX X  .           X   T.ZXhXlXkXgXzXlXlXlXlXlXlXlXlXvXnXbXlXlXlXlXlXlXkXvXnXnXzXlXlXlXlXlXlX",
+"lXkXnX3X$.=.=.*.*.=.-.@.v.zXlXlXlXlXlXkXlXlXlXlXlXlXlXlXkXkXkXlXlXlXlXlXlXlXkXkXkXlXlXlXlXlXlXlX",
+"lXlXkXcXGXFXFXFXFXFXFXGXAXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlX",
+"lXlXlXkXgXgXgXgXgXgXgXgXhXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlX"
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/dominosa-web.png b/icons/dominosa-web.png
new file mode 100644 (file)
index 0000000..00fee90
Binary files /dev/null and b/icons/dominosa-web.png differ
diff --git a/icons/dominosa.ico b/icons/dominosa.ico
new file mode 100644 (file)
index 0000000..3cc7d74
Binary files /dev/null and b/icons/dominosa.ico differ
diff --git a/icons/dominosa.rc b/icons/dominosa.rc
new file mode 100644 (file)
index 0000000..fda910b
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "dominosa.ico"
diff --git a/icons/dominosa.sav b/icons/dominosa.sav
new file mode 100644 (file)
index 0000000..5991f3e
--- /dev/null
@@ -0,0 +1,53 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :8:Dominosa
+PARAMS  :1:6
+CPARAMS :1:6
+DESC    :56:55521461210004364611033535444421636022603153156422620503
+NSTATES :2:46
+STATEPOS:2:33
+MOVE    :6:D18,19
+MOVE    :6:E22,23
+MOVE    :6:E22,30
+MOVE    :5:E9,17
+MOVE    :6:D38,46
+MOVE    :6:E14,15
+MOVE    :5:E6,14
+MOVE    :6:E33,34
+MOVE    :6:E34,42
+MOVE    :6:E26,34
+MOVE    :6:D34,35
+MOVE    :6:E42,50
+MOVE    :6:E16,24
+MOVE    :4:D4,5
+MOVE    :4:D6,7
+MOVE    :6:D15,23
+MOVE    :6:E17,25
+MOVE    :6:D16,17
+MOVE    :6:E11,12
+MOVE    :6:D51,52
+MOVE    :5:E3,11
+MOVE    :6:D10,11
+MOVE    :4:D2,3
+MOVE    :6:E37,45
+MOVE    :6:D49,50
+MOVE    :6:D40,48
+MOVE    :6:D25,26
+MOVE    :6:D24,32
+MOVE    :6:D33,41
+MOVE    :6:D42,43
+MOVE    :6:D27,28
+MOVE    :6:E21,29
+MOVE    :6:D31,39
+MOVE    :6:D47,55
+MOVE    :6:E13,21
+MOVE    :6:E13,14
+MOVE    :6:D12,13
+MOVE    :6:D20,21
+MOVE    :6:D14,22
+MOVE    :6:D29,30
+MOVE    :6:D36,37
+MOVE    :6:D44,45
+MOVE    :6:D53,54
+MOVE    :4:D0,1
+MOVE    :4:D8,9
diff --git a/icons/fifteen-16d24.png b/icons/fifteen-16d24.png
new file mode 100644 (file)
index 0000000..88d068f
Binary files /dev/null and b/icons/fifteen-16d24.png differ
diff --git a/icons/fifteen-16d4.png b/icons/fifteen-16d4.png
new file mode 100644 (file)
index 0000000..40a5aaa
Binary files /dev/null and b/icons/fifteen-16d4.png differ
diff --git a/icons/fifteen-16d8.png b/icons/fifteen-16d8.png
new file mode 100644 (file)
index 0000000..88d068f
Binary files /dev/null and b/icons/fifteen-16d8.png differ
diff --git a/icons/fifteen-32d24.png b/icons/fifteen-32d24.png
new file mode 100644 (file)
index 0000000..12eae23
Binary files /dev/null and b/icons/fifteen-32d24.png differ
diff --git a/icons/fifteen-32d4.png b/icons/fifteen-32d4.png
new file mode 100644 (file)
index 0000000..659378d
Binary files /dev/null and b/icons/fifteen-32d4.png differ
diff --git a/icons/fifteen-32d8.png b/icons/fifteen-32d8.png
new file mode 100644 (file)
index 0000000..12eae23
Binary files /dev/null and b/icons/fifteen-32d8.png differ
diff --git a/icons/fifteen-48d24.png b/icons/fifteen-48d24.png
new file mode 100644 (file)
index 0000000..231343f
Binary files /dev/null and b/icons/fifteen-48d24.png differ
diff --git a/icons/fifteen-48d4.png b/icons/fifteen-48d4.png
new file mode 100644 (file)
index 0000000..9b5d632
Binary files /dev/null and b/icons/fifteen-48d4.png differ
diff --git a/icons/fifteen-48d8.png b/icons/fifteen-48d8.png
new file mode 100644 (file)
index 0000000..231343f
Binary files /dev/null and b/icons/fifteen-48d8.png differ
diff --git a/icons/fifteen-base.png b/icons/fifteen-base.png
new file mode 100644 (file)
index 0000000..4685e06
Binary files /dev/null and b/icons/fifteen-base.png differ
diff --git a/icons/fifteen-ibase.png b/icons/fifteen-ibase.png
new file mode 100644 (file)
index 0000000..3de6ce0
Binary files /dev/null and b/icons/fifteen-ibase.png differ
diff --git a/icons/fifteen-ibase4.png b/icons/fifteen-ibase4.png
new file mode 100644 (file)
index 0000000..7be2b9d
Binary files /dev/null and b/icons/fifteen-ibase4.png differ
diff --git a/icons/fifteen-icon.c b/icons/fifteen-icon.c
new file mode 100644 (file)
index 0000000..0297c9e
--- /dev/null
@@ -0,0 +1,891 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"qX0XqXqXqXqXqXqXtXuXyXyXyXyXyXqX",
+"6X2XeXqXqXqXqXeXjXaXpXaXiXdXwX7X",
+"5X1XeXqXqXqX0XrXsX7XuXwXuXqX,X5X",
+"6X2XeXqXqXqXqXrXsXeXW.>XE.qX2X6X",
+"6X<XqX9X9X9X9XwXaXpXs.p.T 3X4X5X",
+"5X7XsXiXiXiXiXsXdXeX(.{.}.7X3X5X",
+"6XyXaXuXuXyXpXtXyX0XpXiXiXrX1X5X",
+"7XeX0XrXwXyXeX4XqX7X5X6X5X8X2X7X",
+"6XwXpXY.{.M.>X9XyXyXrXrXeXuX8X7X",
+"6XqXgXx.v.$.{.rXeX0XtXyXuXqX<X5X",
+"6XwXtX#X X@X0X7XrXtXI.E.z.tX2X5X",
+"6XrXwXyXiXuXeX6XwXdXi.j...<X5X5X",
+"7X4X<X>X>X>X,X,XuXeXoX@X%XwX2X6X",
+"5X,X0X8X8X9X8XwXsX8XyXtXrXwX<X5X",
+"6X0XfXaXaXaXaXfXaX7X8X7X7X9X5X9X",
+"qXtXuXyXyXyXyXyXtXtXtXtXtXtXyXwX"
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX0X9X0X9X9X9X9X9X9X9X9X0X9X9XqXqX",
+"qXqX0XqXqXqXqXqXqXqXqXqXqXqXqXqXeXuXuXuXuXuXuXuXuXuXuXuXuXuXwXqX",
+"wX6X$XeX0XqXqXqXqXqXqXqXqXqXqX0XMXmXvXvXvXvXvXvXvXvXvXvXmXpX8XwX",
+"eX5X#XrX0XqXqXqXqXqXqXqXqXqXqX0XnXqX8X9X9X9X9X9X9X9X9X8XqXOX5XeX",
+"eX5X#XeX0XqXqXqXqXqXqXqXqXqXqX0XnXrXqXwXqXqXqXwXqXqXwXqXrX$X5XeX",
+"eX5X#XeX0XqXqXqXqXqXqXqXqXqXqX0XnXeX0XqXqXqXqXqX0XqXqX0XeX#X5XeX",
+"eX5X#XeX0XqXqXqXqXqXqXqXqXqXqX0XnXeX0XqX0X0XwX0XiXwXqX0XeX#X5XeX",
+"eX5X#XeX0XqXqXqXqXqXqXqXqXqXqX0XnXeX9XaXG.z }.bX| { fX8XeX#X5XeX",
+"eX5X#XeX0XqXqXqXqXqXqXqXqXqXqX0XnXeX0X0XuXK |.'.H ] gX7XrX#X5XeX",
+"eX5X#XrXqXwXwXwXwXwXwXwXwXwXwXqXnXeX0XwX4Xv J.-.n p >XeXeX#X5XeX",
+"wX5X@X0X8X8X8X8X8X8X8X8X8X8X8X7XbXeX9XpXU.) 9.7XP.r.eX0XeX#X5XeX",
+"eX4X=XlXhXhXhXhXhXhXhXhXhXhXhXhXNXwXqX0XuXkXdXeXaXgXqX0XrX#X5XeX",
+"eX2X7XMXfXjXhXhXhXhXhXhXhXhXlXrXhXtX0XqX0X7X9XqX9X8XqX0XeX#X5XeX",
+"eX2X7XsX5X8X8X8X8X8X8X8X8X7XqX&XiXuXqXwXwXwXwXwXwXwXwXwXtX$X5XeX",
+"eX2X7XgX9XwXqX0XqXwX0X0XwXqXtX;XpXqX5X6X6X6X6X6X6X6X6X5X8XXX5XeX",
+"eX2X7XfX8XqXeXsXrXqXdXuX0XqXrX;XeX7X1X2X2X2X2X2X2X2X2X2X2X>X9XwX",
+"eX2X7XfX8XeX4X~.>X9XR.oXrX0XrX-XsXcXhXjXjXjXjXjXjXjXjXjXzX9X6XwX",
+"eX2X7XfX7XtX;Xh t.7X:.j .XuXeX;XpXeX7X8X8X8X8X8X8X8X8X7X0XoX5XeX",
+"eX2X7XfX8X7XmXu.w.DXJ.g XXuXeX;XpXuX0XwXqX0XqXqX0X0XwXqXrX$X5XeX",
+"eX2X7XfX7XeX6XG Q |.0.a E.aXwX;XpXyX0X0XtXaXqXrXdXpXqXqXeX#X5XeX",
+"eX2X7XfX7XtX;Xb.B..Xn.Q.rX0XrX;XpXyX9XrX*X/.9X1XQ._.wX0XeX#X5XeX",
+"eX2X7XfX8XqXrXhXgXuXjXaXqXqXrX;XpXyX8XiXI.7 @Xk.w x.uX9XeX#X5XeX",
+"eX2X7XfX8XqX0X8X8X0X8X9XqX0XrX-XpXyX0X8XvX^ XX`.@.' 6XwXeX#X5XeX",
+"eX2X7XhXqXrXeXeXeXeXeXeXeXeXuX:XpXyX9XyX{.e y._.9.g 1XeXeX#X5XeX",
+"eX2X8XyX:X1X1X1X1X1X1X1X1X<X4X+XpXyX9XyX].z.K.XXg.+XyX9XrX#X5XeX",
+"eX5X=X<X%X&X&X&X&X&X&X&X&X&X*X%XzXrX0X0XiXjXdXuXkXuX0XqXeX#X5XeX",
+"eX5X@XyXrXrXrXrXrXrXrXrXrXrXrXrXmXeX0XqX9X8X9X0X8X9XqX0XeX#X5XeX",
+"eX5X$XrX0XqXqXqXqXqXqXqXqXqXqX0XnXyXeXrXrXrXrXrXrXrXrXeXuX%X5XeX",
+"eX5X+XqX8X9X9X9X9X9X9X9X9X9X9X8XcX1X>X>X>X>X>X>X>X>X>X>X<X|.5XeX",
+"eX5X3XMXvXvXvXvXvXvXvXvXvXvXvXbXmXrXeXrXrXrXrXrXrXrXrXrXrXrXqXqX",
+"qX0XrXuXuXuXuXuXuXuXuXuXuXuXuXuXyXaXaXaXaXaXaXaXaXaXaXaXaXpXwXqX",
+"qXqX0X9X0X9X9X9X9X9X9X9X9X9X9X0X0X9X9X9X9X9X9X9X9X9X9X9X9X9XqXqX"
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXqXqXqXqXqX",
+"qXqXqXwXeXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX9X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X7X8XqXqXqXqX",
+"qXqXwX6X3XwXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX0XsXlXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXlXgXqXqXqXqX",
+"qX0XtX*XoXtX0XqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX0XSXVXnXmXmXmXmXmXmXmXmXmXmXmXmXmXmXmXNX9X8XwXqXqX",
+"qX0XrX-X+XtX0XqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX0XAXwX6X7X7X7X7X7X7X7X7X7X7X7X7X7X7X8X5X_.0XqXqXqX",
+"qX0XrX=XOXtX0XqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX0XAXuXqXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwX0X{.0XqXqXqX",
+"qX0XrX=XOXtX0XqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX0XAXyX0XqXqXqXqXqXqXqXqXqXqXqXqXqXqXwX9X[.0XqXqXqX",
+"qX0XrX=XOXtX0XqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX0XAXyX0XqXqXqXqX0XqXqXqXqXqXqXqXqXqXwX9X[.0XqXqXqX",
+"qX0XrX=XOXtX0XqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX0XAXyX0XqXqXqXqXeXwXqXqX0XqXwXqXqXqXwX9X[.0XqXqXqX",
+"qX0XrX=XOXtX0XqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX0XAXyX0XqXqXqXeX7X9XqX0XrXwX8XqXqXqXwX9X[.0XqXqXqX",
+"qX0XrX=XOXtX0XqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX0XAXyX0XqX0XuX,.- :.sXrX*X3 } dX9XqXwX9X[.0XqXqXqX",
+"qX0XrX=XOXtX0XqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX0XAXyX0XqXqX0X=XZ ( gXiX[ ; T fX9XqXwX9X[.0XqXqXqX",
+"qX0XrX=XOXtX0XqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX0XAXyX0XqXqX8XbXQ ^ VXd.;.:.H xX8XqXwX9X[.0XqXqXqX",
+"qX0XrX=XOXtX0XqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX0XAXyX0XqXqX9XfXI ! MXI p e $ T.pX9XwX9X[.0XqXqXqX",
+"qX0XtX=X+XyXqXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXqXAXyX0XqX9XpX*., , <.=X!.T M 4XwX0XwX9X[.0XqXqXqX",
+"qX0XrX-XoXqX7X7X7X7X7X7X7X7X7X7X7X7X7X7X7X7X7X6XAXyX0XqXqXwX;X-X=X;XiXhX9X6XuX0XqXwX9X[.0XqXqXqX",
+"qX0XtX*X*XbXlXzXzXzXzXzXzXzXzXzXzXzXzXzXzXzXxXlXFXyX0XqXqXqXtXtXtXtX9X8XwXeX0XqXqXwX9X[.0XqXqXqX",
+"qX0XtX%X8XHXcXnXbXbXbXbXbXbXbXbXbXbXbXbXbXbXMXtXmXiX9XqXqXqX0X0X0X0XqXqX0X0XqXqXqXwX9X[.0XqXqXqX",
+"qX0XtX$X9XzX3X8X7X7X7X7X7X7X7X7X7X7X7X7X7X6XqX].zXpX9XqXqXqXqXqXqXqXqXqXqXqXqXqXqXwX9X[.0XqXqXqX",
+"qX0XtX$X9XbX7XwXwXwXwXwXwXwXwXwXwXwXwXwXwXqXtX.XxXiX9X0X0X0X0X0X0X0X0X0X0X0X0X0X0XqX8X[.0XqXqXqX",
+"qX0XtX$X9XvX7XwXqXqXqXqXqXqXqXqXqXqXqXqXqX0XtX XxXfXrXyXyXyXyXyXyXyXyXyXyXyXyXyXyXyXrX}.0XqXqXqX",
+"qX0XtX$X9XvX7XwXqXqXqX0X0XqXqX0X0XqXqXqXqX0XtX XjX:X.XOXOXOXOXOXOXOXOXOXOXOXOXOXOXOXXX(.qXqXqXqX",
+"qX0XtX$X9XvX7XwXqXqXqXrXeXqXqXeXtXqX0XqXqX0XrX.XdXgXiXpXpXpXpXpXpXpXpXpXpXpXpXpXpXiXaXtX0XqXqXqX",
+"qX0XtX$X9XvX7XwXqXqXqX2X3XqXwX3X-XrXyX0XqX0XtX XcXbXhXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkX-X8XwXqXqX",
+"qX0XtX$X9XvX7XwX9XaXD.5 v 0X,XU f v OXyX0X0XtX XxXyX6X8X8X8X8X8X8X8X8X8X8X8X8X8X7X8X5X`.0XqXqXqX",
+"qX0XtX$X9XvX7XwX0XqX7X0.e 8X6X%XT.$ I.sX9X0XtX XxXpX0XwXqXqXqXqXqXqXqXqXqXqXqXqXqXwX9X{.0XqXqXqX",
+"qX0XtX$X9XvX7XwXqX8XzXM.e 7XkXE.w d *XtX0X0XtX XxXpX9XqXqXqXqXqXqXqXqXqXqXqXqXqXqXwX9X[.0XqXqXqX",
+"qX0XtX$X9XvX7XwXqX9XaXd.9 4X>X.X X% i.jX8X0XtX XxXpX9XqXqXqX0X8X0XqX0X9X8X9XqXqXqXwX9X[.0XqXqXqX",
+"qX0XtX$X9XvX7XwX9XsXZ.0 3 P /.n j G #XtX0X0XtX XxXpX9XqXqX0XuXgXuX0XrXfXjXdXqXqXqXwX9X[.0XqXqXqX",
+"qX0XtX$X9XvX7XwXqXwX6X<X4X,XwX6X>XuXyX0XqX0XtX XxXpX9XqX0XeXXXF..XyX>XJ.b.P.wXqXqXwX9X[.0XqXqXqX",
+"qX0XtX$X9XvX7XwXqXqXwXrXeXrXqXeXrX0X0XqXqX0XtX XxXpX9XqX0XyX<.X [ mXV., Q &.wXqXqXwX9X[.0XqXqXqX",
+"qX0XtX$X9XvX7XwXqXqXqX0X0X0XqXqX0XqXqXqXqX0XtX XxXpX9XqXqX8XgXQ ! NXN.1 { I.uX0XqXwX9X[.0XqXqXqX",
+"qX0XtX$X9XvX7XwXqXqXqXqXqXqXqXqXqXqXqXqXqX0XtX XxXpX9XqXqX8XnX! ] BX@XL.<.@ OXuX9XwX9X[.0XqXqXqX",
+"qX0XtX$X9XvX6XqX0X0X0X0X0X0X0X0X0X0X0X0X0X0XrX|.xXpX9XqX0XwXOXv N ;XY.K.s.O oXiX9XwX9X[.0XqXqXqX",
+"qX0XtX%X9XnXqXyXtXtXtXtXtXtXtXtXtXtXtXtXtXrXpXXXxXpX9XqX9XpX<.V M w.(.H Z N.uX0XqXwX9X[.0XqXqXqX",
+"qX0XtX%X9XyX X@X+X+X+X+X+X+X+X+X+X+X+X+X+X+X#X/.cXpX9XqXqX0XsXhXjXpXuXjXjXsXqXqXqXwX9X[.0XqXqXqX",
+"qX0XtX=X$X3X&X=X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X=XCXuX0XqXqXqX9X8X8X9X0X8X8X9XqXqXqXwX9X[.0XqXqXqX",
+"qX0XrX-XoXuXrXtXtXtXtXtXtXtXtXtXtXtXtXtXtXtXtXeXSXyX0XqXqXqXqXqXqXqXqXqXqXqXqXqXqXwX9X[.0XqXqXqX",
+"qX0XtX=XOXtX0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0XqX9XAXyX0XqXqXqXqXqXqXqXqXqXqXqXqXqXqXwX9X[.0XqXqXqX",
+"qX0XrX=XOXtX0XqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX0XAXyX9XqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX9X[.0XqXqXqX",
+"qX0XrX-X+XrX0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0XqX9XAXiXqXeXwXwXwXwXwXwXwXwXwXwXwXwXwXeX0X[.0XqXqXqX",
+"qX0XtX=XoXuXwXeXeXeXeXeXeXeXeXeXeXeXeXeXeXeXeXeXmX#X}. X X X X X X X X X X X X X X X}.[.qXqXqXqX",
+"qXqXrX:XwXHXAXSXSXSXSXSXSXSXSXSXSXSXSXSXSXSXSXDXAXmXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXMXNXmXqXqXqXqX",
+"qXqXqXqXrXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXeXrXrXrXrXrXrXrXrXrXrXrXrXrXrXrXrXrXrXeXqXqXqXqX",
+"qXqXqXqX0XqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0XqXqXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX"
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/fifteen-web.png b/icons/fifteen-web.png
new file mode 100644 (file)
index 0000000..49306d5
Binary files /dev/null and b/icons/fifteen-web.png differ
diff --git a/icons/fifteen.ico b/icons/fifteen.ico
new file mode 100644 (file)
index 0000000..2b7733e
Binary files /dev/null and b/icons/fifteen.ico differ
diff --git a/icons/fifteen.rc b/icons/fifteen.rc
new file mode 100644 (file)
index 0000000..2e92829
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "fifteen.ico"
diff --git a/icons/fifteen.sav b/icons/fifteen.sav
new file mode 100644 (file)
index 0000000..d81345a
--- /dev/null
@@ -0,0 +1,74 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :7:Fifteen
+PARAMS  :3:4x4
+CPARAMS :3:4x4
+SEED    :15:307905346810973
+DESC    :37:8,11,3,6,14,13,4,2,0,9,12,10,5,1,7,15
+NSTATES :2:66
+STATEPOS:2:47
+MOVE    :4:M1,2
+MOVE    :4:M1,3
+MOVE    :4:M0,3
+MOVE    :4:M0,1
+MOVE    :4:M1,1
+MOVE    :4:M1,2
+MOVE    :4:M0,2
+MOVE    :4:M0,0
+MOVE    :4:M1,0
+MOVE    :4:M1,1
+MOVE    :4:M0,1
+MOVE    :4:M0,0
+MOVE    :4:M1,0
+MOVE    :4:M3,0
+MOVE    :4:M3,1
+MOVE    :4:M1,1
+MOVE    :4:M1,0
+MOVE    :4:M3,0
+MOVE    :4:M3,1
+MOVE    :4:M1,1
+MOVE    :4:M1,0
+MOVE    :4:M2,0
+MOVE    :4:M2,1
+MOVE    :4:M1,1
+MOVE    :4:M1,3
+MOVE    :4:M0,3
+MOVE    :4:M0,1
+MOVE    :4:M1,1
+MOVE    :4:M1,2
+MOVE    :4:M0,2
+MOVE    :4:M0,1
+MOVE    :4:M2,1
+MOVE    :4:M3,1
+MOVE    :4:M3,2
+MOVE    :4:M2,2
+MOVE    :4:M2,3
+MOVE    :4:M3,3
+MOVE    :4:M3,1
+MOVE    :4:M2,1
+MOVE    :4:M2,2
+MOVE    :4:M1,2
+MOVE    :4:M1,3
+MOVE    :4:M2,3
+MOVE    :4:M2,2
+MOVE    :4:M1,2
+MOVE    :4:M0,2
+MOVE    :4:M0,3
+MOVE    :4:M1,3
+MOVE    :4:M1,2
+MOVE    :4:M2,2
+MOVE    :4:M2,3
+MOVE    :4:M0,3
+MOVE    :4:M0,2
+MOVE    :4:M1,2
+MOVE    :4:M2,2
+MOVE    :4:M2,3
+MOVE    :4:M1,3
+MOVE    :4:M1,2
+MOVE    :4:M3,2
+MOVE    :4:M3,3
+MOVE    :4:M1,3
+MOVE    :4:M1,2
+MOVE    :4:M2,2
+MOVE    :4:M2,3
+MOVE    :4:M3,3
diff --git a/icons/filling-16d24.png b/icons/filling-16d24.png
new file mode 100644 (file)
index 0000000..cb429bf
Binary files /dev/null and b/icons/filling-16d24.png differ
diff --git a/icons/filling-16d4.png b/icons/filling-16d4.png
new file mode 100644 (file)
index 0000000..d452613
Binary files /dev/null and b/icons/filling-16d4.png differ
diff --git a/icons/filling-16d8.png b/icons/filling-16d8.png
new file mode 100644 (file)
index 0000000..5627a09
Binary files /dev/null and b/icons/filling-16d8.png differ
diff --git a/icons/filling-32d24.png b/icons/filling-32d24.png
new file mode 100644 (file)
index 0000000..e495387
Binary files /dev/null and b/icons/filling-32d24.png differ
diff --git a/icons/filling-32d4.png b/icons/filling-32d4.png
new file mode 100644 (file)
index 0000000..1b41d00
Binary files /dev/null and b/icons/filling-32d4.png differ
diff --git a/icons/filling-32d8.png b/icons/filling-32d8.png
new file mode 100644 (file)
index 0000000..89feec4
Binary files /dev/null and b/icons/filling-32d8.png differ
diff --git a/icons/filling-48d24.png b/icons/filling-48d24.png
new file mode 100644 (file)
index 0000000..34b4e36
Binary files /dev/null and b/icons/filling-48d24.png differ
diff --git a/icons/filling-48d4.png b/icons/filling-48d4.png
new file mode 100644 (file)
index 0000000..4564da7
Binary files /dev/null and b/icons/filling-48d4.png differ
diff --git a/icons/filling-48d8.png b/icons/filling-48d8.png
new file mode 100644 (file)
index 0000000..5d30e31
Binary files /dev/null and b/icons/filling-48d8.png differ
diff --git a/icons/filling-base.png b/icons/filling-base.png
new file mode 100644 (file)
index 0000000..e5ee0ed
Binary files /dev/null and b/icons/filling-base.png differ
diff --git a/icons/filling-ibase.png b/icons/filling-ibase.png
new file mode 100644 (file)
index 0000000..79091a9
Binary files /dev/null and b/icons/filling-ibase.png differ
diff --git a/icons/filling-ibase4.png b/icons/filling-ibase4.png
new file mode 100644 (file)
index 0000000..e2693b3
Binary files /dev/null and b/icons/filling-ibase4.png differ
diff --git a/icons/filling-icon.c b/icons/filling-icon.c
new file mode 100644 (file)
index 0000000..53019f9
--- /dev/null
@@ -0,0 +1,527 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 256 2 ",
+"   c #E2E2E2",
+".  c #DADADA",
+"X  c #DDDDDD",
+"o  c #DDDDDD",
+"O  c gainsboro",
+"+  c #DDDDDD",
+"@  c #DFDFDF",
+"#  c gray84",
+"$  c gray79",
+"%  c #CBCBCB",
+"&  c #CBCBCB",
+"*  c #CECECE",
+"=  c gray81",
+"-  c #CDCDCD",
+";  c #D7D7D7",
+":  c #E7E7E7",
+">  c #C5C5C5",
+",  c #A5A5A5",
+"<  c #E1E1E1",
+"1  c #D2D2D2",
+"2  c #BCBCBC",
+"3  c gray82",
+"4  c #D7D7D7",
+"5  c gray71",
+"6  c #9F9E9F",
+"7  c #B5B1B5",
+"8  c gray60",
+"9  c #767676",
+"0  c #A2A2A2",
+"q  c #9A9A9A",
+"w  c #868686",
+"e  c gray90",
+"r  c gray75",
+"t  c gray",
+"y  c gray64",
+"u  c #BCBCBC",
+"i  c #DADADA",
+"p  c #E7E7E7",
+"a  c #ECECEC",
+"s  c #DAD8DA",
+"d  c #DDE2DD",
+"f  c #90CE90",
+"g  c #CDD3CD",
+"h  c #ABA9AB",
+"j  c #ACACAC",
+"k  c #B7B7B7",
+"l  c gray63",
+"z  c gray88",
+"x  c #C1C1C1",
+"c  c gray72",
+"v  c gray73",
+"b  c gray78",
+"n  c #D5D5D5",
+"m  c gray90",
+"M  c #EAEAEA",
+"N  c #D4D3D4",
+"B  c #D7D9D7",
+"V  c #A8D2A8",
+"C  c #C5CAC5",
+"Z  c #A3A1A3",
+"A  c #B2B3B2",
+"S  c #B2B2B2",
+"D  c #9D9D9D",
+"F  c gray88",
+"G  c #C3C3C3",
+"H  c gray62",
+"J  c gray91",
+"K  c gray83",
+"L  c #B6B6B6",
+"P  c #CECECE",
+"I  c gray82",
+"U  c #BCBCBC",
+"Y  c #C7C6C7",
+"T  c #E8DCE8",
+"R  c #C3C1C3",
+"E  c gray49",
+"W  c gray62",
+"Q  c gray59",
+"!  c #888888",
+"~  c #E6E6E6",
+"^  c #C1C1C1",
+"/  c gray71",
+"(  c gray77",
+")  c gray79",
+"_  c #D0D0D0",
+"`  c gray89",
+"'  c #E7E7E7",
+"]  c gray82",
+"[  c #D6D8D6",
+"{  c #B0D1B0",
+"}  c #D8DBD8",
+"|  c #D0CFD0",
+" . c #E1E1E1",
+".. c #DFDFDF",
+"X. c #CDCDCD",
+"o. c gray90",
+"O. c gray75",
+"+. c #C3C3C3",
+"@. c #9F9F9F",
+"#. c gray75",
+"$. c #DFDFDF",
+"%. c #E7E7E7",
+"&. c gray93",
+"*. c #D8D7D8",
+"=. c #D8DDD8",
+"-. c #7DC17D",
+";. c #D5DCD5",
+":. c #DEDCDE",
+">. c #F3F4F3",
+",. c gray95",
+"<. c #D5D5D5",
+"1. c #E4E4E4",
+"2. c gray77",
+"3. c #8E8E8E",
+"4. c gray77",
+"5. c gray70",
+"6. c gray68",
+"7. c #D7D7D7",
+"8. c #D8D8D8",
+"9. c #C3C3C3",
+"0. c #CECDCE",
+"q. c #E0DEE0",
+"w. c #CECECE",
+"e. c #A7A7A7",
+"r. c #B9B9B9",
+"t. c #B4B4B4",
+"y. c #AEAEAE",
+"u. c #E6E6E6",
+"i. c gray77",
+"p. c #888888",
+"a. c #A9A9A9",
+"s. c gray64",
+"d. c gray53",
+"f. c gainsboro",
+"g. c gray86",
+"h. c #C6C6C6",
+"j. c gray81",
+"k. c gray84",
+"l. c gray77",
+"z. c #838383",
+"x. c #A9A9A9",
+"c. c gray65",
+"v. c gray53",
+"b. c #E4E4E4",
+"n. c #C0C0C0",
+"m. c #B2B2B2",
+"M. c gray63",
+"N. c gray72",
+"B. c #A5A5A5",
+"V. c #ECECEC",
+"C. c #EFEFEF",
+"Z. c gray86",
+"A. c #D8D8D8",
+"S. c #797979",
+"D. c gray78",
+"F. c #AFAFAF",
+"G. c #A7A7A7",
+"H. c #B2B3B2",
+"J. c #A5A5A5",
+"K. c #DFDFDF",
+"L. c #C3C3C3",
+"P. c #9A9A9A",
+"I. c #B2B2B2",
+"U. c #AEAFAE",
+"Y. c #919091",
+"T. c #D9DAD9",
+"R. c gray86",
+"E. c gray78",
+"W. c #CECECE",
+"Q. c #C1C1C1",
+"!. c gray75",
+"~. c gray58",
+"^. c #B4B4B4",
+"/. c #AFAFAF",
+"(. c #939393",
+"). c #E1E1E1",
+"_. c #C6C6C6",
+"`. c #747474",
+"'. c #A49FA4",
+"]. c #969496",
+"[. c #7B7C7B",
+"{. c #9B989B",
+"}. c #A09BA0",
+"|. c #898989",
+" X c gray57",
+".X c gray67",
+"XX c #898989",
+"oX c #6A6A6A",
+"OX c #A09EA0",
+"+X c #969396",
+"@X c #8B8B8B",
+"#X c #E6E5E6",
+"$X c #C1C2C1",
+"%X c #ADA8AD",
+"&X c #9EC89E",
+"*X c #AFC6AF",
+"=X c #CDC4CD",
+"-X c #B6CAB6",
+";X c #95C195",
+":X c #CAC5CA",
+">X c #C8C9C8",
+",X c #8E8E8E",
+"<X c #B2B3B2",
+"1X c #A7A2A7",
+"2X c #ACCDAC",
+"3X c #A7C5A7",
+"4X c #CDC7CD",
+"5X c #E3E4E3",
+"6X c #C1C2C1",
+"7X c #AAA6AA",
+"8X c #A0C9A0",
+"9X c #B1C7B1",
+"0X c #C8C0C8",
+"qX c #B0C5B0",
+"wX c #95BD95",
+"eX c #C7C2C7",
+"rX c gray78",
+"tX c gray58",
+"yX c gray70",
+"uX c #A29DA2",
+"iX c #ABC9AB",
+"pX c #A7C1A7",
+"aX c #C7C2C7",
+"sX c #E3E4E3",
+"dX c #CACACA",
+"fX c #7D7E7D",
+"gX c #A49EA4",
+"hX c #969396",
+"jX c #929492",
+"kX c #C4C1C4",
+"lX c #CAC5CA",
+"zX c gray64",
+"xX c #8E8E8E",
+"cX c gray65",
+"vX c #888888",
+"bX c #8D8D8D",
+"nX c #CAC7CA",
+"mX c #C1BEC1",
+"MX c #A5A6A5",
+"NX c gray89",
+"BX c gray90",
+"VX c #DAD9DA",
+"CX c #D5D6D5",
+"ZX c #D6D7D6",
+"AX c #DAD9DA",
+"SX c #E0E1E0",
+"DX c #DFE1DF",
+"FX c #DDDCDD",
+"GX c gray84",
+"HX c #D5D5D5",
+"JX c #D7D7D7",
+"KX c #DFDFDF",
+"LX c #E0E1E0",
+"PX c gray88",
+"IX c #DDDCDD",
+"UX c gray90",
+/* pixels */
+"  . X o O + @ # $ % & * = - ; : ",
+"> , < 1 2 3 4 5 6 7 8 9 0 q w e ",
+"r t y u i p a s d f g h j k l z ",
+"x c v b n m M N B V C Z A S D F ",
+"G H J K L P I U Y T R E W Q ! ~ ",
+"^ / ( ) _ ` ' ] [ { } |  ...X.o.",
+"O.+.@.#.$.%.&.*.=.-.;.:.>.,.<.1.",
+"2.3.4.5.6.7.8.9.0.q.w.e.r.t.y.u.",
+"i.p.a.s.d.f.g.h.j.k.l.z.x.c.v.b.",
+"n.m.M.N.B.V.C.Z.A.S.D.F.G.H.J.K.",
+"L.P.I.U.Y.T.R.E.W.Q.!.~.^./.(.).",
+"_.`.'.].[.{.}.|. X.XXXoXOX+X@X#X",
+"$X%X&X*X=X-X;X:X>X,X<X1X2X3X4X5X",
+"6X7X8X9X0XqXwXeXrXtXyXuXiXpXaXsX",
+"dXfXgXhXjXkXlXzXxXcXvXbXnXmXMXNX",
+"BXVXCXZXAXSXDXFXGXHXJXKXLXPXIXUX"
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 69 1 ",
+"  c #2B2B2B",
+". c gray14",
+"X c gray21",
+"o c gray23",
+"O c #444444",
+"+ c #4C4C4C",
+"@ c #545454",
+"# c #5B5B5B",
+"$ c #646464",
+"% c #6C6C6C",
+"& c #747474",
+"* c #7C7C7C",
+"= c #2C962C",
+"- c #2B982B",
+"; c #319A31",
+": c #44A144",
+"> c #55AC55",
+", c #5AA75A",
+"< c #5BAB5B",
+"1 c #64AB64",
+"2 c #6AAD6A",
+"3 c #60B060",
+"4 c #74AF74",
+"5 c #75B975",
+"6 c #7AB27A",
+"7 c #7BBA7B",
+"8 c #848484",
+"9 c #8D8D8D",
+"0 c #949494",
+"q c #9B9B9B",
+"w c #81BD81",
+"e c #8FB78F",
+"r c #96BD96",
+"t c #9CBD9C",
+"y c #A4A4A4",
+"u c #ACACAC",
+"i c #AEB0AE",
+"p c #B5B5B5",
+"a c #B6B8B6",
+"s c #BCBCBC",
+"d c #96C596",
+"f c #99C099",
+"g c #A3C4A3",
+"h c #A7CCA7",
+"j c #A8C3A8",
+"k c #B1C8B1",
+"l c #BBC3BB",
+"z c #BBCABB",
+"x c #C3BEC3",
+"c c #C3C4C3",
+"v c #C5CDC5",
+"b c #CCCCCC",
+"n c #C7D0C7",
+"m c #CDD2CD",
+"M c #D2CCD2",
+"N c #D4D3D4",
+"B c #D5DCD5",
+"V c #DBD6DB",
+"C c #DBDBDB",
+"Z c #DCE3DC",
+"A c #E0D7E0",
+"S c #E1DBE1",
+"D c #E4E4E4",
+"F c #E6EAE6",
+"G c #EAE7EA",
+"H c #EBEBEB",
+"J c #F1EEF1",
+"K c #F3F3F3",
+"L c #FCFCFC",
+/* pixels */
+"DGGDFDGFGFFDDGGHHHHHHHHGGHGGHGDD",
+"DDNDDDDDSCDDDDDbMNNNNNNCCBCCBZFG",
+"Ka#cpppaqyappps$@####@.@@@@#o*KD",
+"Kp%LDKHHbbKFGFHlDHJHHC#NNCNCq&KD",
+"Kp&KJ9aKcbGDDDHaCHhdGB#Nb&uDq&LD",
+"Kp%LaXqLcbHDDGHaCD:3ZC@mC%qD0&KS",
+"Kp%Lp+9KxbHDDDGaCG5>ZC@Mb@&D0&LD",
+"Kp%LKKGKbmKHHGKaDJGFJC#DCbbGy*KZ",
+"Ja$DmNNCpaCNNNNubNNNNbOqqqqy%&KD",
+"Ja#bssscyucsclcqacscca%&&&&*$qJD",
+"Ka&LHKGKbNKHHHKxDJKGHGcLLKKLDvHD",
+"Ka%KH$uLcbGDDDGaCH77GDpDDDDDMsHD",
+"Kp%Lu 9LcbHDFDHaCD:>BDpGGGSJNcHD",
+"Kp%Kc*qKsbHDDDGpCHd7ZDpDSSSSbsHD",
+"Kp*LLLLLNNKGHHKcDJKKHGcKLKJLDcHD",
+"Hso9***8%yvxcccqaccscx&*8848%qKD",
+"KsOq000q@9CmNbNybmNNNcO9000q$&KD",
+"Ka%HMcNH*yLGGGKxSKDbJC#DClNHy&KD",
+"Ka$ClOaD&qKDDDHaCG#&GC@bN@qD0&LD",
+"Ka$CN@yF&qKDDFHaZD++NC#mC$8G0&KD",
+"Ja$Ss&yD&qKDDDGaCHayGN@Mx40Cq&KD",
+"Ka%DNDCD*yLGJHKsDKKLKS#CVDCDq*KD",
+"Jc #@@@#oO$##$#+####$#.#@@@#o$KD",
+"Ka#caxxsyycacas0pascau+sxxxsuuHF",
+"Kp$DVgzCpaCNfmCubCbqCc@VMekAcpHD",
+"Kp$Sl;rAupCt-lCycB%oVl@NM2,AapJD",
+"Ka$Dr;4AppV6-tCybc#Xcc@Nb1<CapJD",
+"Kp$DVvvCpaNNzbVybCVaCb@NbivNlaHD",
+"Ja#cpssxyibbbbNqpsaxsi@bbMbbpuHD",
+"Jvo$#@##O8iyyyi#+####@+uyuyi8&KD",
+"DDGHHKHHGDGHHGHDHHGHHHFGGGGHGDDD",
+"DGDDDDDDGDFDDDDGDDFDDDDFDDDDFGDD"
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 79 1 ",
+"  c #0C0C0C",
+". c #151515",
+"X c #1B1B1B",
+"o c #242424",
+"O c #2D2D2D",
+"+ c #323232",
+"@ c #3D3D3D",
+"# c #424242",
+"$ c #4B4B4B",
+"% c #535353",
+"& c #5D5D5D",
+"* c #646464",
+"= c #6B6B6B",
+"- c #747474",
+"; c #797979",
+": c #0A8A0A",
+"> c #128F12",
+", c #199219",
+"< c #269526",
+"1 c #2C992C",
+"2 c #349C34",
+"3 c #3B9D3B",
+"4 c #3BA13B",
+"5 c #44A344",
+"6 c #4AA44A",
+"7 c #4EA94E",
+"8 c #53A553",
+"9 c #51AA51",
+"0 c #59AE59",
+"q c #66AC66",
+"w c #6DAE6D",
+"e c #6DB56D",
+"r c #73AF73",
+"t c #74B074",
+"y c #76B876",
+"u c #7BB37B",
+"i c #848484",
+"p c #8A8A8A",
+"a c #949494",
+"s c #9B9B9B",
+"d c #85B685",
+"f c #84BE84",
+"g c #8CB98C",
+"h c #90BA90",
+"j c #9FBF9F",
+"k c #A4A4A4",
+"l c #A7A8A7",
+"z c #ABABAB",
+"x c #B4B4B4",
+"c c #B8B7B8",
+"v c #BCBCBC",
+"b c #95C695",
+"n c #9BC89B",
+"m c #A6C1A6",
+"M c #A3CBA3",
+"N c #ABC3AB",
+"B c #AFD0AF",
+"V c #B5C6B5",
+"C c #B9C7B9",
+"Z c #BCC8BC",
+"A c #B4D2B4",
+"S c #C3C3C3",
+"D c #CCCCCC",
+"F c #D1CFD1",
+"G c #D4D4D4",
+"H c #D7D8D7",
+"J c #DAD2DA",
+"K c #DADADA",
+"L c #DBE1DB",
+"P c #E7DDE7",
+"I c #E8DDE8",
+"U c #E5E5E5",
+"Y c #E7E9E7",
+"T c #E9E6E9",
+"R c #ECECEC",
+"E c #F2EBF2",
+"W c #F2F2F2",
+"Q c #FDF6FD",
+"! c #FBFBFB",
+/* pixels */
+"UUTUUUUUUUUUUYUTUUUUUUUUUUUYUUUYUYUYUUYUUUYUYUUY",
+"UUUUUYUYUUYUTUYUUYUYYUYUUYUUTUTUUUUUUTUYTUUUUUTU",
+"YUUURUUUUUUUUUYUUUUUUUURUUUUUUUUURUUUUUUUUYUUTUY",
+"UTYUGRERERRRRYTRRRERERRGURRRRRRRYGRERWRRRWYTYUUU",
+"YURSollkkkkkzpizkklkkkzOX++OOOO+O +++@+++@o-WUUT",
+"UYRSOLHHKGHHUczUHKKKHGLazvccccxZaollkskksx$=WUUU",
+"UUES+RYTU!RTRSvWYYYYYTElUWRWQQE!DOKKHTUHKR==!UUU",
+"YURS+YUTUiFYRZcWUUUUTURlHYUHbMURSOGDD;iGDK*=!UUU",
+"UUWSOYUWa xWRScRUUUTUURkKYE03MRRvOGDG=+GDL*=!UUU",
+"UURS+UEc%#aWRvcEUUYUUURlHRR1e5LWSOGDKv$KDP*=!UUT",
+"UUES+TRS=X-RRScEUUUUTURlHYEe97UWvOGFG*.sGK*=!UYU",
+"UYRS+YUY!DUYRZcWUUUUUURkHYUUBLTRvOGDDkszDU*=!LUU",
+"YUWS+RYYURYTWScWUYYYYTWlKRYYERYWSOLKHPUUGR==!UUU",
+"UURSOKKKKKKKUxxUKKKKKKUsDKKKKKHUcolllkklkx$=WUUY",
+"UURSollklkkkliilkllklkl;slkkklklao++++++++X;WUTU",
+"UUWS+RRRYRRRWDv!YRERRRWzKWRRRRRRUzWRRRRRR!SvWUUU",
+"YURSOUUUYEUURvcRUUUUUUYkGYUTUUUUKkYUUUUUURvcWUUU",
+"UUES+YUED+vRRZvWUUUUUURlKYRA0bTTKkRUUUUUURvvRUUU",
+"UUWSOYUYioxWRScRUUUUUURlHYE55nTTKkYUUUUTURZvWUUY",
+"YURS+YWl@O;WRvcEUUUUUURkHYT2y2LYKkRUUTUUUEvvWUUU",
+"UURS+TYGx$aWRSxWUUUUUURlHYRM7fTUKkRUUUUUURvcWUUU",
+"UURS+YUURRYURcxWPUUUUURlGTURUYUYKkUUUULPPRvcRUYU",
+"UUWS+!!!!!!W!FS!RRRERR!zUWWREREWUz!!!!!!W!GSRUUU",
+"YYRSX=-====--&iScvZvvcSixvcvvvvZx%--==-==;%aWYYU",
+"UURD.*&**&*&=O&DcvvcvcSizSvvvvvSs.**&&*&&=o*WUTU",
+"UUWSOYKUYYUKW-=!RRWRWR!zUWRE!WR!S+RUUYTUK!-=!UUU",
+"TURSODDDzxDSK**!UUUUUURkGYUYvSURZOFDDlxDDK&=!UUY",
+"UURSOGDG%+GDU==!LUUUUUEkGYWi#sYRvOGFF%+GDU*=!UUU",
+"YURSOGDGc#KDP=*!UUUUUURkHYRO%;URSOGDKx#KDL*=!KUT",
+"UURSODDGioxGL=*!UUUUUURlKYW$=+KWvOGDGioxDU*=!UYU",
+"TURSODGD;&aGP*=!UTUUUURlGYRD-xYEvOGGD;&sGP*=!UUY",
+"UURSOGGDKLHDU**!LUUUUURkKYYRWRURvOGDFKLKSK*=!UUU",
+"UURSOHGGGGGFR==!TRRRERWlUWRRYYR!SOKGGFGGGT==!UUU",
+"YURD O+OOOOO+..@+++++++oO+++++++O +O+OOOO+ #EUUU",
+"UURSozllllllxppzllklllz;slllkklzpozllllllzpaRUUU",
+"UUWSOKHKHIKHPxzUHHKTKGUsDKKGUUKLx+KGHPIKHUxxWUUU",
+"YURSOGDFDrCFGzkGDFxtFDGaSGDGiaGGlOGDDurZDGzzWUUY",
+"UUWSOGDJd:jGGzlHFF5<FDKaSFKxX*PGzOGFDg<dHHzzRUUU",
+"TURSOFGm53dJGzlGJr72ZFGaSGD$$%GGzOGDGV1dJGlzWUUY",
+"UURSoGGNq,qJHzlHFh8>mGGaSGS;++DHzOGFCw2uJKzzEUUU",
+"YUWSOGDFPVDDGllHDJGVGDJaSGDKDvGGzOGFDmNGDHlzEUUU",
+"UUESOKKGGJKGUxzKGDFJGFHsDKKGKKGUxOHGGJJGGKzxWUUU",
+"UURSolkkkkkklpsDSSSSSZDisllkkkkliOSSSSSSSDsaWUYU",
+"YURD O++++OO+.$saaaaaasoo+O+OO++oXaaaaaaas$@WUUU",
+"UYUUGRRYRRRYEKKEYYRRRYRGURYRRRRRYGRYYRRYRRKKYUUT",
+"UUUURUUUUUUUUYYUUUUUUUURUUUUUUUUYRUUUUUUUUYYUUUU",
+"UUUYUUUUTUUYYUUYUTUYUUUUUUTUUUTUTUUUUUTUUTUUUYYU",
+"YTUUTUYUYUTUUUUUYUUUTUUYUTUUUYUUUUUYYUUUUUYUUUTU"
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/filling-web.png b/icons/filling-web.png
new file mode 100644 (file)
index 0000000..bf413f9
Binary files /dev/null and b/icons/filling-web.png differ
diff --git a/icons/filling.ico b/icons/filling.ico
new file mode 100644 (file)
index 0000000..9534dfc
Binary files /dev/null and b/icons/filling.ico differ
diff --git a/icons/filling.rc b/icons/filling.rc
new file mode 100644 (file)
index 0000000..095a2ae
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "filling.ico"
diff --git a/icons/filling.sav b/icons/filling.sav
new file mode 100644 (file)
index 0000000..caf0bb2
--- /dev/null
@@ -0,0 +1,38 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :7:Filling
+PARAMS  :3:7x7
+CPARAMS :3:7x7
+SEED    :15:279172739852696
+DESC    :49:0000000031051240010004000001106171000400001013105
+NSTATES :2:30
+STATEPOS:2:13
+MOVE    :4:38_3
+MOVE    :4:39_3
+MOVE    :4:36_4
+MOVE    :4:43_4
+MOVE    :4:35_4
+MOVE    :4:47_5
+MOVE    :4:40_5
+MOVE    :4:34_5
+MOVE    :4:41_5
+MOVE    :4:25_7
+MOVE    :4:23_6
+MOVE    :4:16_6
+MOVE    :4:18_7
+MOVE    :4:19_7
+MOVE    :4:20_7
+MOVE    :4:26_7
+MOVE    :4:24_7
+MOVE    :4:29_6
+MOVE    :4:22_6
+MOVE    :4:15_6
+MOVE    :3:7_4
+MOVE    :3:0_4
+MOVE    :3:1_3
+MOVE    :3:2_3
+MOVE    :3:6_2
+MOVE    :3:5_5
+MOVE    :3:4_5
+MOVE    :3:3_5
+MOVE    :4:10_5
diff --git a/icons/flip-16d24.png b/icons/flip-16d24.png
new file mode 100644 (file)
index 0000000..a0f9366
Binary files /dev/null and b/icons/flip-16d24.png differ
diff --git a/icons/flip-16d4.png b/icons/flip-16d4.png
new file mode 100644 (file)
index 0000000..8f6f09e
Binary files /dev/null and b/icons/flip-16d4.png differ
diff --git a/icons/flip-16d8.png b/icons/flip-16d8.png
new file mode 100644 (file)
index 0000000..a0f9366
Binary files /dev/null and b/icons/flip-16d8.png differ
diff --git a/icons/flip-32d24.png b/icons/flip-32d24.png
new file mode 100644 (file)
index 0000000..b87f342
Binary files /dev/null and b/icons/flip-32d24.png differ
diff --git a/icons/flip-32d4.png b/icons/flip-32d4.png
new file mode 100644 (file)
index 0000000..d5857bf
Binary files /dev/null and b/icons/flip-32d4.png differ
diff --git a/icons/flip-32d8.png b/icons/flip-32d8.png
new file mode 100644 (file)
index 0000000..b87f342
Binary files /dev/null and b/icons/flip-32d8.png differ
diff --git a/icons/flip-48d24.png b/icons/flip-48d24.png
new file mode 100644 (file)
index 0000000..516ad55
Binary files /dev/null and b/icons/flip-48d24.png differ
diff --git a/icons/flip-48d4.png b/icons/flip-48d4.png
new file mode 100644 (file)
index 0000000..401171d
Binary files /dev/null and b/icons/flip-48d4.png differ
diff --git a/icons/flip-48d8.png b/icons/flip-48d8.png
new file mode 100644 (file)
index 0000000..516ad55
Binary files /dev/null and b/icons/flip-48d8.png differ
diff --git a/icons/flip-base.png b/icons/flip-base.png
new file mode 100644 (file)
index 0000000..1b4a61b
Binary files /dev/null and b/icons/flip-base.png differ
diff --git a/icons/flip-ibase.png b/icons/flip-ibase.png
new file mode 100644 (file)
index 0000000..6fd18c0
Binary files /dev/null and b/icons/flip-ibase.png differ
diff --git a/icons/flip-ibase4.png b/icons/flip-ibase4.png
new file mode 100644 (file)
index 0000000..b3d5538
Binary files /dev/null and b/icons/flip-ibase4.png differ
diff --git a/icons/flip-icon.c b/icons/flip-icon.c
new file mode 100644 (file)
index 0000000..1d572aa
--- /dev/null
@@ -0,0 +1,891 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"A.N.N.N.M.Z.L.L.~.Y.F.U.P.I.P.D.",
+"N.] ~ ( Y d.nXiX`.5.7XUXHXJXVXP.",
+"N.~ / O.G g.gX7.E <.DXUXzXKXHXP.",
+"N.( O.e.P n.R.` O.[.FXJX>XAXGXP.",
+"M.Y H Y V 0.%...U.zXVXUXJXUXGXI.",
+"Z.f.s.p.y.l._.jXSX3X0XCXFXFX7XF.",
+"L.cXBXAXPXhXNX,Xd.X.3XlX[.1.5.Y.",
+"L.pXpXrXBXdX:X&.~ d.SXY.o.L _.~.",
+"J.dXMX8XsXcXg.~ &.,XkX+.] 3.aXL.",
+"H.mXIXMXAX0X..g.;XNXQ.[ S.wXdXJ.",
+"C.p.u.s.i.V.0XcXgXiX,XvXJXZXzXL.",
+"M.T J Y M i.AXdXZXCXvXUXHXUXGXI.",
+"N.( O.e.Y s.MX8XyXzXbXLX>XAXHXP.",
+"N.~ / O.J u.LXmXdXhXNXUXxXKXHXP.",
+"N.] ~ ( T p.MXfXsXaXzXJXJXJXVXP.",
+"A.N.N.N.M.C.H.J.L.K.L.I.P.I.P.D."
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"S.S.F.F.F.F.F.F.F.F.F.A.Z.Z.Z.Z.Z.Z.V.C.S.Z.Z.Z.Z.Z.Z.Z.Z.Z.S.S.",
+"S.A.M.M.M.M.M.M.M.M.M.G.P.L.L.K.J.K.W.E.D.I.I.I.I.I.I.I.I.I.D.S.",
+"F.M.( ! ~ ~ ^ ~ ~ / Y |.bXfXfXlXmXkX$X0.J.UXJXKXKXJXKXKXLXFXI.Z.",
+"F.M.! R W E T E W Q L .XBXlXVXuXW.1.U Q 2XUXUXUXUXUXUXUXUXKXI.Z.",
+"F.M.~ W E Q { ~ R ! P  XNXdXb.+.W P K k.zXJXUXUXHXMXKXUXUXKXI.Z.",
+"F.M.~ E Q  .=.o.! Q P }.GX`.L #.=.' T *XjXJXUXLXkXyXZXUXUXKXI.Z.",
+"F.M.^ T { =.D.1.| W P |.ZXq.{ z.z.{ 4.xXpXKXUXbX7XC.CXUXUXKXI.Z.",
+"F.M.~ E ~ o.1.#.^ Q L @X8XE ] <.>.U Q.VXuXLXUXHXfX7XZXUXUXKXI.Z.",
+"F.M.~ W R ! | ^ R ! K *XD.J Y Q ] q.aXxXpXLXUXUXFXbXJXUXUXKXI.Z.",
+"F.M./ Q ! Q W Q ! ! T )...D ..n.<XmXxXkXpXLXUXUXUXUXUXUXUXJXI.Z.",
+"F.M.Y L P P P P P I Y q.:.E.tXmXxXsXfXbXdXHXPXLXLXKXJXJXUXKXI.Z.",
+"A.G.|..X X XXXXX}._.!.'.iXgXtX0XtXgXpX(.U.dXpXpXuXpXjXzX3XK.S.S.",
+"Z.P.bXBXBXNXvXkXcXPXvXrXbXjXmXVXaX/.3.T /.bXkXxXVXcX*Xk.Q 0.E.C.",
+"Z.L.fXlXsXyXsXSXUXIXrXeXlXxX1XM...K S 2.iXfXxXaX!.5.T J U $XW.V.",
+"Z.L.jXjXgXAXcXCXUXNXtXtXmX1X! ( } / K ^.gXsXmXq.U { ` I 1.kXK.Z.",
+"Z.K.jXdXjXfX/.qXVXhXdXwXVXm.( a.u.} ..pXtXxX1X] >.z.[ G Q.mXJ.Z.",
+"Z.L.gXdXVXpX/.tXxXaXhXyXpX..} u.a.( m.VX0XmXn.Q <.x._ ` pXkXK.Z.",
+"Z.P.iXcXUXSXcXCXlXfXgXgX^.K / } ( ! <XmXtXyX..Y ] [ K n.BXfXL.Z.",
+"Z.P.eXHXUXGXgXyXpXlXdXiX2.D K  .n.<XxXjXgX!.G K W w.'.gXxXgXL.Z.",
+"A.H.lXUXnXkXcXNXBXMXAX).Y 2.~.pXVXmXlXnXuX#._ m.<XbXBXzXzXjXL.Z.",
+"S.A.~._.[.XXXX X X XoXF.).iXgXyXwXtXeXwXXXK.0XzXhXiXuXpXaXyXK.Z.",
+"F.M.Y P I P P P P I S oXAXdXgXhXdXyXyXkX5XPXUXJXKXKXLXLXPXGXI.Z.",
+"F.M.^ W ! Q W Q ! ~ I  XMXlXgXaXhXBXUXCXqXPXUXUXUXUXUXUXUXKXI.Z.",
+"F.M.~ W R ! | ^ R ! P  XBXpXlXcXBXUXUXfXyXPXUXUXFXbXJXUXUXKXI.Z.",
+"F.M.~ E ~ o.1.#.^ Q P  XNXyXCXtXqXCXSXiXsXLXUXHXfX7XZXUXUXKXI.Z.",
+"F.M.^ T { =.D.1.| W P XXcXfXcX(./.cXdXgXaXKXUXbX7XC.CXUXUXKXI.Z.",
+"F.M.~ E Q  .=.o.! Q P XXjXDXSXpXfXAXiXzXpXLXUXLXkXyXZXUXUXKXI.Z.",
+"F.M.~ W E Q { ~ R ! I [.vXUXUXVXlXgXdXxXpXLXUXUXHXMXKXUXUXKXI.Z.",
+"F.M.! R W E T E W W P _.PXKXvXfXsXjXxXzXaXLXUXUXUXUXUXUXUXKXI.Z.",
+"F.M.( ! ~ ~ ^ ~ ~ ^ Y ~.cXeXiXgXjXjXgXjXyXGXLXKXKXJXKXKXLXFXI.Z.",
+"S.A.M.M.M.M.M.M.M.M.M.A.G.P.P.L.K.L.L.L.K.I.I.I.I.I.I.I.I.I.D.S.",
+"S.S.F.F.F.F.F.F.F.F.F.S.A.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.S.S."
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.",
+"S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.A.S.D.S.S.S.S.S.S.S.S.S.S.S.S.D.D.D.D.D.D.D.D.D.D.D.D.D.S.S.S.S.",
+"S.S.S.S.F.F.F.F.F.F.F.F.F.F.F.F.F.A.Z.Z.Z.Z.Z.Z.Z.Z.Z.C.V.C.S.C.C.C.C.C.C.C.C.C.C.C.C.C.S.S.S.S.",
+"S.S.S.A.m.n.m.m.m.m.m.m.m.m.m.m.n.H.I.P.P.P.P.P.P.L.K.I.!.T.S.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.D.S.S.S.",
+"S.S.F.m.^ Q ! ! ! ! ! ! ! ! ! ~ Y oXnXgXjXjXjXgXgXxXmXfX X6.S.UXLXLXPXPXPXPXPXPXPXLXPXJXY.C.D.S.",
+"S.S.F.n.Q E W E E W W W E E W Q I OXNXkXzXkXzXMXNXqXL.=.P / =XPXUXUXUXUXUXUXUXUXUXUXUXPXY.C.D.S.",
+"S.S.F.m.! W W W W E T E W W W Q U oXMXjXkXMXlX+Xs.` K Y K z.aXGXUXUXUXUXUXUXUXUXUXIXUXLXY.C.D.S.",
+"S.S.F.m.! E W W W (  .) W W W Q I OXMXjXxX(.,.] ^ E ~ ! R *XpXGXUXUXIXUXDXcXFXUXUXUXUXPXY.C.D.S.",
+"S.S.F.m.! E W W I O.| +.U E W Q I OXmXcX2XP R +.@.) T I 1.cXwXHXUXUXIXUXgXbXhXUXIXUXUXPXY.C.D.S.",
+"S.S.F.m.! W E ( O.2.3.3.O.) E Q I OXnXmXM.) :.5.4.*...K Q.VX0XJXUXUXSXjX5X2XpXUXIXUXUXPXY.C.D.S.",
+"S.S.F.m.! W T  .| 3.'.7.{ X.T ! I oXBXuXo.#.' K.K.' #. .yXbXqXJXUXUXzXmX>X0.5XUXPXUXUXPXY.C.D.S.",
+"S.S.F.m.! W E ) +.3.7.3.+._ E Q I .XFX~.L o.*.8.8.;.` b.NXlXwXJXUXUXAXhX4X;XiXUXIXUXUXPXY.C.D.S.",
+"S.S.F.m.! E W W U O.{ +.U E W Q I oXBX2.P T _ +.O.Q L <XbXzXwXJXUXUXIXUXgXmXhXUXIXUXUXPXY.C.D.S.",
+"S.S.F.m.! E W W E ) X._ E Q W Q P &X3XR Q ~ W ) ] %.R.zXkXxXwXJXUXUXIXUXAXzXDXUXUXUXUXPXY.C.D.S.",
+"S.S.F.m.! W W W W E T E W W W Q P =XB.J R K ^ q.[.gXMXlXkXxXwXHXUXUXUXUXUXUXUXUXUXUXUXLXY.C.D.S.",
+"S.S.F.m.~ Q Q Q Q Q ! Q Q Q Q Q E /.| J @.C.4XmXBXxXlXzXzXxXwXJXUXUXUXUXUXUXUXUXUXUXUXLXY.C.D.S.",
+"S.A.F.n.Y I U I I I I I I I Y Y W r.<.Q.rXcXlXsXsXdXdXaXdXvXrXAXKXHXJXJXJXJXJXHXGXGXIXPXY.C.D.S.",
+"S.S.A.H.oXOXoXOXOXoXoXoX@X@X|.).T.`.eXuX0X6X7X8X7X7XqXiX0XE.G.rXwXwXwXwXwXqX0XwXpXaX-XS.S.S.S.S.",
+"S.D.Z.I.nXNXMXMXMXNXVXNXlXaXgXJXsX5XMXlXxXxXzXxXNXVXpX~.3.Q R.vXxXxXxXzXlXvXVXvX=Xx.( 5.T.C.S.S.",
+"S.S.Z.P.gXkXjXkXzXfXeX9XdXZXUXLX5X8XxXkXkXzXMXcX:Xv.} P D 2.0XdXzXkXkXbXNXuX~.2.E K P |.!.V.S.S.",
+"S.S.Z.P.jXlXzXhXqXyXcXPXUXIXUXxX0XqXxXkXxXsX_.8.Q J T ^ P !.iXaXzXlXzX<Xn...K P Q T *.dXI.C.S.S.",
+"S.S.Z.P.jXzXlX0XFXbXdXxXUXIXUXtXaX9XxXzXsX;.P X.%./ W T } iXqXdXkXMXE.L ` #.X.E ~ K K.MXK.Z.S.S.",
+"S.S.Z.P.jXcXiXgXIXyXCXuXSXUXBXeXhX8XzXMX).P +.O.X.o./ J c.VX7XdXxXhX&.W ;.] @.R E ` 0XxXL.Z.S.S.",
+"S.S.Z.P.jXxXrXjXlX2Xm.>XkXMXfXdXgX8XxXcX7.X.O.M.M.X.%.W ;XNX7XsXBX{.] O.8.K.,.T L d.MXhXP.Z.S.S.",
+"S.S.Z.P.kXfXsXBXjX2Xm.>XzXjXtXxXdX7XNX;XQ %.X.M.M.O.X.7.xXxX8XsXMXw.) +.7.K.,.U E @XMXgXP.Z.S.S.",
+"S.S.Z.P.zXeXmXUXDXuXCXyXPXjXuXvXdX7XVXc.J / o.X.O.@.P ).MXzX7XlX5X^ W _ *.] %.H >.lXzXjXP.Z.S.S.",
+"S.S.Z.I.hXeXIXIXUXcXdXvXHX0XlXxXdXqXiX} T E / %.X.P -.sXzXxX6XvXA.K ~ T o.#._ U (.MXkXjXP.Z.S.S.",
+"S.S.Z.Y.tXkXUXIXUXPXbXuX0XgXzXzXaXiX!.P ^ T J W 6.(.aXxXkXxX0XtX#.R Q P L X.m.1XxXkXzXjXP.Z.S.S.",
+"S.S.Z.U.8XHXUXSXfX0XwXdXzXkXjXkXsX0X2.D L { x.-XxXNXzXkXkXlXuX^.K L E 3.^.iXNXbXkXkXlXjXP.Z.S.S.",
+"S.S.A.F.gXJXhXpXkXMXVXNXMXMXMXMXVXE.W 1.Q.uXVXNXxXzXxXxXxXMXeX=._ c.-XvXVXvXlXzXxXxXcXlXI.Z.S.S.",
+"S.S.S.A.Q.(.}.@X@XoXoXoXOXOXoXoX+XA.E.9XiXwX7X7X8X8X9XqX9X4X{.Z.:XaXpXwX0XqXwXwXwXwXeXqXL.Z.S.S.",
+"S.S.F.m.T Y Y I I I I I I I U Y G +XVXsXaXdXdXdXgXhXaX0X6XuX$XKXIXGXGXHXJXJXJXJXJXHXJXFXY.C.D.S.",
+"S.S.F.m.~ W Q Q Q Q ! Q Q Q Q ! Y oXMXkXzXzXvXxXdXrXyXvXUXCX<XKXUXUXUXUXUXUXUXUXUXUXUXPXY.C.D.S.",
+"S.S.F.m.! W W W W E T E W W W Q U oXMXjXzXlXuXtXgXCXUXUXUXuX8XJXUXUXUXUXUXUXUXUXUXUXUXLXY.C.D.S.",
+"S.S.F.m.! E W W E ) X._ E Q W Q I OXMXkXgX0XkXkXNXIXIXUXZXeXtXHXUXUXIXUXAXzXDXUXUXUXUXPXY.C.D.S.",
+"S.S.F.m.! E W W U O.{ +.U E W Q I OXMXzXqXAXDXzXkXZXUXUXdXsXrXHXUXUXIXUXgXmXhXUXIXUXUXPXY.C.D.S.",
+"S.S.F.m.! W E ) +.3.7.3.+._ E Q I oXNXfXyXvXyX:X>XuXcXLXqXcXwXJXUXUXAXhX4X;XiXUXIXUXUXPXY.C.D.S.",
+"S.S.F.m.! W T  .| 3.'.7.{ X.T ! I oXVXeXcXdXCXm.m.CXdXxXtXbXwXJXUXUXzXmX>X0.5XUXPXUXUXPXY.C.D.S.",
+"S.S.F.m.! W E ( O.2.3.3.O.) E Q I oXNX9XPXbXuX2X2XyXnXyXgXcXwXJXUXUXSXjX5X2XpXUXIXUXUXPXY.C.D.S.",
+"S.S.F.m.! E W W I O.| +.U E W Q I @XkXsXUXUXZXjXlXFXZXqXxXzXwXJXUXUXIXUXgXbXhXUXIXUXUXPXY.C.D.S.",
+"S.S.F.m.! E W W W (  .) W W W Q I @XpXZXUXIXUXVXlXhX9XhXlXxXwXJXUXUXIXUXDXcXFXUXUXUXUXPXY.C.D.S.",
+"S.S.F.m.! W W W W E T E W W W Q Y |.fXUXUXIXBXdXrXiXlXzXkXxXwXHXUXUXUXUXUXUXUXUXUXIXUXLXY.C.D.S.",
+"S.S.F.n.Q E W E E W W W E E W W Y (.HXLXzXrXeXfXxXcXzXlXlXcXeXHXUXUXUXUXUXUXUXUXUXUXUXPXY.C.D.S.",
+"S.S.F.m.^ Q ! ! ! ! ! ! ! ! ! ~ T W.jX8XtXhXzXkXjXjXjXjXjXlXqXFXIXLXPXPXPXPXPXPXPXLXPXJXY.C.D.S.",
+"S.S.S.A.m.n.m.m.m.m.m.m.m.m.m.m.m.A.F.U.Y.I.P.P.P.P.P.P.P.I.L.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.Y.D.S.S.S.",
+"S.S.S.S.F.F.F.F.F.F.F.F.F.F.F.F.F.S.A.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.C.C.C.C.C.C.C.C.C.C.C.C.C.S.S.S.S.",
+"S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.D.D.D.D.D.D.D.D.D.D.D.D.D.S.S.S.S.",
+"S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S."
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/flip-web.png b/icons/flip-web.png
new file mode 100644 (file)
index 0000000..c9836bb
Binary files /dev/null and b/icons/flip-web.png differ
diff --git a/icons/flip.ico b/icons/flip.ico
new file mode 100644 (file)
index 0000000..9b6b1c4
Binary files /dev/null and b/icons/flip.ico differ
diff --git a/icons/flip.rc b/icons/flip.rc
new file mode 100644 (file)
index 0000000..003643c
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "flip.ico"
diff --git a/icons/flip.sav b/icons/flip.sav
new file mode 100644 (file)
index 0000000..82b4c49
--- /dev/null
@@ -0,0 +1,20 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :4:Flip
+PARAMS  :4:5x5c
+CPARAMS :4:5x5c
+SEED    :15:158897339725978
+DESC    :165:c400007100001c4000071000018400043100011c400047100011c400046100010c400047100011c4000471000118400043100011c400047100011c400046100010c000047000011c0000470000118,5c18b48
+NSTATES :2:12
+STATEPOS:1:4
+MOVE    :4:M4,3
+MOVE    :4:M3,0
+MOVE    :4:M2,2
+MOVE    :4:M3,2
+MOVE    :4:M2,3
+MOVE    :4:M0,2
+MOVE    :4:M0,3
+MOVE    :4:M1,4
+MOVE    :4:M0,0
+MOVE    :4:M1,0
+MOVE    :4:M1,1
diff --git a/icons/flood-16d24.png b/icons/flood-16d24.png
new file mode 100644 (file)
index 0000000..63025bd
Binary files /dev/null and b/icons/flood-16d24.png differ
diff --git a/icons/flood-16d4.png b/icons/flood-16d4.png
new file mode 100644 (file)
index 0000000..f95cb62
Binary files /dev/null and b/icons/flood-16d4.png differ
diff --git a/icons/flood-16d8.png b/icons/flood-16d8.png
new file mode 100644 (file)
index 0000000..dc7d4ac
Binary files /dev/null and b/icons/flood-16d8.png differ
diff --git a/icons/flood-32d24.png b/icons/flood-32d24.png
new file mode 100644 (file)
index 0000000..c8ca42d
Binary files /dev/null and b/icons/flood-32d24.png differ
diff --git a/icons/flood-32d4.png b/icons/flood-32d4.png
new file mode 100644 (file)
index 0000000..14fb3b3
Binary files /dev/null and b/icons/flood-32d4.png differ
diff --git a/icons/flood-32d8.png b/icons/flood-32d8.png
new file mode 100644 (file)
index 0000000..ad2d213
Binary files /dev/null and b/icons/flood-32d8.png differ
diff --git a/icons/flood-48d24.png b/icons/flood-48d24.png
new file mode 100644 (file)
index 0000000..4725c48
Binary files /dev/null and b/icons/flood-48d24.png differ
diff --git a/icons/flood-48d4.png b/icons/flood-48d4.png
new file mode 100644 (file)
index 0000000..f0fb909
Binary files /dev/null and b/icons/flood-48d4.png differ
diff --git a/icons/flood-48d8.png b/icons/flood-48d8.png
new file mode 100644 (file)
index 0000000..3d951c3
Binary files /dev/null and b/icons/flood-48d8.png differ
diff --git a/icons/flood-base.png b/icons/flood-base.png
new file mode 100644 (file)
index 0000000..02a6e76
Binary files /dev/null and b/icons/flood-base.png differ
diff --git a/icons/flood-ibase.png b/icons/flood-ibase.png
new file mode 100644 (file)
index 0000000..02a6e76
Binary files /dev/null and b/icons/flood-ibase.png differ
diff --git a/icons/flood-ibase4.png b/icons/flood-ibase4.png
new file mode 100644 (file)
index 0000000..f3ca3a9
Binary files /dev/null and b/icons/flood-ibase4.png differ
diff --git a/icons/flood-icon.c b/icons/flood-icon.c
new file mode 100644 (file)
index 0000000..ca42a1d
--- /dev/null
@@ -0,0 +1,719 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 240 2 ",
+"   c #D5D5D5",
+".  c #D0CFC9",
+"X  c #CFCEC4",
+"o  c #CFCEC5",
+"O  c #CFCEC5",
+"+  c #CFCEC5",
+"@  c #CFCEC5",
+"#  c #CFCEC5",
+"$  c #CFCEC5",
+"%  c #D0CEC4",
+"&  c #C9CCCF",
+"*  c #C3CBD2",
+"=  c #CCCAD2",
+"-  c #D3C3D2",
+";  c #D2C9D2",
+":  c #D5D6D5",
+">  c #5663BC",
+",  c #4153DA",
+"<  c #4657D4",
+"1  c #4557D6",
+"2  c #4657D4",
+"3  c #4958D1",
+"4  c #4857D2",
+"5  c #4758D4",
+"6  c #3B53DA",
+"7  c #9B694A",
+"8  c #E9781D",
+"9  c #739029",
+"0  c #0DDB1D",
+"q  c #47C046",
+"w  c #D8CFD8",
+"e  c #CFCEC5",
+"r  c #4254D5",
+"t  c #2945FF",
+"y  c #2F49FD",
+"u  c #2F49FF",
+"i  c #2C49FF",
+"p  c #1E45FF",
+"a  c #2145FF",
+"s  c #314BFE",
+"d  c #2043FF",
+"f  c #AE6836",
+"g  c #FF8000",
+"h  c #74A005",
+"j  c green",
+"k  c #2DDC29",
+"l  c #D9CBD9",
+"z  c #4657D4",
+"x  c #2F49FF",
+"c  c #364DF7",
+"v  c #334DFF",
+"b  c #4349C8",
+"n  c #8D585A",
+"m  c #7D536A",
+"M  c #3549EB",
+"N  c #2F4BFF",
+"B  c #5E4E92",
+"V  c #885471",
+"C  c #476277",
+"Z  c #0C8870",
+"A  c #3E877B",
+"S  c #D7D2D3",
+"D  c #4557D5",
+"F  c #2F49FF",
+"G  c #334DFA",
+"H  c #2149FF",
+"J  c #504EA9",
+"K  c #FF8E00",
+"L  c #E37F0B",
+"P  c #3447E2",
+"I  c #334DFF",
+"U  c #2B4AFD",
+"Y  c #2349FF",
+"T  c #2D4AFE",
+"R  c #283FFF",
+"E  c #4A5ADE",
+"W  c #D6D5CB",
+"Q  c #4349C8",
+"!  c #544EAE",
+"~  c #654474",
+"^  c #E05300",
+"/  c #C34D0D",
+"(  c #3345E1",
+")  c #324DFF",
+"_  c #364BF7",
+"`  c #354EFF",
+"'  c #4041CB",
+"]  c #522E9F",
+"[  c #65509F",
+"{  c #D4D6D0",
+"}  c #4858D2",
+"|  c #1F45FF",
+" . c #895964",
+".. c #FF8E00",
+"X. c #D95505",
+"o. c #F80000",
+"O. c #E20407",
+"+. c #3445E2",
+"@. c #3350FF",
+"#. c #364CF9",
+"$. c #2454FF",
+"%. c #7B2677",
+"&. c red",
+"*. c #DB2A2D",
+"=. c #CBD9D9",
+"-. c #CFCEC5",
+";. c #4858D2",
+":. c #2146FF",
+">. c #7D536A",
+",. c #F37E00",
+"<. c #C34D0C",
+"1. c #E90000",
+"2. c #D30807",
+"3. c #323CCC",
+"4. c #3045ED",
+"5. c #3642DE",
+"6. c #2948F2",
+"7. c #71246C",
+"8. c #F10000",
+"9. c #C6302F",
+"0. c #CDD8D8",
+"q. c #4557D5",
+"w. c #2E49FF",
+"e. c #3549E9",
+"r. c #2D46EE",
+"t. c #524698",
+"y. c #F07300",
+"u. c #D1650B",
+"i. c #6F0695",
+"p. c #6902B1",
+"a. c #AA0828",
+"s. c #FB0000",
+"d. c #6B5C08",
+"f. c #00F500",
+"g. c #32C62C",
+"h. c #D8CDD8",
+"j. c #4557D5",
+"k. c #2E49FF",
+"l. c #364EFA",
+"z. c #2A4CFF",
+"x. c #564DA9",
+"c. c #FF8400",
+"v. c #E57408",
+"b. c #7E0497",
+"n. c #7800B4",
+"m. c #C00423",
+"M. c #756404",
+"N. c #2DDA29",
+"B. c #CFCEC5",
+"V. c #4559D3",
+"C. c #2D4CFF",
+"Z. c #344DF9",
+"A. c #324CFF",
+"S. c #3949DF",
+"D. c #5B4F9F",
+"F. c #544CA7",
+"G. c #3B35C8",
+"H. c #3A29D4",
+"J. c #31782E",
+"K. c #3BB400",
+"L. c #297544",
+"P. c #1861B0",
+"I. c #41759B",
+"U. c #D7D3D0",
+"Y. c #CFCFC5",
+"T. c #484ED5",
+"R. c #323BFF",
+"E. c #3649FD",
+"W. c #344DFF",
+"Q. c #2F4BFF",
+"!. c #1C3EFF",
+"~. c #2041FF",
+"^. c #3054FE",
+"/. c #3243FF",
+"(. c #0BB63A",
+"). c #198A67",
+"_. c #3824FF",
+"`. c #5155DA",
+"'. c #D6D6CB",
+"]. c #D1CBCC",
+"[. c #348078",
+"{. c #118B6D",
+"}. c #285FB3",
+"|. c #3347FF",
+" X c #3F54CD",
+".X c #818A6A",
+"XX c #737D79",
+"oX c #354CEB",
+"OX c #3044FF",
+"+X c #4E7D37",
+"@X c #71AE00",
+"#X c #3A8132",
+"$X c #0B8579",
+"%X c #3C8B79",
+"&X c #D7D2D3",
+"*X c #D2C5D2",
+"=X c #22D325",
+"-X c #188673",
+";X c #2733FF",
+":X c #5166A5",
+">X c yellow",
+",X c #E3E40A",
+"<X c #3247E1",
+"1X c #1D42FF",
+"2X c #AE5E34",
+"3X c #FF6F00",
+"4X c #749905",
+"5X c #2DDC29",
+"6X c #D2C9D2",
+"7X c #45BF49",
+"8X c #21E81A",
+"9X c #3D897D",
+"0X c #4A52F6",
+"qX c #6573A0",
+"wX c #E1E121",
+"eX c #C7C834",
+"rX c #505FC9",
+"tX c #435BE7",
+"yX c #A3734E",
+"uX c #F08421",
+"iX c #7A992E",
+"pX c #15E326",
+"aX c #4EC74E",
+"sX c #D8CFD8",
+"dX c #D8CFD8",
+"fX c #D9CADA",
+"gX c #D7D2D3",
+"hX c #D6D6CA",
+"jX c #D4D3D0",
+"kX c #CBCBD9",
+"lX c #CDCDD8",
+"zX c #D6D5CD",
+"xX c #D7D5CB",
+"cX c #D0D3D6",
+"vX c #CAD2D9",
+"bX c #D3D0D8",
+"nX c #DACAD9",
+"mX c #D8CFD8",
+"MX c #D5D6D5",
+"NX c white",
+/* pixels */
+"  . X o O + @ # $ % & * = - ; : ",
+". > , < 1 2 3 4 5 6 7 8 9 0 q w ",
+"e r t y u i p a s d f g h j k l ",
+"o z x c v b n m M N B V C Z A S ",
+"O D F G H J K L P I U Y T R E W ",
+"+ 2 i Q ! ~ ^ / ( ) _ ` ' ] [ { ",
+"@ } |  ...X.o.O.+.@.#.$.%.&.*.=.",
+"-.;.:.>.,.<.1.2.3.4.5.6.7.8.9.0.",
+"O q.w.e.r.t.y.u.i.p.a.s.d.f.g.h.",
+"O j.k.l.z.x.c.v.b.n.m.&.M.j N.l ",
+"B.V.C.Z.A.S.D.F.G.H.J.K.L.P.I.U.",
+"Y.T.R.E.W.Q.!.~.^./.(.j )._.`.'.",
+"].[.{.}.|. X.XXXoXOX+X@X#X$X%X&X",
+"*X=Xj -X;X:X>X,X<X1X2X3X4Xj 5Xl ",
+"6X7X8X9X0XqXwXeXrXtXyXuXiXpXaXsX",
+": dXfXgXhXjXkXlXzXxXcXvXbXnXmXMX"
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 173 2 ",
+"   c #3B253C",
+".  c #6E291C",
+"X  c #6F003A",
+"o  c #574600",
+"O  c #645601",
+"+  c #5F1A58",
+"@  c #6F0D4D",
+"#  c #771B55",
+"$  c #603E46",
+"%  c #58007B",
+"&  c #0F6342",
+"*  c #0C7A51",
+"=  c #147263",
+"-  c #146663",
+";  c #5F505D",
+":  c #6A4855",
+">  c #744C55",
+",  c #784D54",
+"<  c #70474C",
+"1  c #5F6858",
+"2  c #737B55",
+"3  c #564165",
+"4  c #684964",
+"5  c #654563",
+"6  c #774E68",
+"7  c #4A7B68",
+"8  c #71636F",
+"9  c #777B68",
+"0  c #B60000",
+"q  c #A70105",
+"w  c #992300",
+"e  c #B62B01",
+"r  c #BE2A00",
+"t  c #C80207",
+"y  c #D30101",
+"u  c #DB0000",
+"i  c #E00000",
+"p  c #FE0101",
+"a  c #F60505",
+"s  c #AF5900",
+"d  c #B55902",
+"f  c #966030",
+"g  c #A56B31",
+"h  c #B27431",
+"j  c #CE6B03",
+"k  c #C56707",
+"l  c #D36B00",
+"z  c #DC6D02",
+"x  c #D16700",
+"c  c #D27100",
+"v  c #DF7300",
+"b  c #E06C00",
+"n  c #E06600",
+"m  c #E97200",
+"M  c #F67E07",
+"N  c #FB7D03",
+"B  c #860146",
+"V  c #A34242",
+"C  c #B54243",
+"Z  c #B77E43",
+"A  c #A87242",
+"S  c #05A204",
+"D  c #02A00C",
+"F  c #328C32",
+"G  c #2FA830",
+"H  c #31A331",
+"J  c #3BB63B",
+"K  c #26A226",
+"L  c #04C302",
+"P  c #03CA01",
+"I  c #09C804",
+"U  c #05C709",
+"Y  c #01D304",
+"T  c #01DD01",
+"R  c #00E401",
+"E  c #06F706",
+"W  c #01FE01",
+"Q  c #118258",
+"!  c #449E44",
+"~  c #42A342",
+"^  c #43B543",
+"/  c #42BB42",
+"(  c #619661",
+")  c #72A772",
+"_  c #A7A804",
+"`  c #BCBC3B",
+"'  c #F78604",
+"]  c #FE8302",
+"[  c #FF8A00",
+"{  c #D3D302",
+"}  c #C5C60A",
+"|  c #FEFE02",
+" . c #F6F708",
+".. c #A2A242",
+"X. c #B7B743",
+"o. c #3A198E",
+"O. c #1E38BB",
+"+. c #2335B0",
+"@. c #441A9A",
+"#. c #6B0093",
+"$. c #6E009A",
+"%. c #64009E",
+"&. c #433D8C",
+"*. c #4115A4",
+"=. c #7F01AF",
+"-. c #7B01BD",
+";. c #444B96",
+":. c #5E6388",
+">. c #414DA4",
+",. c #4752A3",
+"<. c #4853A3",
+"1. c #4151AB",
+"2. c #575DA8",
+"3. c #555EA3",
+"4. c #575FBA",
+"5. c #5A66B5",
+"6. c #5766BA",
+"7. c #2E3FC8",
+"8. c #2C3CC6",
+"9. c #293FD4",
+"0. c #2F3EDE",
+"q. c #243ADB",
+"w. c #1C39F0",
+"e. c #2434E3",
+"r. c #263DE0",
+"t. c #2E3DE0",
+"y. c #2F3DEB",
+"u. c #283BFF",
+"i. c #7E00C6",
+"p. c #2844C6",
+"a. c #2D42DB",
+"s. c #2941D8",
+"d. c #1944FF",
+"f. c #2643E0",
+"g. c #2C45EB",
+"h. c #2446FF",
+"j. c #2D47FF",
+"k. c #2649FF",
+"l. c #2B4AFF",
+"z. c #3149F6",
+"x. c #3443FF",
+"c. c #3840FE",
+"v. c #344DFE",
+"b. c #384DF8",
+"n. c #2652FF",
+"m. c #2C51FE",
+"M. c #3651FE",
+"N. c #3850FC",
+"B. c #3450F6",
+"V. c #8600AB",
+"C. c #8600B7",
+"Z. c #8A00C1",
+"A. c #D6D4C7",
+"S. c #D6D5CC",
+"D. c #D7CFD7",
+"F. c #DAC7DA",
+"G. c #D9CAD9",
+"H. c #C7D0DB",
+"J. c #C9D1DA",
+"K. c #D3D4D3",
+"L. c #D9D6D4",
+"P. c #D4D9D4",
+"I. c #D5D5D9",
+"U. c #DDDDDE",
+"Y. c #E0DED2",
+"T. c #E3DBDE",
+"R. c #DCE2DE",
+"E. c #E1E0D4",
+"W. c #D2D2E4",
+"Q. c #D2DBE4",
+"!. c #E4D3E4",
+"~. c #E1D8E1",
+"^. c #D3E4E4",
+/* pixels */
+"K.K.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.P.K.L.L.L.P.L.P.P.L.K.K.",
+"K.I.S.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.S.J.J.H.J.G.F.F.F.I.K.L.",
+"I.S.:.>.<.,.,.,.<.,.,.,.,.<.,.,.,.,.<.1.3 h g g f F G H K ( ~.K.",
+"I.A.,.h.j.j.x.j.j.j.j.j.j.j.j.j.j.j.j.h.4 ] N ] b P W W W ^ !.K.",
+"I.A.<.j.N.v.v.v.v.v.v.v.v.b.b.b.v.v.b.l.4 N M ] z I W E W ^ !.K.",
+"I.A.<.j.v.v.v.v.v.v.v.v.M.v.v.b.v.v.v.k.4 [ ' [ b P W E W ^ !.K.",
+"I.A.,.j.v.v.v.v.v.v.v.a.7.7.p.p.z.v.v.l.3 x k j d D Y U P ~ ~.K.",
+"I.A.,.j.v.j.v.v.v.v.k.: m z m d 9.v.v.v.g.q.f.r.r.0.t.0.e.2.L.K.",
+"I.A.,.j.v.v.j.v.v.v.k.> [ ] [ l 9.v.v.v.v.v.M.v.v.M.M.M.l.5.Y.K.",
+"I.A.<.j.v.v.v.v.v.b.l.> ] ' N x 9.v.v.v.v.v.v.v.v.v.b.b.h.5.L.D.",
+"I.A.,.j.v.v.v.h.h.l.d.: [ ' [ c 9.v.v.v.v.v.v.v.v.l.n.n.d.6.E.P.",
+"I.A.<.j.b.v.s.: , , : . r e r w 9.v.v.v.v.v.v.v.g.+ # # @ 6 R.K.",
+"I.A.<.j.b.v.7.v [ ] [ e p p p y 9.M.v.v.v.v.v.v.f.t p a p C ^.K.",
+"I.A.<.j.v.M.7.z ] N ' e p p p y 9.v.j.v.v.v.x.v.f.t p a p C ^.S.",
+"I.A.,.j.v.M.p.m [ ] [ e p p p y s.M.v.M.v.M.v.M.f.t p p p C ^.D.",
+"I.A.,.j.z.M.7.d z x c w y y y 0 +.a.9.9.9.9.s.a.O.q i y y V ^.K.",
+"I.A.<.j.v.l.z.9.9.s.q.$ z x z s % $.#.%.X u y u 0 S R Y Y ~ !.S.",
+"I.A.<.j.v.v.v.v.M.M.h.> [ ' [ l $.C.C.Z.B p p p i I W W W ^ ~.K.",
+"I.A.,.j.v.v.v.v.v.v.k.> ] M ] j %.=.=.i.B p a p u I W E W ^ !.K.",
+"I.A.,.j.v.v.v.v.v.v.h.> [ ] [ l #.C.V.-.B p p p i P W E W ^ W.K.",
+"I.A.,.j.v.v.v.v.v.v.l.&.< > , : o.@.@.*.  O O O o & Q * * 7 T.S.",
+"I.A.,.j.v.v.j.v.v.v.v.j.h.k.k.l.m.M.B.x.= W W W T 8.c.x.u.4.Y.K.",
+"I.A.<.j.v.v.j.v.v.v.v.v.v.b.v.v.v.v.v.u.= W E W T p.M.B.l.5.Y.K.",
+"I.A.<.j.M.M.M.v.v.v.v.v.b.v.b.v.v.v.v.x.= W W W T p.M.N.l.5.L.D.",
+"I.S.;.e.f.t.t.v.v.v.v.g.q.r.r.r.v.v.v.x.- R T R S +.y.t.e.2.E.K.",
+"P.D.F P U Y D t.M.z.k.1 { } { _ 9.v.z.l.3 l k j s S Y U P ! !.K.",
+"P.F.H W W W U 0.M.v.u.2 | | | { 9.v.v.k.4 [ ] ] m Y W W W ^ !.K.",
+"P.G.H W E W U t.M.N.d.2 |  .| { s.M.b.l.4 [ ' ] v P W E W ^ !.K.",
+"P.F.H W E W U q.j.j.u.2 | | | { q.j.z.u.4 N N ] n P W E W ^ !.K.",
+"K.D.( J ^ / ! 2.5.5.4.9 ` X.X...3.5.6.6.8 h Z Z A ! / ^ J ) U.K.",
+"K.K.T.!.!.W.!.R.L.Y.Y.U.W.W.W.W.Y.Y.P.L.T.Q.Q.Q.Q.!.!.!.!.~.K.K.",
+"I.K.K.S.K.K.S.K.K.K.I.K.S.K.K.K.K.P.K.P.K.S.K.K.S.P.K.K.K.K.K.K."
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 183 2 ",
+"   c #1E331B",
+".  c #6C1502",
+"X  c #77010F",
+"o  c #5D072B",
+"O  c #6D113B",
+"+  c #70153E",
+"@  c #593734",
+"#  c #6A3930",
+"$  c #531A0D",
+"%  c #214803",
+"&  c #127412",
+"*  c #316900",
+"=  c #347100",
+"-  c #0A7932",
+";  c #0B7731",
+":  c #2A792C",
+">  c #0D5632",
+",  c #7A4300",
+"<  c #54433D",
+"1  c #6D423A",
+"2  c #65423B",
+"3  c #744532",
+"4  c #744639",
+"5  c #654B2C",
+"6  c #6D733C",
+"7  c #626738",
+"8  c #1E2374",
+"9  c #383868",
+"0  c #52007A",
+"q  c #483969",
+"w  c #0A415C",
+"e  c #17536C",
+"r  c #52556A",
+"t  c #9A1D00",
+"y  c #950400",
+"u  c #A70900",
+"i  c #A80401",
+"p  c #B30202",
+"a  c #BE0100",
+"s  c #A21D00",
+"d  c #830123",
+"f  c #DC0000",
+"g  c #D70101",
+"h  c #E10000",
+"j  c #ED0000",
+"k  c #FE0101",
+"l  c #934901",
+"z  c #9D5000",
+"x  c #A75C01",
+"c  c #AA5C02",
+"v  c #B25A03",
+"b  c #B75B02",
+"n  c #A86001",
+"m  c #85582B",
+"M  c #CA6900",
+"N  c #D16701",
+"B  c #D96D00",
+"V  c #DB7400",
+"C  c #E16101",
+"Z  c #EC6E01",
+"A  c #F26700",
+"S  c #FC7E01",
+"D  c #FE7400",
+"F  c #877774",
+"G  c #9A7070",
+"H  c #08AA01",
+"J  c #02AA08",
+"K  c #01A602",
+"L  c #01BA01",
+"P  c #07B500",
+"I  c #2D842D",
+"U  c #01C200",
+"Y  c #00CD00",
+"T  c #00DC01",
+"R  c #01E301",
+"E  c #00EE00",
+"W  c #03F601",
+"Q  c #02FE01",
+"!  c #07FF07",
+"~  c #708E70",
+"^  c #709570",
+"/  c #709A70",
+"(  c #6B956C",
+")  c #9C9B03",
+"_  c #B2B205",
+"`  c #A6A603",
+"'  c #F48301",
+"]  c #FE8401",
+"[  c #FF8B01",
+"{  c #F68B00",
+"}  c #DEDC02",
+"|  c #E0DF00",
+" . c #EDEC01",
+".. c #FDFD02",
+"X. c #888875",
+"o. c #9A8570",
+"O. c #9A9A70",
+"+. c #9C916E",
+"@. c #2A1C8D",
+"#. c #162A98",
+"$. c #2E208D",
+"%. c #302A8D",
+"&. c #223AA7",
+"*. c #2933A8",
+"=. c #243BAA",
+"-. c #2838A8",
+";. c #2535B2",
+":. c #2437BA",
+">. c #2838BB",
+",. c #1A34A9",
+"<. c #5F008F",
+"1. c #6A008D",
+"2. c #60009C",
+"3. c #720191",
+"4. c #6A00A6",
+"5. c #7F00A4",
+"6. c #7F00B1",
+"7. c #7000B7",
+"8. c #3D4683",
+"9. c #374385",
+"0. c #747B86",
+"q. c #787C9A",
+"w. c #767A97",
+"e. c #1724C9",
+"r. c #1E39D5",
+"t. c #1E3BDD",
+"y. c #1E34DD",
+"u. c #2B34D5",
+"i. c #223CDC",
+"p. c #2B39D7",
+"a. c #283CC9",
+"s. c #1239EA",
+"d. c #133AFA",
+"f. c #122CF7",
+"g. c #233BED",
+"h. c #2B35E5",
+"j. c #233EFE",
+"k. c #343BFF",
+"l. c #3133E4",
+"z. c #7501C0",
+"x. c #1E42DC",
+"c. c #2243DD",
+"v. c #1C40FF",
+"b. c #2446FF",
+"n. c #244DFF",
+"m. c #2D49FF",
+"M. c #2943FC",
+"N. c #3149F5",
+"B. c #3544FF",
+"V. c #334CFE",
+"C. c #394DFE",
+"Z. c #3047F1",
+"A. c #3551FF",
+"S. c #3952FF",
+"D. c #254AF7",
+"F. c #8501A7",
+"G. c #8201B4",
+"H. c #8602BB",
+"J. c #8C03C2",
+"K. c #818F81",
+"L. c #ABB2AB",
+"P. c #D2D0C5",
+"I. c #D2D1CC",
+"U. c #C4CDD5",
+"Y. c #CACFD5",
+"T. c #D5C5D5",
+"R. c #D2CFD2",
+"E. c #D4D4D4",
+"W. c #D9D7D5",
+"Q. c #D5D9D5",
+"!. c #D6D6D9",
+"~. c #CED1D2",
+"^. c #E7E6DF",
+"/. c #DFDFEA",
+"(. c #E9DFE9",
+"). c #E1DFE1",
+"_. c #DFE4EA",
+"`. c #E7E6E1",
+"'. c #E8E7E2",
+"]. c #E1E3E9",
+"[. c #E9E0E9",
+"{. c #E1E9E9",
+"}. c #E5E9E7",
+/* pixels */
+"E.E.E.E.!.E.W.E.E.Q.E.E.!.E.!.E.Q.E.E.E.E.E.E.E.E.E.W.E.E.E.!.E.E.E.!.E.E.E.Q.E.E.E.E.E.!.E.E.E.",
+"E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.W.E.!.E.E.E.!.E.E.E.!.E.E.W.E.E.E.E.E.E.!.E.E.E.!.E.W.E.E.Q.E.",
+"E.!.E.W.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.W.W.W.W.W.W.W.Q.Q.Q.E.Q.!.W.E.E.E.",
+"E.E.E.E.I.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.P.~.U.U.U.U.U.Y.T.T.T.T.T.T.E.E.E.E.E.",
+"E.E.!.I.r 9.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.9.< m m m m m 5 : I I I I & K.(.E.W.E.",
+"E.E.!.P.8.b.m.V.m.m.m.m.m.m.m.m.m.m.m.m.m.m.m.V.m.m.m.m.V.v.3 ] S S S ] c T Q Q Q ! W / (.E.E.E.",
+"E.W.!.P.8.m.C.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.S.b.3 ] S S S ] x T Q Q Q ! E / (.~.E.E.",
+"E.E.W.P.8.m.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.b.3 ] S ] S ] c T Q Q Q Q E / (.~.E.E.",
+"!.E.!.P.8.m.V.V.V.V.V.V.V.V.V.V.V.V.N.A.V.V.V.V.V.V.V.V.V.b.3 ] S S S ] x T Q Q Q ! R / (.~.!.E.",
+"E.E.!.P.8.m.V.V.V.V.V.V.V.V.V.V.V.A.S.A.V.S.V.C.V.V.V.V.V.b.3 [ ] ] ' [ n T Q Q Q Q W / (.E.E.W.",
+"!.E.!.P.8.m.V.V.V.V.V.V.V.m.V.V.V.a.:.:.:.:.:.:.N.V.V.V.V.b.@ M b b b M , K L L L L K ~ [.~.E.E.",
+"E.E.!.P.8.V.V.V.m.V.V.V.V.V.V.C.b.@ C N N N V l i.S.V.V.V.V.i.r.r.r.r.r.i.u.u.u.u.p.e.w.'.I.E.E.",
+"W.E.!.P.8.m.V.V.V.V.V.V.V.V.V.C.b.1 [ ] ] ] [ v i.V.V.V.V.V.V.S.V.C.S.V.V.S.S.V.S.S.D.w.^.~.E.E.",
+"E.E.!.P.8.m.V.V.V.V.V.V.V.V.V.C.b.1 ] ' S S ] v t.V.V.V.V.V.V.V.V.V.V.V.V.V.V.N.V.V.g.q.^.~.E.E.",
+"E.E.!.P.8.m.V.V.V.V.V.V.V.V.V.C.b.1 ] S S S ] c t.C.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.S.g.q.`.E.E.E.",
+"E.!.!.P.8.m.V.V.V.V.V.V.V.V.V.C.b.1 ] S ] S ] v t.S.V.V.V.V.V.V.V.V.V.V.V.V.C.C.C.C.g.w.`.E.E.E.",
+"E.E.!.P.8.m.V.V.V.V.S.b.b.b.b.M.v.2 ] ' ' ' ] n t.S.V.V.V.V.V.V.V.V.V.V.V.n.n.n.n.n.v.w.`.E.E.E.",
+"E.E.!.P.8.m.S.V.V.S.a.@ 1 1 1 1 2 $ t t t t s . i.S.V.V.V.V.V.V.V.V.V.S.>.o + O O + O r }.I.E.E.",
+"E.E.!.P.8.m.V.V.V.V.:.N [ ] ] ] { t k k k k k p x.V.V.V.V.V.V.V.V.V.V.A.-.h k k k k k G _.E.E.E.",
+"!.E.!.P.8.m.V.V.V.S.:.N ] S ] S ' t k k k k k p x.S.V.V.V.m.V.V.N.m.V.A.*.f k k k k j G {.E.W.E.",
+"E.E.!.P.8.m.V.V.m.S.:.N ] ] S S ' t k k k k k p x.C.V.V.V.V.V.V.V.V.V.A.*.h k k k k j G }.Q.!.E.",
+"E.E.!.P.8.m.V.V.N.S.:.N ] ] S S ' t k k k k k p x.C.V.m.V.V.N.V.V.V.N.V.*.f k k k k j G {.E.E.E.",
+"E.!.!.P.8.m.V.V.m.S.:.N [ ] ] ] { t k k k k k p x.S.V.S.A.S.V.S.S.S.V.S.*.h k k k k j G {.I.E.E.",
+"E.E.!.P.8.m.V.A.N.S.:.l b v v v c . a p p p a X #.;.;.;.;.;.;.;.;.;.-.>.8 y p p u p u F {.E.E.E.",
+"Q.E.!.P.8.m.V.V.V.V.N.t.t.t.t.i.s.@ V M M M V l 0 3.1.1.1.1.X h g g g h y U R T T R Y ^ [.~.E.E.",
+"E.E.!.P.8.m.V.V.V.V.V.S.S.S.C.S.n.1 ] ] [ ] [ v 4.J.H.H.H.z.d k k k k k u R Q Q Q ! E / [.E.!.E.",
+"!.E.!.P.8.m.V.V.m.V.V.V.V.V.m.C.b.1 ] S ] S ] v 2.H.6.6.G.7.d k k k k k u R Q Q Q Q E / [.I.E.W.",
+"E.W.!.P.8.m.V.V.V.V.V.V.V.V.V.V.b.1 ] S ] S ] v 2.G.6.6.G.7.d k k k k k u R Q Q Q ! E / [.~.E.E.",
+"W.E.!.P.8.m.V.V.V.V.V.V.V.V.V.V.b.1 ] ' ] S ] v 2.H.6.6.H.7.d k k k k k u R ! Q Q ! E / (.~.E.E.",
+"E.E.!.P.8.m.V.V.V.V.V.V.V.V.V.S.b.1 ] S S ] ] v <.G.5.5.F.4.d k k k k k u R Q Q Q Q W / (.~.E.E.",
+"E.E.!.P.8.m.V.V.V.m.V.V.V.V.V.V.A.%.q q q q q 9 $.$.$.$.$.@.  = * * * = % w e e e e w 0.'.E.E.E.",
+"!.E.!.P.8.m.V.V.V.V.V.V.V.V.V.m.A.V.m.m.n.m.m.A.n.A.A.A.A.B.- Q Q Q Q Q J l.C.B.B.B.g.w.`.E.E.E.",
+"E.W.!.P.8.m.V.V.V.V.V.V.V.V.V.V.V.V.N.V.C.N.N.V.V.V.V.V.A.k.- Q Q Q W Q J p.A.N.A.A.g.q.^.E.E.E.",
+"E.E.!.P.8.m.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.k.- Q Q Q Q Q J l.A.A.V.S.g.q.^.E.E.E.",
+"E.E.!.P.8.m.V.N.V.m.N.V.V.V.V.m.V.V.V.N.V.N.V.V.V.V.V.V.A.k.- Q W Q W Q J l.S.N.m.S.s.q.^.E.E.E.",
+"!.E.!.P.8.V.S.V.V.S.S.V.V.V.V.V.V.V.A.S.S.A.A.A.V.V.m.V.A.k.- Q Q Q Q Q J h.A.V.S.S.g.q.^.E.E.W.",
+"E.E.!.I.9 ,.=.=.&.&.&.Z.V.V.V.V.V.>.-.-.-.-.-.-.N.V.V.V.A.B.> P H H H P & #.=.=.=.=.#.0.`.E.E.E.",
+"E.Q.Q.T.: R T R T R K u.A.V.m.S.j.7  .}  .|  .) y.S.m.V.V.b.# A B C C A l U R T T R Y ^ [.~.E.E.",
+"E.!.Q.T.I Q Q Q Q Q L u.V.V.V.S.j.6 .........._ y.S.V.V.V.b.3 [ S ] ] [ x T ! Q Q ! E / [.~.E.E.",
+"W.E.Q.T.I Q Q Q Q Q L u.A.V.V.S.j.7 .........._ y.S.V.V.C.b.3 ] S S S ] x T Q Q Q ! E / [.E.E.E.",
+"E.E.Q.T.I Q Q Q Q Q L u.S.N.V.S.j.6 .........._ y.S.V.V.S.b.3 ] S S ] ] x T Q Q Q Q E / [.~.E.E.",
+"E.E.Q.T.I Q ! ! Q Q L u.S.S.S.S.j.6 .........._ i.S.C.V.S.n.3 ] ] ] ] ] n T ! Q ! ! E / [.E.E.E.",
+"Q.E.Q.T.: Q E E R Q P e.M.g.g.g.f.7 .. . . ...` e.M.g.g.g.d.# D Z Z Z S z Y Q R E W T ^ ].E.E.E.",
+"E.E.E.R.K.( / / / / ~ w.w.q.q.q.w.X.O.O.O.O.O.X.w.w.q.q.q.w.F o.o.o.o.o.F ^ / / / / ( L.).E.E.E.",
+"W.E.E.E.].(.(.[.[.[.[.`.^.^.^.`.^.].)././.)./.].^.^.^.^.`.^.]._._._._._.].[.[.[.(.(.[.).E.E.W.E.",
+"E.!.E.E.E.I.I.~.~.~.E.E.~.E.~.~.E.E.E.E.E.E.I.E.~.E.~.~.E.E.E.I.E.R.I.E.I.E.E.~.~.~.E.E.E.E.E.E.",
+"E.E.E.E.Q.E.W.E.E.E.!.E.E.E.E.E.E.E.E.E.E.E.E.W.E.E.E.E.W.E.E.Q.!.E.E.E.E.E.E.E.E.E.E.W.E.E.E.E.",
+"E.W.E.!.E.E.E.E.E.E.E.E.W.E.E.E.E.E.!.E.E.E.!.E.E.E.E.E.E.E.!.E.E.E.E.E.E.E.W.E.E.E.E.!.E.E.E.W."
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/flood-web.png b/icons/flood-web.png
new file mode 100644 (file)
index 0000000..8a45e1e
Binary files /dev/null and b/icons/flood-web.png differ
diff --git a/icons/flood.ico b/icons/flood.ico
new file mode 100644 (file)
index 0000000..eeeca52
Binary files /dev/null and b/icons/flood.ico differ
diff --git a/icons/flood.rc b/icons/flood.rc
new file mode 100644 (file)
index 0000000..e4dbf2d
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "flood.ico"
diff --git a/icons/flood.sav b/icons/flood.sav
new file mode 100644 (file)
index 0000000..ac4adf7
--- /dev/null
@@ -0,0 +1,14 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :5:Flood
+PARAMS  :7:6x6c6m5
+CPARAMS :7:6x6c6m5
+SEED    :15:967543368167853
+DESC    :39:032242034203340350204502505323231342,17
+NSTATES :1:6
+STATEPOS:1:6
+MOVE    :2:M3
+MOVE    :2:M2
+MOVE    :2:M0
+MOVE    :2:M5
+MOVE    :2:M3
diff --git a/icons/galaxies-16d24.png b/icons/galaxies-16d24.png
new file mode 100644 (file)
index 0000000..91b8124
Binary files /dev/null and b/icons/galaxies-16d24.png differ
diff --git a/icons/galaxies-16d4.png b/icons/galaxies-16d4.png
new file mode 100644 (file)
index 0000000..384138a
Binary files /dev/null and b/icons/galaxies-16d4.png differ
diff --git a/icons/galaxies-16d8.png b/icons/galaxies-16d8.png
new file mode 100644 (file)
index 0000000..80b16d3
Binary files /dev/null and b/icons/galaxies-16d8.png differ
diff --git a/icons/galaxies-32d24.png b/icons/galaxies-32d24.png
new file mode 100644 (file)
index 0000000..6dcdbd8
Binary files /dev/null and b/icons/galaxies-32d24.png differ
diff --git a/icons/galaxies-32d4.png b/icons/galaxies-32d4.png
new file mode 100644 (file)
index 0000000..61bcdaf
Binary files /dev/null and b/icons/galaxies-32d4.png differ
diff --git a/icons/galaxies-32d8.png b/icons/galaxies-32d8.png
new file mode 100644 (file)
index 0000000..6dcdbd8
Binary files /dev/null and b/icons/galaxies-32d8.png differ
diff --git a/icons/galaxies-48d24.png b/icons/galaxies-48d24.png
new file mode 100644 (file)
index 0000000..3361ab4
Binary files /dev/null and b/icons/galaxies-48d24.png differ
diff --git a/icons/galaxies-48d4.png b/icons/galaxies-48d4.png
new file mode 100644 (file)
index 0000000..105c8aa
Binary files /dev/null and b/icons/galaxies-48d4.png differ
diff --git a/icons/galaxies-48d8.png b/icons/galaxies-48d8.png
new file mode 100644 (file)
index 0000000..3361ab4
Binary files /dev/null and b/icons/galaxies-48d8.png differ
diff --git a/icons/galaxies-base.png b/icons/galaxies-base.png
new file mode 100644 (file)
index 0000000..672ad8b
Binary files /dev/null and b/icons/galaxies-base.png differ
diff --git a/icons/galaxies-ibase.png b/icons/galaxies-ibase.png
new file mode 100644 (file)
index 0000000..d6426f6
Binary files /dev/null and b/icons/galaxies-ibase.png differ
diff --git a/icons/galaxies-ibase4.png b/icons/galaxies-ibase4.png
new file mode 100644 (file)
index 0000000..321ff85
Binary files /dev/null and b/icons/galaxies-ibase4.png differ
diff --git a/icons/galaxies-icon.c b/icons/galaxies-icon.c
new file mode 100644 (file)
index 0000000..f1a8be8
--- /dev/null
@@ -0,0 +1,891 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"9X|.'.].].].].{.'.`.`.`.`._..X9X",
+"|.f.~.Y.I.Y.~.u.'.>X$X@X=X%Xc.qX",
+"'.^.cXiXyXpXzXE.zXUXKXGXUXIX/.eX",
+"].Y.iX7X4X7XuXK.jXUXGXSXkXnX~.wX",
+"].I.yX4X2X5XeXQ.B.R.M.|.vXaX).5X",
+"].T.sX9X5X0X0X9X9XiX}.+XCXMXAXeX",
+"].R.sX9XrXpXpXtX0XpX|.#XUXIXCXeX",
+"].I.yX7XY.P.T.H.1XrX.Xm.+X[.).0X",
+"].Y.sX3XF.wXtXE.+XuX7X+XOX Xn.tX",
+"].E.uX2XI.nXFX/.$XqX0XiXjXdXT.tX",
+"{.H.sX:XZ.6X8XY.{.iX>X.X}.'.x.rX",
+"{.A.cX2Xe.R.E.I.*XbX(.C.;XOX X0X",
+"].E.0X<XS.dXpXyX8X6X}.#XUXIXCXeX",
+"].T.gX6XM.0X8X6X9XsX|.@XUXLXVXeX",
+"|.E.rX7Xb.m.C.l.:XwX%Xb.R.G.W.iX",
+"9X9XqXqXtXqXwX5X9XqXeXwX8X8X3XqX"
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"qXwXsXfXfXfXfXfXfXfXfXfXfXfXfXsXfXgXfXfXfXfXfXfXfXfXfXgXdXaXwXqX",
+"wX8X).T.E.E.E.E.E.E.E.E.E.E.E.).T.U.Y.Y.Y.Y.T.U.Y.Y.Y.U.!._.9XwX",
+"sX).b 2.*.-.-.-.%.*.-.-.-.=.;.l 8.u.t.t.y.e.7.y.t.t.r.j.T  .pX0X",
+"fXR.2.SXfXjXjXlXpXdXkXjXjXjXzX+.BXUXIXUXUXKXVXUXUXUXKXUXF.L.xX7X",
+"fXE.*.fX5X8X8X9X1X4X9X8X8X7X0X .mXUXKXKXLXFXbXPXKXKXFXUXA.J.zX7X",
+"fXE.-.jX8XqXqXwX4X7XwXqXqXqXeX..VXUXIXUXUXKXNXUXIXUXKXUXF.L.xX7X",
+"fXE.-.jX8XqX0XwX4X7XwXqXqX0XeX..mXUXJXKXLXFXnXUXLXBXDXUXD.J.zX7X",
+"fXE.-.lX9XwXwXrX5X8XeXwXwXwXrXX.VXUXIXUXUXKXCXUX>X4X$XUXJ.L.xX7X",
+"fXE.%.pX1X4X4X5X,X1X5X4X4X3X0XW i.n.x.x.v.s.|.uXwXUXzXrXF.| sX0X",
+"fXE.*.dX4X7X7X8X1X3X8X7X7X6X0XQ.M.V.V.B.G.7.3.MX1XUXaX9XjX'.wXqX",
+"fXE.-.kX9XwXwXeX5X8XeXwXwXwXwX0XhXhXhXgXnX+X6.UX7X;X1XLXFXBXrX0X",
+"fXE.-.jX8XqXqXwX4X7XwXqXqXqXqX1X8X9X9X8XiX/.4.UXKXIXIXUXAXmXrX0X",
+"fXE.-.jX8XqXqXwX4X7XwXqXqXqXqX2X0XqXqX0XaX_.4.UXJXUXPXUXSXBXrX0X",
+"fXE.-.jX8XqXqXwX3X6XwX0XqXqXqX1X0XqXqX0XaX_.4.UXKXIXPXUXZXNXrX0X",
+"fXE.-.kX8XwXqXwX9XqXrXeXeXeXrX8XqXwXwX0XaX_.4.UXLXUXUXUXDXVXrX0X",
+"fXE.$.uX,X2X<XeXz.R O.X.O...} ` ,X3X2X1X7X|.G &.@.#.@.*.^ *.wXqX",
+"fXE.=.jX7X0X7XkX5./.UXbXkXSXDXo.-XrX0X0XqX9X-X-X-X-X&X0X7.D.lX8X",
+"fXE.-.kX8XqX8XlX5.+XLX-X4X8XUX$.-XtXqXqXwX9X5XyXtXtXwXvXs.Y.zX7X",
+"fXE.-.jX7XqX7XlX4.+XeXBXUX2XNX=.=XrX0X0XqX9X2XwX0XqX7XkXu.U.zX7X",
+"fXE.-.jXwXeXwXlX4.+XrXxXUX:XCX*.*XiXrXtXrX8X2XqX0X0X7XjXy.I.zX7X",
+"fXE.=.zX|.'.{.xX5.oXUX4X5XsXUX#.,X2X`.'.4XwX8XsXaXaXuXNXg.Y.zX7X",
+"fXR.>.3X-XUX1X<X0.S.cXtXwXaXiX{ %X|.DXmX'.7X].)._.)./.+X=.c.jX8X",
+"fXR.;.+XaXUXlXOX0.D ;.+.#.@.o.;.@X#XUXUXoX(.D 9.3.4.4.6.*.0.qXqX",
+"fXR.:.pX).yX`.uX9.P.NXsXfXfXgXeXrX[.4X-X%X{.3.UXLXUXUXUXDXVXrX0X",
+"fXE.=.zX2XoX<XcX5.C.dX6X8X8X9X,X0XeX$X&XfX).3.UXGXLXKXIXVXmXrX0X",
+"fXE.-.jX9XuX0XkX6.A.hX9XqXqXwX2X0XqXyXrXpX_.4.UXLXUXUXUXSXBXrX0X",
+"fXE.-.jX7X0X7XkX6.V.sX6X8X8X8X<X0XqX0X9XaX_.3.UXGXKXKXIXVXNXrX0X",
+"fXE.;.lX9XwX9XzX8.T.ZXhXkXkXlXtXwXwXwXqXsX`.5.UXLXUXUXUXDXVXrX0X",
+"fXE.&.aX2X5X2XsX5.` k.u.i.p.y.j.4X5X5X4XwX`.T Y.D.F.F.J.M.8XiX9X",
+"sX/.5.pX3X6X5XqX{.h.z.l.l.l.l.R :X8X5X6X7X1Xb.g.j.j.g.M.( U.jX8X",
+"qX0X0XwXwXwXwXqXyXjXjXjXjXjXkXtXqXwXwXwXwXeXgXkXkXkXjXlXsXwXqXqX",
+"qXqXwXqXqXqXqXqX0X8X8X8X8X8X8XqXqXqXqXqXqXqX8X8X8X8X8X8X0XqXqXqX"
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXqX0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0X0XqXqXqXqX",
+"qXqXqXwXiXaXaXaXaXaXaXaXaXaXaXaXaXaXaXaXaXaXaXiXaXaXaXaXaXaXaXaXaXaXaXaXaXaXaXaXaXsXiXaXwXqXqXqX",
+"qXqXwX8X|.).).).).).).).).).).).).).).).).).).|.(./././././././.(.(./././././././.^.}.`.5XwXqXqX",
+"qX0XiX|.- z z z z z z z z k k z z z z z z z l * b V B B B B B B M b V B B B B B N A > g =XtX0XqX",
+"qX0XaX).x fXyXuXuXuXuXuXpX8X0XpXuXuXuXuXyXpXqXk xXUXIXUXUXUXUXUXSXnXUXIXUXUXUXUXLXUXP .XvX7XqXqX",
+"qX0XaX).z yX9X0X0X0X0X0XwX2X3XwX0X0X0X0X0XwX4Xj zXUXPXIXIXIXIXUXAXvXUXPXIXIXIXIXKXUXP .XvX7XqXqX",
+"qX0XaX).z uX0XqXqXqXqX0XeX2X4XwXqXqXqXqX0XeX5Xj xXUXIXUXUXUXIXUXAXbXUXIXUXUXUXUXLXUXP .XvX7XqXqX",
+"qX0XaX).z uX0XqXqXqXqXqXeX2X4XeXqXqXqXqXqXeX5Xj xXUXIXUXUXUXUXUXSXbXUXIXUXUXUXUXLXUXP .XvX7XqXqX",
+"qX0XaX).z uX0XqXqXqXqXqXeX2X4XeXqXqXqXqXqXeX5Xj xXUXIXUXUXUXUXUXSXbXUXIXPXIXIXIXLXUXP .XvX7XqXqX",
+"qX0XaX).z uX0XqXqXqXqXqXeX2X4XeXqXqXqXqXqXeX5Xj xXUXIXUXUXUXUXUXSXbXUXPXUXUXUXIXKXUXP .XvX7XqXqX",
+"qX0XaX).z uX0X0XqXqXqX0XwX2X4XwX0XqXqXqX0XeX5Xj jXUXJXKXKXKXJXPXBXxXUXUX8X'.'.tXUXUXP }.cX7XqXqX",
+"qX0XaX).z pXwXeXeXeXeXwXtX4X6XtXeXeXeXeXwXtX7Xz xXUXIXUXUXUXUXUXDXnXUX9X].KXFX/.iXUXI XXvX7XqXqX",
+"qX0XaX).k 8X2X2X2X2X2X2X4X-X:X4X2X2X2X2X2X4X,Xy #.5.2.2.2.2.2.4.=.P.LXW.SXUXUXnX!.UXJ Y 6XeX0XqX",
+"qX0XaX).k 0X3X4X4X4X4X4X6X:X>X5X4X4X4X4X4X4X4Xp.:.,.,.,.,.,.:.9.Z { UXQ.CXUXUXxX).UXT.p.2XrX0XqX",
+"qX0XaX).z pXwXwXeXeXeXwXtX4X5XrXwXeXeXeXeXwXeXqXxXcXcXcXcXcXlXKXM.{ UXeX/.VXmXQ.kXUXbXHXiX0XqXqX",
+"qX0XaX).z uX0XqXqXqXqX0XeX2X4XwX0XqXqXqXqXqXqX-X7X8X8X8X8X8X5XgXi.{ UXIXpX}. XhXUXUXlXAXiX0XqXqX",
+"qX0XaX).z uX0XqXqXqXqXqXeX2X4XeXqXqXqXqXqXqXqX>X0XqXqXqXqXqX8XlXs.{ UXJXUXUXUXIXPXUXxXGXiX9XqXqX",
+"qX0XaX).z uX0XqXqXqXqXqXeX2X4XeXqXqXqXqXqXqXqX>X0XqXqXqXqXqX8XlXs.{ UXKXIXPXPXIXIXUXxXGXiX9XqXqX",
+"qX0XaX).z uX0XqXqXqXqXqXeX2X4XeXqXqXqXqXqXqXqX>X0XqXqXqXqXqX8XlXs.{ UXKXUXUXUXUXIXUXxXGXiX9XqXqX",
+"qX0XaX).z uX0XqXqXqXqXqXeX2X4XwX0XqXqXqXqX0XqX>X0XqXqXqXqXqX8XlXs.{ UXJXIXIXIXIXIXUXxXFXiX0XqXqX",
+"qX0XaX).z uX0XqXqXqXqXqXeX3X6XtXeXeXeXeXeXeXeX,X0XqXqXqXqXqX8XlXs.} UXKXUXUXUXUXIXUXcXGXiX9XqXqX",
+"qX0XaX).z uXqXqXqXqXqXqXeX2X,X6X5X5X5X5X5X5X5X;XqXwXqXqXqXqX9XzXs.] UXSXHXHXHXHXGXLXkXCXuX0XqXqX",
+"qX0XaX_.j 4X:X>X>X>X>X-XwX2.6 x j h g h j k f s +X1X:X>X>X>X;X0Xc.: n l z z z z z c t l %XyX0XqX",
+"qX0XaX).z yX0X0X0X0X0X7XxX{ e.PXhXnXZXVXzXvXhXj $XyX0X0X0X0X0XwX2XXX%X#X$X$X$X#X+X2Xb !.kX8XqXqX",
+"qX0XaX).z uX0XqXqXqXqX7XcX{ l.UXPXCX8XsXIXUXHXx $XyX0XqXqXqXqXwX7X5XiXyXyXyXyXyXrXxXZ OXcX7XqXqX",
+"qX0XaX).z uX0XqXqXqXqX7XcX[ h.UXwXQ.wX$X_.UXGXz $XyX0XqXqXqXqXwX6X<XwX0X0X0X0X0X8XgXB oXcX7XqXqX",
+"qX0XaX).z uX0XqXqXqXqX7XcX[ k.UXQ.SXUXUX&X0XUXk $XyX0XqXqXqXqXwX6X1XeXqXqXqXqXqX8XhXB OXcX7XqXqX",
+"qX0XaX).z uX0X0X9X9X0X7XcX[ x.UX^.UXGXUXyX*XUXk #XyX0X0X9X0XqXwX6X1XeXqXqXqXqXqX8XhXB OXcX7XqXqX",
+"qX0XaX).z uX9XrXpXpXuX6XvX[ j.UX/.fXUXUX`.hXUXl $XyXqXpXpXiX0XwX6X1XeXqXqXqXqXqX8XhXB OXcX7XqXqX",
+"qX0XaX).z yXrX,XY.P.OXeXxX[ g.UXbX~.|.~.>XUXSXl #XpX7XW.P.'.tXwX5X>X0X8X8X8X8X8X6XdXB .XcX7XqXqX",
+"qX0XaX).z sX<XD.dXmXW.`.VX] k.UXKXUXUXUXIXUXHXc &XuXS.8XVX XR.aX9XtXxXlXlXlXlXlXhXSXG OXcX7XqXqX",
+"qX0XaX).j pXP.uXUXUXGXD.gX .( '.K.I.W.T.P.T.J.t -X'.*XUXLXUXR.>X#Xs.s.s.s.s.s.s.p.z.y 9.eXqXqXqX",
+"qX0XaX_.h eXD.lXUXPXUXF.eX+.9 ~ P I I I I U L _ 2XR.4XUXHXUX!.:Xz.5 @.[ { { { { { } { { =XtX0XqX",
+"qX0XaX).l hXOXW.IXUX-XI.CX{ e.LXjXxXxXxXxXxXxXwXyX2XH.AXUXyXS.nXd.| UXKXUXUXUXUXIXUXnXHXiX0XqXqX",
+"qX0XaX).z tXyX[.K.Y.R.9XxX| <.jX4X8X8X8X8X7X8X-X9XyX$XH.R.K.3XxXa.[ UXFXKXKXKXKXJXIXkXAXiX0XqXqX",
+"qX0XaX).z uX9XuXqX7XiX8XcX| 2.xX8XqXqXqXqXqXqX>XqX0XyXrX5XuX0XkXs.{ UXKXUXUXUXUXIXUXxXGXiX9XqXqX",
+"qX0XaX).z uX0X0XqXwX0X7XcX| 2.xX8XqXqXqXqXqXqX>X0XqX0XqXwX0X8XlXs.{ UXKXUXUXUXUXIXUXxXGXiX9XqXqX",
+"qX0XaX).z uX0XqXqXqXqX7XcX| 2.xX8XqXqXqXqXqXqX>X0XqXqXqXqXqX8XlXs.{ UXKXUXUXUXUXIXUXxXGXiX9XqXqX",
+"qX0XaX).z uX0XqXqXqXqX7XcX| 2.xX8XqXqXqXqXqXqX>X0XqXqXqXqXqX8XlXs.{ UXKXUXUXUXUXIXUXxXGXiX9XqXqX",
+"qX0XaX).z yX0X0XqXqXqX7XxX| <.kX5X8X8X8X8X8X9X;X0XqXqXqXqXqX8XlXs.[ UXGXLXLXLXLXKXUXkXFXiX9XqXqX",
+"qX0XaX).x pXwXeXeXeXeX9XbX .0.FXsXhXhXhXhXhXhX0XeXeXeXeXeXeX0XxXd.} UXKXUXUXUXUXIXUXbXHXiX0XqXqX",
+"qX0XaX_.j 5X,X,X,X,X,X;XyX+.1 H V C C C C C N K %X2X,X,X,X,X:XeXj.> ! P I I I I I Y F -XsX9XqXqX",
+"qX0XaX_.x eX7X7X8X8X8X7XwX|.A.G.F.F.F.F.F.H.C.y #XwX7X8X8X8X7XqX&XZ.S.A.A.A.A.A.Z.U.a XXzX7XqXqX",
+"qXqXwX6X#XeXwXwXwXwXwXwXwXtXdXgXgXgXgXgXgXgXfX&X9XeXwXwXwXwXwXwXeXsXhXgXgXgXgXgXgXkX:X9XrX0XqXqX",
+"qXqXqXwXyXqXqXqXqXqXqXqXqX0X9X9X9X9X9X9X9X9X9XyXwXqXqXqXqXqXqXqX0X9X9X9X9X9X9X9X9X8XtXwX0XqXqXqX",
+"qXqXqXqX0XqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX0XqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX0XqXqXqXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX"
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/galaxies-web.png b/icons/galaxies-web.png
new file mode 100644 (file)
index 0000000..9430cee
Binary files /dev/null and b/icons/galaxies-web.png differ
diff --git a/icons/galaxies.ico b/icons/galaxies.ico
new file mode 100644 (file)
index 0000000..6a961d7
Binary files /dev/null and b/icons/galaxies.ico differ
diff --git a/icons/galaxies.rc b/icons/galaxies.rc
new file mode 100644 (file)
index 0000000..f39a704
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "galaxies.ico"
diff --git a/icons/galaxies.sav b/icons/galaxies.sav
new file mode 100644 (file)
index 0000000..42d18bc
--- /dev/null
@@ -0,0 +1,51 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :8:Galaxies
+PARAMS  :5:7x7dh
+CPARAMS :5:7x7dh
+SEED    :15:483644862786314
+DESC    :13:ikrqfedzljjsp
+NSTATES :2:43
+STATEPOS:2:37
+MOVE    :5:E12,9
+MOVE    :5:E8,11
+MOVE    :5:E5,12
+MOVE    :5:E7,12
+MOVE    :5:E4,13
+MOVE    :5:E2,11
+MOVE    :5:E3,10
+MOVE    :4:E2,5
+MOVE    :4:E4,5
+MOVE    :4:E6,7
+MOVE    :4:E8,1
+MOVE    :5:E10,1
+MOVE    :4:E9,2
+MOVE    :4:E6,3
+MOVE    :4:E7,4
+MOVE    :5:E10,3
+MOVE    :5:E10,5
+MOVE    :5:E11,6
+MOVE    :5:E13,6
+MOVE    :5:E8,13
+MOVE    :5:E12,7
+MOVE    :6:E12,11
+MOVE    :6:E13,12
+MOVE    :4:E8,9
+MOVE    :4:E7,8
+MOVE    :4:E7,6
+MOVE    :4:E9,6
+MOVE    :4:E8,5
+MOVE    :4:E9,4
+MOVE    :4:E5,2
+MOVE    :4:E4,1
+MOVE    :4:E3,6
+MOVE    :4:E2,7
+MOVE    :4:E3,8
+MOVE    :4:E3,4
+MOVE    :4:E4,9
+MOVE    :4:E2,9
+MOVE    :5:E5,10
+MOVE    :5:E6,11
+MOVE    :4:E2,3
+MOVE    :4:E2,1
+MOVE    :5:E1,12
diff --git a/icons/guess-16d24.png b/icons/guess-16d24.png
new file mode 100644 (file)
index 0000000..30d0b77
Binary files /dev/null and b/icons/guess-16d24.png differ
diff --git a/icons/guess-16d4.png b/icons/guess-16d4.png
new file mode 100644 (file)
index 0000000..90ad211
Binary files /dev/null and b/icons/guess-16d4.png differ
diff --git a/icons/guess-16d8.png b/icons/guess-16d8.png
new file mode 100644 (file)
index 0000000..b900783
Binary files /dev/null and b/icons/guess-16d8.png differ
diff --git a/icons/guess-32d24.png b/icons/guess-32d24.png
new file mode 100644 (file)
index 0000000..7bd5959
Binary files /dev/null and b/icons/guess-32d24.png differ
diff --git a/icons/guess-32d4.png b/icons/guess-32d4.png
new file mode 100644 (file)
index 0000000..6c9604d
Binary files /dev/null and b/icons/guess-32d4.png differ
diff --git a/icons/guess-32d8.png b/icons/guess-32d8.png
new file mode 100644 (file)
index 0000000..adfbb00
Binary files /dev/null and b/icons/guess-32d8.png differ
diff --git a/icons/guess-48d24.png b/icons/guess-48d24.png
new file mode 100644 (file)
index 0000000..48b76f2
Binary files /dev/null and b/icons/guess-48d24.png differ
diff --git a/icons/guess-48d4.png b/icons/guess-48d4.png
new file mode 100644 (file)
index 0000000..e478528
Binary files /dev/null and b/icons/guess-48d4.png differ
diff --git a/icons/guess-48d8.png b/icons/guess-48d8.png
new file mode 100644 (file)
index 0000000..6447329
Binary files /dev/null and b/icons/guess-48d8.png differ
diff --git a/icons/guess-base.png b/icons/guess-base.png
new file mode 100644 (file)
index 0000000..1739dab
Binary files /dev/null and b/icons/guess-base.png differ
diff --git a/icons/guess-ibase.png b/icons/guess-ibase.png
new file mode 100644 (file)
index 0000000..15505b1
Binary files /dev/null and b/icons/guess-ibase.png differ
diff --git a/icons/guess-ibase4.png b/icons/guess-ibase4.png
new file mode 100644 (file)
index 0000000..fa81bb8
Binary files /dev/null and b/icons/guess-ibase4.png differ
diff --git a/icons/guess-icon.c b/icons/guess-icon.c
new file mode 100644 (file)
index 0000000..bb73841
--- /dev/null
@@ -0,0 +1,778 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 252 2 ",
+"   c #D6D5D5",
+".  c #D3DBDB",
+"X  c #CAC4C4",
+"o  c #D2DCDC",
+"O  c #D1D8D8",
+"+  c #CAC5C4",
+"@  c #D5DDE2",
+"#  c #CECDCF",
+"$  c #CCCCCA",
+"%  c #D5D5E3",
+"&  c #CBCBC8",
+"*  c #CFCFD3",
+"=  c #D5D5D9",
+"-  c #D3D3D2",
+";  c gray83",
+":  c #D5D5D5",
+">  c #D3E1E1",
+",  c #D28181",
+"<  c #F00000",
+"1  c #D17071",
+"2  c #D65F5D",
+"3  c #EF0201",
+"4  c #C98E8B",
+"5  c #E3EC34",
+"6  c #EAE61B",
+"7  c #C7C792",
+"8  c #EDEB13",
+"9  c #E0DF37",
+"0  c #C7C7CB",
+"q  c #C5C5C5",
+"w  c #CBCBCA",
+"e  c #D5D5D5",
+"r  c #D1E4E4",
+"t  c #D65D5E",
+"y  c red",
+"u  c #D54646",
+"i  c #DD3736",
+"p  c #C86A5A",
+"a  c #F0FF15",
+"s  c #FBF801",
+"d  c #C6C560",
+"f  c yellow",
+"g  c #F0F015",
+"h  c #BCBBB7",
+"j  c #B0B0B2",
+"k  c #BEBEBD",
+"l  c #D8D8D8",
+"z  c #D8DAD9",
+"x  c #CDC8C8",
+"c  c #9C5B7C",
+"v  c #CCC1C8",
+"b  c #C1BAAE",
+"n  c #967F50",
+"m  c #D1D5E1",
+"M  c #C7AE85",
+"N  c #C6A16B",
+"B  c #CDDCE9",
+"V  c #C59C60",
+"C  c #C9B593",
+"Z  c #C8CED5",
+"A  c #B5B4B2",
+"S  c gray76",
+"D  c #D8D8D8",
+"F  c #E0DED1",
+"G  c #7B87D6",
+"H  c #1642FF",
+"J  c #6A70E0",
+"K  c #3CE72E",
+"L  c green",
+"P  c #72BD65",
+"I  c #FE0919",
+"U  c #F50604",
+"Y  c #C66C6C",
+"T  c #FB0000",
+"R  c #F52122",
+"E  c #989696",
+"W  c #5C5D5D",
+"Q  c #8D8C8C",
+"!  c #DFDFDF",
+"~  c #DFDED2",
+"^  c #8893D7",
+"/  c #1F42FF",
+"(  c #7A7DE2",
+")  c #4CE546",
+"_  c #00FD04",
+"`  c #83C37E",
+"'  c #F3242E",
+"]  c #EC1E18",
+"[  c #C78484",
+"{  c #F11310",
+"}  c #E63332",
+"|  c #C4C5C5",
+" . c #B9BABA",
+".. c #C0C0C0",
+"X. c #D8D8D8",
+"o. c #D6D9DB",
+"O. c #D2C9BC",
+"+. c #B78359",
+"@. c #D1C4B9",
+"#. c #CDBAA9",
+"$. c #B6914A",
+"%. c #D4D6DA",
+"&. c #C97F7F",
+"*. c #C96565",
+"=. c #CDE1E1",
+"-. c #C85A5A",
+";. c #CE8F8F",
+":. c #B9C5C5",
+">. c #ABA8A8",
+",. c #CECECE",
+"<. c gray84",
+"1. c #D1DBE4",
+"2. c #D59358",
+"3. c #FF7200",
+"4. c #D3853F",
+"5. c #DC8330",
+"6. c #FF7700",
+"7. c #C78252",
+"8. c #F10A12",
+"9. c #FC0200",
+"0. c #C45757",
+"q. c #FE0000",
+"w. c #F81919",
+"e. c #958F8F",
+"r. c #7E8080",
+"t. c gray",
+"y. c #D4D9E0",
+"u. c #D6B793",
+"i. c #EE830D",
+"p. c #D7B185",
+"a. c #D1A27D",
+"s. c #E07A23",
+"d. c #CBB8A9",
+"f. c #D95154",
+"g. c #DC3D3D",
+"h. c #CCB0AD",
+"j. c #E73328",
+"k. c #DE5A51",
+"l. c #CFD5D5",
+"z. c #CAC9C9",
+"x. c gray79",
+"c. c #D7D7D7",
+"v. c #DCDBD4",
+"b. c #ACB4D7",
+"n. c #4A60DE",
+"m. c #9BA6E1",
+"M. c #D7DB86",
+"N. c #D4D936",
+"B. c #CCD6BD",
+"V. c #D26167",
+"C. c #D64F4C",
+"Z. c #C6C5CD",
+"A. c #8643B1",
+"S. c #A276C3",
+"D. c #AFB4AF",
+"F. c #7A797A",
+"G. c #A2A2A2",
+"H. c gainsboro",
+"J. c #E2E0D1",
+"K. c #6975D4",
+"L. c #0F2EFF",
+"P. c #4B5BDB",
+"I. c #EAE820",
+"U. c #C7B450",
+"Y. c #EF0014",
+"T. c #FF0400",
+"R. c #A1557F",
+"E. c #7200B7",
+"W. c #7F13AC",
+"Q. c #A7A4A8",
+"!. c #858685",
+"~. c #9D9D9D",
+"^. c #DDDDDD",
+"/. c #DBDBD5",
+"(. c #B6B9D6",
+"). c #5B69CB",
+"_. c #A9ADDE",
+"`. c #DBD296",
+"'. c #CDCB42",
+"]. c #D4D6CD",
+"[. c #CC7174",
+"{. c #CE5B5A",
+"}. c #CDD4D3",
+"|. c #9051A8",
+" X c #A47CB4",
+".X c #DBE0D9",
+"XX c #D2D1D3",
+"oX c #CDCDCD",
+"OX c gray84",
+"+X c #D3D3E1",
+"@X c #D7D685",
+"#X c #FAFA00",
+"$X c #E3D773",
+"%X c #53D46B",
+"&X c #03EC14",
+"*X c #93C894",
+"=X c #E93A45",
+"-X c #E62F28",
+";X c #BB9BAF",
+":X c #8323AF",
+">X c #9C50B9",
+",X c #A3A5A3",
+"<X c #6A6A6A",
+"1X c #979797",
+"2X c gray87",
+"3X c #D1D1E4",
+"4X c #D5D55C",
+"5X c #DED444",
+"6X c #26DC34",
+"7X c #69B858",
+"8X c #FF0316",
+"9X c #FE0400",
+"0X c #A65E87",
+"qX c #6F00B4",
+"wX c #861CB3",
+"eX c #8F8C90",
+"rX c #4E4F4D",
+"tX c #838383",
+"yX c #E1E1E1",
+"uX c #D6D6D9",
+"iX c #CFCFC9",
+"pX c #C3C476",
+"aX c #D4CEC5",
+"sX c #B4CDB8",
+"dX c #7BC378",
+"fX c #D0D9D8",
+"gX c #CA9B9C",
+"hX c #C68B8B",
+"jX c #D4DDDA",
+"kX c #A583B1",
+"lX c #B9A5C1",
+"zX c #CBCEC9",
+"xX c #B2B2B3",
+"cX c #C1C1C1",
+"vX c #D8D8D8",
+"bX c #D5D5D4",
+"nX c #D5D5D8",
+"mX c #CECEDF",
+"MX c #D4D5D8",
+"NX c #DAD4D9",
+"BX c #DFCFDF",
+"VX c #D8D5D6",
+"CX c #D1DCDC",
+"ZX c #D0DDDD",
+"AX c #D7D6D6",
+"SX c #D7DED4",
+"DX c #D6DBD4",
+"FX c #D9D8D9",
+"GX c gray87",
+"HX c #DADADA",
+"JX c gray83",
+"KX c white",
+/* pixels */
+"  . X o O + @ # $ % & * = - ; : ",
+"> , < 1 2 3 4 5 6 7 8 9 0 q w e ",
+"r t y u i y p a s d f g h j k l ",
+"z x c v b n m M N B V C Z A S D ",
+"F G H J K L P I U Y T R E W Q ! ",
+"~ ^ / ( ) _ ` ' ] [ { } |  ...X.",
+"o.O.+.@.#.$.%.&.*.=.-.;.:.>.,.<.",
+"1.2.3.4.5.6.7.8.9.0.q.w.e.r.t.D ",
+"y.u.i.p.a.s.d.f.g.h.j.k.l.z.x.c.",
+"v.b.n.m.M.N.B.V.C.Z.A.S.D.F.G.H.",
+"J.K.L.P.I.f U.Y.T.R.E.W.Q.!.~.^.",
+"/.(.)._.`.'.].[.{.}.|. X.XXXoXOX",
+"+X@X#X$X%X&X*X=X-X;X:X>X,X<X1X2X",
+"3X4Xf 5X6XL 7X8X9X0XqXwXeXrXtXyX",
+"uXiXpXaXsXdXfXgXhXjXkXlXzXxXcXvX",
+"bXnXmXMXNXBXVXCXZXAXSXDXFXGXHXJX"
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 200 2 ",
+"   c black",
+".  c #1B1B1B",
+"X  c #252525",
+"o  c #393738",
+"O  c gray35",
+"+  c #696969",
+"@  c #7A7A7A",
+"#  c #DC0506",
+"$  c #DC0E0E",
+"%  c #D61717",
+"&  c #E40202",
+"*  c #EB0000",
+"=  c #E20B09",
+"-  c #F40000",
+";  c #FE0202",
+":  c #E81212",
+">  c #E50D15",
+",  c #C92726",
+"<  c #D72425",
+"1  c #C83130",
+"2  c #CB3D3D",
+"3  c #DA3C2F",
+"4  c #E02B3E",
+"5  c #E97706",
+"6  c #FF7600",
+"7  c #FE7D02",
+"8  c #F47400",
+"9  c #EE7408",
+"0  c #D87C25",
+"q  c #D17C38",
+"w  c #D67113",
+"e  c #BF4C4C",
+"r  c #BD5754",
+"t  c #BB4B5D",
+"y  c #BA5163",
+"u  c #B86666",
+"i  c #BA7A7A",
+"p  c #B2727A",
+"a  c #C94747",
+"s  c #C45B5B",
+"d  c #C75652",
+"f  c #D34C41",
+"g  c #C26558",
+"h  c #CF5763",
+"j  c #C37A7B",
+"k  c #C36F69",
+"l  c #01E600",
+"z  c #02FE02",
+"x  c #00F600",
+"c  c #0CEB15",
+"v  c #17E31D",
+"b  c #2BD42C",
+"n  c #12D410",
+"m  c #41D733",
+"M  c #55BF53",
+"N  c #67BF67",
+"B  c #2FCA40",
+"V  c #49CC4B",
+"C  c #59C464",
+"Z  c #6EC175",
+"A  c #FE8203",
+"S  c #E58115",
+"D  c #CA823B",
+"F  c #D98C34",
+"G  c #D9DA0A",
+"H  c #D6D516",
+"J  c #DFE41B",
+"K  c #E5E403",
+"L  c #E9E903",
+"P  c #F3F300",
+"I  c #FEFE02",
+"U  c #E4E716",
+"Y  c #C9C935",
+"T  c #D7D630",
+"R  c #CDE03F",
+"E  c #BF8C59",
+"W  c #81B07C",
+"Q  c #BBBC62",
+"!  c #B5B579",
+"~  c #B58D67",
+"^  c #C8884E",
+"/  c #C1BE57",
+"(  c #C19B78",
+")  c #C49161",
+"_  c #C6B566",
+"`  c #B8C161",
+"'  c #C9CF48",
+"]  c #C3D060",
+"[  c #C3C366",
+"{  c #710E99",
+"}  c #792C99",
+"|  c #7202A3",
+" . c #7507A5",
+".. c #7C01B2",
+"X. c #7F15A9",
+"o. c #7E27A1",
+"O. c #7D46AF",
+"+. c #6A73BD",
+"@. c #263EEC",
+"#. c #233EFB",
+"$. c #2F43D4",
+"%. c #2A43FD",
+"&. c #2742F4",
+"*. c #364FFF",
+"=. c #3951FF",
+"-. c #4A59C6",
+";. c #6C7AC9",
+":. c #7B74C8",
+">. c #8204B4",
+",. c #8401B9",
+"<. c #8026A4",
+"1. c #864E9B",
+"2. c #937A9C",
+"3. c #BA7787",
+"4. c #8548A5",
+"5. c #8556AE",
+"6. c #926FAC",
+"7. c #7ACA8A",
+"8. c #7788CA",
+"9. c #6784CD",
+"0. c #888888",
+"q. c #959595",
+"w. c #9F9F9C",
+"e. c #A98685",
+"r. c #BC8888",
+"t. c #AB9B9C",
+"y. c #BA9999",
+"u. c #BB9D80",
+"i. c #BBBC86",
+"p. c #BBA996",
+"a. c #BDBD96",
+"s. c #A8A583",
+"d. c #989CB4",
+"f. c #918FB0",
+"g. c #B59DB1",
+"h. c #9DA1BD",
+"j. c #A4A4A4",
+"k. c #ACACAC",
+"l. c #A8A6A6",
+"z. c #BBAAA9",
+"x. c #BAB9AB",
+"c. c #ACA9B9",
+"v. c #B8ABB5",
+"b. c #ABBBB8",
+"n. c #B5B5B4",
+"m. c #BBBBBB",
+"M. c #BAB2BE",
+"N. c #A9BDA4",
+"B. c #C28782",
+"V. c #C19896",
+"C. c #C5AC93",
+"Z. c #C0AFAF",
+"A. c #C5B6AC",
+"S. c #C2BCBC",
+"D. c #C2B6B5",
+"F. c #8FC28C",
+"G. c #BCC393",
+"H. c #9ACAA6",
+"J. c #B8C1B7",
+"K. c #BDC4AD",
+"L. c #C0C384",
+"P. c #C2C190",
+"I. c #C2C1B8",
+"U. c #C1C1AC",
+"Y. c #8B82C9",
+"T. c #BEBDC1",
+"R. c #B5B5C7",
+"E. c #C9BCCB",
+"W. c #BBC6CB",
+"Q. c #C4C3C3",
+"!. c #CCCDCC",
+"~. c #C7C9C7",
+"^. c #D1CDCC",
+"/. c #CDD0CD",
+"(. c #D1D2CD",
+"). c #D6D8CF",
+"_. c #CCCCD5",
+"`. c #D2CED3",
+"'. c #D6CDD9",
+"]. c #CDD5D5",
+"[. c #CED9D9",
+"{. c #C9D7DA",
+"}. c #D5D5D5",
+"|. c #D9D7D7",
+" X c #D6DAD5",
+".X c #DADAD6",
+"XX c #D4D4DC",
+"oX c #D4DCDD",
+"OX c #DADBDB",
+"+X c #DCD5DC",
+"@X c #E0DFD9",
+"#X c #DDE3DB",
+"$X c #DADAE1",
+"%X c #D8D9E6",
+"&X c #CFDCE7",
+"*X c #E0D9E8",
+"=X c #DBE2E4",
+"-X c #D5E6E6",
+";X c #E3E4E3",
+":X c #E6E9E4",
+/* pixels */
+"}.}.}.}.|.OX}.}.}.}.OX}.}.}.}.|.|.}.}.}. XOX|.}.}.}.}.}.}.}.}.}.",
+"}.}.}.#XQ.S.-X}. X[.D._.oX}.%X~.I.%X}.}._.m.~.$X}.}.}.|.}.}.}.}.",
+"}.}.oXi # & e [.Q.1 * % p.%X! G K ' _.~.Y L H a.XXJ.Q.Q.T.!.|.}.",
+"}.OXv.& ; ; - r.s ; ; ; < n.L I I P i.[ I I I H E.!.(.n.oXQ. X}.",
+"}.=Xy.* ; ; ; u a ; ; ; > s.P I I I ` ' I P I K I.T.n.~.l.}.}.|.",
+"}.|.Q.% ; ; & v.i - ; ; a W.H I I K A.i.P I I Y XXw.q.k.0.!.|.}.",
+"}.}.oXD.f 3 V.#X[.j 4 h ~.%XD.' ' P.XXXXL.' ] ~.XX!.!.}.~.XX}.}.",
+"}.}. X/.8.9.W..XOXH.C 7.'.|.!.p y D.oXoXz.t 3. XOX!.!.}.Q.|.}.}.",
+"`.|.^.-.#.%.$.R.F.l z x M '.1 ; ; # D.y.& ; - e -Xo X +   T.OX}.",
+"}..Xh.@.=.=.%.:.m z z z c e.* ; ; ; u a ; ; ; & ~.0.@ l.O !.}.}.",
+"}..Xc.@.=.=.#.Y.V z z z v t.* ; ; ; i s ; ; ; $ ~.m.k.S.j.}.}.}.",
+"}.}.).;.&.&.-.^.K.n z l F.=Xr * - , !.D.% ; & i =Xl.j.m.q.].|.}.",
+"}.}.}. Xd.f./.|.*X~.W b.*X}.-Xt.e.{.OX$XW.~ c.-X/.#X:XOXOX|.}.}.",
+"}.}.%Xu.S 5 ) ].Q.D 9 0 p.=Xr.: : d [.~.2 : < y.#X2.@ Q.k.}.}.}.",
+"}.OXx.5 A A 8 p.) 6 A 7 0 c.= ; ; - r.k ; ; ; % [.o . n.q.!.|.}.",
+"}.oXp.8 A A 7 ~ ^ A 7 A S e.- ; ; ; u a ; ; ; & S.Q.n.!.n.}.}.}.",
+"}. XQ.w 6 6 8 x.( 8 7 6 g W.% ; ; * z.j - ; ; 1 oXw.q.k.0.!.|.}.",
+"XX}.|.A.F F p.$X`.( q ) I.%XD.a 2 V. X].j 3 d I.OX!.!.}.Q.}.}.}.",
+"}.}.|.].8.;.T.OXXXK.` G.OX#X].~ u T.oXOXM.5.f. X|.].!.|.!. X}.}.",
+"}.}.(.-.#.%.$.R.P.K I P / {.1 ; ; # S.g.| >.| 1.:Xo X +   m.|.}.",
+"}.@Xd.&.=.=.%.;.T I I I U e.- ; ; ; g 5.>.>.,. .~.@ + j.O !.|.}.",
+"].OXR.@.*.=.#.8.' I I I J t.* ; ; - k 5...>.,. .m.}.}.m.l.].}.}.",
+"}.|.(.+.&.%.-.!.x.G I L ! -Xd - ; , ~.M.{ ..| 2..Xm.Q.c.q.!.|.}.",
+"}.}.}.|.f.f._.|.%XE.! x.%X|.-Xt.e.{.|.#X~.2.k.#X}.OXOX;X:X|.}.}.",
+"}.}.XXi.U L Q %X~.V c b N.$Xr.% : s {.!.1.X.<.c.:X0.0.l.+ !.}.}.",
+"}.XXm.K I I P C.C z z z b v.= ; ; - B.6.| >.>.{ |.o X +   m.|.}.",
+"}.$Xa.P I I I _ B z z z c e.- ; ; ; g O.,.>.>. .Q.j.q.m.@ }.|.}.",
+"}.|.Q.G I I L A.Z x z z b E.% ; ; & C.6.| >...} #Xo . +   m.OX}.",
+"}.}.OXx.Y T i.{.`.N b V I.$Xz.2 4 r.oX).6.<.1.m.;Xk.j.T.q.}.}.}.",
+"}.}.}.XXR.M.XX|.}.'.v.E.OX}.|.W.b.[.}. X Xn.~.|.}.OX$X X;X|.}.}.",
+"}.}.}.}.(.!.(.}.}.]././.}.|.}.!.`.!.}.}.!.!.!.}.}.}.}.}.].}.}.}.",
+"}.|.}.}.|.|.}.}.}.}.}.|.}.}.}. X}.|.}.}.}.|.}.}.}.}.}.|.}.}.}.}."
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 203 2 ",
+"   c black",
+".  c #0B0B0B",
+"X  c #1E1E1E",
+"o  c gray13",
+"O  c #343434",
+"+  c #3B3B3B",
+"@  c #454545",
+"#  c #585858",
+"$  c #616161",
+"%  c gray43",
+"&  c #7C7B7B",
+"*  c #717171",
+"=  c #BC211E",
+"-  c #B82827",
+";  c #B23433",
+":  c #BB1A1A",
+">  c #C90B0A",
+",  c #D30202",
+"<  c #DC0000",
+"1  c #D20A0A",
+"2  c #C31615",
+"3  c #E40000",
+"4  c #EC0000",
+"5  c #F30000",
+"6  c #FE0202",
+"7  c #C92828",
+"8  c #CB3036",
+"9  c #CE342F",
+"0  c #B17437",
+"q  c #B76F27",
+"w  c #BD6A18",
+"e  c #D46E09",
+"r  c #E66C00",
+"t  c #F47301",
+"y  c #FD7D01",
+"u  c #FB7600",
+"i  c #ED7000",
+"p  c #CC7926",
+"a  c #AD4545",
+"s  c #BC4A4A",
+"d  c #AB5757",
+"f  c #B85857",
+"g  c #BF544E",
+"h  c #AF7B48",
+"j  c #AB6969",
+"k  c #B96663",
+"l  c #AB7A7A",
+"z  c #B37878",
+"x  c #BB7169",
+"c  c #C0524C",
+"v  c #C04547",
+"b  c #29B829",
+"n  c #3CAE3B",
+"m  c #40B23E",
+"M  c #02DE02",
+"N  c #07D406",
+"B  c #0AC80A",
+"V  c #00E800",
+"C  c #01FE01",
+"Z  c #01F401",
+"A  c #35C83A",
+"S  c #52B057",
+"D  c #5CBB62",
+"F  c #6DB16E",
+"G  c #3FC242",
+"H  c #43C44E",
+"J  c #BDBD17",
+"K  c #B9B924",
+"L  c #B3B437",
+"P  c #B0AF3E",
+"I  c #FF8302",
+"U  c #C3823C",
+"Y  c #D8D805",
+"T  c #C9C811",
+"R  c #E3E300",
+"E  c #EBEA00",
+"W  c #F3F300",
+"Q  c #FEFE02",
+"!  c #F7FB03",
+"~  c #C5CA3B",
+"^  c #CDCB25",
+"/  c #AF8156",
+"(  c #ADAD47",
+")  c #B1B353",
+"_  c #B1B155",
+"`  c #B6997B",
+"'  c #B28968",
+"]  c #B3B365",
+"[  c #B6B877",
+"{  c #AEAD70",
+"}  c #C2A069",
+"|  c #C2C349",
+" . c #680A8E",
+".. c #690393",
+"X. c #6A009A",
+"o. c #6B178C",
+"O. c #7400A4",
+"+. c #7701AC",
+"@. c #7C00B1",
+"#. c #3546BF",
+"$. c #4B57B3",
+"%. c #414FB7",
+"&. c #5B67B1",
+"*. c #6D75AC",
+"=. c #6A73B3",
+"-. c #2C3DD8",
+";. c #1F39F2",
+":. c #243CEC",
+">. c #233EFC",
+",. c #3346CC",
+"<. c #2943FC",
+"1. c #2944FF",
+"2. c #334CFE",
+"3. c #3952FF",
+"4. c #3750FF",
+"5. c #4555CC",
+"6. c #777EC1",
+"7. c #5B6DCA",
+"8. c #8203B4",
+"9. c #8501BC",
+"0. c #8804BC",
+"q. c #845996",
+"w. c #8B6799",
+"e. c #947D9D",
+"r. c #9073A6",
+"t. c #7B84B2",
+"y. c #7384C2",
+"u. c #858585",
+"i. c #8D8D8D",
+"p. c #9B9A9A",
+"a. c #959494",
+"s. c #908D8F",
+"d. c #AB8787",
+"f. c #B28B8B",
+"g. c #B38686",
+"h. c #AC9393",
+"j. c #AD9D9C",
+"k. c #B39897",
+"l. c #B28E94",
+"z. c #8DB190",
+"x. c #B3A986",
+"c. c #BEB299",
+"v. c #ADAE91",
+"b. c #9A87A5",
+"n. c #989AB7",
+"m. c #A598A8",
+"M. c #B295A7",
+"N. c #97A3B6",
+"B. c #A3A3A3",
+"V. c #ABABAB",
+"C. c #AAA6AA",
+"Z. c #B3A3A3",
+"A. c #B5AAA8",
+"S. c #B6B7AD",
+"D. c #B7B5A6",
+"F. c #ABACB5",
+"G. c #B8A9B4",
+"H. c #B4B3B3",
+"J. c #BBBBBC",
+"K. c #B7B7B7",
+"L. c #AAB8B0",
+"P. c #C2B8BC",
+"I. c #B2C1BB",
+"U. c #C2C2BD",
+"Y. c #BEBEC5",
+"T. c #ADBFC2",
+"R. c #C3BEC2",
+"E. c #BCC2C3",
+"W. c #BBC7C9",
+"Q. c #B7C9CE",
+"!. c #C3C4C4",
+"~. c #C3C4CD",
+"^. c #C4CDCD",
+"/. c #CCCCCB",
+"(. c #CAC6C5",
+"). c #D3CDCB",
+"_. c #D4D3CB",
+"`. c #DAD8CD",
+"'. c #CDCDDC",
+"]. c #C7C8D3",
+"[. c #CBD3D5",
+"{. c #CCD9D9",
+"}. c #C8D3D9",
+"|. c #D5D5D5",
+" X c #D9D6D6",
+".X c #DADBD6",
+"XX c #D4D3DB",
+"oX c #D4DDDD",
+"OX c #DBDBDB",
+"+X c #D7D8D6",
+"@X c #D1CDD3",
+"#X c #DDE2DB",
+"$X c #E2E5DB",
+"%X c #D7C9E5",
+"&X c #CADAE3",
+"*X c #D4DAE3",
+"=X c #D9D9E3",
+"-X c #E4D9E4",
+";X c #D5E2E2",
+":X c #DBE2E2",
+">X c #DCECEC",
+",X c #E4E6E4",
+"<X c white",
+/* pixels */
+"|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.",
+"|.|.|.|.|.+X|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.+X|.|.|.|.|.|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.OX:XOX|.|.|.|.|.OXOX+X|.|.|.|..XOXOXOX|.|.|.|.|.OXOX.X|.|.|.|.|.|.|.|.|.|.|.|.|.|.",
+"|.|.|.|.|. X;XE.A.!.oX X|. XoXJ.A.^.*X|.|.OX'.J.S.~.OX|.|.OX'.K.H.~.OX+X|.OXOX|.+XOXOX|.+X|.|.|.",
+"|.|.|.|.|.{.j 1 < > l oX X^.d 1 3 2 d.oX X!.( Y Y T v.=XOXY.P Y R K D.XX+XJ.V.~.~.V.J.OX|.|.|.|.",
+"|.|.|.|.;Xj 5 6 6 6 3 l >Xd 6 6 6 6 < h.*X( Q Q Q Q Y S.'.K Q Q Q Q T J.|.S.<XV.V.<XH./.+X|.|.|.",
+"|.|.|.|.^.2 6 6 6 6 6 8 E.> 6 6 6 6 6 s K.Y ! ! Q Q Q _ D.R Q Q Q Q W { *XS./.K.H./.H.|.|.|.|.|.",
+"|.|.|.OXE.1 6 6 6 6 6 7 B.< 6 6 6 6 6 8 v.E Q Q Q Q Q ) v.W Q Q Q Q Q _ *X(.B.|.|.B./.OX|.|.|.|.",
+"|.|.|. X{.; 6 6 6 6 6 s Q.2 6 6 6 6 6 f W.T Q Q Q Q W [ K.Y W Q Q Q E x.=XB.u.H.H.u.B.+X|.|.|.|.",
+"|.|.|.|.#Xk., 6 6 6 > Z.>Xd.< 6 6 6 : J.=X{ R Q Q Q K R.*X] E Q Q W L ]..XV.u.J.K.u.C.+X|.|.|.|.",
+"|.|.|.|.|.oXV.s 9 f H.oX_.oXj.s 8 f J.OX|.XXv.| ~ ] R.=X X'.x.~ ~ { ~.OX+X|.!.+X+X~.|.+X|.|.|.|.",
+"|.|.|.|.|.|.$XJ.N.E.$X|.|. X=XI.z.Q.-X|.|.OX&XC.h.Q.:XXX|.OX&Xm.m.].:X|.|.OX,XOXOX,XOX|.|.|.|.|.",
+"|.|.|.|.+X/.&.-.:.-.*./.#X!.n M Z N F %XOXJ.- 3 4 1 l oX+XH.= 4 4 2 h.OX.XB.# J.J.# B.OX|.|.|.|.",
+"|.|.|.|.$X*.>.3.2.2.;.b.OXS C C C C V z.=X; 6 6 6 6 , Z.{.= 6 6 6 6 , V.,XO   # $   O OX|.|.|.|.",
+"|.|.|.XXU.,.2.2.2.2.2.5.D.N C C C C C G G.3 6 6 6 6 6 f Z.3 6 6 6 6 6 j >Xl X B.B.X *.`.|.|.|.|.",
+"|.+X|.OXU.,.2.2.2.2.2.,.B.V C C C C C A M.3 6 6 6 6 6 s h.5 6 6 6 6 6 d ;XQ.K..X.XP.`.`.+X|.|.|.",
+"|.|.|.|.`.$.2.3.2.3.1.&.).b C C C C C F ].> 6 6 6 6 5 e.U., 6 6 6 6 4 h.:Xp.& V.V.F b.%X|.|.|.|.",
+"|.|.|.|.OXV.-.1.1.>.#.K.-Xz.N C C Z b R.:Xd.< 6 6 5 ; ^.$Xx 3 6 6 5 a {..XF.i.J.P.s.G. X|.|.|.|.",
+"|.|.|.|.|..XJ.=.7.t.U.OX|.OXH.D H F R.OX|.oXA.f g l [. X|.oXk.f c g.[. X|.|.@X|.+X_. X X|.|.|.|.",
+"|.|.|.|.|.OX&Xc.` P.*X X|.OXXXA.f.P.oX X|.OX{.j.h.E.;X|.|.OX[.j.h.W.;X|.|.#X,XOX|.+X X X|.|.|.|.",
+"|.|.+X|.XX^.0 r t r h ^.,XJ.q i u r / [.#XH.: 4 5 < j oXOXj.2 4 5 , l oXOXa.+ V./.B.I.OX|.|.|.|.",
+"|.|.|.|.&X/ u I I I t ' &X0 y I I I i f.&X- 6 6 6 6 < k.}.2 6 6 6 6 , A.,XO   # H.& p.+X|.|.|. X",
+"|.|.|.OXW.e I y y I I p F.e I y I y y U C.3 6 6 6 6 6 f j.4 6 6 6 6 6 j >Xs.O C./.B.U.+X|.|.|.|.",
+"|.|.|.+XE.e I y I y I p V.e I y I I y U m.3 6 6 6 6 6 g h.5 6 6 6 6 6 f ;X/.K.|.@XF./.|.|.|.|.|.",
+"|.|.|.|.{.h y I y I u / &Xq y I I I t ' }.: 6 6 6 6 4 g.W.> 6 6 6 6 3 j.:Xp.& C.M.F p.XX|.|.|.|.",
+"|.|.|.|.OXJ.p t u t q R.:XA.e u y t 0 ~.:Xh., 6 6 4 a [.:Xd., 6 6 3 d oX XH.a.!.R.a.H.+X|.|.|.|.",
+"|.|.|.|.|.OXY.` } ` ^.oX|.OXE.` ' k.[.+X|.oXJ.z x k.{.OX|.oXH.x x Z.{.|.|.+X|.+XXXXXXX+X|.|.|.|.",
+"|.|.|.|.|.+X/.n.y.N._.+X|. X~.v.[ D.'.|.|.OX~.f.l A.=X|.|.OXY.b.r.H.#X|.|.OXOXOX X-XOX|.|.|.|.|.",
+"|.|.|.|.OXI.%.:.<.:.$.(.-XS.J W Q E ( ~.:XZ.> 5 6 3 d {.OXm...O.+...w.#XOXu.o B.B.o u.OX|.|.|.|.",
+"|.|.|.|.`.&.>.3.4.3.;.*.|.L Q Q Q Q W { }.= 6 6 6 6 3 f.^. .9.8.8.0.X.m.,XO   $ $   O OX|.|.|.|.",
+"|.|.|.OXU.,.4.2.2.2.4.5.D.Y Q Q Q Q Q ~ m.4 6 6 6 6 6 g n.O.8.8.@.8.+.q.$XB.@ J.H.# B.OX|.|.|.|.",
+"|.|.|.OX(.,.2.2.2.2.2.,.D.Y Q Q Q ! Q ~ m.3 6 6 6 6 6 g m.O.8.@.8.8.@.q.#XY.J.(.@XH./. X|.|.|.|.",
+"|.|.|.|.`.&.>.3.3.3.<.*.|.L Q Q Q Q W { '.- 6 6 6 6 3 l.^.o.9.8.8.9.X.C.|.H.<XF.B.& p.XX|.|.|.|.",
+"|.|.|.|.OXU.%.:.<.:.$.(.#XH.J W ! R ( ].=XZ.2 5 6 < d {.OXm. .O.+...w.OX|.H.H.J.!.p.J.OX|.|.|.|.",
+"|.|.|.|.|.OX_.n.=.F.`..X|.OX].c.[ P.XX X|.OX^.l.l S.;X X|.OX(.b.b.J.#X|. X|./.|.+XOX|.|.|.|.|.|.",
+"|.|.|.|.|.XX~.[ | c.~.XX|.OXE.F S z.@X-X|.OXK.x k l.[.+X|.OXH.w.w.m.+X+X|.OX|.OXOX_.OX|.|.|.+X|.",
+"|.|.|.|.OXK.T W Q W K J.-XB.B C C Z b R.;Xh., 6 6 5 a [..Xe...+.+.X.*.). X* . p.a.. * OX|.|.|.|.",
+"|.|.|.|.'.L Q Q Q Q Q ) '.b C C C C Z F @X2 6 6 6 6 5 g.~. .8.8.8.0.O.b.$XO   $ $   + #X|.|.|.|.",
+"|.|.|.OXY.T Q Q Q Q Q ^ C.V C C C C C A M.3 6 6 6 6 6 c n.O.8.8.8.8.@.q.$XK.* (./.% K.OX|.|.|.|.",
+"|.|.|.OXY.T Q Q Q Q ! ^ F.N C C C C C G M.3 6 6 6 6 6 f n.X.0.8.8.8.+.w.,Xs.+ A.V.+ s.OX|.|.|.|.",
+"|.|.|.|.*X_ ! Q Q Q W { %Xm C C C C V { &X; 6 6 6 6 < k.[.o.@.8.8.9...V.:XO   # $   O OX|.|.|.|.",
+"|.|.|.|.|.~.P R W R ( ].#XR.b V Z M S %XoXH.- 3 5 , j oXOXF.o.O.O...b.OXOXp.@ H.H.+ p.OX|.|.|.|.",
+"|.|.|.|.|. X'.D.x.S.XX|.|.OX%Xv.z.L.=X|.@XOX[.k.f.J.;X X|.OX/.m.b.U.OX|.|.OX,X=XOX,XOX|.|.|.|.+X",
+"|.|.|.|.|.|./.J.F.E.|.|.|.|.^.Y.G.R._.|.|.|./.K.L.!._.|.|.|.~.K.H.(.|.|.|.|.|.|.|.|.|.|.+X|.|.|.",
+"|.|.|.|.|.|.|./.)./.|.|.|.|.[./.^.@X|.|.|.|.@X/.@X/.|.+X|.|.@X/././.+X+X|.|.|.|.|.|.|.+X|.|.+X|.",
+"|.+X|.|.|.|.|.+XXX+X|.|.|.|.|. X+X+X|.|.|.|.|.+X+X+X|.|.|.|.|.+XXX+X|.|.|.|.|.|.|.|.|.|.|.|.|.|.",
+"+X|.+X|.|.|.|.|.+X|.|.|.|.|.|.|.|.|.|.+X|.|.|.|.|.|.|.|.|.|.|.|.|.+X|.|.|.|.|.|.|.|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.+X|.|.|.|.|.|.|.|.|.|.|.|.|.+X|.|.|.|.|.|.+X|.|.|.|.|.|.+X|.|.|.|.|.|.|.|.|.|.|."
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/guess-web.png b/icons/guess-web.png
new file mode 100644 (file)
index 0000000..661bb5d
Binary files /dev/null and b/icons/guess-web.png differ
diff --git a/icons/guess.ico b/icons/guess.ico
new file mode 100644 (file)
index 0000000..9237250
Binary files /dev/null and b/icons/guess.ico differ
diff --git a/icons/guess.rc b/icons/guess.rc
new file mode 100644 (file)
index 0000000..d6ab798
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "guess.ico"
diff --git a/icons/guess.sav b/icons/guess.sav
new file mode 100644 (file)
index 0000000..69852bf
--- /dev/null
@@ -0,0 +1,15 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :5:Guess
+PARAMS  :9:c6p4g10Bm
+CPARAMS :9:c6p4g10Bm
+SEED    :15:313100730915729
+DESC    :8:b5f3faed
+UI      :7:0,0,0,0
+NSTATES :1:6
+STATEPOS:1:6
+MOVE    :8:G1,1,2,2
+MOVE    :8:G4,3,1,1
+MOVE    :8:G5,5,1,1
+MOVE    :8:G4,2,1,6
+MOVE    :8:G2,3,1,6
diff --git a/icons/icon.pl b/icons/icon.pl
new file mode 100755 (executable)
index 0000000..fcb1aa3
--- /dev/null
@@ -0,0 +1,270 @@
+#!/usr/bin/perl 
+
+# Take a collection of input image files and convert them into a
+# multi-resolution Windows .ICO icon file.
+#
+# The input images can be treated as having four different colour
+# depths:
+#
+#  - 24-bit true colour
+#  - 8-bit with custom palette
+#  - 4-bit using the Windows 16-colour palette (see comment below
+#    for details)
+#  - 1-bit using black and white only.
+#
+# The images can be supplied in any input format acceptable to
+# ImageMagick, but their actual colour usage must already be
+# appropriate for the specified mode; this script will not do any
+# substantive conversion. So if an image intended to be used in 4-
+# or 1-bit mode contains any colour not in the appropriate fixed
+# palette, that's a fatal error; if an image to be used in 8-bit
+# mode contains more than 256 distinct colours, that's also a fatal
+# error.
+#
+# Command-line syntax is:
+#
+#   icon.pl -depth imagefile [imagefile...] [-depth imagefile [imagefile...]]
+#
+# where `-depth' is one of `-24', `-8', `-4' or `-1', and tells the
+# script how to treat all the image files given after that option
+# until the next depth option. For example, you might execute
+#
+#   icon.pl -24 48x48x24.png 32x32x24.png -8 32x32x8.png -1 monochrome.png
+#
+# to build an icon file containing two differently sized 24-bit
+# images, one 8-bit image and one black and white image.
+#
+# Windows .ICO files support a 1-bit alpha channel on all these
+# image types. That is, any pixel can be either opaque or fully
+# transparent, but not partially transparent. The alpha channel is
+# separate from the main image data, meaning that `transparent' is
+# not required to take up a palette entry. (So an 8-bit image can
+# have 256 distinct _opaque_ colours, plus transparent pixels as
+# well.) If the input images have alpha channels, they will be used
+# to determine which pixels of the icon are transparent, by simple
+# quantisation half way up (e.g. in a PNG image with an 8-bit alpha
+# channel, alpha values of 00-7F will be mapped to transparent
+# pixels, and 80-FF will become opaque).
+
+# The Windows 16-colour palette consists of:
+#  - the eight corners of the colour cube (000000, 0000FF, 00FF00,
+#    00FFFF, FF0000, FF00FF, FFFF00, FFFFFF)
+#  - dim versions of the seven non-black corners, at 128/255 of the
+#    brightness (000080, 008000, 008080, 800000, 800080, 808000,
+#    808080)
+#  - light grey at 192/255 of full brightness (C0C0C0).
+%win16pal = (
+    "\x00\x00\x00\x00" => 0,
+    "\x00\x00\x80\x00" => 1,
+    "\x00\x80\x00\x00" => 2,
+    "\x00\x80\x80\x00" => 3,
+    "\x80\x00\x00\x00" => 4,
+    "\x80\x00\x80\x00" => 5,
+    "\x80\x80\x00\x00" => 6,
+    "\xC0\xC0\xC0\x00" => 7,
+    "\x80\x80\x80\x00" => 8,
+    "\x00\x00\xFF\x00" => 9,
+    "\x00\xFF\x00\x00" => 10,
+    "\x00\xFF\xFF\x00" => 11,
+    "\xFF\x00\x00\x00" => 12,
+    "\xFF\x00\xFF\x00" => 13,
+    "\xFF\xFF\x00\x00" => 14,
+    "\xFF\xFF\xFF\x00" => 15,
+);
+@win16pal = sort { $win16pal{$a} <=> $win16pal{$b} } keys %win16pal;
+
+# The black and white palette consists of black (000000) and white
+# (FFFFFF), obviously.
+%win2pal = (
+    "\x00\x00\x00\x00" => 0,
+    "\xFF\xFF\xFF\x00" => 1,
+);
+@win2pal = sort { $win16pal{$a} <=> $win2pal{$b} } keys %win2pal;
+
+@hdr = ();
+@dat = ();
+
+$depth = undef;
+foreach $_ (@ARGV) {
+    if (/^-(24|8|4|1)$/) {
+       $depth = $1;
+    } elsif (defined $depth) {
+       &readicon($_, $depth);
+    } else {
+       $usage = 1;
+    }
+}
+if ($usage || length @hdr == 0) {
+    print "usage: icon.pl ( -24 | -8 | -4 | -1 ) image [image...]\n";
+    print "             [ ( -24 | -8 | -4 | -1 ) image [image...] ...]\n";
+    exit 0;
+}
+
+# Now write out the output icon file.
+print pack "vvv", 0, 1, scalar @hdr; # file-level header
+$filepos = 6 + 16 * scalar @hdr;
+for ($i = 0; $i < scalar @hdr; $i++) {
+    print $hdr[$i];
+    print pack "V", $filepos;
+    $filepos += length($dat[$i]);
+}
+for ($i = 0; $i < scalar @hdr; $i++) {
+    print $dat[$i];
+}
+
+sub readicon {
+    my $filename = shift @_;
+    my $depth = shift @_;
+    my $pix;
+    my $i;
+    my %pal;
+
+    # Determine the icon's width and height.
+    my $w = `identify -format %w $filename`;
+    my $h = `identify -format %h $filename`;
+
+    # Read the file in as RGBA data. We flip vertically at this
+    # point, to avoid having to do it ourselves (.BMP and hence
+    # .ICO are bottom-up).
+    my $data = [];
+    open IDATA, "convert -set colorspace sRGB -flip -depth 8 $filename rgba:- |";
+    push @$data, $rgb while (read IDATA,$rgb,4,0) == 4;
+    close IDATA;
+    # Check we have the right amount of data.
+    $xl = $w * $h;
+    $al = scalar @$data;
+    die "wrong amount of image data ($al, expected $xl) from $filename\n"
+      unless $al == $xl;
+
+    # Build the alpha channel now, so we can exclude transparent
+    # pixels from the palette analysis. We replace transparent
+    # pixels with undef in the data array.
+    #
+    # We quantise the alpha channel half way up, so that alpha of
+    # 0x80 or more is taken to be fully opaque and 0x7F or less is
+    # fully transparent. Nasty, but the best we can do without
+    # dithering (and don't even suggest we do that!).
+    my $x;
+    my $y;
+    my $alpha = "";
+
+    for ($y = 0; $y < $h; $y++) {
+       my $currbyte = 0, $currbits = 0;
+       for ($x = 0; $x < (($w+31)|31)-31; $x++) {
+           $pix = ($x < $w ? $data->[$y*$w+$x] : "\x00\x00\x00\xFF");
+           my @rgba = unpack "CCCC", $pix;
+           $currbyte <<= 1;
+           $currbits++;
+           if ($rgba[3] < 0x80) {
+               if ($x < $w) {
+                   $data->[$y*$w+$x] = undef;
+               }
+               $currbyte |= 1; # MS has the alpha channel inverted :-)
+           } else {
+               # Might as well flip RGBA into BGR0 while we're here.
+               if ($x < $w) {
+                   $data->[$y*$w+$x] = pack "CCCC",
+                     $rgba[2], $rgba[1], $rgba[0], 0;
+               }
+           }
+           if ($currbits >= 8) {
+               $alpha .= pack "C", $currbyte;
+               $currbits -= 8;
+           }
+       }
+    }
+
+    # For an 8-bit image, check we have at most 256 distinct
+    # colours, and build the palette.
+    %pal = ();
+    if ($depth == 8) {
+       my $palindex = 0;
+       foreach $pix (@$data) {
+           next unless defined $pix;
+           $pal{$pix} = $palindex++ unless defined $pal{$pix};
+       }
+       die "too many colours in 8-bit image $filename\n" unless $palindex <= 256;
+    } elsif ($depth == 4) {
+       %pal = %win16pal;
+    } elsif ($depth == 1) {
+       %pal = %win2pal;
+    }
+
+    my $raster = "";
+    if ($depth < 24) {
+       # For a non-24-bit image, flatten the image into one palette
+       # index per pixel.
+       $pad = 32 / $depth; # number of pixels to pad scanline to 4-byte align
+       $pmask = $pad-1;
+       for ($y = 0; $y < $h; $y++) {
+           my $currbyte = 0, $currbits = 0;
+           for ($x = 0; $x < (($w+$pmask)|$pmask)-$pmask; $x++) {
+               $currbyte <<= $depth;
+               $currbits += $depth;
+               if ($x < $w && defined ($pix = $data->[$y*$w+$x])) {
+                   if (!defined $pal{$pix}) {
+                        my $pixprintable = unpack "H*", $pix;
+                       die "illegal colour value $pixprintable at pixel ($x,$y) in $filename\n";
+                   }
+                   $currbyte |= $pal{$pix};
+               }
+               if ($currbits >= 8) {
+                   $raster .= pack "C", $currbyte;
+                   $currbits -= 8;
+               }
+           }
+       }
+    } else {
+       # For a 24-bit image, reverse the order of the R,G,B values
+       # and stick a padding zero on the end.
+       #
+       # (In this loop we don't need to bother padding the
+       # scanline out to a multiple of four bytes, because every
+       # pixel takes four whole bytes anyway.)
+       for ($i = 0; $i < scalar @$data; $i++) {
+           if (defined $data->[$i]) {
+               $raster .= $data->[$i];
+           } else {
+               $raster .= "\x00\x00\x00\x00";
+           }
+       }
+       $depth = 32; # and adjust this
+    }
+
+    # Prepare the icon data. First the header...
+    my $data = pack "VVVvvVVVVVV",
+      40, # size of bitmap info header
+      $w, # icon width
+      $h*2, # icon height (x2 to indicate the subsequent alpha channel)
+      1, # 1 plane (common to all MS image formats)
+      $depth, # bits per pixel
+      0, # no compression
+      length $raster, # image size
+      0, 0, 0, 0; # resolution, colours used, colours important (ignored)
+    # ... then the palette ...
+    if ($depth <= 8) {
+       my $ncols = (1 << $depth);
+       my $palette = "\x00\x00\x00\x00" x $ncols;
+       foreach $i (keys %pal) {
+           substr($palette, $pal{$i}*4, 4) = $i;
+       }
+       $data .= $palette;
+    }
+    # ... the raster data we already had ready ...
+    $data .= $raster;
+    # ... and the alpha channel we already had as well.
+    $data .= $alpha;
+
+    # Prepare the header which will represent this image in the
+    # icon file.
+    my $header = pack "CCCCvvV",
+      $w, $h, # width and height (this time the real height)
+      1 << $depth, # number of colours, if less than 256
+      0, # reserved
+      1, # planes
+      $depth, # bits per pixel
+      length $data; # size of real icon data
+
+    push @hdr, $header;
+    push @dat, $data;
+}
diff --git a/icons/inertia-16d24.png b/icons/inertia-16d24.png
new file mode 100644 (file)
index 0000000..da44eb5
Binary files /dev/null and b/icons/inertia-16d24.png differ
diff --git a/icons/inertia-16d4.png b/icons/inertia-16d4.png
new file mode 100644 (file)
index 0000000..32b0792
Binary files /dev/null and b/icons/inertia-16d4.png differ
diff --git a/icons/inertia-16d8.png b/icons/inertia-16d8.png
new file mode 100644 (file)
index 0000000..9620641
Binary files /dev/null and b/icons/inertia-16d8.png differ
diff --git a/icons/inertia-32d24.png b/icons/inertia-32d24.png
new file mode 100644 (file)
index 0000000..3184683
Binary files /dev/null and b/icons/inertia-32d24.png differ
diff --git a/icons/inertia-32d4.png b/icons/inertia-32d4.png
new file mode 100644 (file)
index 0000000..eaa93dd
Binary files /dev/null and b/icons/inertia-32d4.png differ
diff --git a/icons/inertia-32d8.png b/icons/inertia-32d8.png
new file mode 100644 (file)
index 0000000..c3c70f0
Binary files /dev/null and b/icons/inertia-32d8.png differ
diff --git a/icons/inertia-48d24.png b/icons/inertia-48d24.png
new file mode 100644 (file)
index 0000000..b86be48
Binary files /dev/null and b/icons/inertia-48d24.png differ
diff --git a/icons/inertia-48d4.png b/icons/inertia-48d4.png
new file mode 100644 (file)
index 0000000..894d2d6
Binary files /dev/null and b/icons/inertia-48d4.png differ
diff --git a/icons/inertia-48d8.png b/icons/inertia-48d8.png
new file mode 100644 (file)
index 0000000..71b1ccd
Binary files /dev/null and b/icons/inertia-48d8.png differ
diff --git a/icons/inertia-base.png b/icons/inertia-base.png
new file mode 100644 (file)
index 0000000..329028f
Binary files /dev/null and b/icons/inertia-base.png differ
diff --git a/icons/inertia-ibase.png b/icons/inertia-ibase.png
new file mode 100644 (file)
index 0000000..ab303d1
Binary files /dev/null and b/icons/inertia-ibase.png differ
diff --git a/icons/inertia-ibase4.png b/icons/inertia-ibase4.png
new file mode 100644 (file)
index 0000000..dfa5e89
Binary files /dev/null and b/icons/inertia-ibase4.png differ
diff --git a/icons/inertia-icon.c b/icons/inertia-icon.c
new file mode 100644 (file)
index 0000000..7812e5c
--- /dev/null
@@ -0,0 +1,546 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 248 2 ",
+"   c #D5D5D5",
+".  c #CECECE",
+"X  c gray85",
+"o  c gainsboro",
+"O  c gray81",
+"+  c #D0D0D0",
+"@  c gray83",
+"#  c gray82",
+"$  c #D2D2D2",
+"%  c LightGray",
+"&  c #D2D2D2",
+"*  c gray84",
+"=  c #D5D5D5",
+"-  c #D5D5D5",
+";  c #D5D5D5",
+":  c #D5D5D5",
+">  c #D2D2D2",
+",  c #E4E4E4",
+"<  c gray59",
+"1  c #717171",
+"2  c #E2E2E2",
+"3  c #CECECE",
+"4  c gray72",
+"5  c gray78",
+"6  c gray",
+"7  c gray75",
+"8  c #E6E6E6",
+"9  c gray91",
+"0  c #E6E6E6",
+"q  c #E9E9E9",
+"w  c gray84",
+"e  c #D0D0D0",
+"r  c gainsboro",
+"t  c #9D9D9D",
+"y  c #353535",
+"u  c gray3",
+"i  c #6F6F6F",
+"p  c gray80",
+"a  c gray73",
+"s  c #D8D8D8",
+"d  c gray82",
+"f  c gray72",
+"g  c gray90",
+"h  c gray88",
+"j  c #DDDDDD",
+"k  c #E1E1E1",
+"l  c #C8C8C8",
+"z  c #CECECE",
+"x  c #DDDDDD",
+"c  c gray31",
+"v  c #0C0C0C",
+"b  c gray3",
+"n  c #1D1D1D",
+"m  c #B9B9B9",
+"M  c #D7D7D7",
+"N  c gray84",
+"B  c gray85",
+"V  c #CDCDCD",
+"C  c #E2E2E2",
+"Z  c #E1E1E1",
+"A  c gray87",
+"S  c #E2E2E2",
+"D  c #CBCBCB",
+"F  c gray81",
+"G  c #D7D7D7",
+"H  c #D7D7D7",
+"J  c #3C3D3D",
+"K  c #131313",
+"L  c #B9B9B9",
+"P  c LightGray",
+"I  c gray68",
+"U  c #D1D2D2",
+"Y  c #C6C6C6",
+"T  c #B2B2B2",
+"R  c #E7E7E7",
+"E  c #DFDFDF",
+"W  c #DFDFDF",
+"Q  c #E1E1E1",
+"!  c #CACACA",
+"~  c #CECECE",
+"^  c #D3D2D2",
+"/  c #D4D5D5",
+"(  c #C9C8C8",
+")  c #B8B5B5",
+"_  c #D9DBDB",
+"`  c gray79",
+"'  c #C7C8C8",
+"]  c #CCC9C9",
+"[  c #C7C6C6",
+"{  c #CBCCCC",
+"}  c #D0D0D0",
+"|  c #CBCBCB",
+" . c gray77",
+".. c gray80",
+"X. c gray",
+"o. c #D2D2D2",
+"O. c #D4D5D5",
+"+. c #DBD6D6",
+"@. c #B8C1C1",
+"#. c #A8BDBD",
+"$. c #D8D1D1",
+"%. c #CED0D0",
+"&. c #DDD5D5",
+"*. c #A5B9B9",
+"=. c #B9C2C2",
+"-. c #D8D3D3",
+";. c #DADBDB",
+":. c gray71",
+">. c #222222",
+",. c #A5A5A5",
+"<. c #E1E1E1",
+"1. c gray81",
+"2. c #DAD9D9",
+"3. c #B9BFBF",
+"4. c #8ADEDE",
+"5. c #8CF4F4",
+"6. c #A2BCBC",
+"7. c #D6CECE",
+"8. c #A2BCBC",
+"9. c #8DF4F4",
+"0. c #89DEDE",
+"q. c #BCC3C3",
+"w. c #AAA8A8",
+"e. c #2A2A2A",
+"r. c #2A2A2A",
+"t. c #040404",
+"y. c gray62",
+"u. c #DADADA",
+"i. c #DAD9D9",
+"p. c #B9BFBF",
+"a. c #8BDFDF",
+"s. c #8DF5F5",
+"d. c #A1BCBC",
+"f. c #D6CECE",
+"g. c #A2BCBC",
+"h. c #8DF4F4",
+"j. c #89DEDE",
+"k. c #BCC2C2",
+"l. c #AFADAD",
+"z. c #141515",
+"x. c black",
+"c. c #0B0B0B",
+"v. c #9D9D9D",
+"b. c #DADADA",
+"n. c #D3D4D4",
+"m. c #DDD8D8",
+"M. c #B7BFBF",
+"N. c #A2B7B7",
+"B. c #DBD3D3",
+"V. c #CDD0D0",
+"C. c #DDD5D5",
+"Z. c #A5B9B9",
+"A. c #B9C2C2",
+"S. c #D9D4D4",
+"D. c #DBDBDC",
+"F. c #B6B8B6",
+"G. c #232723",
+"H. c #A6A8A6",
+"J. c #DFDFDF",
+"K. c gray81",
+"L. c gray83",
+"P. c #CECFCF",
+"I. c #D3D1D1",
+"U. c #D5D1D1",
+"Y. c #CECFCF",
+"T. c #CCCBCB",
+"R. c #C6C8C8",
+"E. c #CCC9C9",
+"W. c #C8C6C6",
+"Q. c #CACACB",
+"!. c #CCCDCB",
+"~. c #D3C9D3",
+"^. c #CEBCCE",
+"/. c #D2C7D2",
+"(. c #CCCDCC",
+"). c #D2D2D2",
+"_. c #D5D5D5",
+"`. c gray84",
+"'. c #D5D5D5",
+"]. c #D4D5D5",
+"[. c #D7D7D7",
+"{. c #CBCBCB",
+"}. c gray69",
+"|. c #D1D2D2",
+" X c #C5C5C5",
+".X c #B4B6B3",
+"XX c #CAC1CA",
+"oX c #659F65",
+"OX c #21D721",
+"+X c #59A359",
+"@X c #C3B9C3",
+"#X c #D4D7D4",
+"$X c #D5D5D5",
+"%X c #D5D5D5",
+"&X c #D5D5D5",
+"*X c #D5D5D5",
+"=X c gray84",
+"-X c gray79",
+";X c gray83",
+":X c #D7D7D7",
+">X c gray85",
+",X c #D1D2D1",
+"<X c #BEB7BE",
+"1X c #16DA16",
+"2X c green",
+"3X c #09E209",
+"4X c #B3B7B3",
+"5X c #D8D7D8",
+"6X c #D5D5D5",
+"7X c #D5D5D5",
+"8X c #D7D7D7",
+"9X c gray78",
+"0X c gray73",
+"qX c #D7D7D7",
+"wX c gray81",
+"eX c #BABCBA",
+"rX c #C2B7C2",
+"tX c #34B534",
+"yX c #00F900",
+"uX c #26BD26",
+"iX c #B9B2B9",
+"pX c #D6D8D6",
+"aX c #D5D5D5",
+"sX c #D5D5D5",
+"dX c gray82",
+"fX c #BCBCBC",
+"gX c #CACACA",
+"hX c #C1C1C1",
+"jX c gray77",
+"kX c #D4D2D4",
+"lX c #B6B0B6",
+"zX c #9EB99E",
+"xX c #B2AFB2",
+"cX c #D0CED0",
+"vX c #D2D3D2",
+"bX c #D5D5D5",
+"nX c #D5D5D5",
+"mX c gray85",
+"MX c #D5D5D5",
+"NX c #D7D7D7",
+"BX c #D8D8D8",
+"VX c #D4D5D4",
+"CX c #DADBDA",
+"ZX c #E0D9E0",
+"AX c gray86",
+"SX c #D5D6D5",
+"DX c #D5D5D5",
+"FX c white",
+/* pixels */
+"  . X o O + @ # $ % & * = - ; : ",
+"> , < 1 2 3 4 5 6 7 8 9 0 q w e ",
+"r t y u i p a s d f g h j k l z ",
+"x c v b n m M N B V C Z A S D F ",
+"G H J K L P I U Y T R E W Q ! ~ ",
+"^ / ( ) _ ` ' ] [ { } |  ...X.o.",
+"O.+.@.#.$.%.&.*.=.-.;.:.>.,.<.1.",
+"2.3.4.5.6.7.8.9.0.q.w.e.r.t.y.u.",
+"i.p.a.s.d.f.g.h.j.k.l.z.x.c.v.b.",
+"n.m.M.N.B.V.C.Z.A.S.D.F.G.H.J.K.",
+"L.P.I.U.Y.T.R.E.W.Q.!.~.^./.(.).",
+"_.`.'.].[.{.}.|. X.XXXoXOX+X@X#X",
+"$X%X&X*X=X-X;X:X>X,X<X1X2X3X4X5X",
+"6X6X6X7X8X9X0XqXwXeXrXtXyXuXiXpX",
+"6X6X6XaXsXdXfXgXhXjXkXlXzXxXcXvX",
+"6X6X6X6XbXnXmXMXNXBXVXCXZXAXSXDX"
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 78 1 ",
+"  c #020202",
+". c #0C0C0C",
+"X c gray15",
+"o c #2B2B2B",
+"O c #3C3C3C",
+"+ c #464646",
+"@ c gray29",
+"# c gray33",
+"$ c #5B5B5B",
+"% c gray38",
+"& c #6C6C6C",
+"* c gray46",
+"= c #13BC13",
+"- c #1FBB1F",
+"; c #2DAD2D",
+": c #20B920",
+"> c #3CA53C",
+", c #0ECA0E",
+"< c #00D100",
+"1 c #0AD20A",
+"2 c #12C112",
+"3 c #00E200",
+"4 c #00F200",
+"5 c #02FE02",
+"6 c #4FA34F",
+"7 c #49A849",
+"8 c #628F62",
+"9 c #748574",
+"0 c #758875",
+"q c #7E837E",
+"w c #77A577",
+"e c #7AA07A",
+"r c #7A9595",
+"t c #7C9B9B",
+"y c #69B3B3",
+"u c #74C3C3",
+"i c #77C8C8",
+"p c #808080",
+"a c #8A838A",
+"s c #8D8D8D",
+"d c #928992",
+"f c #949494",
+"g c #9A939A",
+"h c #9C9C9C",
+"j c #A29BA2",
+"k c #9DA2A2",
+"l c #A3A3A3",
+"z c #A8A6A6",
+"x c #ABA2AB",
+"c c #ACACAC",
+"v c #B2A3B2",
+"b c #B2AFB2",
+"n c #BCADBC",
+"m c #B5B5B5",
+"M c #B7B8B7",
+"N c #BBBBBB",
+"B c #BFC2BF",
+"V c #8AEBEB",
+"C c #8DF0F0",
+"Z c #97FBFB",
+"A c #9EFFFF",
+"S c #A2FFFF",
+"D c #C4C4C4",
+"F c #C6C9C6",
+"G c #CCCCCC",
+"H c #D3CCCC",
+"J c #CDD0CD",
+"K c #D5C6D5",
+"L c #DBCBDB",
+"P c #D5D5D5",
+"I c #DED6DE",
+"U c #DBDCDB",
+"Y c #E2DFDF",
+"T c #E3D2E3",
+"R c #E5DBE5",
+"E c #E3E3E3",
+"W c #EEEEEE",
+"Q c #F2F2F2",
+/* pixels */
+"PPPUPPPPIPPPPUPPPPIPPPPUPPPPIPPP",
+"PPGGGPUGGGGJGJJGPJGGJPJJJJGJJJPP",
+"PPPGPm*PJJFDPGmPDNUGPQWQQQQWQPPP",
+"PPPPU$ hEPGPGdMUGfcPUQYEEEEEEmPP",
+"PPUGo   &EJGsGUPPEgcEQUYUUEUUmPP",
+"PPG# mX  hPNMUPPPPJcEQUYEEUEUMPP",
+"PE@ .O.   dYUPPPPPUPUQUYUEEUUmPP",
+"PUcO     &DDFPPPPPPMEQUYEUUEUbPP",
+"PPEN. . +EPFsPUPPEllEQUUUEERUmPP",
+"PPPUBO *PPGPNsFPPlkPUQEEEEEEEmPP",
+"PPPGEz#PPJGGUNcPBcPKEUDDDFDFDcPP",
+"PJFGFJEFGGDDGGPHJJGFFDNNMMNNNNPP",
+"PPPPUGzUPPJGUPUcGUPPFPUW$oEUUGPP",
+"PPPUJfykRPJGPUlytHIPGPP#  ODPGPP",
+"PPUHtVSuhRGFEkuSVrHPDW$.%. OEGJP",
+"PPHtCZZAikPPkuSZACtGG@ og   oNPP",
+"PPJtVAZSikPPkuSZACrGG@      oNPP",
+"PPUJrVSuhRGGRhuSVrHPDW%    OEGHU",
+"PPPUHfyhUPGGPUlytHUPFPP#  ODUGPP",
+"PPPPUJcIPUJJUPUcGUPPGPPW#oEUPGJP",
+"PPGFFGJGFGDDGGJJJJGGBFGHDBJFGDPP",
+"PPPPPPJPPPGGUBcIDcPJDPHxLTmJPFPP",
+"PPPPPUPPPUGPNsFPPjhUGPgr670dGJJP",
+"PPPPPPPPPPJDsPUPPElzPj9155<8gGPP",
+"PPPPPPIPPIJBDIPPPPPNGn;55552xGPP",
+"PPPPPPPPPPGPUJPPPPPUGK=5555<MPGP",
+"PPPPPPUPPPPNNUPPPUGcJx75555:jFPP",
+"PPPPPPPPPPGGsGUPPYfmPc*-44,9jGPP",
+"PPPPPPPPPPGPGsmUJfzUFUlaewagPGPP",
+"PUPPPPPPPUJJUJNUFDUPGPUBIRDPPGPP",
+"PPPPPPUPPPPPPPUPPUPPPPPUPJUPPPPP",
+"PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP"
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 97 2 ",
+"   c #010101",
+".  c #0B0B0B",
+"X  c #121212",
+"o  c #181818",
+"O  c gray14",
+"+  c gray17",
+"@  c gray20",
+"#  c #3C3C3C",
+"$  c #454545",
+"%  c #4E4E4E",
+"&  c #545454",
+"*  c #5D5D5D",
+"=  c #5E7A7A",
+"-  c gray39",
+";  c #616B6B",
+":  c #6A6A6A",
+">  c #6A7575",
+",  c #617C7C",
+"<  c #6A7979",
+"1  c #757575",
+"2  c #7C7C7C",
+"3  c #1F9F1F",
+"4  c #02AC02",
+"5  c #0DA20D",
+"6  c #05B005",
+"7  c #00BC00",
+"8  c #249424",
+"9  c #3D8C3D",
+"0  c #359335",
+"q  c #389238",
+"w  c #00C100",
+"e  c #00CE00",
+"r  c #00D000",
+"t  c #00D900",
+"y  c #00E200",
+"u  c #00EC00",
+"i  c #01FE01",
+"p  c #468C46",
+"a  c #4F8F4F",
+"s  c #6F896F",
+"d  c #778C77",
+"f  c #7D807D",
+"g  c #788978",
+"h  c #837F83",
+"j  c #5A8484",
+"k  c #5F8989",
+"l  c #5D9696",
+"z  c #5E9A9A",
+"x  c #619C9C",
+"c  c #77D4D4",
+"v  c #7BD3D3",
+"b  c #7EDCDC",
+"n  c #858585",
+"m  c #858885",
+"M  c #8A8A8A",
+"N  c #8B948B",
+"B  c #938893",
+"V  c #9C8E9C",
+"C  c #939393",
+"Z  c #9C939C",
+"A  c #9C9B9B",
+"S  c #9EA19E",
+"D  c #A297A2",
+"F  c #A39EA3",
+"G  c #A4A4A4",
+"H  c #A7AAA7",
+"J  c #ACA4AC",
+"K  c #ADADAD",
+"L  c #B5ADAD",
+"P  c #B5A8B5",
+"I  c #B4B3B3",
+"U  c #B7B8B7",
+"Y  c #BCBCBC",
+"T  c #BFC2BF",
+"R  c #CDBCCD",
+"E  c #88E7E7",
+"W  c #8BECEC",
+"Q  c #93F6F6",
+"!  c #96FBFB",
+"~  c #9BFEFE",
+"^  c #A3FFFF",
+"/  c #C4C4C4",
+"(  c #C8C2C2",
+")  c #CACACA",
+"_  c #CFD0CF",
+"`  c #D5D5D5",
+"'  c #D4D8D4",
+"]  c #D7D9D9",
+"[  c #DBDCDC",
+"{  c #E0DEDE",
+"}  c #E6D7E6",
+"|  c #E6D8E6",
+" . c #E2E2E2",
+".. c #EAE7EA",
+"X. c #ECECEC",
+"o. c #F1F1F1",
+"O. c #FCFCFC",
+/* pixels */
+"` ` ` ` ` ` ` ` ` ` ` ` ` ] ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ] ` ` ` ` ` ` ` ",
+"` ` ` ` ` ` ` ] ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ] ` ' ` ` ` ` ` ` ' ` ] ` ` ` ` ` ",
+"` ` ` ` ] ` ] ] ] ] ` ] ] ` [ ` ` ` [ ` ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ` ] ] ] ] ] ` ` ` ` ` ",
+"` ` ` ` ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) / ) ) ) ) ) ( ) ) ( ) ) ) / ) / ) ) ) / ) ) / ) ` ` ] ` ",
+"` ` ` ` ) ) ) ) ) T Y / ) ) ) ) ( ( ) ) ` _ ) ) ) ) ` ) ) ( _ o.o.o.o.o.o.o.o.o.o.X.o._ ` ' ` ` ",
+"` ` ` ` ` ` ` [ ..#   K ..` ] ] ) ) '  .I f ) ] ` m G  .] _ [ O.o.o.o.o.o.o.o.o.o.o. .U ` ' ` ` ",
+"` ` ` ` ` ` ] ` M X   % Y { ` ` ) )  .A 2 / ] ` ` ) m M { ) | O.] [ [ [ ] [ ] [ [  ./ K ` ` ` ` ",
+"` ` ` ` ` ` _ #         . A [ ` / ` I 2 { ] ` ` ` ]  .M S ] ] O.[ {  .{  . . . .|  ./ K ] ` ` ` ",
+"` ` ` ` ] o.*   @ $       X / ..) ) 2 ) ] ` ' ` ` ` ` ` 2 / { O.[  .{ {  .[  .[ {  ./ K ] ` ` ] ",
+"` ` ` [ I 1 o   H O..       % A ) ) / ` ` ` ` ` ` ` ` ] ) / [ O.[ [ [  .[  .[  .[ ../ L [ ` ` ` ",
+"` ` `  .n       . .           O ) ) [ ` ` ` ` ` ` ` ` ` [ ) [ O.[  .[  . .[  .[ {  ./ K ` ` ` ` ",
+"` ` ` { A @ .               O - ) ) ] ` ` ` ` ` ` ` ` ` ] ) { O.[ [  .[ [  .[  . . .) L ` ` ` ` ",
+"` ` ` ` [ ..$               I X./ ) M ` ` ` ` ` ` ` ` [ M / { O.{ { {  . .[  .[ [  .) K ` ` ` ` ",
+"` ` ` ` ` ] I o           : [ ` / ] A C ..` ` ` ` _ ..G M ` ] O.]  . .[ [  .[  .[  ./ I ] ` ` ` ",
+"` ` ` ` ` ` { Y % .   O M ] ` ` ) ) ] f A ] ] ` ` [ H 1 ` _ [ O.] { { { { {  .[ [  ./ K ] ` ` ` ",
+"` ` ` ` ` ` ` ] ..+   G X.` ` ] ) _ ` [ B 2 ) [ ` n n ` [ ) [ O. . . . . . . . . . .) I ] ` ` ` ",
+"` ` ` ` _ ` ` _ _ G C / ` ` _ ` ( ) ` ` [ / ) ` _ T ] ` ` ) [ ..I I I I I I I I I U K I ` ` ` ` ",
+"` ` ` ` / / ( / / ) _ / R / ( ( Y Y / / T ) / T ( ) T / / T Y / Y Y Y Y ) ) Y Y Y Y Y Y ` ` ` ` ",
+"` ` ` ` ` ` ` ] ] [ [ ` _ ' ` [ ) _ ` ` ] ` ] { ] ' ] ] ` _ / ] ] ] [ _ % * ` ] ` ] ] / ` ` ` ` ",
+"` ` ` ` ` ` ` ` { A > ` ] ` ` ` ) ) ] ` ` { / ; I | _ ` ` _ ) ] ` `  .A   . I [ ` ` ` / ` ] ` ` ",
+"` ` ] ` ` ` `  .A x v < _ [ _ [ ) ) ` ` [ R , b k L [ ` ` _ ) ` ` ` &         : ] ` ` / ' ` ` ` ",
+"` ` ` ` ` ` { A x ~ ^ v > _ [ ` ) ) ` [ ( = E ^ Q j L  .` ` ( `  .&             :  .` ( ` ] ` ` ",
+"` ` ` ` ` { A l ~ ~ Q ^ v > _ { / ) [ ( = E ^ ! ~ ! j L  .) / ` A      .I       X I _ / ` ] ` ` ",
+"` ` ` ` { A l ~ ~ ~ ~ ~ ^ E > ` ) _ ) = W ~ ! ~ ! ~ ! j I ` ` $       : %           * ` ) ` ` ` ",
+"` ` ` ` [ A z ^ ~ ~ ~ ! ^ b > ` / ` ) = W ^ ! ! ! ^ ! j I ` ` $                     * ` _ ] ` ` ",
+"` ` ` ` `  .A l ~ ~ Q ^ v > ` { ) ) [ ( = W ^ ! ~ ! j L  .) ( ` A               X K _ / ` ` ` ` ",
+"` ` ` ] ` `  .A l ~ ^ v < ` ` ` / ) ` [ ( = E ^ Q j L [ ` _ / ]  .&             : ..] / ` ` ` ` ",
+"` ` ` ` ` ` `  .A x v < _ [ ` ` ) ) ` ` [ ( , b k L  .` ` ` / ` ] ` &       . : ] ` ' ( ` ` ` ` ",
+"` ` ` ` ` ` ` ` | A > ` ] ` ` ] ) ) ] ` ` ] / ; I [ _ ` ` ) / ] ` ` { S     I  .` ` ` / ` ` ` ` ",
+"` ` ` ` ` ` ` ` ` { { ] ` ] ` ] ) ) [ ] ` ` [ { [ ' ] ' ] ` ) ] ] ` ] ) % * ` ` ` ` ] / ` ` ` ` ",
+"' ` ` ` / ( / / / / / / / / / / Y T / ( / ) / / / ) R T / / Y ( / / / ) ` ` / / / / / Y ` ` ` ` ",
+"` ` ` ' _ _ ` ` _ ` _ _ _ ` ` ` / / ` ` [ / ) ` ) T ] ` ` ) / ' _ ] ) T ` ` / _ ] ) ` / ` ` ` ` ",
+"` ` ` ` ` ] ` ` ] ` ` ] ` ` ` [ ) ) ] ] C 2 ) [ ` m m _ ] ` / ` { K 2 P R R J 2 U | ` / ` ` ` ` ",
+"` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ] ) ) [ f A [ ` ` ` | H 1 ` _ / | G n C q 5 5 p Z 2 K [ T ` ` ` ` ",
+"` ` ] ` ` ` ` ` ` ` ` ] ` ` ` ] ( ` A C  .` ` ` ` `  .H m ] ) T 2 C 4 i i i i 5 A 2 ) ) ` ` ` ` ",
+"` ` ` ` ] ` ` ` ` ` ` ` ` ` ` ` ) ) n ` ` ` ` ` ` ` ` [ M / _ A V 3 i i i i i i 0 Z H ) ` ] ` ` ",
+"` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ] / ) ` ` ` ` ` ` ` ` ` ` ] _ /  .N e i i i i i i 7 S [ / ` ` ` ` ",
+"` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ] ) ) [ ` ` ` ` ` ` ` ` ` ] ` T ..d y i i i i i i r C ..T ` ` ` ` ",
+"` ] ` ` ` ` ` ` ` ` ` ` ` ` ` ] ) ) / ` ` ` ` ` ` ` ` ` ) ) ( ` C w i i i i i i 6 F _ / ` ` ` ` ",
+"` ` ` ` ` ] ` ` ` ` ` ` ` ` ` ] ) _ 2 ) ] ` ` ` ` ` ` ` 1 ) _ A V q i i i i i u a B H ) ` ` ` ` ",
+"` ` ` ] ` ` ` ` ` ` ` ` { ` ` ` / ` I 2 { ] ` ` ` ] ..M A [ / ` 2 Z 8 y i i t q D f ] T ` ` ` ` ",
+"` ` ` ` ` ` ] ` ` ` ` ` ` ` ` ` ) )  .A 2 ) ] ` ` ) n M [ _ / [ Y 2 Z d 9 9 g V 2 ) ] R _ ] ` ` ",
+"` ` ` ` ` ` ` ` ] ` ` ` ` ` ` ] ) ) ] { K 2 ) ] ` n G { ] _ R ` [ ) m I } } J N ` ] ` / ` ` ` ' ",
+"` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ) ) ` ` [ [ ` ` ` ` [ ` ` ` ( { ` ] [ _ ` ` ` { ` ` ` ) ` ` ` ` ",
+"` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ",
+"` { ` ` ` ` ` ` ` ` ` ` ` ` ` ] ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ",
+"` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ",
+"` ` ` ` ` ` ` ` ` ` ` { ` ` ` ` ] ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ] ` ` ] ` ` ` ` ` "
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/inertia-web.png b/icons/inertia-web.png
new file mode 100644 (file)
index 0000000..8e83c80
Binary files /dev/null and b/icons/inertia-web.png differ
diff --git a/icons/inertia.ico b/icons/inertia.ico
new file mode 100644 (file)
index 0000000..58ba5a9
Binary files /dev/null and b/icons/inertia.ico differ
diff --git a/icons/inertia.rc b/icons/inertia.rc
new file mode 100644 (file)
index 0000000..cc3dceb
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "inertia.ico"
diff --git a/icons/inertia.sav b/icons/inertia.sav
new file mode 100644 (file)
index 0000000..a6d6fae
--- /dev/null
@@ -0,0 +1,30 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :7:Inertia
+PARAMS  :3:8x8
+CPARAMS :3:8x8
+SEED    :15:739970145068932
+DESC    :64:sbgwsmswwgggwggmmbwgwbssbwbsbwbbwsSmgbbsbbmsgbmssgmggbmmwmbmmwsw
+UI      :2:D0
+NSTATES :2:21
+STATEPOS:1:5
+MOVE    :1:6
+MOVE    :1:5
+MOVE    :1:3
+MOVE    :1:1
+MOVE    :1:6
+MOVE    :1:0
+MOVE    :1:5
+MOVE    :1:7
+MOVE    :1:5
+MOVE    :1:6
+MOVE    :1:1
+MOVE    :1:3
+MOVE    :1:4
+MOVE    :1:2
+MOVE    :1:6
+MOVE    :1:5
+MOVE    :1:3
+MOVE    :1:1
+MOVE    :1:5
+MOVE    :1:3
diff --git a/icons/keen-16d24.png b/icons/keen-16d24.png
new file mode 100644 (file)
index 0000000..ba7e2ac
Binary files /dev/null and b/icons/keen-16d24.png differ
diff --git a/icons/keen-16d4.png b/icons/keen-16d4.png
new file mode 100644 (file)
index 0000000..d7f65db
Binary files /dev/null and b/icons/keen-16d4.png differ
diff --git a/icons/keen-16d8.png b/icons/keen-16d8.png
new file mode 100644 (file)
index 0000000..24d206e
Binary files /dev/null and b/icons/keen-16d8.png differ
diff --git a/icons/keen-32d24.png b/icons/keen-32d24.png
new file mode 100644 (file)
index 0000000..ba37160
Binary files /dev/null and b/icons/keen-32d24.png differ
diff --git a/icons/keen-32d4.png b/icons/keen-32d4.png
new file mode 100644 (file)
index 0000000..d2674e9
Binary files /dev/null and b/icons/keen-32d4.png differ
diff --git a/icons/keen-32d8.png b/icons/keen-32d8.png
new file mode 100644 (file)
index 0000000..abad710
Binary files /dev/null and b/icons/keen-32d8.png differ
diff --git a/icons/keen-48d24.png b/icons/keen-48d24.png
new file mode 100644 (file)
index 0000000..8e2aa64
Binary files /dev/null and b/icons/keen-48d24.png differ
diff --git a/icons/keen-48d4.png b/icons/keen-48d4.png
new file mode 100644 (file)
index 0000000..a96e876
Binary files /dev/null and b/icons/keen-48d4.png differ
diff --git a/icons/keen-48d8.png b/icons/keen-48d8.png
new file mode 100644 (file)
index 0000000..b566eca
Binary files /dev/null and b/icons/keen-48d8.png differ
diff --git a/icons/keen-base.png b/icons/keen-base.png
new file mode 100644 (file)
index 0000000..29c0a93
Binary files /dev/null and b/icons/keen-base.png differ
diff --git a/icons/keen-ibase.png b/icons/keen-ibase.png
new file mode 100644 (file)
index 0000000..6dc0e06
Binary files /dev/null and b/icons/keen-ibase.png differ
diff --git a/icons/keen-ibase4.png b/icons/keen-ibase4.png
new file mode 100644 (file)
index 0000000..04065d9
Binary files /dev/null and b/icons/keen-ibase4.png differ
diff --git a/icons/keen-icon.c b/icons/keen-icon.c
new file mode 100644 (file)
index 0000000..09734ca
--- /dev/null
@@ -0,0 +1,667 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 241 2 ",
+"   c gray2",
+".  c #202020",
+"X  c #272727",
+"o  c #272727",
+"O  c gray13",
+"+  c gray13",
+"@  c gray14",
+"#  c #161616",
+"$  c gray10",
+"%  c #272727",
+"&  c gray16",
+"*  c gray13",
+"=  c gray13",
+"-  c #232323",
+";  c #1E1E1E",
+":  c #010101",
+">  c #2A2A2A",
+",  c #979797",
+"<  c #A9A9A9",
+"1  c #D0D0D0",
+"2  c gray88",
+"3  c gainsboro",
+"4  c gray92",
+"5  c #9A9A9A",
+"6  c #868686",
+"7  c #9D9D9D",
+"8  c #B5B4B5",
+"9  c #E6E2E6",
+"0  c gray87",
+"q  c #E6E6E6",
+"w  c #C6C6C6",
+"e  c gray4",
+"r  c gray16",
+"t  c gray65",
+"y  c gray75",
+"u  c #D8D8D8",
+"i  c #E9E9E9",
+"p  c gray90",
+"a  c #F4F4F4",
+"s  c #9F9F9F",
+"d  c gray56",
+"f  c #BBBBBB",
+"g  c #C6CAC6",
+"h  c #CBDCCB",
+"j  c #E8E7E8",
+"k  c #EFEFEF",
+"l  c #CECECE",
+"z  c gray4",
+"x  c gray13",
+"c  c gray90",
+"v  c #EEEEEE",
+"b  c #E9E9E9",
+"n  c gray90",
+"m  c gray89",
+"M  c #F3F3F3",
+"N  c #959595",
+"B  c #BFC0BF",
+"V  c #FBF8FB",
+"C  c #D7E2D7",
+"Z  c #68B368",
+"A  c #73B773",
+"S  c #FAF2FA",
+"D  c #C9CBC9",
+"F  c gray4",
+"G  c #222222",
+"H  c gray87",
+"J  c #E7E7E7",
+"K  c gray90",
+"L  c #E6E6E6",
+"P  c gray89",
+"I  c #F4F4F4",
+"U  c gray59",
+"Y  c #BABBBA",
+"T  c #EEEEEE",
+"R  c #E6E4E6",
+"E  c #74B874",
+"W  c #94C494",
+"Q  c #F8F1F8",
+"!  c #CACBCA",
+"~  c gray4",
+"^  c #222222",
+"/  c gray87",
+"(  c #E7E7E7",
+")  c #E4E4E4",
+"_  c gray90",
+"`  c #E2E2E2",
+"'  c #F3F3F3",
+"]  c #959595",
+"[  c #B8B9B8",
+"{  c #F5F0F5",
+"}  c #BCD3BC",
+"|  c #42A342",
+" . c #A4C9A4",
+".. c #EFECEF",
+"X. c #C8C9C8",
+"o. c gray4",
+"O. c #222222",
+"+. c #E4E4E4",
+"@. c gray93",
+"#. c gray92",
+"$. c gray92",
+"%. c gray91",
+"&. c #F9F9F9",
+"*. c gray60",
+"=. c gray78",
+"-. c #FBFCFB",
+";. c #F3F2F3",
+":. c #FDF7FD",
+">. c #F3F2F3",
+",. c #FBFBFB",
+"<. c gray85",
+"1. c #0B0B0B",
+"2. c #1E1E1E",
+"3. c #C5C5C5",
+"4. c #CDCDCD",
+"5. c #CACACA",
+"6. c #CBCBCB",
+"7. c #C8C8C8",
+"8. c #D7D7D7",
+"9. c gray51",
+"0. c gray49",
+"q. c #A9A9A9",
+"w. c #9C9D9C",
+"e. c #9C9D9C",
+"r. c #959595",
+"t. c #9A9A9A",
+"y. c #848484",
+"u. c #060606",
+"i. c #202020",
+"p. c #D2D2D2",
+"a. c #DADADA",
+"s. c #D8D8D8",
+"d. c #D8D8D8",
+"f. c #D5D5D5",
+"g. c #E4E4E4",
+"h. c #939393",
+"j. c #6F6F6F",
+"k. c gray40",
+"l. c gray46",
+"z. c #949394",
+"x. c gray71",
+"c. c #C6C7C6",
+"v. c #929192",
+"b. c black",
+"n. c #222222",
+"m. c gray89",
+"M. c gray92",
+"N. c #E9E9E9",
+"B. c #E9E9E9",
+"V. c #E7E7E7",
+"C. c #F6F6F6",
+"Z. c #A0A0A0",
+"A. c gray57",
+"S. c #898A89",
+"D. c #9F9E9F",
+"F. c #AEADAE",
+"G. c #E0E3E0",
+"H. c #FEFEFE",
+"J. c gray73",
+"K. c #222222",
+"L. c #DFDFDF",
+"P. c gray91",
+"I. c gray90",
+"U. c #E6E6E6",
+"Y. c gray89",
+"T. c #F4F4F4",
+"R. c gray58",
+"E. c #C0C1C0",
+"W. c #FFFDFF",
+"Q. c #C4DBC4",
+"!. c #36A436",
+"~. c #A8CBA8",
+"^. c #FAF4FA",
+"/. c #AFB0AF",
+"(. c #222222",
+"). c #DFDFDF",
+"_. c gray91",
+"`. c gray90",
+"'. c #E6E6E6",
+"]. c gray89",
+"[. c gray59",
+"{. c #B9BAB9",
+"}. c #F3F0F3",
+"|. c #D0DCD0",
+" X c #69B369",
+".X c #64B064",
+"XX c #FDF6FD",
+"oX c #B0B1B0",
+"OX c #DDDDDD",
+"+X c #E6E6E6",
+"@X c gray89",
+"#X c #E4E4E4",
+"$X c #E1E1E1",
+"%X c #F1F1F1",
+"&X c gray58",
+"*X c gray73",
+"=X c #F3F0F3",
+"-X c #CDDACD",
+";X c #6FB56F",
+":X c #81BB81",
+">X c #FAF4FA",
+",X c #AFB1AF",
+"<X c #232323",
+"1X c gray92",
+"2X c #F4F4F4",
+"3X c #F1F1F1",
+"4X c gray95",
+"5X c #EFEFEF",
+"6X c white",
+"7X c #9D9D9D",
+"8X c #C1C1C1",
+"9X c gray97",
+"0X c #EAEAEA",
+"qX c #E1E8E1",
+"wX c #F2EDF2",
+"eX c #F9F9F9",
+"rX c #B7B7B7",
+"tX c gray10",
+"yX c gray68",
+"uX c #B4B4B4",
+"iX c #B2B2B2",
+"pX c #B2B2B2",
+"aX c gray69",
+"sX c gray74",
+"dX c gray46",
+"fX c #A9A9A9",
+"gX c #D4D5D4",
+"hX c #CBCBCB",
+"jX c #CECDCE",
+"kX c gray79",
+"lX c #D6D7D6",
+"zX c gray62",
+"xX c black",
+"cX c black",
+"vX c #090909",
+"bX c gray4",
+"nX c gray4",
+"mX c #090A09",
+"MX c gray4",
+"NX c gray4",
+"BX c gray3",
+/* pixels */
+"  . X o O + @ # $ % & * = - ; : ",
+"> , < 1 2 3 4 5 6 7 8 9 0 q w e ",
+"r t y u i p a s d f g h j k l z ",
+"x c v b n m M N B V C Z A S D F ",
+"G H J K L P I U Y T R E W Q ! ~ ",
+"^ / ( ) _ ` ' ] [ { } |  ...X.o.",
+"O.+.@.#.$.%.&.*.=.-.;.:.>.,.<.1.",
+"2.3.4.5.6.7.8.9.0.q.w.e.r.t.y.u.",
+"i.p.a.s.d.f.g.h.j.k.l.z.x.c.v.b.",
+"n.m.M.N.B.V.C.Z.A.S.D.F.G.H.J.b.",
+"K.L.P.I.U.Y.T.R.E.W.Q.!.~.^./.b.",
+"(.)._.`.'.].I [.{.}.|. X.XXXoXb.",
+"+ OX+X@X#X$X%X&X*X=X-X;X:X>X,Xb.",
+"<X1X2X3X4X5X6X7X8X9X0XqXwXeXrXb.",
+"tXyXuXiXpXaXsXdXfXgXhXjXkXlXzXb.",
+"b.b.b.b.b.xXb.cXvXbXnXmXMXNXBXb."
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 230 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray4",
+"@  c #0C0C0C",
+"#  c #1E1E1E",
+"$  c #222222",
+"%  c #232323",
+"&  c #252525",
+"*  c #272727",
+"=  c #2A2A2A",
+"-  c gray17",
+";  c gray18",
+":  c #2F2F2F",
+">  c gray19",
+",  c #313131",
+"<  c #323232",
+"1  c gray20",
+"2  c #343434",
+"3  c gray21",
+"4  c #373737",
+"5  c gray22",
+"6  c #393939",
+"7  c #3A3A3A",
+"8  c #3C3C3C",
+"9  c gray24",
+"0  c #3E3E3E",
+"q  c #007900",
+"w  c gray25",
+"e  c #414141",
+"r  c gray27",
+"t  c #505050",
+"y  c gray32",
+"u  c #535553",
+"i  c #5A5A5A",
+"p  c #5D5D5D",
+"a  c gray37",
+"s  c #5F5F5F",
+"d  c gray38",
+"f  c #6D6D6D",
+"g  c #717371",
+"h  c #727272",
+"j  c #767676",
+"k  c #7B7B7B",
+"l  c #7C7C7C",
+"z  c #008000",
+"x  c #008500",
+"c  c #008700",
+"v  c #098609",
+"b  c #149214",
+"n  c #1B951B",
+"m  c #1D941D",
+"M  c #239823",
+"N  c #299A29",
+"B  c #2B9B2B",
+"V  c #339E33",
+"C  c #389F38",
+"Z  c #36A036",
+"A  c #3FA33F",
+"S  c #40A440",
+"D  c #47A647",
+"F  c #4BA94B",
+"G  c #4CAB4C",
+"H  c #53AB53",
+"J  c #50AD50",
+"K  c #67B367",
+"L  c #6EB56E",
+"P  c #7DBC7D",
+"I  c #7EBC7E",
+"U  c #7FBC7F",
+"Y  c #838383",
+"T  c gray53",
+"R  c #888988",
+"E  c gray54",
+"W  c #8B8B8B",
+"Q  c gray55",
+"!  c #8D8D8D",
+"~  c #8D8E8D",
+"^  c #8E8E8E",
+"/  c gray57",
+"(  c #929292",
+")  c #939393",
+"_  c #959595",
+"`  c gray60",
+"'  c #9B9B9B",
+"]  c gray62",
+"[  c #81BE81",
+"{  c #83BE83",
+"}  c #84BD84",
+"|  c #A0A0A0",
+" . c #A4A4A4",
+".. c #A5A5A5",
+"X. c gray65",
+"o. c #A9A9A9",
+"O. c gray67",
+"+. c #ACACAC",
+"@. c #AEAEAE",
+"#. c #AFAFAF",
+"$. c gray69",
+"%. c #B1B1B1",
+"&. c #B2B2B2",
+"*. c gray70",
+"=. c #B4B4B4",
+"-. c gray71",
+";. c #B7B7B7",
+":. c #B9B9B9",
+">. c gray73",
+",. c #BBBBBB",
+"<. c gray",
+"1. c gray75",
+"2. c #88C188",
+"3. c #8AC18A",
+"4. c #8BC18B",
+"5. c #8EC38E",
+"6. c #94C594",
+"7. c #97C497",
+"8. c #9EC99E",
+"9. c #A2CBA2",
+"0. c #AACEAA",
+"q. c #AECDAE",
+"w. c #AFD0AF",
+"e. c #B4D2B4",
+"r. c #B7D3B7",
+"t. c #B9D4B9",
+"y. c #BAD7BA",
+"u. c #C0C0C0",
+"i. c gray76",
+"p. c #C3C3C3",
+"a. c gray77",
+"s. c #C4C5C4",
+"d. c #C6C6C6",
+"f. c gray78",
+"g. c #C8C8C8",
+"h. c gray79",
+"j. c #CACACA",
+"k. c gray81",
+"l. c #C7DAC7",
+"z. c #C8DAC8",
+"x. c gray82",
+"c. c LightGray",
+"v. c gray83",
+"b. c gray84",
+"n. c #D7D7D7",
+"m. c #D2DED2",
+"M. c #D3DED3",
+"N. c #D8D6D8",
+"B. c #DED4DE",
+"V. c #D8D8D8",
+"C. c #DFDFDF",
+"Z. c #DAE1DA",
+"A. c #DBE2DB",
+"S. c #DFE1DF",
+"D. c #DEE3DE",
+"F. c #DFE3DF",
+"G. c #E0D6E0",
+"H. c gray88",
+"J. c #E1E1E1",
+"K. c #E0E3E0",
+"L. c #E1E2E1",
+"P. c #E1E3E1",
+"I. c #E2E2E2",
+"U. c #E2E3E2",
+"Y. c gray89",
+"T. c #E1E4E1",
+"R. c #E2E4E2",
+"E. c #E2E5E2",
+"W. c #E3E4E3",
+"Q. c #E3E5E3",
+"!. c #E4E4E4",
+"~. c #E4E5E4",
+"^. c #E5E4E5",
+"/. c gray90",
+"(. c #E4E6E4",
+"). c #E5E6E5",
+"_. c #E6E6E6",
+"`. c #E7E7E7",
+"'. c #EBE2EB",
+"]. c #E8E7E8",
+"[. c #E9E7E9",
+"{. c #EAE7EA",
+"}. c gray91",
+"|. c #E8E9E8",
+" X c #E9E9E9",
+".X c #EAE8EA",
+"XX c #EAE9EA",
+"oX c #EBE8EB",
+"OX c #EAEAEA",
+"+X c #EAEBEA",
+"@X c gray92",
+"#X c #EBECEB",
+"$X c #ECE8EC",
+"%X c #ECE9EC",
+"&X c #EDE9ED",
+"*X c #EDEBED",
+"=X c #EEE9EE",
+"-X c #EFE9EF",
+";X c #EFEAEF",
+":X c #ECECEC",
+">X c gray93",
+",X c #EEEEEE",
+"<X c #EFEFEF",
+"1X c #F1E9F1",
+"2X c #F0EAF0",
+"3X c #F1EAF1",
+"4X c #F2EBF2",
+"5X c #F3EBF3",
+"6X c #F0EDF0",
+"7X c #F5ECF5",
+"8X c #F6EDF6",
+"9X c #F7EDF7",
+"0X c #F8EDF8",
+"qX c #FAEEFA",
+"wX c gray94",
+"eX c #F1F1F1",
+"rX c gray95",
+"tX c #F4F4F4",
+"yX c gray96",
+"uX c #F6F6F6",
+"iX c gray97",
+"pX c #FFF1FF",
+"aX c #F8F8F8",
+"sX c #F9F9F9",
+"dX c gray98",
+"fX c #FBFBFB",
+"gX c #FEF9FE",
+"hX c #FDFDFD",
+"jX c #FEFEFE",
+"kX c white",
+/* pixels */
+"                                                                ",
+"  + 3 8 6 ; ; > > > > > 2 > 1 @ * 8 8 > 5 > > > > > > > > ;     ",
+"  6 V.j >.hXhXeX,X,X,X6XeX,XdX8 -.` ^ dXv.<XeX6XeXeX,X,XuXx.O   ",
+"  > eXl Y s.' j. X!.~.!.!.J.6X5 -.u.i u.s |  XJ.~.~.~.!.,Xj.    ",
+"  6 p.= %.s.+.k.*X!.~.].].!.eX6 -.t h S.Q s.*X].~.~.~.!.,Xj.    ",
+"  2 J.1.k.eXeX*X~.].].~.~.!.eX6 +.x.p.`. X6XZ.m.*X*X~.!.*Xj.O   ",
+"  > eX,X*X~.~.~.].~.].~.].!.eX5 -.dX,XQ.7X4.n b D S.].!.,Xj.O   ",
+"  > ,X!.~.~.].~.~.].~.].~.!.eX5 %.eXJ.~.*Xr.l.r.q q.4XQ.,Xj.    ",
+"  > eX~.~.].~.].~.].~.].~.!.eX5 *.rX~.].Q.4XpXL m A. X~.,Xj.    ",
+"  > 6X~.].~.].~.].~.].~.].~.eX5 %.eX~.~.~. XK N Q.6X~.~.,Xj.O   ",
+"  > eX!.~.].~.].~.].~.].~.~.eX5 *.eX!.J.7X[ q Z A w.4XJ.,Xj.    ",
+"  > ,X!.].~.].~.].~.].~.~.!.eX5 *.rX!.!.*Xt.5.4.I z. XQ.,Xj.O   ",
+"  > ,X~.~.].~.].~.].~.].~.~.eX5 %.eX~.].~.*XqX7XqX&X~.~.,Xj.    ",
+"  > ,XJ.!.!.~.~.!.~.~.~.!.!.eX6 %.eXJ.!.~.Q.J.J.J.!.~.J. Xj.O   ",
+"  3 uX,X,X6X,X<X,X6X,X<X,X*XdX6 ,.hX,XeX6XeXeXeXeXeXeX,XdXb.O   ",
+"  & +.........X.......X.....+.= & w e 6 r 6 3 3 6 6 6 6 6 >     ",
+"  > J.b.V.V.V.V.V.V.V.V.V.b.J.3 ^ +.R o.j #.>.>.#.&.%.#.t.f   O ",
+"  > eX]. X X X*X*X X X*X X XuX6 u.Q s ^ p | &.|  XtXtX<XgX_   O ",
+"  > ,X!.~.~.].~.~.~.].~.~.~.6X8 ` y 9 p d ^ g u (.~.!.G.rXQ   O ",
+"  > eX~.~.].~.].~. X~.].~.~.eX6 -.!.+.j.R V.B.B.]. X~.!.uX^   O ",
+"  2 6X~.].~.].~.].~.].~.~.~.eX6 %.uX6X#XgXy.G H F m.*XJ.uX^   O ",
+"  > 6X~.~.].~.].~.].~.].~.~.eX6 *.eXJ.J.4X7.v H P Q.].J.uX^   O ",
+"  > <X~.].~.].~.].~.].~.~.~.eX5 %.rX!.Q.4X9.M B V m.].J.uX^   O ",
+"  > ,X~.~.].~.].~.].~.].`.Q.eX5 *.eX!.].~.~.7XJ.x [ 7XS.uX^   O ",
+"  > <X~.~.~.].~.~.].~.].~.~.eX5 *.rX~.~.4X7.[ [ x 9.7XJ.uX^   O ",
+"  > 6X~.~. X~.].].~.].~.].!.<X5 *.rX!.~.*X0.S Z } *X~.J.uX^   O ",
+"  3 ,XJ.~.].].~.~.].~.~.~.`.eX5 %.rX~.~.~.4X7X7X7X~.].J.uX^   O ",
+"  > ,XJ.!.J.J.!.!.J.!.J.!.G.,X5 %.6XJ.~.~.J.Q.J.J.~.~.J.eX^   O ",
+"  3 hXuXuXuXuXuXuXuXuXuXuXuXhX8 >.dX*X,X,X X,X,X,X,X,X XhX/   O ",
+"  # / ^ Q ^ Q ^ ^ ^ ^ W ^ W _ % | x.s.j.j.k.j.j.j.j.j.j.V.l   O ",
+"                                O   O O O   O O     O   O       ",
+"    O O O O O O O O O O O O O                                   "
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 73 1 ",
+"  c #010101",
+". c gray3",
+"X c #131313",
+"o c #1B1B1B",
+"O c #252525",
+"+ c #2B2B2B",
+"@ c #333333",
+"# c #3E3E3E",
+"$ c #007E00",
+"% c #434343",
+"& c gray30",
+"* c #545454",
+"= c #5D5D5D",
+"- c #626262",
+"; c #6C6C6C",
+": c #707270",
+"> c #7B7B7B",
+", c #008400",
+"< c #028A02",
+"1 c #0B8E0B",
+"2 c #138E13",
+"3 c #149114",
+"4 c #199219",
+"5 c #218F21",
+"6 c #209720",
+"7 c #259825",
+"8 c #2D9B2D",
+"9 c #3AA13A",
+"0 c #42A442",
+"q c #49A749",
+"w c #4DA84D",
+"e c #54AB54",
+"r c #5AAE5A",
+"t c #60B060",
+"y c #69B569",
+"u c #71B771",
+"i c #76B976",
+"p c #838383",
+"a c #8A8A8A",
+"s c #929292",
+"d c #9B9C9B",
+"f c #82BE82",
+"g c #A4A4A4",
+"h c #ACACAC",
+"j c #B5B5B5",
+"k c #BABABA",
+"l c #86C086",
+"z c #8AC18A",
+"x c #95C695",
+"c c #9AC79A",
+"v c #A7CDA7",
+"b c #ACCFAC",
+"n c #B7D3B7",
+"m c #B9D5B9",
+"M c #C7C7C7",
+"N c #CCCCCC",
+"B c #C0D7C0",
+"V c #C4D9C4",
+"C c #CFD1CF",
+"Z c #CCDCCC",
+"A c #D5D5D5",
+"S c #D1DED1",
+"D c #DCDCDC",
+"F c #DEE3DE",
+"G c #E5E6E5",
+"H c #E9E7E9",
+"J c #ECEBEC",
+"K c #F1E7F1",
+"L c #F3EBF3",
+"P c #FAEEFA",
+"I c #F4F4F4",
+"U c #FFF3FF",
+"Y c #FAFAFA",
+/* pixels */
+"                                                ",
+"                  .                       .     ",
+"                                                ",
+"   .O@@+OOOOOOOOOOOOOOO X+@@OOOOOOOOOOOOOOOO.   ",
+"   OJjdDHGHHGJJJHHGJGJIXpJdjJHJHHHGGJHJHJGGJO   ",
+"   OH>&%IIIIGGGGHHGHGGJXpM;OdYajYGHHGHHGHHGJO   ",
+"   OHYp;DassGGGGGGGGHGJX>YJ%ks--hJGGGHGGGGGGO   ",
+"   OI-okSddgGGGGHGGGGGJXpFX=DD;sFGFGGGGGGGGJO   ",
+"   OJ;=>HIIIGGGGGGGGGGJXpN*-hINDJHKJGGGGGGHGO   ",
+"   OHYYIHGGGGGGJGGGGGGJX>YIIIGJHHSVZJHGGGGHJO . ",
+"   OJFFGGGGGGGGGGGGGGGJo>YDGGGHH0<<<eFHGGGHHO   ",
+"   OHGGGGGGGGJGGGJJGGGJX>YGGGGHGefv8$zLGGHGJO   ",
+"   OHGHGHGHGGGGGGGGGGGJX>YFGGGGGJKUi$lIGGGGJO   ",
+"   OJGGGGGGGGGGGGGGGHGJX>YGGGGGHGJZ44SJGGGGGO   ",
+" . OHGHGGHGGGGGGGGGGGGJX>IGGGGGGLm34VKGGGGGJO   ",
+"   OJHGGGGGGGGGGGGHJHHJX>YFGHGGHv17SHFGGGGGJO   ",
+"   OGGGGHGGGGHHGGGGGGHJXuPGGGGHG4$241uKGGGGHO   ",
+"   OJGHGGGGHGGGGGGGGGGJX>PFHGGHFitrrecJGGGHJO   ",
+"   OJGGGGGGGGHGGGJGGHGJX>UFGGGGHKPULPJGGGGHJO   ",
+"   OJGGHGGGGGGGGGGGGHJJX>PFHGGGGGFGGGGGGGHGHO   ",
+"   OGHHGGHGGGGGGGGGGHGJX>YGGHGGGGGGGGHGGGGGGO   ",
+"   OHGFGGFFGGGFGGGGFGFJX>YFGGGHGGGGHGGGGGGHJO   ",
+"   OYJIIIIIIIIIIIIIIIJYX>YHJJJJJJIJJJIJKJJJJO   ",
+"   ossssssssssssssssssdX.XXXXXXXXXXXXXXXXXXo.   ",
+"   OMMMMVMMMMMMMMMMMMMNo%p>a>pap>p>>>>>>>>p*    ",
+"   OIJJJJJLJJIJJJJJJJJIXpYM*Hj*jYJYJYYYYYYYj    ",
+"   OHGHHGGGGHGGGGGGGHGJX>J-+j@a@k:apJFGFGDJg    ",
+"   OGGHGGGGGHGGGJGGGHGJXas@+;#A+Ap.NJGGGGGIg .  ",
+"   OJGGGGGGGGGGGGGGGGGJX>Ddod;+-C:d>HGGGGGIg    ",
+"   OJGGHGGGGGGJGGGGGHGJX>YINHGjGKLYKPHGGGGIg .  ",
+"   OJGGGGHGGGGGGGGGGGHJX>YDJGGJJxrrrrZHGGGIg    ",
+"   OJGGGGHJGGGGGGGJGGGJX>YGGGGGKe,642nJGGFIg    ",
+"   OJGGGGGGGGGGJGGGGGGJouPGHHGGLr1izZJGGGGIg    ",
+"   OGGGGGGGGGGGGGGGGGGJX:PDGGGFJe<1<4BJGHFIg    ",
+"   OJHGGGGJGGGGGGGGGGGJX>YGHGGGHnBGy$rKGGFLg .  ",
+"   OGGHGGGGGGGGGHGGGGGIX>UDHHGGGLPUn<0LGGGIg    ",
+"   OJGGGGJGGGGGGGGGGHHJX>YDGGGHFqzb9$iKGGGIg .  ",
+"   OJGGHGGGGJGGGGHGHHHJX>UGGGGGG0<,<eGHGGGIg    ",
+" . OHGGGGGGGGJGGGGGHGGJX>YFGGGGHHSVSJHGGGGIg .  ",
+"   OJHGGGGGGGGGGGGGGGGIX>IGGGGHGGHJJGGGGGDIg    ",
+"   OHGGGGGHGGHGGGGGGGGJX>YFGGGGHGGGGGHHGGGIg .  ",
+"   OGGGGGHFGGFFGGGGGDGJX>YGGHHGGGGGGGGGHGGIg    ",
+"   OYIIIIIIIIIIIIIIIIIYX>IFHGGGGGHHGGGGGGGIg .  ",
+"   oggggggggggggggggggh.iYHJJGJHJGJGHJJHHGIg    ",
+"                        X+OOOOOOOOOOOOOOOOOo    ",
+"    .    .    . .    .                          ",
+"                                                ",
+"                                                "
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/keen-web.png b/icons/keen-web.png
new file mode 100644 (file)
index 0000000..530202c
Binary files /dev/null and b/icons/keen-web.png differ
diff --git a/icons/keen.ico b/icons/keen.ico
new file mode 100644 (file)
index 0000000..a32c5b2
Binary files /dev/null and b/icons/keen.ico differ
diff --git a/icons/keen.rc b/icons/keen.rc
new file mode 100644 (file)
index 0000000..1839d0f
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "keen.ico"
diff --git a/icons/keen.sav b/icons/keen.sav
new file mode 100644 (file)
index 0000000..4adbd42
--- /dev/null
@@ -0,0 +1,62 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :4:Keen
+PARAMS  :3:5de
+CPARAMS :3:5de
+SEED    :15:846699649745236
+DESC    :48:a__a_3a_5a_a_a_3b_bac_,a5m15a7a10s2s2d2s3m40m2s2
+AUXINFO :52:6105af67c6ebc8de056b59ebc9a463aa54e75f647055c0a6c1bd
+NSTATES :2:53
+STATEPOS:2:39
+MOVE    :6:P0,4,2
+MOVE    :6:P0,4,4
+MOVE    :6:P0,4,5
+MOVE    :6:P1,4,2
+MOVE    :6:P1,4,4
+MOVE    :6:P1,4,5
+MOVE    :6:P1,3,2
+MOVE    :6:P1,3,4
+MOVE    :6:P1,3,5
+MOVE    :6:R2,2,4
+MOVE    :6:R1,2,2
+MOVE    :6:P1,3,2
+MOVE    :6:P1,4,2
+MOVE    :6:R0,4,2
+MOVE    :6:R2,3,2
+MOVE    :6:R2,4,1
+MOVE    :6:R1,4,4
+MOVE    :6:R1,3,5
+MOVE    :6:P3,4,3
+MOVE    :6:P3,4,5
+MOVE    :6:P4,4,3
+MOVE    :6:P4,4,5
+MOVE    :6:R4,4,5
+MOVE    :6:R3,4,3
+MOVE    :6:P3,1,2
+MOVE    :6:P3,1,5
+MOVE    :6:P3,0,2
+MOVE    :6:P3,0,5
+MOVE    :6:R3,2,1
+MOVE    :6:R3,3,4
+MOVE    :6:P2,0,3
+MOVE    :6:P2,0,5
+MOVE    :6:P2,1,3
+MOVE    :6:P2,1,5
+MOVE    :6:P0,1,1
+MOVE    :6:P0,1,3
+MOVE    :6:P1,1,1
+MOVE    :6:P1,1,3
+MOVE    :6:R2,0,3
+MOVE    :6:R2,1,5
+MOVE    :6:R3,0,5
+MOVE    :6:R3,1,2
+MOVE    :6:R4,1,4
+MOVE    :6:R4,2,3
+MOVE    :6:R4,0,2
+MOVE    :6:R4,3,1
+MOVE    :6:R0,2,5
+MOVE    :6:R0,3,3
+MOVE    :6:R1,1,3
+MOVE    :6:R0,1,1
+MOVE    :6:R1,0,1
+MOVE    :6:R0,0,4
diff --git a/icons/lightup-16d24.png b/icons/lightup-16d24.png
new file mode 100644 (file)
index 0000000..d6e0aa2
Binary files /dev/null and b/icons/lightup-16d24.png differ
diff --git a/icons/lightup-16d4.png b/icons/lightup-16d4.png
new file mode 100644 (file)
index 0000000..c7e80d3
Binary files /dev/null and b/icons/lightup-16d4.png differ
diff --git a/icons/lightup-16d8.png b/icons/lightup-16d8.png
new file mode 100644 (file)
index 0000000..b147cf8
Binary files /dev/null and b/icons/lightup-16d8.png differ
diff --git a/icons/lightup-32d24.png b/icons/lightup-32d24.png
new file mode 100644 (file)
index 0000000..c5f81ac
Binary files /dev/null and b/icons/lightup-32d24.png differ
diff --git a/icons/lightup-32d4.png b/icons/lightup-32d4.png
new file mode 100644 (file)
index 0000000..cbf6012
Binary files /dev/null and b/icons/lightup-32d4.png differ
diff --git a/icons/lightup-32d8.png b/icons/lightup-32d8.png
new file mode 100644 (file)
index 0000000..86f2ac4
Binary files /dev/null and b/icons/lightup-32d8.png differ
diff --git a/icons/lightup-48d24.png b/icons/lightup-48d24.png
new file mode 100644 (file)
index 0000000..1f30b3e
Binary files /dev/null and b/icons/lightup-48d24.png differ
diff --git a/icons/lightup-48d4.png b/icons/lightup-48d4.png
new file mode 100644 (file)
index 0000000..8713acb
Binary files /dev/null and b/icons/lightup-48d4.png differ
diff --git a/icons/lightup-48d8.png b/icons/lightup-48d8.png
new file mode 100644 (file)
index 0000000..c9e90be
Binary files /dev/null and b/icons/lightup-48d8.png differ
diff --git a/icons/lightup-base.png b/icons/lightup-base.png
new file mode 100644 (file)
index 0000000..4a4149d
Binary files /dev/null and b/icons/lightup-base.png differ
diff --git a/icons/lightup-ibase.png b/icons/lightup-ibase.png
new file mode 100644 (file)
index 0000000..56f72f8
Binary files /dev/null and b/icons/lightup-ibase.png differ
diff --git a/icons/lightup-ibase4.png b/icons/lightup-ibase4.png
new file mode 100644 (file)
index 0000000..60519b3
Binary files /dev/null and b/icons/lightup-ibase4.png differ
diff --git a/icons/lightup-icon.c b/icons/lightup-icon.c
new file mode 100644 (file)
index 0000000..a422ddf
--- /dev/null
@@ -0,0 +1,576 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 232 2 ",
+"   c #E4E4E4",
+".  c #D8D8DC",
+"X  c #D7D7DC",
+"o  c #D8D8DC",
+"O  c #D8D8DC",
+"+  c #D8D8DB",
+"@  c #D8D8DC",
+"#  c #D8D8DC",
+"$  c #D8D8DC",
+"%  c #D8D8DB",
+"&  c #D8D8DB",
+"*  c #D8D8DC",
+"=  c #D8D8DC",
+"-  c #D7D7DB",
+";  c #D9D9DC",
+":  c gray90",
+">  c #DEDED2",
+",  c #E6E62C",
+"<  c #EEEE1C",
+"1  c #ECEC21",
+"2  c #EDED1F",
+"3  c #D5D543",
+"4  c #EBEB21",
+"5  c #ECEC20",
+"6  c #EEEE1D",
+"7  c #E4E42C",
+"8  c #D9D93D",
+"9  c #EFEF1B",
+"0  c #EBEB23",
+"q  c #F0F016",
+"w  c #D9D94E",
+"e  c #DDDDE9",
+"r  c #DFDFD0",
+"t  c #F7F710",
+"y  c yellow",
+"u  c #FFFF01",
+"i  c #E1E12C",
+"p  c #FEFE01",
+"a  c yellow",
+"s  c #F4F410",
+"d  c #E6E625",
+"f  c #FDFD04",
+"g  c #E5E539",
+"h  c #DBDBEB",
+"j  c #DFDFD0",
+"k  c #F2F211",
+"l  c #FCFC00",
+"z  c #FAFA03",
+"x  c #FBFB01",
+"c  c #DFDF2D",
+"v  c #FDFD04",
+"b  c #FDFD03",
+"n  c #F3F312",
+"m  c #E6E627",
+"M  c #FCFC06",
+"N  c #E4E43B",
+"B  c #DBDBEA",
+"V  c #E0E0D1",
+"C  c #F7F710",
+"Z  c #FFFF01",
+"A  c #E3E32E",
+"S  c #FEFE01",
+"D  c yellow",
+"F  c #F4F410",
+"G  c #E7E725",
+"H  c #FDFD04",
+"J  c #E5E539",
+"K  c #DBDBEB",
+"L  c #D4D4CC",
+"P  c #9A9A20",
+"I  c #9C9C12",
+"U  c #999913",
+"Y  c #999915",
+"T  c #ACAC3A",
+"R  c #E8E82C",
+"E  c #E2E22A",
+"W  c #E5E527",
+"Q  c #DBDB35",
+"!  c #D2D245",
+"~  c #EAEA20",
+"^  c #E2E219",
+"/  c #EAEA19",
+"(  c #D3D356",
+")  c #DDDDE8",
+"_  c #C0C0C0",
+"`  c #030305",
+"'  c #010106",
+"]  c #0F0F17",
+"[  c black",
+"{  c #68681A",
+"}  c #FFFF0A",
+"|  c #F6F608",
+" . c #FCFC04",
+".. c #EFEF19",
+"X. c #E4E425",
+"o. c #E1E11C",
+"O. c #CFCF80",
+"+. c #DDDD28",
+"@. c #E1E137",
+"#. c #DCDCEC",
+"$. c #C3C3C3",
+"%. c #5A5A56",
+"&. c #A2A29A",
+"*. c #0D0D19",
+"=. c #65650F",
+"-. c #FFFF03",
+";. c #FBFB00",
+":. c #F8F810",
+">. c #D6D622",
+",. c #E2E2BD",
+"<. c white",
+"1. c #E9E9D5",
+"2. c #CCCC48",
+"3. c #E1E1E6",
+"4. c #C3C3C3",
+"5. c gray35",
+"6. c #A2A29E",
+"7. c #0D0D1D",
+"8. c #646411",
+"9. c #FFFF05",
+"0. c #FBFB02",
+"q. c #F9F911",
+"w. c #D0D025",
+"e. c #E2E2CC",
+"r. c #EBEBE2",
+"t. c #C7C74F",
+"y. c #E1E1E5",
+"u. c #C0C0C0",
+"i. c gray1",
+"p. c #010101",
+"a. c #0F0F0C",
+"s. c #696916",
+"d. c #FFFF02",
+"f. c #F7F700",
+"g. c #FEFE00",
+"h. c #F1F112",
+"j. c #E8E822",
+"k. c #EAEA2E",
+"l. c #E2E2A6",
+"z. c #E8E83E",
+"x. c #E4E435",
+"c. c #DCDCED",
+"v. c LightGray",
+"b. c #8E8E8E",
+"n. c #909090",
+"m. c #8E8E8D",
+"M. c #8B8B8E",
+"N. c #A2A291",
+"B. c #DBDBA5",
+"V. c #D5D5A3",
+"C. c #D7D7A3",
+"Z. c #D4D4A6",
+"A. c #5A5A34",
+"S. c #454505",
+"D. c #434303",
+"F. c #444400",
+"G. c #5D5D3A",
+"H. c #E1E1E4",
+"J. c gray88",
+"K. c #EFEFEF",
+"L. c gray96",
+"P. c gray95",
+"I. c #F7F7F6",
+"U. c #D7D7DB",
+"Y. c #E5E5F1",
+"T. c #E7E7F2",
+"R. c #E8E8F3",
+"E. c #E7E7F2",
+"W. c #252526",
+"Q. c #040405",
+"!. c #262625",
+"~. c #E4E4E4",
+"^. c #DADADA",
+"/. c gray93",
+"(. c #888888",
+"). c gray19",
+"_. c gray93",
+"`. c #CDCDCC",
+"'. c #E5E5E2",
+"]. c #E5E5E3",
+"[. c #E6E6E3",
+"{. c #E5E5E3",
+"}. c #292927",
+"|. c #080805",
+" X c #292927",
+".X c #E4E4E4",
+"XX c #DDDDDD",
+"oX c #E6E6E6",
+"OX c #CECECE",
+"+X c #B2B2B2",
+"@X c gray92",
+"#X c #D0D0D0",
+"$X c #E7E7E7",
+"%X c #E7E7E7",
+"&X c gray91",
+"*X c #E7E7E7",
+"=X c #252525",
+"-X c #040404",
+";X c #252525",
+":X c #E4E4E4",
+">X c gray87",
+",X c #D8D8D8",
+"<X c gray90",
+"1X c #E9E9E9",
+"2X c gray87",
+"3X c gray80",
+"4X c #DDDDDD",
+"5X c gray87",
+"6X c gray87",
+"7X c gray87",
+"8X c #373737",
+"9X c #0E0E0E",
+"0X c #191919",
+"qX c #0E0E0E",
+"wX c gray22",
+"eX c #E4E4E4",
+"rX c gray90",
+"tX c #E1E1E1",
+"yX c #DFDFDF",
+"uX c gray87",
+"iX c gray88",
+"pX c #E1E1E1",
+"aX c gray88",
+"sX c gray88",
+"dX c gray88",
+"fX c gray88",
+"gX c #DADADA",
+"hX c #D8D8D8",
+"jX c #D8D8D8",
+"kX c #D8D8D8",
+"lX c #DADADA",
+"zX c #E6E6E6",
+/* pixels */
+"  . X o O + @ # $ % & * = - ; : ",
+"> , < 1 2 3 4 5 6 7 8 9 0 q w e ",
+"r t y u y i p a y s d y f y g h ",
+"j k l z x c v b y n m y M y N B ",
+"V C y Z y A S D y F G y H y J K ",
+"L P I U Y T R E W Q ! ~ ^ / ( ) ",
+"_ ` ' ] [ { } |  ...X.o.O.+.@.#.",
+"$.[ %.&.*.=.-.;.y :.>.,.<.1.2.3.",
+"4.[ 5.6.7.8.9.0.y q.w.e.<.r.t.y.",
+"u.i.p.a.[ s.d.f.g.h.j.k.l.z.x.c.",
+"v.b.n.m.M.N.B.V.C.Z.A.S.D.F.G.H.",
+"J.K.L.P.I.U.Y.T.R.E.W.[ Q.[ !.~.",
+"^./.(.)._.`.'.].[.{.}.[ |.[  X.X",
+"XXoXOX+X@X#X$X%X&X*X=X[ -X[ ;X:X",
+">X,X<X1X2X3X4X5X6X7X8X9X0XqXwXeX",
+"rXtXyXuXiXpXaXsXdXfXgXhXjXkXlXzX"
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 109 2 ",
+"   c #010101",
+".  c #00000F",
+"X  c gray3",
+"o  c #010111",
+"O  c #141414",
+"+  c #1D1D1D",
+"@  c #20201E",
+"#  c #35351D",
+"$  c #202020",
+"%  c #3F3F25",
+"&  c gray20",
+"*  c #3D3D30",
+"=  c #3B3B3A",
+"-  c #414127",
+";  c #3D3D41",
+":  c #414141",
+">  c #4C4C4C",
+",  c #5B5B5B",
+"<  c #676767",
+"1  c gray43",
+"2  c #7E7E7E",
+"3  c #BFBF1F",
+"4  c #878725",
+"5  c #88882F",
+"6  c #929223",
+"7  c #92922E",
+"8  c #8A8A31",
+"9  c #BDBD2A",
+"0  c #ACAC32",
+"q  c #C6C60B",
+"w  c #D3D300",
+"e  c #DBDB00",
+"r  c #DFDF08",
+"t  c #C3C310",
+"y  c #E1E100",
+"u  c #EDED00",
+"i  c #F1F100",
+"p  c #FFFF01",
+"a  c #F5F50D",
+"s  c #FEFE0D",
+"d  c #EDED1B",
+"f  c #F4F410",
+"g  c #FFFF11",
+"h  c #F4F418",
+"j  c #CDCD26",
+"k  c #CCCC28",
+"l  c #DEDE26",
+"z  c #DBDB2E",
+"x  c #CACA33",
+"c  c #D2D235",
+"v  c #DDDD30",
+"b  c #D7D73C",
+"n  c #D8D83C",
+"m  c #E0E030",
+"M  c #E2E23B",
+"N  c #8A8A5E",
+"B  c #8B8B66",
+"V  c #ACAC61",
+"C  c #ADAD6F",
+"Z  c #B5B564",
+"A  c #B4B475",
+"S  c #C5C54D",
+"D  c #CACA4C",
+"F  c #D2D246",
+"G  c #C6C654",
+"H  c #C9C953",
+"J  c #C4C459",
+"K  c #CFCF5A",
+"L  c #D2D253",
+"P  c #C6C665",
+"I  c #CECE66",
+"U  c #C5C569",
+"Y  c #CACA68",
+"T  c #D0D064",
+"R  c #C6C671",
+"E  c #8B8B8B",
+"W  c #979797",
+"Q  c #B2B283",
+"!  c #B0B089",
+"~  c #B2B291",
+"^  c gray64",
+"/  c #ABABAB",
+"(  c #B1B1A5",
+")  c #B6B6AD",
+"_  c #B9B9B9",
+"`  c gray76",
+"'  c #C9C9C7",
+"]  c #C3C3CF",
+"[  c #CCCCCC",
+"{  c #D7D7CC",
+"}  c #D9D9CF",
+"|  c #C7C7D4",
+" . c #CCCCD3",
+".. c #CCCCDC",
+"X. c #D4D4D4",
+"o. c #D6D6DE",
+"O. c #DDDDDD",
+"+. c #DCDCED",
+"@. c #DADAF0",
+"#. c #E5E5E5",
+"$. c #E9E9E4",
+"%. c #E2E2E9",
+"&. c #EBEBEA",
+"*. c #F1F1ED",
+"=. c #E4E4F5",
+"-. c #ECECFF",
+";. c #F2F2F3",
+":. c #F7F7FF",
+">. c #FEFEFD",
+/* pixels */
+"#.#.&.&.&.&.$.*.&.&.&.&.&.&.&.&.&.&.&.$.&.&.&.&.&.*.&.&.&.&.#.#.",
+"#.#........... .......X...............X. ......... .......X.#.#.",
+"&.{ x z z z z z z l S G l z z z z z z c Z z v z z z z v l A +.$.",
+"&.{ a p p p p p p p m n p p p p p p p a G p p p p p p p p P +.$.",
+"&.{ a p p p p p p p v F p p p p p p p f H p p p p p p p p P +.$.",
+"&.{ a p p p p p p p v n p p p p p p p a H p p p p p p p p P +.$.",
+"&.{ i p p p p p p p v b p p p p p p p a H p p p p p p p p P +.$.",
+"&.{ a p p p p p p p v b p p p p p p p a G p p p p p p p p P +.$.",
+"&.{ a p p p p p p p v b p p p p p p p a G p p p p p p p p P +.&.",
+"=.{ u p p p p p p p z b p p p p p p p a H p p p p p p p p P +.$.",
+"&.} h s g g s s g p M F p f a a a f a d J a f f f f f f a U +.$.",
+";._ # - % - - % % % * Z H G H G H H H J A D G H L L K G D A +.$.",
+";./                 . j p p p p p p p a G p p p e w u p p P +.$.",
+";./   X     + X     o j p p p p p p p a G p e V  .o.~ q p P +.$.",
+";./       E [ ^     o j p p p p p p p a H p V :.>.>.>.) y I +.$.",
+";./     + O.O X.:   o j p p p p p p p a L w ' >.>.>.>.-.9 K +.$.",
+";./     O #.O X.:   o j p p p p p p p a L w  .>.>.>.>.-.9 K @.&.",
+";./       E [ ^     o j p p p p p p p a K i ! >.>.>.>.| w T +.$.",
+";./   X   X $ X     o k p p p p p p p f G p q ( -.-.| 0 p P +.$.",
+";./                 o k p p p p p p p a H p p u j x y p p P +.$.",
+";._ & = = = ; = = = ; A P U U U U U P U N 4 5 7 6 6 7 8 4 B %.$.",
+"&.X.#.;.&.&.&.*.&.;.X.] =.+.+.+.+.@.+.=.:         X       = &.#.",
+"&.X.o.&.#.#.#.#.#.&.[ ' &.$.$.&.&.$.#.*.:                 = &.#.",
+"&.X.O.&.#.&.#.&.#.&. .' &.$.#.#.#.&.#.&.:   X             = &.#.",
+"&.X.O.#.&., O 2 ;.&. .' &.%.#.#.#.#.%.*.:                 = &.#.",
+"&.X.O.#.;.=   < ;.#.{ ' &.%.&.#.#.%.$.;.;             X   = &.#.",
+"&.X.O.#.&.W 1 / &.&.[ ' &.#.#.#.#.#.#.&.:                 = &.%.",
+"&.X.O.&.#.;.>.&.$.&. .' &.#.#.#.#.#.#.&.:                 = &.$.",
+"&.X.+.&.&.#.#.%.&.&.X.' &.#.&.&.&.&.#.;.- . X             = &.#.",
+"&.X.' X.X.X.X.{ X.X.` ` o.X.X.X.X.X.X.O.> X O O O O O O X : &.#.",
+"&.$.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.o.O.O.o.O.o.O.O.O.&.#.",
+"$.#.&.#.&.&.#.&.&.&.&.&.&.&.#.&.#.&.#.&.&.#.$.&.&.&.&.&.&.&.#.#."
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 112 2 ",
+"   c #010101",
+".  c #0C0C0C",
+"X  c #050518",
+"o  c #151515",
+"O  c #1E1E1E",
+"+  c #20201F",
+"@  c #242424",
+"#  c gray16",
+"$  c #33332A",
+"%  c #3E3E34",
+"&  c #3D3D3D",
+"*  c gray20",
+"=  c #404036",
+"-  c #43433F",
+";  c #6D6D38",
+":  c #74743E",
+">  c #414142",
+",  c #767641",
+"<  c #7C7C43",
+"1  c #75755E",
+"2  c #616161",
+"3  c #767676",
+"4  c #9E9E12",
+"5  c #A6A60E",
+"6  c #B5B506",
+"7  c #BBBB02",
+"8  c #B3B309",
+"9  c #A2A215",
+"0  c #9E9E27",
+"q  c #9B9B28",
+"w  c #9A9A30",
+"e  c #C4C400",
+"r  c #CDCD00",
+"t  c #D7D700",
+"y  c #E4E400",
+"u  c #ECEC00",
+"i  c #F5F502",
+"p  c #FEFE01",
+"a  c #FAFA0A",
+"s  c #EBEB14",
+"d  c #F2F214",
+"f  c #FFFF13",
+"g  c #FFFF18",
+"h  c #D3D334",
+"j  c #D9D939",
+"k  c #E2E22C",
+"l  c #EDED2A",
+"z  c #8A8A4E",
+"x  c #919144",
+"c  c #969648",
+"v  c #8A8A57",
+"b  c #969652",
+"n  c #999953",
+"m  c #9B9B5F",
+"M  c #BEBE4F",
+"N  c #B7B755",
+"B  c #8A8A65",
+"V  c #AEAE6E",
+"C  c #B6B663",
+"Z  c #BBBB65",
+"A  c #BABA69",
+"S  c #AEAE71",
+"D  c #A7A77B",
+"F  c #ADAD7B",
+"G  c #B2B272",
+"H  c #B2B27B",
+"J  c #C6C64C",
+"K  c #C9C94F",
+"L  c #D1D146",
+"P  c #C2C250",
+"I  c #CACA50",
+"U  c #CFCF5F",
+"Y  c #C0C06B",
+"T  c #838383",
+"R  c #8E8E87",
+"E  c #8E8E8E",
+"W  c #9E9E85",
+"Q  c #9E9E9D",
+"!  c #A6A685",
+"~  c #AEAE85",
+"^  c #A6A69B",
+"/  c #ADAD9A",
+"(  c #B1B19D",
+")  c #A6A6A6",
+"_  c #A5A5A9",
+"`  c gray66",
+"'  c #ACACB5",
+"]  c #B6B6B6",
+"[  c #BDBDBD",
+"{  c #BDBDC5",
+"}  c #C6C6C5",
+"|  c #C4C4CF",
+" . c #CACACA",
+".. c #D0D0CF",
+"X. c #CECED4",
+"o. c #C9C9DD",
+"O. c #D3D3D3",
+"+. c #D3D3DD",
+"@. c #DBDBDB",
+"#. c #D3D3E7",
+"$. c #DBDBE7",
+"%. c #DDDDEA",
+"&. c #E5E5E5",
+"*. c #E9E9E6",
+"=. c #E5E5EA",
+"-. c #EBEBEA",
+";. c #F0F0ED",
+":. c #E2E2F3",
+">. c #EAEAFB",
+",. c #F3F3F3",
+"<. c #F4F4FF",
+"1. c #FEFEFE",
+/* pixels */
+"&.&.&.&.&.&.&.*.&.&.*.&.&.&.&.&.&.&.&.&.&.&.*.&.&.&.&.&.&.&.&.&.&.&.*.&.&.&.&.&.&.&.&.&.&.&.&.&.",
+"&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.*.&.&.&.&.&.&.&.&.&.&.&.*.&.&.&.&.=.*.&.&.",
+"&.*.=.*.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.*.-.-.-.-.-.-.-.-.*.-.*.-.&.&.&.&.",
+"*.*.&.$.X.o.o.o.o.o.o.o.o.o.X.o.X.X.o.o.o.o.o.o.o.o.o.o.o.X.O.o.o.o.o.o.o.o.o.o.X.o.o.O.&.&.&.=.",
+"*.&.-. .N J P P J P P P P J J J S V J P J M J P P P J P J C D J P P P P P P P P P P M ^ *.&.*.*.",
+"*.*.-.O.s p p p p p p p p p a p I L p p p p p p p p p p p k Z p p p p p p p p p p p p / =.&.&.&.",
+"=.&.-.X.s p a p p a p p p a p p I L p a a p p a p a p a p k Z p p p p p p a p p a p i / =.&.&.&.",
+"&.&.-.X.s p p p p p p p p p a p I L p p p p p p p p p p p k Z p a p p p p p p p p p a / =.&.&.&.",
+"&.&.-.X.s p p p p p p p p p p p I L p p p p p p p p p p p k Z p p p p p p p p p p p a / =.&.*.&.",
+"&.&.-.X.s p p p p p p p p p p p I L p p p p p p p p p p p k Z p a p p p p p p p p p p / =.&.=.&.",
+"&.&.-.X.s p p p p p p p p p p p I L p p p p p p p p p p p k Z p p p p p p p p p p p i / =.&.&.&.",
+"&.&.-.X.s p p p p p p p p p a p I L p p p p p p p p p p p k Z p p p p p p p p p p p i / *.&.&.&.",
+"&.&.-.X.s p p p p p p p p p p p I L p p p p p p p p p p p k Z p p p p p p p p p p p p / =.&.&.&.",
+"&.&.-.X.s p p p p p p p p p p p I L p p p p p p p p p p p k Z p p p p p p p p p p p i / *.&.*.=.",
+"&.&.-.O.s p p p p p p p p p p p K L p p p p p p p p p p p k Z p a p p p p p p p p p i / =.&.=.*.",
+"&.&.-.X.s p p p p p p p p p a p K K p p p p p p p p p p p k Z p p p p p p p p p p p a / =.&.&.&.",
+"&.&.-.O.l f g g g g g g g g g a U K a d d d d d d d d d a j A a s d d d d d d d d d s / =.=.&.&.",
+"&.&.,.] $ = % % % % % = % % % % - D G G G G G G G G G G G F ! G G G G G S G G G G G G ) *.&.&.&.",
+"&.&.,.`                         X h p p p p p p p p p p p k Z p a p p p p p p p p p p / =.&.&.&.",
+"&.&.,.`                         X h p p p p p p p p p p p k Z p p p i 6 0 0 8 u p p i / *.&.-.&.",
+"&.&.;._                         X h p p p p p p p p p p p k Z p a t z | >.>.X.v r p p / =.&.&.&.",
+"&.&.,._   .     . ) &.] O       X h p p p p p p p p p p p k Z p u v <.1.1.1.1.<.B y a / =.&.&.&.",
+"&.&.,._         3 1.> @.]       X h p p p p p p p p p p p k Z p 5 o.1.1.1.1.1.1.#.9 p / =.&.&.&.",
+"&.&.,.)          .}   E ,.      X h p p p p p p p p p p p k Z p c <.1.1.1.1.1.1.1.n i / =.&.&.&.",
+"&.&.,._          . .  E -.      X h p p p p p p p p p p p k A p b 1.1.1.1.1.1.1.1.m i ( *.&.&.&.",
+"&.&.,.)         3 1.> @.]       X h p p p p p p p p p p p k Z p q :.1.1.1.1.1.1.>.q p / =.&.&.&.",
+"&.@.,.)         . Q &.] +       X h p p p p p p p p p p p k Z p r Q 1.1.1.1.1.1._ e a / =.=.&.&.",
+"&.&.,.)                         X h p p p p p p p p p p p k Z p p 5 _ <.1.1.1.' 4 p i / =.&.=.*.",
+"&.&.,._   .           .         X h p a p p p p p p p p p k Z p p p 7 w W W x 6 p p i / =.&.&.*.",
+"&.&.,._                         X h p p p p p p p p p a p k Y p a a a p i i p a a p a ( =.&.&.&.",
+"&.&.,.] * & & & > & & > & & & & > ! H H H H H H H H H H G ~ 1 : , : : , < < , : : , ; R -.&.&.&.",
+"&.&.-.O.@.,.-.-.-.-.-.-.-.-.-.,.} { =.$.$.$.$.$.$.$.$.$.%.+.O                         2 ,.&.&.&.",
+"&.&.*. .O.-.&.&.&.&.&.&.&.&.$.-.[ { -.*.*.*.-.*.*.*.*.*.-.@.+                     .   2 ,.&.&.&.",
+"&.&.-.O.O.-.&.*.&.&.&.&.&.&.&.-.[ } -.&.&.&.&.*.&.&.&.&.*.@.+                         2 ,.&.&.&.",
+"*.&.-.O.O.-.*.&.*.<.,.,.*.&.&.-.[ } -.&.&.&.&.&.&.&.&.&.*.@.O                         2 ,.&.&.&.",
+"*.*.-. .O.-.&.-.} T R E &.-.&.-.[ } -.&.*.&.&.&.&.&.&.&.-.@.O                         2 ,.&.&.*.",
+"=.&.-.O.O.-.$.,.Q     . O.-.&.-.[ } -.&.*.*.&.&.*.*.&.&.-.@.O                         2 ,.&.&.&.",
+"&.&.-.O.O.-.&.,.Q     . @.-.&.-.[ } -.&.=.&.&.&.=.*.&.&.*.@.O                         2 ;.&.&.&.",
+"&.&.-. .O.-.&.,.` o O @ @.*.&.-.[ } -.&.&.&.&.&.&.&.&.&.=.@.+                         2 ;.&.&.&.",
+"&.&.-.X.@.-.&.&.&.&.=.*.&.&.=.;.{ } -.&.&.&.=.*.&.&.&.&.-.@.+                         2 ,.&.&.&.",
+"*.&.-. .O.-.&.&.*.*.*.=.&.&.&.-.[ } -.&.&.&.&.*.&.&.&.&.-.@.O                         2 ,.&.&.&.",
+"*.*.-.O.O.*.&.&.&.&.=.*.&.&.&.-.[ } -.&.&.&.&.&.&.&.&.=.*.@.O                         2 ,.&.&.&.",
+"=.&.-...@.-.-.-.*.*.*.-.*.-.*.,.[ } ,.&.&.-.-.-.*.=.-.*.-.@.O                         2 ,.&.&.&.",
+"&.&.-.O.{  .}  .| } } } }  .} | ] ]  .} } } } } }  .} }  .} & O @ @ @ @ @ @ @ @ # # o 3 ;.&.&.&.",
+"&.&.&.*.&.$.&.@.@.&.$.&.&.$.&.@.&.&.$.&.@.&.&.$.&.&.@.&.$.&.*.*.-.*.=.-.*.*.-.*.*.*.*.*.*.&.&.&.",
+"&.&.&.=.*.*.&.&.*.*.&.&.&.&.*.*.*.&.&.&.*.&.&.*.&.&.&.&.=.*.&.&.*.*.=.*.&.&.*.=.=.=.*.&.=.&.&.&.",
+"*.&.&.&.&.*.&.&.&.&.=.&.*.&.&.&.&.&.*.&.&.&.&.&.&.=.&.&.&.*.&.&.&.=.&.&.&.&.&.&.=.&.*.=.&.&.*.*.",
+"*.&.&.&.&.&.*.&.&.&.*.*.&.&.&.&.=.*.*.&.*.&.&.&.&.*.*.&.&.&.&.=.&.*.&.&.=.*.*.&.&.&.&.&.&.&.&.=."
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/lightup-web.png b/icons/lightup-web.png
new file mode 100644 (file)
index 0000000..d48aa4d
Binary files /dev/null and b/icons/lightup-web.png differ
diff --git a/icons/lightup.ico b/icons/lightup.ico
new file mode 100644 (file)
index 0000000..2774b76
Binary files /dev/null and b/icons/lightup.ico differ
diff --git a/icons/lightup.rc b/icons/lightup.rc
new file mode 100644 (file)
index 0000000..f3d0cee
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "lightup.ico"
diff --git a/icons/lightup.sav b/icons/lightup.sav
new file mode 100644 (file)
index 0000000..21b59bd
--- /dev/null
@@ -0,0 +1,24 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :8:Light Up
+PARAMS  :10:7x7b20s4d1
+CPARAMS :10:7x7b25s4d1
+SEED    :15:538922615851330
+DESC    :25:b3gB0c0dBaBaBa1aBd1c01g0b
+NSTATES :2:16
+STATEPOS:2:16
+MOVE    :4:L1,0
+MOVE    :4:L2,1
+MOVE    :4:L3,0
+MOVE    :4:L0,3
+MOVE    :4:L6,1
+MOVE    :4:L3,4
+MOVE    :4:I6,5
+MOVE    :4:I1,5
+MOVE    :4:I2,6
+MOVE    :4:I3,6
+MOVE    :4:I4,5
+MOVE    :4:I5,6
+MOVE    :4:L5,5
+MOVE    :4:I6,4
+MOVE    :4:I4,2
diff --git a/icons/loopy-16d24.png b/icons/loopy-16d24.png
new file mode 100644 (file)
index 0000000..453bdd9
Binary files /dev/null and b/icons/loopy-16d24.png differ
diff --git a/icons/loopy-16d4.png b/icons/loopy-16d4.png
new file mode 100644 (file)
index 0000000..25972da
Binary files /dev/null and b/icons/loopy-16d4.png differ
diff --git a/icons/loopy-16d8.png b/icons/loopy-16d8.png
new file mode 100644 (file)
index 0000000..453bdd9
Binary files /dev/null and b/icons/loopy-16d8.png differ
diff --git a/icons/loopy-32d24.png b/icons/loopy-32d24.png
new file mode 100644 (file)
index 0000000..d521848
Binary files /dev/null and b/icons/loopy-32d24.png differ
diff --git a/icons/loopy-32d4.png b/icons/loopy-32d4.png
new file mode 100644 (file)
index 0000000..4cbbc93
Binary files /dev/null and b/icons/loopy-32d4.png differ
diff --git a/icons/loopy-32d8.png b/icons/loopy-32d8.png
new file mode 100644 (file)
index 0000000..d521848
Binary files /dev/null and b/icons/loopy-32d8.png differ
diff --git a/icons/loopy-48d24.png b/icons/loopy-48d24.png
new file mode 100644 (file)
index 0000000..ad93828
Binary files /dev/null and b/icons/loopy-48d24.png differ
diff --git a/icons/loopy-48d4.png b/icons/loopy-48d4.png
new file mode 100644 (file)
index 0000000..2e020e3
Binary files /dev/null and b/icons/loopy-48d4.png differ
diff --git a/icons/loopy-48d8.png b/icons/loopy-48d8.png
new file mode 100644 (file)
index 0000000..ad93828
Binary files /dev/null and b/icons/loopy-48d8.png differ
diff --git a/icons/loopy-base.png b/icons/loopy-base.png
new file mode 100644 (file)
index 0000000..cf71c8d
Binary files /dev/null and b/icons/loopy-base.png differ
diff --git a/icons/loopy-ibase.png b/icons/loopy-ibase.png
new file mode 100644 (file)
index 0000000..9cdfcf6
Binary files /dev/null and b/icons/loopy-ibase.png differ
diff --git a/icons/loopy-ibase4.png b/icons/loopy-ibase4.png
new file mode 100644 (file)
index 0000000..9167cbd
Binary files /dev/null and b/icons/loopy-ibase4.png differ
diff --git a/icons/loopy-icon.c b/icons/loopy-icon.c
new file mode 100644 (file)
index 0000000..cb46570
--- /dev/null
@@ -0,0 +1,891 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"hXwXlXhXzXtXaXlXhXkXeX0XqXqX9XhX",
+"wXF.xXcXNXE.#XNXnXuX$.x.x.x.h.rX",
+"lXxXkX.XiXcXvX6X&XsXz.AXmXNXbXkX",
+"jXlX7XW _.vXxX).4.gXf.cXhXkXfXjX",
+"zXmXjX).eXmXJX1X&XNXx.bXkXlXcXlX",
+"tXE.xXbXMX}.b.R.E.m.<.vXfXxX*XwX",
+"aX#XzXsXCXV.V.&X X X#XlXdXVX(.:X",
+"zXvXlXjXZXK.=XJXMXBXvXvXb.>X#X:X",
+"kXjXcXlXDXK.XXMXhXjXhXbX5.3X#X:X",
+"kXuXiXrXcXV.XXBXjXkXiXuX3XgX(.>X",
+"eX#.l.b.b.:.#XvXhXuX#.j.z.l.t.0X",
+"0Xb.CX*XhXnXlXlXcXiXl.BXcXbXcXkX",
+"qXz.VX@.`.bXdXjXlXyXj.vXjXkXgXjX",
+"0XM.SX/.aXCXMXNXCXlXz.bXkXlXxXlX",
+"wX0.XX+XXXQ.(.}.|.~.t.cXgXxX3XyX",
+"kX5X:X;X>X:X:X>X>X:X0XkXjXlXyXfX"
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"lXlXcXlXlXlXlXlXlXlXkXxXzXlXlXlXlXlXlXkXxXlXhXjXjXjXjXjXhXzXzXlX",
+"lXzXdXlXzXzXzXzXzXlXxXfXkXzXzXzXzXzXlXxXaXcXCXBXVXVXVXBXCXkXhXzX",
+"cXdX3 V.MXpXdXdXsXfXgXq g.BXpXdXdXdXsXjXC d J S D D D D F s 7XnX",
+"lXlXV.,XvXjXgXfXjXkXzXA.*XbXhXgXfXhXgXZXI e.-X}. X X X XXXL.yXcX",
+"lXzXMXvXlXlXCXDXbXkXzXMXbXkXzXCXSXnXgXJX( R.UXMXVXVXVXBXCXMXkXlX",
+"lXzXpXjXkXmXB.@.8XbXlXpXhXzXjXu.*.,XxXFX~ G.AXgXjXjXjXjXkXaXhXzX",
+"lXzXdXhXMX*XI q.$.FXjXdXjXxXaXtX_ r.AXSX~ H.DXhXlXlXlXlXzXfXhXzX",
+"lXzXdXhXNXOXE m.} FXjXdXjXkXNXP.! rXlXGX~ H.DXhXlXlXlXlXzXfXhXzX",
+"lXzXsXjXlXvXr.E &XmXkXdXjXzXjXY Y ^.bXDX~ H.DXhXlXlXlXlXzXfXhXzX",
+"lXlXfXkXlXzXNXmXnXjXzXfXhXhXgXbXjXfXdXSX~ H.DXhXlXlXlXlXzXfXjXzX",
+"kXxXfXzXzXzXkXlXkXzXcXsXMXFXSXAXSXDXCXUX] P.DXjXzXzXzXlXcXfXgXzX",
+"xXfX0 A.MXpXdXdXdXfXsXV m R I I I I P ! * t.BXpXdXdXdXdXjX[ 2XmX",
+"zXkXg.*XbXhXjXjXjXhXMXV r.{.Q.~.~.~.!.).k.XXbXhXgXfXhXgXNX$.;XMX",
+"lXzXBXbXlXzXlXlXlXhXFXR }.UXVXZXZXZXZXAXVXbXlXzXZXFXnXgXSX9.=XNX",
+"lXzXpXjXlXlXlXlXlXhXSXI Q.VXgXjXjXjXjXkXpXgXzXzXz.<.<XxXZX5.=XNX",
+"lXzXdXkXlXlXlXlXlXhXSXI ~.ZXjXlXlXlXlXzXfXjXzXsX5X| <.SXVX5.=XNX",
+"lXzXdXkXlXlXlXlXlXhXSXI ~.ZXjXlXlXlXlXzXfXjXkXMX.X~ 5XxXZX5.=XNX",
+"lXzXdXkXlXlXlXlXlXhXSXI ~.ZXjXlXlXlXlXzXdXhXlXvXT A R.vXCX5.=XNX",
+"lXlXsXgXhXgXgXgXgXsXCXP !.ZXjXlXlXlXlXlXdXfXhXfXlXaXiXdXBX4.=XNX",
+"kXxXhXAXJXGXGXGXGXSXUX~ ).AXkXzXzXzXlXcXjXVXJXGXGXJXJXSXUXt.*XNX",
+"xXsXC J ( ~ ~ ~ ~ ~ ' = c.BXpXfXfXdXdXjXR Z _ ~ ~ ~ ~ ~ ( k 6XnX",
+"lXxXl e.T.G.F.D.G.H.P.0.XXbXgXjXjXjXfXBXG *.Q.F.H.H.H.H.K.k.tXvX",
+"hXCXJ ;XUXAXIXUXHXSXFXVXbXlXzXzXzXzXhXJX_ Q.UXZXDXDXDXSXFXBXjXlX",
+"jXBXS }.MXdXm.t.5XzXhXpXhXzXlXlXlXlXgXGX~ F.ZXfXhXhXhXhXjXaXhXzX",
+"jXVXD  XVXjX~.g K.AXjXdXjXlXlXlXlXlXgXGX~ H.DXhXlXlXlXlXzXfXhXzX",
+"jXVXD  XNXcX,Xn V.SXjXdXjXlXlXlXlXlXgXGX~ H.DXhXlXlXlXlXzXfXhXzX",
+"jXVXD |.AXwX8.Q W.VXkXdXjXlXlXlXlXlXgXGX~ H.DXhXlXlXlXlXzXfXhXzX",
+"jXBXD }.mXgXqXpXnXfXjXsXgXhXhXhXhXhXsXSX~ H.SXhXlXlXlXlXzXdXhXzX",
+"jXCXG *XUXCXFXSXCXAXSXlXNXSXAXAXAXAXBXUX` L.FXjXzXzXzXzXxXkXhXzX",
+"zXhXk | q.4.5.5.5.5.9.R #.9.5.5.5.5.4.t.p k.BXaXfXfXfXdXkX5.6XnX",
+"zXhX9X:X=X=X=X=X=X=X=X,X;X=X=X=X=X=X=X*X3XtXjXhXhXhXhXhXhX6XhXzX",
+"lXzXbXMXNXNXNXNXNXNXNXMXMXNXNXNXNXNXNXNXmXcXlXzXzXzXzXzXzXnXzXlX"
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"lXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlX",
+"lXlXlXlXhXkXlXlXlXlXlXlXlXlXlXlXlXhXkXlXlXlXlXlXlXlXlXlXlXlXhXjXlXkXkXkXkXkXkXkXkXkXkXhXlXlXlXlX",
+"lXlXlXzXAXbXkXlXlXlXlXlXlXlXlXlXzXAXbXkXlXlXlXlXlXlXlXlXlXlXZXmXzXxXxXxXxXxXxXxXxXzXxXVXzXlXlXlX",
+"lXlXzXhXT.5XbXlXzXzXzXzXzXzXzXxXjXR.3XbXlXzXzXzXzXzXzXzXxXkXY.<XcXhXjXjXjXjXjXjXjXkXfX`.gXzXlXlX",
+"lXhXAXR.  ) bXyXpXpXpXpXpXpXyXvXU.  ~ bXyXpXpXpXpXpXpXyXcXY.  @ * & & & & & & & & = X 6 fXxXkXlX",
+"lXkXbX5XW H.MXgXjXjXjXjXjXjXhXcX6XQ F.MXgXjXjXjXjXjXjXhXcX6X$ #.Q.F.J.J.J.J.J.J.H.K.A.=.sXxXlXlX",
+"lXlXkXbXmXMXlXzXlXlXhXgXkXzXzXkXbXmXMXlXzXzXkXhXhXkXzXlXlXmX> K.UXCXSXSXSXSXSXSXSXSXFXvXjXzXlXlX",
+"lXlXlXlXyXgXzXlXlXkXAXKXvXkXlXlXlXyXgXzXlXkXmXJXHXvXkXlXzXxX; N.ZXdXhXhXhXhXhXhXhXhXjXuXjXzXlXlX",
+"lXlXlXzXpXjXlXlXlXbXZ.5.8XbXkXlXzXpXjXlXkXbX&X6.r.8XbXjXzXcX; V.FXhXlXlXlXlXlXlXlXlXzXpXjXzXlXlX",
+"lXlXlXzXpXjXlXhXSXa.y ! k lXzXkXzXpXjXlXjXVXc.:.N c cXlXlXcX; V.FXhXlXlXlXlXlXlXlXlXzXpXjXzXlXlX",
+"lXlXlXzXpXjXlXjXSXn 2.lX# +XNXjXzXpXjXlXlXkXxXUX1.h nXkXlXcX; V.FXhXlXlXlXlXlXlXlXlXzXpXjXzXlXlX",
+"lXlXlXzXpXjXlXjXSXm 1.kX# @XNXjXzXpXjXlXlXjXZXg.v uXMXjXzXcX; V.FXhXlXlXlXlXlXlXlXlXzXpXjXzXlXlX",
+"lXlXlXzXpXjXlXhXSXh.r U c zXlXkXzXpXjXlXhXAXh.  ^ p.aXzXlXcX; V.FXhXlXlXlXlXlXlXlXlXzXpXjXzXlXlX",
+"lXlXlXzXpXjXlXlXlXnXJ.e.wXbXkXlXzXpXjXlXjXmX/.i.q.i.gXzXlXcX; V.FXhXlXlXlXlXlXlXlXlXzXpXjXzXlXlX",
+"lXlXlXzXyXhXzXlXlXkXAXJXcXkXlXlXzXyXhXlXlXjXBXGXHXGXlXkXlXxX; V.FXhXlXlXlXlXlXlXlXlXzXuXjXzXlXlX",
+"lXlXlXxXvXcXkXlXlXlXhXgXkXlXlXkXxXvXvXzXzXzXkXhXhXjXzXzXxXvX: C.DXgXlXlXlXlXlXlXlXkXxXxXjXlXlXlX",
+"lXlXzXjXU.5XbXlXzXzXzXzXzXzXzXxXlXP.2XbXlXzXzXzXzXzXzXlXxXlX: B.FXjXzXzXzXzXzXzXzXxXkX^.dXxXlXlX",
+"lXhXZXR.  ) bXyXpXpXpXpXpXpXyXvXP.  $ - = = = = = = = = - -   R vXyXpXpXpXpXpXpXuXvXP., hXxXkXlX",
+"lXkXbX4XU F.MXgXjXjXjXjXjXjXhXvX3X+ #.Y.Z.S.S.S.S.S.S.S.D.S.R B.MXgXjXjXjXjXjXjXhXvX,Xh pXcXkXlX",
+"lXlXkXbXnXMXlXzXlXlXlXlXlXlXlXzXbX; Y.UXCXDXDXDXDXDXDXDXDXFXnXMXlXzXzXkXhXhXkXzXlXzXkXZ tXcXkXlX",
+"lXlXlXlXyXgXzXlXlXlXlXlXlXlXkXzXlX= Z.CXdXhXhXhXhXhXhXhXhXjXyXgXzXlXkXnXJXGXvXkXlXxXdXB yXcXkXlX",
+"lXlXlXzXpXjXlXlXlXlXlXlXlXlXlXzXzX= S.DXhXlXlXlXlXlXlXlXlXzXpXjXzXkXbX-Xq.u.9XbXjXxXfXB tXcXkXlX",
+"lXlXlXzXpXjXlXlXlXlXlXlXlXlXlXzXzX= S.DXhXlXlXlXlXlXlXlXlXzXpXjXzXjXVXv.&.M x xXlXzXfXB tXcXkXlX",
+"lXlXlXzXpXjXlXlXlXlXlXlXlXlXlXzXzX= S.DXhXlXlXlXlXlXlXlXlXzXpXjXzXlXkXzXUX8.s vXlXzXfXB tXcXkXlX",
+"lXlXlXzXpXjXlXlXlXlXlXlXlXlXlXzXzX= S.DXhXlXlXlXlXlXlXlXlXzXpXjXzXlXjXZXc.z wXNXjXxXfXB tXcXkXlX",
+"lXlXlXzXpXjXlXlXlXlXlXlXlXlXlXzXzX= S.DXhXlXlXlXlXlXlXlXlXzXpXjXzXhXZXx.  ^ s.pXxXzXfXB tXcXkXlX",
+"lXlXlXzXpXjXlXlXlXlXlXlXlXlXlXzXzX= S.DXhXlXlXlXlXlXlXlXlXzXpXjXzXjXMX^.r.8.e.dXzXzXfXB tXcXkXlX",
+"lXlXlXzXyXhXlXlXlXlXlXlXlXlXkXzXlX= S.DXhXlXlXlXlXlXlXlXlXzXuXhXlXlXjXBXHXJXHXzXkXzXdXB tXcXkXlX",
+"lXlXlXxXcXcXlXzXzXzXzXzXzXzXlXxXxX- D.DXhXlXlXlXlXlXlXlXkXzXcXcXlXzXzXkXgXhXhXlXlXxXgXV tXcXkXlX",
+"lXlXlXkXU.6XmXxXcXcXcXcXcXcXxXvXlX- S.FXjXzXzXzXzXzXzXzXzXzXT.3XmXxXcXcXcXcXcXcXxXbXfXV yXcXkXlX",
+"lXhXZXY.  & : ; ; ; ; ; ; ; ; : :   ! bXyXpXpXpXpXpXpXuXcXT.  % : ; ; ; ; ; ; ; ; > # 7 fXxXkXlX",
+"lXjXnX<Xo #.K.N.V.V.V.V.V.V.V.C.B.P B.MXgXjXjXjXjXjXjXhXcX4X@ X.L.N.V.V.V.V.V.V.V.C.b.o.sXxXlXlX",
+"lXlXzXcX= Q.UXZXFXSXZXZXSXFXDXDXFXbXMXlXzXzXzXzXzXzXzXlXlXmX> L.UXZXFXFXFXFXFXFXFXDXGXcXjXzXlXlX",
+"lXkXxXhX& F.CXdXgXcXFXSXzXgXhXgXjXyXgXzXlXlXlXlXlXlXlXkXzXxX; N.ZXsXhXhXhXhXhXhXhXgXjXuXjXzXlXlX",
+"lXkXxXjX& J.SXgXbX;X7.i.6XvXkXlXzXpXjXlXlXlXlXlXlXlXlXlXzXcX; V.FXhXlXlXlXlXlXlXlXlXzXpXjXzXlXlX",
+"lXkXxXjX& J.SXfXNXW.u.M x bXlXkXzXpXjXlXlXlXlXlXlXlXlXlXzXcX; V.FXhXlXlXlXlXlXlXlXlXzXpXjXzXlXlX",
+"lXkXxXjX& J.SXhXkXnX~.c &.CXjXlXzXpXjXlXlXlXlXlXlXlXlXlXzXcX; V.FXhXlXlXlXlXlXlXlXlXzXpXjXzXlXlX",
+"lXkXxXjX& J.SXhXjXBXW.H A zXlXkXzXpXjXlXlXlXlXlXlXlXlXlXzXcX; V.FXhXlXlXlXlXlXlXlXlXzXpXjXzXlXlX",
+"lXkXxXjX& J.SXfXBXz.f.R u kXzXkXzXpXjXlXlXlXlXlXlXlXlXlXzXcX; V.FXhXlXlXlXlXlXlXlXlXzXpXjXzXlXlX",
+"lXkXxXjX& J.SXfXmX`.-.w.7XbXkXlXzXpXjXlXlXlXlXlXlXlXlXlXzXcX; V.FXhXlXlXlXlXlXlXlXlXzXpXjXzXlXlX",
+"lXkXxXjX& H.SXhXjXBXKXGXvXkXlXkXzXuXhXlXlXlXlXlXlXlXlXkXlXxX; V.FXhXlXlXlXlXlXlXlXlXzXuXjXzXlXlX",
+"lXkXzXkX= L.FXjXxXlXhXjXlXxXxXzXcXvXvXzXxXxXxXxXxXxXxXzXxXbX> C.DXgXlXlXlXlXlXlXlXkXxXxXjXlXlXlX",
+"lXkXxXfX$ Z.BXaXfXfXfXfXfXfXfXfXdXP.,XkXdXfXfXfXfXfXfXdXgXfX= m.GXjXzXzXzXzXzXzXzXxXjX^.fXzXlXlX",
+"lXjXVX).> l Z B B B B B B B B V N , f Z B B B B B B B B V V : o.cXuXpXpXpXpXpXpXuXxX/.i fXxXkXlX",
+"lXlXzXgXdXpXtXyXtXtXtXtXtXtXyXtXyXhXpXtXyXtXtXtXtXtXtXtXtXyXfXsXjXjXjXjXjXjXjXjXjXjXfXfXzXlXlXlX",
+"lXlXlXzXxXcXcXcXcXcXcXcXcXcXcXcXcXxXcXcXcXcXcXcXcXcXcXcXcXcXxXxXzXzXzXzXzXzXzXzXzXlXzXxXlXlXlXlX",
+"lXlXlXlXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXlXlXlXlXlXlXlXlXlXlXlXlXkXlXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlX"
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/loopy-web.png b/icons/loopy-web.png
new file mode 100644 (file)
index 0000000..fc0f23f
Binary files /dev/null and b/icons/loopy-web.png differ
diff --git a/icons/loopy.ico b/icons/loopy.ico
new file mode 100644 (file)
index 0000000..9a498ef
Binary files /dev/null and b/icons/loopy.ico differ
diff --git a/icons/loopy.rc b/icons/loopy.rc
new file mode 100644 (file)
index 0000000..583ee8f
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "loopy.ico"
diff --git a/icons/loopy.sav b/icons/loopy.sav
new file mode 100644 (file)
index 0000000..1161181
--- /dev/null
@@ -0,0 +1,120 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :5:Loopy
+PARAMS  :7:7x7t0de
+CPARAMS :7:7x7t0de
+DESC    :31:02g222b3b2e2a2b322b2a2a3a2a1d1b
+NSTATES :3:113
+STATEPOS:2:75
+MOVE    :2:3n
+MOVE    :2:0n
+MOVE    :2:1n
+MOVE    :2:2n
+MOVE    :2:4n
+MOVE    :2:6y
+MOVE    :2:5y
+MOVE    :3:25n
+MOVE    :2:9n
+MOVE    :3:28y
+MOVE    :3:27y
+MOVE    :3:42n
+MOVE    :3:30n
+MOVE    :3:45y
+MOVE    :3:44y
+MOVE    :3:59n
+MOVE    :3:47n
+MOVE    :3:62y
+MOVE    :3:61y
+MOVE    :3:76n
+MOVE    :3:64n
+MOVE    :3:79y
+MOVE    :3:78y
+MOVE    :3:93n
+MOVE    :3:81n
+MOVE    :4:110y
+MOVE    :4:111y
+MOVE    :3:99y
+MOVE    :3:98y
+MOVE    :3:24n
+MOVE    :3:39y
+MOVE    :3:23y
+MOVE    :3:22y
+MOVE    :3:26n
+MOVE    :3:37n
+MOVE    :3:38y
+MOVE    :3:54n
+MOVE    :3:69y
+MOVE    :3:53y
+MOVE    :3:40y
+MOVE    :2:7y
+MOVE    :3:88y
+MOVE    :3:87y
+MOVE    :4:102n
+MOVE    :3:90n
+MOVE    :3:52n
+MOVE    :3:41y
+MOVE    :3:55n
+MOVE    :3:43n
+MOVE    :2:8n
+MOVE    :3:10y
+MOVE    :3:12y
+MOVE    :3:11n
+MOVE    :3:13y
+MOVE    :3:29n
+MOVE    :3:15y
+MOVE    :3:14n
+MOVE    :3:16y
+MOVE    :3:32y
+MOVE    :3:31n
+MOVE    :3:18y
+MOVE    :3:17n
+MOVE    :3:19y
+MOVE    :3:20y
+MOVE    :3:21n
+MOVE    :3:33y
+MOVE    :3:35y
+MOVE    :3:36n
+MOVE    :3:50y
+MOVE    :3:57y
+MOVE    :3:58y
+MOVE    :3:72n
+MOVE    :3:60n
+MOVE    :3:74y
+MOVE    :4:104y
+MOVE    :4:107n
+MOVE    :4:106n
+MOVE    :3:92n
+MOVE    :4:109n
+MOVE    :3:89y
+MOVE    :3:77n
+MOVE    :3:75n
+MOVE    :3:73y
+MOVE    :3:85n
+MOVE    :3:70n
+MOVE    :3:56y
+MOVE    :3:67n
+MOVE    :3:71y
+MOVE    :3:68y
+MOVE    :3:84n
+MOVE    :3:82n
+MOVE    :3:83y
+MOVE    :3:97n
+MOVE    :3:86y
+MOVE    :4:101y
+MOVE    :4:100n
+MOVE    :4:103y
+MOVE    :4:105y
+MOVE    :4:108y
+MOVE    :3:96n
+MOVE    :3:95y
+MOVE    :3:94y
+MOVE    :3:91y
+MOVE    :3:80y
+MOVE    :3:66n
+MOVE    :3:65y
+MOVE    :3:63y
+MOVE    :3:51n
+MOVE    :3:46n
+MOVE    :3:34y
+MOVE    :3:48n
+MOVE    :3:49y
diff --git a/icons/magnets-16d24.png b/icons/magnets-16d24.png
new file mode 100644 (file)
index 0000000..aadfdf6
Binary files /dev/null and b/icons/magnets-16d24.png differ
diff --git a/icons/magnets-16d4.png b/icons/magnets-16d4.png
new file mode 100644 (file)
index 0000000..3b1e455
Binary files /dev/null and b/icons/magnets-16d4.png differ
diff --git a/icons/magnets-16d8.png b/icons/magnets-16d8.png
new file mode 100644 (file)
index 0000000..1aaffb9
Binary files /dev/null and b/icons/magnets-16d8.png differ
diff --git a/icons/magnets-32d24.png b/icons/magnets-32d24.png
new file mode 100644 (file)
index 0000000..cd3340f
Binary files /dev/null and b/icons/magnets-32d24.png differ
diff --git a/icons/magnets-32d4.png b/icons/magnets-32d4.png
new file mode 100644 (file)
index 0000000..6c1d57d
Binary files /dev/null and b/icons/magnets-32d4.png differ
diff --git a/icons/magnets-32d8.png b/icons/magnets-32d8.png
new file mode 100644 (file)
index 0000000..757c287
Binary files /dev/null and b/icons/magnets-32d8.png differ
diff --git a/icons/magnets-48d24.png b/icons/magnets-48d24.png
new file mode 100644 (file)
index 0000000..54b1403
Binary files /dev/null and b/icons/magnets-48d24.png differ
diff --git a/icons/magnets-48d4.png b/icons/magnets-48d4.png
new file mode 100644 (file)
index 0000000..46b5af2
Binary files /dev/null and b/icons/magnets-48d4.png differ
diff --git a/icons/magnets-48d8.png b/icons/magnets-48d8.png
new file mode 100644 (file)
index 0000000..ace4e78
Binary files /dev/null and b/icons/magnets-48d8.png differ
diff --git a/icons/magnets-base.png b/icons/magnets-base.png
new file mode 100644 (file)
index 0000000..50901d5
Binary files /dev/null and b/icons/magnets-base.png differ
diff --git a/icons/magnets-ibase.png b/icons/magnets-ibase.png
new file mode 100644 (file)
index 0000000..8a13917
Binary files /dev/null and b/icons/magnets-ibase.png differ
diff --git a/icons/magnets-ibase4.png b/icons/magnets-ibase4.png
new file mode 100644 (file)
index 0000000..aef8868
Binary files /dev/null and b/icons/magnets-ibase4.png differ
diff --git a/icons/magnets-icon.c b/icons/magnets-icon.c
new file mode 100644 (file)
index 0000000..d8b9941
--- /dev/null
@@ -0,0 +1,636 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 251 2 ",
+"   c #D5D7D7",
+".  c #D5D2D2",
+"X  c #D5CACA",
+"o  c #D4CACA",
+"O  c #D5CDCD",
+"+  c #CFCBCB",
+"@  c #CCCDCD",
+"#  c #CECECE",
+"$  c #CDCDCD",
+"%  c gray83",
+"&  c #D7D7D7",
+"*  c LightGray",
+"=  c LightGray",
+"-  c LightGray",
+";  c LightGray",
+":  c #D5D5D5",
+">  c #D4C4C4",
+",  c #CD2323",
+"<  c #CD1515",
+"1  c #CA1D1D",
+"2  c #D91111",
+"3  c #5E1A1A",
+"4  c #071313",
+"5  c #100D0D",
+"6  c gray1",
+"7  c gray27",
+"8  c #C8C8C8",
+"9  c #ACACAC",
+"0  c #AFAFAF",
+"q  c #AEAEAE",
+"w  c gray70",
+"e  c #D4B6B6",
+"r  c #CC0505",
+"t  c #D06868",
+"y  c #CE7D7D",
+"u  c #DB0404",
+"i  c #4B0000",
+"p  c #061515",
+"a  c #383535",
+"s  c gray15",
+"d  c #2C2C2C",
+"f  c #BCBCBC",
+"g  c gray65",
+"h  c #A9A9A9",
+"j  c gray66",
+"k  c #AEAEAE",
+"l  c LightGray",
+"z  c #D3B1B1",
+"x  c #CC1B1B",
+"c  c #D1ABAA",
+"v  c #CFBDBC",
+"b  c #DC2726",
+"n  c #450000",
+"m  c #293A39",
+"M  c #908E8C",
+"N  c #767876",
+"B  c #393939",
+"V  c #BAB9B9",
+"C  c #A9A7A7",
+"Z  c #ABA9A9",
+"A  c #A9A7A7",
+"S  c #AFAFAF",
+"D  c LightGray",
+"F  c #D4BBBC",
+"G  c #D20303",
+"H  c #D52325",
+"J  c #D23133",
+"K  c #E20000",
+"L  c #510202",
+"P  c black",
+"I  c #050202",
+"U  c #2B2A2B",
+"Y  c #C1C4C4",
+"T  c #A6AEAE",
+"R  c #A9B3B3",
+"E  c #A8B1B1",
+"W  c #AEB1B1",
+"Q  c #D3D2D2",
+"!  c #D6D5D6",
+"~  c #B7A592",
+"^  c #AC8F75",
+"/  c #AF9078",
+"(  c #B1987F",
+")  c #91957E",
+"_  c #7A977D",
+"`  c #839A82",
+"'  c #7A9479",
+"]  c #A2B2A4",
+"[  c #D7CCCE",
+"{  c #C7A3A2",
+"}  c #C99D9D",
+"|  c #C8A1A1",
+" . c #CBBEBE",
+".. c #D5D7D7",
+"X. c #BDCEBD",
+"o. c #2AA732",
+"O. c #18A524",
+"+. c #19A524",
+"@. c #20A62B",
+"#. c #19A21D",
+"$. c #2DA72D",
+"%. c #22A322",
+"&. c #20A01D",
+"*. c #4BBC59",
+"=. c #D26D6E",
+"-. c #CD0000",
+";. c #CF2D2D",
+":. c #CD0000",
+">. c #D15555",
+",. c #D6E4E4",
+"<. c #B8CCB8",
+"1. c #20991E",
+"2. c #3CA239",
+"3. c #42A43F",
+"4. c #1F981C",
+"5. c #169615",
+"6. c #1E991E",
+"7. c #43A542",
+"8. c #319B2E",
+"9. c #36B246",
+"0. c #D05E5E",
+"q. c #CE2828",
+"w. c #D3C3C3",
+"e. c #CF5252",
+"r. c #CF4A4A",
+"t. c #D6E3E3",
+"y. c #B7CBB7",
+"u. c #199719",
+"i. c #359F35",
+"p. c #3BA13B",
+"a. c #189618",
+"s. c #139411",
+"d. c #1D971A",
+"f. c #41A33E",
+"g. c #309929",
+"h. c #34B042",
+"j. c #CE5F5E",
+"k. c #CB2828",
+"l. c #D1C3C3",
+"z. c #CD5252",
+"x. c #CD4B4B",
+"c. c #D6E3E3",
+"v. c #C2CFC2",
+"b. c #46B046",
+"n. c #34AC34",
+"m. c #34AB34",
+"M. c #3EAE3D",
+"N. c #26AA2F",
+"B. c #2FAE3E",
+"V. c #26AD35",
+"C. c #23A92F",
+"Z. c #52C56C",
+"A. c #D96465",
+"S. c #D70000",
+"D. c #D92E2E",
+"F. c #D80000",
+"G. c #D74B4B",
+"H. c #D5E4E4",
+"J. c #D5D3D5",
+"K. c #878E87",
+"L. c #737E73",
+"P. c #798278",
+"I. c #6E7F74",
+"U. c #A57E75",
+"Y. c #C68175",
+"T. c #C2796F",
+"R. c #BD7367",
+"E. c #D7AFAA",
+"W. c #886768",
+"Q. c #380000",
+"!. c #490909",
+"~. c #3A0000",
+"^. c #764D4D",
+"/. c #DFE3E3",
+"(. c #B7B7B7",
+"). c #070707",
+"_. c #040101",
+"`. c #800101",
+"'. c #DD0000",
+"]. c #CE3435",
+"[. c #CD1011",
+"{. c #DE2D2D",
+"}. c #636565",
+"|. c #070606",
+" X c #4C4C4C",
+".X c gray89",
+"XX c #AFAFAF",
+"oX c gray15",
+"OX c #868685",
+"+X c #8F8D8C",
+"@X c #223232",
+"#X c #770000",
+"$X c #DD3434",
+"%X c #D1CAC9",
+"&X c #CE9897",
+"*X c #DF4343",
+"=X c #5E5959",
+"-X c #464545",
+";X c #989595",
+":X c #656262",
+">X c #525050",
+",X c #E2E2E2",
+"<X c #B4B4B4",
+"1X c gray6",
+"2X c #2A2A2A",
+"3X c #312E2E",
+"4X c #010F0F",
+"5X c #7B0000",
+"6X c #DB0606",
+"7X c #CE8383",
+"8X c #CC4D4D",
+"9X c #DD2E2E",
+"0X c gray38",
+"qX c #0B0C0C",
+"wX c #363535",
+"eX c #161616",
+"rX c gray30",
+"tX c gray89",
+"yX c #C8C8C8",
+"uX c #3E3E3E",
+"iX c #202020",
+"pX c #272424",
+"aX c #212C2C",
+"sX c #923232",
+"dX c #D82828",
+"fX c #CC3838",
+"gX c #CB2424",
+"hX c #DA6161",
+"jX c #8F9494",
+"kX c #1F1E1E",
+"lX c #272727",
+"zX c #1B1B1B",
+"xX c #7C7C7C",
+"cX c gray88",
+"vX c #D8D8D8",
+"bX c gray87",
+"nX c #DDDDDD",
+"mX c #DDDDDD",
+"MX c gainsboro",
+"NX c #D7DBDB",
+"BX c #D5DDDD",
+"VX c #D5D9D9",
+"CX c #D5DBDB",
+"ZX c #D5DDDD",
+"AX c #DBDCDC",
+"SX c #DDDDDD",
+"DX c #DDDDDD",
+"FX c #DDDDDD",
+"GX c gainsboro",
+"HX c #D5D5D5",
+"JX c white",
+/* pixels */
+"  . X o O + @ # $ % & * = - ; : ",
+"> , < 1 2 3 4 5 6 7 8 9 0 q w ; ",
+"e r t y u i p a s d f g h j k l ",
+"z x c v b n m M N B V C Z A S D ",
+"F G H J K L P I P U Y T R E W Q ",
+"! ~ ^ / ( ) _ ` ' ] [ { } |  ...",
+"X.o.O.+.@.#.$.%.&.*.=.-.;.:.>.,.",
+"<.1.2.3.4.5.6.7.8.9.0.q.w.e.r.t.",
+"y.u.i.p.a.s.d.f.g.h.j.k.l.z.x.c.",
+"v.b.n.m.M.N.B.V.C.Z.A.S.D.F.G.H.",
+"J.K.L.P.I.U.Y.T.R.E.W.Q.!.~.^./.",
+"(.).P _.P `.'.].[.{.}.P |.P  X.X",
+"XXoXOX+X@X#X$X%X&X*X=X-X;X:X>X,X",
+"<X1X2X3X4X5X6X7X8X9X0XqXwXeXrXtX",
+"yXuXiXpXaXsXdXfXgXhXjXkXlXzXxXcX",
+"vXbXnXmXMXNXBXVXCXZXAXSXDXFXGXHX"
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 140 2 ",
+"   c #010101",
+".  c #080404",
+"X  c #0B0A0A",
+"o  c #111111",
+"O  c #131A1A",
+"+  c #1B1B1B",
+"@  c #320202",
+"#  c #252525",
+"$  c #2A2A2A",
+"%  c #322F32",
+"&  c #2F3537",
+"*  c #373637",
+"=  c #393537",
+"-  c #393639",
+";  c #3A3A3A",
+":  c #7D0000",
+">  c #5D3537",
+",  c #444444",
+"<  c #4C4C4C",
+"1  c #4C5353",
+"2  c #535353",
+"3  c #715353",
+"4  c #616161",
+"5  c gray46",
+"6  c #7D767E",
+"7  c #820202",
+"8  c #9B0202",
+"9  c #A21A1A",
+"0  c #C70302",
+"q  c #CB0101",
+"w  c #CC0D0D",
+"e  c #D50101",
+"r  c #DA0101",
+"t  c #CD1414",
+"y  c #CC1B1B",
+"u  c #D61A1A",
+"i  c #CE2626",
+"p  c #CD2D2D",
+"a  c #CE3535",
+"s  c #CE3C3C",
+"d  c #D23436",
+"f  c #D93537",
+"g  c #D33538",
+"h  c #D43D3D",
+"j  c #CF4545",
+"k  c #CF4B4B",
+"l  c #D44242",
+"z  c #D34848",
+"x  c #CF5252",
+"c  c #D35555",
+"v  c #D05B5B",
+"b  c #D06262",
+"n  c #CE7676",
+"m  c #C6767E",
+"M  c #D07070",
+"N  c #DD747D",
+"B  c #D17979",
+"V  c #E2757E",
+"C  c #048E02",
+"Z  c #069105",
+"A  c #0A9106",
+"S  c #0C930B",
+"D  c #10950F",
+"F  c #139713",
+"G  c #1B9617",
+"H  c #169816",
+"J  c #1B991B",
+"K  c #219A1F",
+"L  c #1F9A20",
+"P  c #239B22",
+"I  c #2B9F2B",
+"U  c #2FA02F",
+"Y  c #31A131",
+"T  c #38A337",
+"R  c #3BA43B",
+"E  c #42A642",
+"W  c #41AB47",
+"Q  c #43AC48",
+"!  c #4EAA4E",
+"~  c #4CAF51",
+"^  c #57AD57",
+"/  c #5AAE5A",
+"(  c #65B266",
+")  c #6AB36A",
+"_  c #827A82",
+"`  c #808080",
+"'  c gray55",
+"]  c #908E8E",
+"[  c #939393",
+"{  c #9D9B9B",
+"}  c #BB9393",
+"|  c #86BF8A",
+" . c #A7A4A4",
+".. c #ABABAB",
+"X. c #B1B1B1",
+"o. c #BCBCBC",
+"O. c #D28484",
+"+. c #D38C8C",
+"@. c #D39393",
+"#. c #D29B9B",
+"$. c #D99CA1",
+"%. c #D4A5A4",
+"&. c #D3A9A9",
+"*. c #CCBEBE",
+"=. c #D3B2B2",
+"-. c #D4BBBB",
+";. c #96D1A6",
+":. c #99D1A7",
+">. c #ADC8AD",
+",. c #A6D1A6",
+"<. c #AAD2AA",
+"1. c #BDCEBD",
+"2. c #B4D5BE",
+"3. c #BFC6C6",
+"4. c #BFC9C9",
+"5. c #C3C3C4",
+"6. c #C2CECE",
+"7. c #D4C5C5",
+"8. c #DEC2C9",
+"9. c #D5CCCC",
+"0. c #CAD1CA",
+"q. c #C5D9CE",
+"w. c #CED9CE",
+"e. c #DFCFD6",
+"r. c #C2D0D0",
+"t. c #CBD0D1",
+"y. c #C8DAD2",
+"u. c #D4D4D4",
+"i. c #DAD6D9",
+"p. c #D5DBDB",
+"a. c #DDDBDD",
+"s. c #DFE0DF",
+"d. c #D6E2E2",
+"f. c #DCE2E2",
+"g. c #D6ECEC",
+"h. c #D6F6F6",
+"j. c #D7FAFA",
+"k. c #E3E3E3",
+"l. c #F3F3F3",
+"z. c #F8F8F8",
+/* pixels */
+"u.u.u.u.u.i.u.p.u.p.u.u.u.u.u.i.u.u.u.u.u.u.u.p.u.u.u.u.u.u.p.u.",
+"u.p.p.u.u.u.u.u.9.u.u.u.u.u.t.u.u.u.u.a.u.u.u.u.u.u.u.u.u.u.u.u.",
+"u.9.z t u t t y y u 9 O + + + + + + + ' s.o.....X.X.X...X.X.u.u.",
+"d.&.q q q w t q q e 8                 ; u.................X.u.p.",
+"p.&.q q q +.7.q 0 e 8                 ; s.................X.u.u.",
+"d.&.q i s %.9.j p e 8     * , , , < . * p...................u.u.",
+"d.&.q v h.u.u.h.#.e 8     o.l.k.k.l.$ $ a.................X.u.u.",
+"d.&.q t y #.9.i y e 8     # $ # # $   ; a......... .......X.u.u.",
+"p.&.q w q O.=.q q e 8   .             * i.................X.u.u.",
+"p.=.q q q q 0 q 0 e 8                 < s... . . . . . . ...u.u.",
+"u.u.$.N N N N N N V m 6 6 6 6 6 6 6 _ 5.i.0.6.6.4.r.r.r.4.5.u.u.",
+"u.i.2.;.:.;.;.:.;.;.;.,.,.,.,.,.,.,.<.u.a.-.+.@.@.@.@.+.&.s.u.i.",
+"i.1.J A A S D S Z S Z Z S Z S S S Z C ( e.y q q q q q q q &.s.t.",
+"a.>.S R Y H J P E J J J L E J J H R P W 8.w q q s B w 0 q @.d.u.",
+"a.>.F P / I H / U H J J H R ^ S T ^ S ~ 8.0 q q M g.w q 0 @.d.u.",
+"a.>.F G G ^ ( P H J J J J D U ) ~ J S ~ 8.q p &.-.u.=.+.q @.s.u.",
+"a.>.F H G ^ ( I F J J J J F Y ) ! H S ~ 8.q a =.-.u.=.+.q @.d.u.",
+"a.>.F L / I H / U H J J H R / S R ^ D ~ 8.0 q q M g.w q q @.d.u.",
+"a.>.Z T I F H J R H J H H R H H D R J W 8.q q 0 a n w 0 q @.d.9.",
+"i.t.E H J P P J J J J K P J K K P J G | 8.e r r r r r r r @.d.u.",
+"u.i.s.w.w.w.w.w.y.w.w.q.q.q.q.q.q.q.y.f.7.7 7 7 7 7 7 7 : } d.u.",
+"u.u.4 % - * * * = * > f g g g d d d g %.4.                [ s.u.",
+"f...                @ e q q q q q q q l 4..   . .   . .   [ k.u.",
+"s...                @ e 0 q w 9.v q q h 4..               [ k.9.",
+"s...  o $ # # $ +   @ e q y a d.B u q h 6.  X $ # # $ +   [ k.u.",
+"s...  4 z.k.k.z.{   @ e q 7.g.u.p.h.p d 6.  ; z.k.k.l.o.  [ f.u.",
+"s...  + , , , , $   @ e q d c p.O.s 0 h 4.  o , , , , *   [ k.u.",
+"s...  .         .   @ e 0 q w p.b q q h 4..           .   [ f.u.",
+"a.X.                @ e q q q t w q q z 6.X   .           { a.u.",
+"u.i.` < 2 2 2 2 2 1 3 c x x c z k x v =.f.] < 2 2 2 2 < 5 u.u.u.",
+"u.u.k.k.k.k.k.k.k.k.k.d.d.d.d.d.d.d.d.p.u.f.k.k.k.k.k.k.k.u.u.u.",
+"u.u.t.9.u.u.t.u.9.t.u.u.u.u.u.9.u.u.u.u.u.u.u.0.u.u.u.u.u.u.p.u."
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 122 2 ",
+"   c #010101",
+".  c #0B0B0B",
+"X  c #131313",
+"o  c #181818",
+"O  c #3A0101",
+"+  c #222222",
+"@  c gray21",
+"#  c #343D3D",
+"$  c #3D3C3C",
+"%  c #663D3D",
+"&  c #424242",
+"*  c #5B5B5B",
+"=  c #636363",
+"-  c #6D6D6D",
+";  c gray46",
+":  c #7B7B7B",
+">  c #920101",
+",  c #AB0101",
+"<  c #A53D3D",
+"1  c #B73D3D",
+"2  c #CB0101",
+"3  c #CC0B0B",
+"4  c #D40101",
+"5  c #D80101",
+"6  c #CD1111",
+"7  c #CD1D1D",
+"8  c #CE2424",
+"9  c #CE2B2B",
+"0  c #CE3333",
+"q  c #CE3D3C",
+"w  c #D63D3D",
+"e  c #CF4343",
+"r  c #CC4846",
+"t  c #CF5252",
+"y  c #CD5959",
+"u  c #D05555",
+"i  c #D05959",
+"p  c #D06464",
+"a  c #D06B6B",
+"s  c #CE7676",
+"d  c #D17474",
+"f  c #D17C7C",
+"g  c #059305",
+"h  c #0D950D",
+"j  c #129712",
+"k  c #169816",
+"l  c #1A991A",
+"z  c #21991E",
+"x  c #239A21",
+"c  c #2A9E2A",
+"v  c #2EA02E",
+"b  c #34A234",
+"n  c #3CA43C",
+"m  c #45A745",
+"M  c #49A748",
+"N  c #4BA94B",
+"B  c #54AB54",
+"V  c #5FAF5F",
+"C  c #64B164",
+"Z  c #69B066",
+"A  c #6BB36B",
+"S  c #72B26F",
+"D  c #75B675",
+"F  c #78B778",
+"G  c #7CB87B",
+"H  c #808080",
+"J  c gray56",
+"K  c #909090",
+"L  c #999799",
+"P  c #9A999A",
+"I  c #AC999B",
+"U  c #8BBD8B",
+"Y  c #A09EA0",
+"T  c #A7A7A7",
+"R  c #AAAAAA",
+"E  c #B4B4B4",
+"W  c #BBBBBB",
+"Q  c #D28282",
+"!  c #D28B8B",
+"~  c #C5999B",
+"^  c #C99898",
+"/  c #D29090",
+"(  c #D69799",
+")  c #D4999A",
+"_  c #D9999B",
+"`  c #D59FA0",
+"'  c #D2A2A2",
+"]  c #D3ADAD",
+"[  c #D4B3B3",
+"{  c #D4BBBB",
+"}  c #96C196",
+"|  c #B7CBB7",
+" . c #BDCDBD",
+".. c #C2CCBF",
+"X. c #C5C5C5",
+"o. c #C9C7C7",
+"O. c #C2CDC0",
+"+. c #CBCCCC",
+"@. c #D4C4C4",
+"#. c #D6CCCC",
+"$. c #CFD1CF",
+"%. c #CBDED7",
+"&. c #CEDED9",
+"*. c #D5D4D4",
+"=. c #D9D2D5",
+"-. c #D7DED7",
+";. c #D8DED7",
+":. c #DBD7DB",
+">. c #D5DCDC",
+",. c #DBDBDB",
+"<. c #D6E2E2",
+"1. c #DBE2E3",
+"2. c #D6ECEC",
+"3. c #D6F1F1",
+"4. c #D7FDFD",
+"5. c #E2E2E2",
+"6. c #E2EAEA",
+"7. c #E8E8E8",
+"8. c #E4E7F1",
+"9. c #E3F2F2",
+"0. c #F4F4F4",
+"q. c white",
+/* pixels */
+"*.*.,.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.;.*.*.*.",
+"*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.=.*.*.*.*.*.*.*.*.",
+"*.*.*.*.=.*.*.*.=.*.*.=.*.*.=.*.*.*.*.*.*.$.$.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.=.*.*.*.*.*.*.",
+"*.*.*.*.>.<.<.<.>.<.<.<.<.<.<.>.<.5.5.5.5.5.5.5.5.5.1.5.1.*.*.:.,.*.,.,.,.*.*.>.,.>.;.>.*.*.*.*.",
+"*.*.*.>.' e q w q q e q w q q w < # $ $ $ $ $ $ $ $ $ $ - =.*.*.W E E E W E E E E E E E *.*.*.*.",
+"*.*.*.@.6 2 2 2 2 2 2 2 2 2 2 5 >                         ; 5.W T R R T T T R T T R R T +.:.*.*.",
+"*.*.>.[ 2 2 2 2 2 8 8 3 2 2 2 4 >             .       .   * 7.E T R R R R R R R R R R R +.,.*.*.",
+"*.*.>.[ 2 2 2 2 6 #.>.9 2 2 2 5 >         .     .         * 7.E R R R R R R R R R R R R $.=.*.*.",
+"*.=.>.[ 2 4 2 2 3 @.>.8 2 2 2 4 >                         * 7.E T R R R R R R R R R R R $.,.,.*.",
+",.*.>.[ 2 2 e f f =.*.Q f u 2 4 >       = : ; ; ; ; H @   * 7.E R R R R R R R R R R R R +.*.*.*.",
+"*.*.>.[ 2 2 ! 4.3.*.*.2.4.] 2 5 >       +.q.0.0.0.0.q.-   = 7.R T R R R R R R R R R R R $.*.*.*.",
+"*.*.>.[ 2 2 r f f $.>.Q f y 2 5 >       = : ; : ; ; : @   * 7.E T R R R R R R R R T R R +.,.*.*.",
+"*.*.<.[ 2 2 2 2 3 #.>.8 2 2 2 4 >                         * 7.E T R R R R R R R R R R R $.=.*.*.",
+"*.*.>.[ 2 2 4 2 3 #.>.8 2 2 2 4 >         .           .   * 7.E R R R R R R R R R R R T +.,.*.*.",
+"*.*.>.[ 2 2 2 2 2 8 8 3 2 2 2 5 >                         * 7.R T R R R R R R R R R R R $.*.*.*.",
+"*.*.*.*.e 2 2 2 2 2 2 2 2 2 2 4 >                       . T ,.X.R T R T T R T T R T T T $.*.*.*.",
+"*.*.*.*.*.' ) ) ) ) ) ) ) ) ) _ ~ P P P P P P P P P P P W ,.*.*.$.+.+.+.+.+.+.+.+.+.X.X.*.*.*.*.",
+"*.*.*.*.:.&.%.%.&.%.&.%.&.%.%.%.>.-.-.-.-.-.-.-.-.-.-.-.>.*.*.<.=.#.#.#.#.#.#.#.#.#.<.>.*.*.*.*.",
+"*.*.*.=.G x x x x x x x x x x x x x x x x x x x x x x z m ..,.] 8 3 6 3 3 6 3 3 3 3 i *.*.*.*.*.",
+"*.*.,...k k h h k l k k k j k k k k k k h k k k k k k k g D 8.r 2 2 2 2 2 2 2 2 2 2 2 ' <.*.*.*.",
+"*.*.:.| l k n v k l l l l m l l k l k l N k l l l k b n g A 8.q 2 2 2 2 0 a 0 2 2 2 2 ) <.$.*.*.",
+"*.-.:.| k k n D l k l k A N h l k l l k B C h l j x D b g A 8.0 2 2 2 2 Q 4.f 2 3 2 2 ) <.=.*.*.",
+"*.*.:.| k l j b D l j A N h l k l l l l h B C h c D c j h C 8.q 2 2 2 2 d 3.s 2 2 2 2 ( <.*.=.*.",
+"*.*.:.| k l l h b A A m h l l l l k l k l h N A C v j x g C 8.q 2 8 [ [ @.*.@.] [ 8 2 ) <.*.*.*.",
+"*.*.:.| l l l l h G U k k l l l l l l l k j x } C h l x h A 8.q 2 9 2.2.>.*.>.2.2.8 2 ) <.*.*.*.",
+"*.*.:.| k l k k A n v D l k k l k l k l j c F x N C j l g C 8.q 2 6 q 0 ! 2.! 0 e 3 2 ) <.*.*.*.",
+"*.*.>.| k l l A m j k b D x k l l l l k c F c k k B C k h Z 8.q 2 2 2 2 f 4.f 2 2 2 2 ^ <.*.*.*.",
+"*.*.:.| k j B N j l l h n V k l l l l l C c j l l h B N g Z 2.q 2 2 2 2 p #.p 2 3 2 2 ) <.=.*.*.",
+"*.*.:. .k k l j k k k k j k k k k k k k k j k k k k j l g S 8.0 2 2 2 2 2 2 2 2 2 2 2 ) <.*.*.*.",
+"*.*.*.*.G x l x x x x x x x x x x x x x x x x x x x x x M ..<.w 4 4 4 4 4 4 4 4 4 4 4 ) >.=.*.*.",
+"*.*.*.*.,.>.;.;.;.;.-.-.-.-.>.;.>.%.%.%.%.%.%.%.&.%.%.%.>.=.<.1 , , , , , , , , , , , ^ <.*.*.*.",
+"*.*.=.,.*.Y P P P P P P P P P P I _ ) ) _ ) ) ) _ ( _ ( { &.5.$                       L 5.*.*.*.",
+"*.*.*.*.&                       O 5 2 2 2 2 2 2 2 2 2 2 3 ) 6.$                       L 5.$.*.*.",
+"*.*.,.E                         O 5 2 2 2 2 2 2 2 2 2 2 2 y 9.$                       L 5.*.*.*.",
+"*.=.1.E                         O 4 2 2 2 2 9 ' f 2 2 3 2 y 6.$                       L 1.*.*.*.",
+"*.*.,.E                         O 4 2 2 2 2 e 4.{ 2 2 2 2 u 9.$                       P 5.*.*.*.",
+"*.*.,.E     X + + + + + + o     O 5 2 2 7 7 t <.[ 7 8 6 2 y 9.$     + + + + + + + .   L 5.$.*.*.",
+"*.*.:.E     H 7.*.;.,.,.7.P     O 5 2 2 { <.>.*.*.,.2.p 2 y 9.$   + ,.,.:.,.,.;.,.+   P 5.*.*.*.",
+"*.*.;.E     ; ,.X.o.o.X.*.J     O 5 2 2 ' $.#.*.*.@.*.i 2 y 9.$   + +.+.o.+.o.o.+.+   P 1.*.*.*.",
+"*.*.,.E       . . . . . . .     O 5 2 2 3 2 e <.] 3 3 3 2 y 9.$   . . . . . . . X     L 5.=.*.*.",
+"*.*.,.E                         O 5 2 2 2 2 q 4.{ 2 2 2 2 i 9.$                       P 5.$.*.*.",
+"*.*.;.E                         O 5 2 2 2 2 8 ! a 2 3 2 2 u 9.@                       L 5.*.*.*.",
+"*.*.*.X.X                       O 4 2 2 2 2 2 2 2 2 2 2 2 s 9.*                       E ,.*.*.*.",
+"*.*.*.,.T & $ $ & $ $ $ $ $ & # % w q w e q w e e q q q a =.,.+.= $ & $ $ & $ $ & $ K ,.*.*.*.*.",
+"*.*.*.*.:.5.5.5.5.1.5.5.5.,.5.5.1.<.<.<.<.<.<.<.<.>.<.<.<.*.*.>.5.1.5.5.5.5.5.5.5.1.5.*.*.*.*.*.",
+"*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.=.*.*.=.=.*.$.=.*.*.*.*.*.*.=.$.*.*.*.$.*.*.*.*.*.*.*.*.",
+"*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.:.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.",
+"*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.,.*.*.*."
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/magnets-web.png b/icons/magnets-web.png
new file mode 100644 (file)
index 0000000..e158f96
Binary files /dev/null and b/icons/magnets-web.png differ
diff --git a/icons/magnets.ico b/icons/magnets.ico
new file mode 100644 (file)
index 0000000..18cbf73
Binary files /dev/null and b/icons/magnets.ico differ
diff --git a/icons/magnets.rc b/icons/magnets.rc
new file mode 100644 (file)
index 0000000..54197a4
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "magnets.ico"
diff --git a/icons/magnets.sav b/icons/magnets.sav
new file mode 100644 (file)
index 0000000..3c317b7
--- /dev/null
@@ -0,0 +1,33 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :7:Magnets
+PARAMS  :6:6x5dtS
+CPARAMS :6:6x5dtS
+SEED    :15:705856238774945
+DESC    :56:2.2..1,.3.2.,2.21..,2..0.,TLRTLRBLRBTTLRLRBBLRTTTTLRBBBB
+AUXINFO :60:ebae280db3eec279c628b6cfe4aca5a03ba24d7eba91169f1bdf275fce3f
+NSTATES :2:24
+STATEPOS:2:15
+MOVE    :4:.1,3
+MOVE    :4:.0,1
+MOVE    :4:?0,1
+MOVE    :4:.2,1
+MOVE    :4:?2,1
+MOVE    :4:.2,4
+MOVE    :4:?2,4
+MOVE    :4:+2,3
+MOVE    :4:.3,3
+MOVE    :4:.0,2
+MOVE    :4:?0,2
+MOVE    :4:+1,4
+MOVE    :4:+0,2
+MOVE    :4:+0,0
+MOVE    :4:+1,1
+MOVE    :4:.2,2
+MOVE    :4:+2,0
+MOVE    :4:+3,1
+MOVE    :4:.4,0
+MOVE    :4:+5,1
+MOVE    :4:.5,3
+MOVE    :4:+4,3
+MOVE    :4:.4,2
diff --git a/icons/map-16d24.png b/icons/map-16d24.png
new file mode 100644 (file)
index 0000000..ef5971f
Binary files /dev/null and b/icons/map-16d24.png differ
diff --git a/icons/map-16d4.png b/icons/map-16d4.png
new file mode 100644 (file)
index 0000000..51dc330
Binary files /dev/null and b/icons/map-16d4.png differ
diff --git a/icons/map-16d8.png b/icons/map-16d8.png
new file mode 100644 (file)
index 0000000..cc8d350
Binary files /dev/null and b/icons/map-16d8.png differ
diff --git a/icons/map-32d24.png b/icons/map-32d24.png
new file mode 100644 (file)
index 0000000..c7b5b29
Binary files /dev/null and b/icons/map-32d24.png differ
diff --git a/icons/map-32d4.png b/icons/map-32d4.png
new file mode 100644 (file)
index 0000000..5ab8dca
Binary files /dev/null and b/icons/map-32d4.png differ
diff --git a/icons/map-32d8.png b/icons/map-32d8.png
new file mode 100644 (file)
index 0000000..0d328b7
Binary files /dev/null and b/icons/map-32d8.png differ
diff --git a/icons/map-48d24.png b/icons/map-48d24.png
new file mode 100644 (file)
index 0000000..c2ad1fd
Binary files /dev/null and b/icons/map-48d24.png differ
diff --git a/icons/map-48d4.png b/icons/map-48d4.png
new file mode 100644 (file)
index 0000000..16354a6
Binary files /dev/null and b/icons/map-48d4.png differ
diff --git a/icons/map-48d8.png b/icons/map-48d8.png
new file mode 100644 (file)
index 0000000..f728fb5
Binary files /dev/null and b/icons/map-48d8.png differ
diff --git a/icons/map-base.png b/icons/map-base.png
new file mode 100644 (file)
index 0000000..e1caaf1
Binary files /dev/null and b/icons/map-base.png differ
diff --git a/icons/map-ibase.png b/icons/map-ibase.png
new file mode 100644 (file)
index 0000000..e1caaf1
Binary files /dev/null and b/icons/map-ibase.png differ
diff --git a/icons/map-ibase4.png b/icons/map-ibase4.png
new file mode 100644 (file)
index 0000000..13f47e8
Binary files /dev/null and b/icons/map-ibase4.png differ
diff --git a/icons/map-icon.c b/icons/map-icon.c
new file mode 100644 (file)
index 0000000..67011db
--- /dev/null
@@ -0,0 +1,640 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 256 2 ",
+"   c gray90",
+".  c #DCDEDB",
+"X  c #DADCD9",
+"o  c #DBDDD9",
+"O  c #DADFDA",
+"+  c #DADAD8",
+"@  c #DBD9D8",
+"#  c #DEDDDC",
+"$  c #E1E1E1",
+"%  c #DEDBDA",
+"&  c #DEDCD8",
+"*  c #DFDDD8",
+"=  c #DFDDD8",
+"-  c #DFDDD8",
+";  c #DFDEDA",
+":  c gray90",
+">  c #DADCD9",
+",  c #83996C",
+"<  c #829B6A",
+"1  c #84956A",
+"2  c #838A65",
+"3  c #8C7B61",
+"4  c #886F56",
+"5  c #C7C1BB",
+"6  c #EAEEF2",
+"7  c #BCA294",
+"8  c #C1A663",
+"9  c #CDB770",
+"0  c #CAB36E",
+"q  c #CAB26B",
+"w  c #C8B476",
+"e  c #E0DFDC",
+"r  c #DFDFDF",
+"t  c #B1BBA6",
+"y  c #819969",
+"u  c #817355",
+"i  c #8B6E57",
+"p  c #866D52",
+"a  c #B4A89B",
+"s  c #E9EAEB",
+"d  c #E4E4E3",
+"f  c #B68E7C",
+"g  c #BC995B",
+"h  c #CBB364",
+"j  c #C9AF63",
+"k  c #CEB561",
+"l  c #CAB36E",
+"z  c #E0DFDB",
+"x  c #E1E1E1",
+"c  c #E9E7EB",
+"v  c #D1D3D0",
+"b  c #D0CECA",
+"n  c #D6D3D0",
+"m  c #CECBC9",
+"M  c #DEDEDF",
+"N  c #EDF2F5",
+"B  c #CABCB4",
+"V  c #AB755A",
+"C  c #B28168",
+"Z  c #B18364",
+"A  c #B08164",
+"S  c #BD9D60",
+"D  c #CEB970",
+"F  c #E0DFDC",
+"G  c #E2E2E2",
+"H  c #D9D9D8",
+"J  c #E4E4E5",
+"K  c #EAEBEB",
+"L  c #EAEBEC",
+"P  c #E0E1E1",
+"I  c #D8D8D8",
+"U  c #B7ADA3",
+"Y  c #8F7962",
+"T  c #A97A60",
+"R  c #B98269",
+"E  c #B47F66",
+"W  c #B37F67",
+"Q  c #B17B61",
+"!  c #A99067",
+"~  c #DEDEDB",
+"^  c #E2E2E2",
+"/  c gray86",
+"(  c #E6E6E6",
+")  c #E6E6E6",
+"_  c #E3E2E2",
+"`  c #DDDDDD",
+"'  c #E1E3E5",
+"]  c #B6ACA5",
+"[  c #886950",
+"{  c #A67A61",
+"}  c #A78063",
+"|  c #B18266",
+" . c #A88164",
+".. c #8D825C",
+"X. c #947664",
+"o. c #DBDCDB",
+"O. c #E2E2E2",
+"+. c #D8D8D8",
+"@. c gray90",
+"#. c #E8E8E7",
+"$. c #E3E4E6",
+"%. c #DCDBD6",
+"&. c #CDB97D",
+"*. c #CCC5A0",
+"=. c #847A5E",
+"-. c #866F55",
+";. c #788C5E",
+":. c #8C7458",
+">. c #8C8962",
+",. c #799B64",
+"<. c #8A8164",
+"1. c #DFDBDB",
+"2. c gray88",
+"3. c #D8D8D8",
+"4. c gray87",
+"5. c #DADADA",
+"6. c #D5D4D5",
+"7. c #D1CCC0",
+"8. c #C1A95E",
+"9. c #BEA159",
+"0. c #8E805C",
+"q. c #789062",
+"w. c #809565",
+"e. c #7E885D",
+"r. c #7B9262",
+"t. c #7D8E5E",
+"y. c #8D7C63",
+"u. c #DEDCDB",
+"i. c #E1E1E1",
+"p. c #E2E2E1",
+"a. c #E4E3E3",
+"s. c #E6E7E8",
+"d. c #B9B0A6",
+"f. c #886F55",
+"g. c #8D765B",
+"h. c #8D7259",
+"j. c #A77A61",
+"k. c #949462",
+"l. c #819C65",
+"z. c #79875F",
+"x. c #896F57",
+"c. c #887055",
+"v. c #927A64",
+"b. c #DDDCDB",
+"n. c #E1E1E1",
+"m. c #E6E7E8",
+"M. c #ECEEEF",
+"N. c #EEEFF0",
+"B. c #D0CAC7",
+"V. c #8C735A",
+"C. c #8C7359",
+"Z. c #8D755A",
+"A. c #8B6D58",
+"S. c #B7955E",
+"D. c #BFB064",
+"F. c #A7975D",
+"G. c #88815A",
+"H. c #887056",
+"J. c #927C64",
+"K. c #DDDCDB",
+"L. c #DFDFDE",
+"P. c #C8C3BE",
+"I. c #D0CAC5",
+"U. c #B9B3A9",
+"Y. c #847A5C",
+"T. c #897257",
+"R. c #8B7059",
+"E. c #876F58",
+"W. c #866B57",
+"Q. c #AF975E",
+"!. c #D5BA68",
+"~. c #D0B766",
+"^. c #889360",
+"/. c #886E55",
+"(. c #937963",
+"). c #DDDCDB",
+"_. c #DBD9D7",
+"`. c #8A735C",
+"'. c #876B50",
+"]. c #837052",
+"[. c #789461",
+"{. c #7C9062",
+"}. c #90835A",
+"|. c #AA905B",
+" X c #A77C60",
+".X c #B99163",
+"XX c #CAB365",
+"oX c #CDB366",
+"OX c #90945F",
+"+X c #7D7055",
+"@X c #8A8766",
+"#X c #DDDDDB",
+"$X c #DBD9D8",
+"%X c #937B62",
+"&X c #8C7458",
+"*X c #8A6D5A",
+"=X c #987C5F",
+"-X c #7E9967",
+";X c #879D65",
+":X c #CFBA66",
+">X c #C1A065",
+",X c #AD7965",
+"<X c #B39060",
+"1X c #CDB566",
+"2X c #C8B066",
+"3X c #88945E",
+"4X c #7F9C71",
+"5X c #DDDEDC",
+"6X c #DCD7D8",
+"7X c #B48E67",
+"8X c #BEA65C",
+"9X c #A18659",
+"0X c #B67D62",
+"qX c #A37F60",
+"wX c #769061",
+"eX c #989259",
+"rX c #BD985F",
+"tX c #B0865F",
+"yX c #CAB162",
+"uX c #CCB263",
+"iX c #D0B465",
+"pX c #C7AF5F",
+"aX c #A0A169",
+"sX c #DDDEDB",
+"dX c #E1DCDB",
+"fX c #A5856F",
+"gX c #B59F67",
+"hX c #D2BE71",
+"jX c #B5956C",
+"kX c #B58170",
+"lX c #AB926B",
+"zX c #928361",
+"xX c #AC846A",
+"cX c #B3886E",
+"vX c #C4AD6E",
+"bX c #CBB570",
+"nX c #C9B370",
+"mX c #CBB46D",
+"MX c #CDB678",
+"NX c #E0DFDC",
+"BX c #E6E5E5",
+"VX c #DDDBDC",
+"CX c #DCDBDA",
+"ZX c #E0DFDC",
+"AX c #E0DEDB",
+"SX c #DEDCDB",
+"DX c #E2DFDC",
+"FX c #E0DEDB",
+"GX c #DEDBDC",
+"HX c #DFDCDC",
+"JX c #DFDEDB",
+"KX c #E0DFDC",
+"LX c #E0DFDC",
+"PX c #DFDFDB",
+"IX c #DFDFDC",
+"UX c gray90",
+/* pixels */
+"  . X o O + @ # $ % & * = - ; : ",
+"> , < 1 2 3 4 5 6 7 8 9 0 q w e ",
+"r t y u i p a s d f g h j k l z ",
+"x c v b n m M N B V C Z A S D F ",
+"G H J K L P I U Y T R E W Q ! ~ ",
+"^ / ( ) _ ` ' ] [ { } |  ...X.o.",
+"O.+.@.#.$.%.&.*.=.-.;.:.>.,.<.1.",
+"2.3.4.5.6.7.8.9.0.q.w.e.r.t.y.u.",
+"i.p.a.s.d.f.g.h.j.k.l.z.x.c.v.b.",
+"n.m.M.N.B.V.C.Z.A.S.D.F.G.H.J.K.",
+"L.P.I.U.Y.T.R.E.W.Q.!.~.^./.(.).",
+"_.`.'.].[.{.}.|. X.XXXoXOX+X@X#X",
+"$X%X&X*X=X-X;X:X>X,X<X1X2X3X4X5X",
+"6X7X8X9X0XqXwXeXrXtXyXuXiXpXaXsX",
+"dXfXgXhXjXkXlXzXxXcXvXbXnXmXMXNX",
+"BXVXCXZXAXSXDXFXGXHXJXKXLXPXIXUX"
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 155 2 ",
+"   c #77734E",
+".  c #7D6C50",
+"X  c #7D7656",
+"o  c #7C7957",
+"O  c #7C7759",
+"+  c #7C7D59",
+"@  c #84674C",
+"#  c #83694E",
+"$  c #856D54",
+"%  c #8A6E55",
+"&  c #856F5A",
+"*  c #8C6F5A",
+"=  c #926D55",
+"-  c #926F58",
+";  c #847255",
+":  c #8B7256",
+">  c #8F7855",
+",  c #86755D",
+"<  c #8C745A",
+"1  c #927956",
+"2  c #9D7B55",
+"3  c #90735B",
+"4  c #9B735B",
+"5  c #947E5E",
+"6  c #9B7C5F",
+"7  c #A4745C",
+"8  c #AC765B",
+"9  c #A47C5D",
+"0  c #AA7A5E",
+"q  c #8D7760",
+"w  c #9F7E60",
+"e  c #927F6B",
+"r  c #A37D61",
+"t  c #AC7C63",
+"y  c #A67A68",
+"u  c #AA7E69",
+"i  c #B17660",
+"p  c #B37E66",
+"a  c #B57F68",
+"s  c #76865C",
+"d  c #7A855C",
+"f  c #748C5C",
+"g  c #7B8B5E",
+"h  c #7B915E",
+"j  c #7C8E60",
+"k  c #749762",
+"l  c #7C9464",
+"z  c #7E9A66",
+"x  c #7E9369",
+"c  c #7D9C68",
+"v  c #7DA069",
+"b  c #8B8256",
+"n  c #84845D",
+"m  c #88835C",
+"M  c #828C5E",
+"N  c #8C945F",
+"B  c #8E995F",
+"V  c #92965C",
+"C  c #92985F",
+"Z  c #AD825F",
+"A  c #A08858",
+"S  c #AB8B59",
+"D  c #B08D5E",
+"F  c #A69057",
+"G  c #A39E5E",
+"H  c #A89F5F",
+"J  c #B4945E",
+"K  c #B29C5D",
+"L  c #BA9E5D",
+"P  c #B4A35F",
+"I  c #B9A15D",
+"U  c #809765",
+"Y  c #819A67",
+"T  c #8D9960",
+"R  c #829B6A",
+"E  c #919B61",
+"W  c #918976",
+"Q  c #879A72",
+"!  c #8C9C7B",
+"~  c #AD8260",
+"^  c #AE836C",
+"/  c #B48066",
+"(  c #B88066",
+")  c #B28B61",
+"_  c #B48269",
+"`  c #BC8169",
+"'  c #A89362",
+"]  c #B69E66",
+"[  c #B99B62",
+"{  c #AF8670",
+"}  c #A68978",
+"|  c #AFA267",
+" . c #B4A560",
+".. c #BCA362",
+"X. c #BFAA60",
+"o. c #B5A56E",
+"O. c #BCA768",
+"+. c #BEAA6C",
+"@. c #BDAB71",
+"#. c #C1A75F",
+"$. c #C3AA5C",
+"%. c #C1A465",
+"&. c #C3AC65",
+"*. c #C8AE63",
+"=. c #C4AE6B",
+"-. c #C6B163",
+";. c #CCB365",
+":. c #C6B16B",
+">. c #CCB46A",
+",. c #CFB868",
+"<. c #D2B666",
+"1. c #D3BA66",
+"2. c #D0B669",
+"3. c #D2BA69",
+"4. c #9F9285",
+"5. c #A79A8C",
+"6. c #B99B8C",
+"7. c #ACA297",
+"8. c #ADA398",
+"9. c #B1A79C",
+"0. c #B3A99E",
+"q. c #B6ADA4",
+"w. c #B8B0A7",
+"e. c #BDB5AE",
+"r. c #BDBAB5",
+"t. c #C6B98F",
+"y. c #C4BEAE",
+"u. c #C5BCB7",
+"i. c #CBBFB9",
+"p. c #C1C5BC",
+"a. c #C8C3BF",
+"s. c #D1C5BF",
+"d. c #CAC6C1",
+"f. c #CDC9C6",
+"g. c #CDCDCC",
+"h. c #D3CEC0",
+"j. c #D6D1C1",
+"k. c #D3D1CF",
+"l. c #D5D5D4",
+"z. c #D9D6D5",
+"x. c #D7D8D6",
+"c. c #DBD9D6",
+"v. c #D6D6D9",
+"b. c #D8D7D9",
+"n. c #DCDCDC",
+"m. c #DEDEE1",
+"M. c #DFE1E2",
+"N. c #E5E5E5",
+"B. c #E8E8E7",
+"V. c #E5E6E9",
+"C. c #E5E8EA",
+"Z. c #E9E9E9",
+"A. c #EEEFF0",
+"S. c #EEF1F3",
+"D. c #F3F6FA",
+/* pixels */
+"N.N.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.C.Z.B.Z.Z.Z.C.Z.Z.Z.Z.Z.Z.Z.Z.Z.B.N.",
+"N.N.x.x.c.x.x.c.x.x.x.z.z.c.c.n.n.n.c.z.c.c.c.c.c.c.c.n.c.c.N.N.",
+"Z.x.x U z z z x z x O < , $ 4.N.b.N.u.7 ] ;.&.&.=.&.&.&.&.+.n.Z.",
+"Z.c.l z U Y z + X o ; < 3 # q.S.N.A.d.8 %.<.;.;.;.;.<.;.<.:.n.Z.",
+"Z.c.! R l Y + * < < 3 < # 8.C.N.N.A.a.7 ..<.;.>.>.;.;.>.;.=.n.B.",
+"Z.n.m.c.Q f . % $ $ % # 7.A.N.N.Z.k._ / ~ #.;.-.*.;.;.;.<.=.n.Z.",
+"Z.n.n.Z.n.p.r.d.d.d.r.f.Z.Z.Z.N.Z.6.8 / p t ~ ~ ~ 0 I 3.;.=.n.Z.",
+"Z.n.n.B.n.z.N.A.Z.A.c.n.v.g.c.Z.C.} p / / / / / / _ t I <.:.n.Z.",
+"Z.n.n.n.c.N.N.N.N.N.N.M.l.x.k.z.5.# 4 / / / / / / / / t L O.n.B.",
+"Z.n.c.v.Z.N.Z.N.N.N.n.l.l.M.0.. : : 4 / ~ / / / / / p _ = , n.B.",
+"Z.n.n.v.Z.N.N.N.N.N.l.B.N.M.u.# < % / _ / ` / / / r n t 4 q n.Z.",
+"Z.n.n.b.N.N.Z.N.B.N.b.Z.n.k.D.w.$ < - 7 6 w 6 / ` 6 k 5 : < n.Z.",
+"C.n.n.b.Z.N.Z.N.N.N.x.C.t.+.n.x.$ < < $ s z f 0 r g z z + < n.Z.",
+"Z.n.c.b.C.N.N.C.B.z.N.s.$.;.:.o.f X = ; z d # % d c Y R + < n.Z.",
+"Z.n.g.k.l.n.Z.c.z.n.S.s.-.1.1.$.n o o l Y d , o g Y Y z o q n.Z.",
+"Z.n.n.Z.N.l.n.g.b.z.v.u.I $.I P i n c Y Y c z Y l g l + % q n.Z.",
+"B.n.n.B.B.N.n.M.g.$ < < : % : $ p 9 l l z U Y d $ $ ; : : q n.Z.",
+"B.n.n.B.N.N.B.B.N.e $ < < < < : 4 ` A G N z l $ 3 < 3 < : q n.Z.",
+"Z.n.n.B.N.N.N.N.Z.c.q : < : < < & 4 9 2.*.V M > ; X < < : q n.Z.",
+"B.n.n.N.N.N.N.N.V.q.: : < < : < < $ > *.;.<.;.;.B s * : : q n.Z.",
+"Z.n.V.S.S.Z.D.y., % : < : < < : < < 1 ;.;.;.>.;.E d : < : 3 n.Z.",
+"Z.b.8.0.9.9.q.W X O ; : < < < < : % 1 ;.;.;.;.;.E d : < 3 q n.Z.",
+"Z.z.$ @ $ # @ X v Y l o $ : > $ = a / ;.;.;.;.;.T s * < $ , n.Z.",
+"Z.z.< < < < < ; g Y Y z g H *.&.D p t J ;.,.;.;. .g O $ g U n.Z.",
+"Z.b.< < < < < < $ M Y Y z G 1.2.-.Z _ t Z J ;.;.3. .g l Y R n.B.",
+"Z.z.: = > % < % t t g Y U l H <.;.) a t t J ;.;.;.<. .l z R n.Z.",
+"Z.c.y D ;.A $ * _ / t g z x C -.;.) i J ;.;.;.;.;.;.<.P g x n.Z.",
+"Z.c.u [ 1.;.P J t / p t M c + : -.) t P ,.;.;.;.:.;.;.>.I o.n.B.",
+"Z.c.t 2 F ;.<.1.L t / ` 7 V b $ S Z / 0 %.;.;.;.;.;.;.;.1.>.n.C.",
+"Z.c.^ < < :.:.:.O.u _ ^ u %.' , y ^ _ u ..:.:.=.:.=.=.:.:.+.n.Z.",
+"Z.N.n.n.n.n.n.n.n.n.n.n.n.n.n.n.n.n.n.n.n.n.n.n.n.n.n.n.n.n.B.B.",
+"N.N.Z.Z.Z.C.Z.Z.B.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.C.Z.Z.Z.Z.B.Z.Z.Z.Z.Z.N.N."
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 106 2 ",
+"   c #7B684F",
+".  c #7E6A52",
+"X  c #767554",
+"o  c #787755",
+"O  c #767855",
+"+  c #82674C",
+"@  c #89664F",
+"#  c #82694D",
+"$  c #846D54",
+"%  c #8B6C55",
+"&  c #836E58",
+"*  c #877056",
+"=  c #897157",
+"-  c #86705A",
+";  c #8C735A",
+":  c #867C5A",
+">  c #897A59",
+",  c #90765C",
+"<  c #9E735A",
+"1  c #A2755C",
+"2  c #A6785F",
+"3  c #AA7A5F",
+"4  c #AC765A",
+"5  c #A27761",
+"6  c #A47962",
+"7  c #AB7B63",
+"8  c #A67F6B",
+"9  c #AC7F68",
+"0  c #B27E65",
+"q  c #74865A",
+"w  c #758A5C",
+"e  c #798E5F",
+"r  c #77905D",
+"t  c #79905F",
+"y  c #798E61",
+"u  c #769564",
+"i  c #7B9363",
+"p  c #7D9965",
+"a  c #7E9468",
+"s  c #9B8555",
+"d  c #8F915A",
+"f  c #92925A",
+"g  c #A8875A",
+"h  c #A9895A",
+"j  c #AE9759",
+"k  c #AE9A59",
+"l  c #B29C5C",
+"z  c #B7A15B",
+"x  c #BDA65F",
+"c  c #BEA85F",
+"v  c #819A66",
+"b  c #809669",
+"n  c #839D69",
+"m  c #AF8169",
+"M  c #B48166",
+"N  c #B88267",
+"B  c #B78268",
+"V  c #B98469",
+"C  c #B39E62",
+"Z  c #B5A062",
+"A  c #BEA661",
+"S  c #BFA962",
+"D  c #B7A469",
+"F  c #C0A75F",
+"G  c #C1AA5F",
+"H  c #C8AE5C",
+"J  c #C0A760",
+"K  c #C1AA65",
+"L  c #C8AF60",
+"P  c #C2AC68",
+"I  c #CDB366",
+"U  c #CEB668",
+"Y  c #D1B666",
+"T  c #D2B867",
+"R  c #D0B768",
+"E  c #D3B969",
+"W  c #9F9387",
+"Q  c #A1968A",
+"!  c #A3988E",
+"~  c #A69C92",
+"^  c #A89E94",
+"/  c #B59F94",
+"(  c #9EA993",
+")  c #BEB28E",
+"_  c #B5A095",
+"`  c #BFB493",
+"'  c #C6C5C3",
+"]  c #C8C7C5",
+"[  c #C9C9C7",
+"{  c #CCCCCC",
+"}  c #D2D0CF",
+"|  c #D6D5D5",
+" . c #D8D7D7",
+".. c #D7D8D7",
+"X. c #D9D8D7",
+"o. c #D6D7DA",
+"O. c #D7D8DC",
+"+. c #DADADA",
+"@. c #E0E0DF",
+"#. c #DFDFE0",
+"$. c #E6E6E6",
+"%. c #E8E8E7",
+"&. c #E5E6EA",
+"*. c #E7E9EA",
+"=. c #E9EAEA",
+"-. c gray94",
+/* pixels */
+"$.$.$.%.$.$.$.$.%.$.$.$.$.%.%.$.$.$.$.$.$.%.$.$.$.$.$.$.%.$.$.$.%.$.$.$.$.$.$.$.$.$.$.$.$.$.$.$.",
+"$.$.$.%.$.$.$.$.$.$.$.$.$.$.$.$.$.$.$.$.$.%.$.$.$.%.%.$.$.$.$.$.$.$.$.$.$.$.$.$.$.$.$.$.$.&.$.$.",
+"$.%.$.$.%.%.%.=.=.=.=.%.%.%.=.%.=.=.%.=.%.=.%.=.%.*.%.=.=.%.&.*.*.*.=.=.*.*.=.=.%.=.%.=.$.$.$.$.",
+"$.$.$.$.+.o.X.X......... . ... .X.X.X.X.X.o.+.X.O.X.+.X.X.X.X.X.X.X.X.X.X.X.+. .+. .+.X.$.$.$.$.",
+"$.$.%.+.a a a a a a a a a a a y & - - ; ; - ] +...+.+.] 8 6 C P S P S S P S S S P P P C +.=.$.$.",
+"%.$.%...a v v p v v v p t i i q $ ; ; ; ; $ ..=.%.%.=. .7 7 J T I I U U U U U I R L I P +.=.$.$.",
+"$.$.=.+.a v v v v v p O $ . $ $ ; ; ; , $ Q %.$.$.$.%.| 7 3 J U I I I I I I I I I I I K +.=.$.$.",
+"$.$.%...y i i v v p O = , ; , ; ; ; ; # W *.%.$.$.$.=.o.7 7 J T I I I P I I I I I I I P  .=.$.$.",
+"$.$.%.+.' X.( e n i $ ; ; ; ; ; ; ; $ W =.$.$.$.$.$.=./ 0 0 h I E U U U U U I I I I I P +.=.$.$.",
+"$.%.%.X.O.-.*.( e w . = = & & % & # W =.$.$.$.$.$.%./ 4 M M 7 h c c c G G c U I I I I K X.%.$.$.",
+"$.%.=.+.| %.$.$...[ ' | } } | ' [  .*.=.%.%.$.$.%.| 6 V M 0 B 7 3 3 3 3 3 2 h U I L I P X.%.$.$.",
+"$.%.%.+.X.%.$.$.#.| +.=.=.=.=.+.| #.X.X.X.+.$.$.=.X.7 0 M B 0 B B M M M M B 7 h U I I P X.=.$.$.",
+"$.$.%.+.X.=.$.{ ..| $.$.$.$.$.$.| ..+.{ { { +.-.=.! @ 0 M M M M 7 M M M M M V 7 h U T P +.%.$.$.",
+"%.$.%.+.X.%.{ #.=.%.%.$.$.$.$.$.%.%.#.} $.+.[ +.~ # = % 0 M M 7 M M M M M 0 0 N 7 g K C +.%.$.$.",
+"$.$.*.O.+.+.| =.$.$.$.$.$.$.$.%.$.[ X.{ +...- * $ , * % 0 M M M M M M M 0 M M M V 1 # * o.%.$.$.",
+"$.$.*.X.+.#.o.%.$.$.$.$.$.$.%.$.[ #.-.+.+...$ , ; = % M M m M M 0 m M M M 0 6 7 0 6 & ; +.%.$.$.",
+"$.%.%.+.+.+.X.%.$.%.$.$.$.$.=.X.o.%.%.X.o.%.! # , % % 0 V M N M M M M M M 2 w q 0 7 $ ; ..%.$.%.",
+"%.%.%.+.+.+.o.%.$.$.$.$.$.%.%.+.o.%.=.O.+.=.*.Q $ ; * % 3 2 2 7 2 2 M 0 B 2 i i < < $ ; ..=.$.$.",
+"$.$.%.+.+.+.o.%.$.$.$.$.$.%.%.+.X.=.&.` ) &.-.+.; ; ; ; $   q y i q 7 V 0 : p a y q $ ; ..=.$.$.",
+"$.$.%.+.+.#.X.=.$.$.$.$.$.$.%.o.X.*.` H H ` +.' . $ ; ; , $ i v i q < 2 : p p v n i $ - o.%.$.$.",
+"%.$.%.O.+.#.+.=.$.%.$.$.%.%.$.{ %...A T I L P l w w $ , % o a t . * $ . t v a v a i $ ; o.=.$.$.",
+"$.$.*.X.{ } { +.#.%.$.$...o.[ #.%.| K Y I I T A e w   $ o p n e . * * . t v v p v e $ ; o.=.$.$.",
+"$.$.%.+.{ +.o.o.{ #.-.O.} #.%.=.-.+.P T U I T A 1 < q t a v v p e e e t v n v n p O % - o.=.%.$.",
+"$.%.=.O.+.=.%.%.$.{ X.{ { +.| | | ] l J A A J l 7 3 i v v v v v n v n v e e y r O = ; ; ..%.$.$.",
+"%.$.=.+.| %.$.$.%.$.....+.[ - ; = * % $ $ * = # 2 M : i n v v v v v p o $ . * $ ; ; ; -  .=.$.$.",
+"$.$.=.O.X.%.$.%.$.%.%.%.=.+.$ ; ; ; ; ; ; ; ; $ 2 V 0 > t t a v v v o % ; ; ; ; ; ; , ;  .%.$.$.",
+"$.$.%.X.X.%.$.$.$.$.$.$.$.%.~ # ; ; ; ; ; ; ; = % 0 V 3 l G f u n t $ , ; ; ; ; ; ; ; -  .*.$.$.",
+"%.$.%.+.X.%.$.$.$.$.$.$.$.$.=.^ * ; ; ; ; ; ; ; * % 3 < G E I f t q . $ $ $ ; ; % ; , - X.=.$.$.",
+"$.%.%.+.| *.$.&.$.$.$.$.=.=.=.^ # ; ; ; ; = ; ; ; ; $ $ x U I I x A S k w q $ , ; ; ; ;  .=.$.$.",
+"$.$.=.+.| *.$.$.$.$.$.$.} ..! + , ; ; ; ; ; ; ; ; ; ; $ x Y I I U I E S i y $ ; ; ; ; * +.*.$.$.",
+"$.$.%.O.+.-.%.=.*.%.-.+.; * * , ; ; ; ; = ; ; ; ; ; , % A T I I I I Y x t t $ , ; ; ; -  .=.$.$.",
+"$.$.%.X.]  .} } | } ..' . * * # = ; ; ; ; ; ; ; ; % $   A U L I I I Y x i y $ , ; ; ; -  .%.$.$.",
+"%.$.%.X.- & * * * * ; . q i e y O % ; ; ; ; ; ; = % 3 1 x Y I I I I U A a e $ ; ; ; ; ; +.=.%.%.",
+"$.$.*.X.; ; ; ; ; ; ; $ y n v v p o $ * $ * $ $ % M N 3 c Y I I I L T Z i y $ ; ; = $ . o.%.$.$.",
+"$.$.*.X.; ; ; ; ; ; ; ; X p v p v v t w k G c c h 7 M 7 g I T U I I I I f i o = = o i y o.=.$.$.",
+"$.$.*.X.* ; ; ; ; ; ; ; % X p n v v n t K E U T U h 0 B 7 h x c L I I I I f u o X p v a +.%.$.$.",
+"&.$.=. .; , ; ; ; ; ; ; * % > i v v v i d I I I Y S 3 M M M 7 1 A U I P U Y f i v v v a ..%.%.%.",
+"$.$.=. .$ $ % % = ; ; % % 0 0 > p v a v u f L I U x 7 M M 7 3 1 A U I I L I I f i v v a ..%.$.$.",
+"$.$.=. .5 < j P s $ , $ 3 M M 0 > p v a v y S E U x 7 M 7 h K G I I I I I I I I f u v a X.*.$.$.",
+"$.$.=.| 9 0 J T U s $ . 2 V 0 N 0 > p v v i k x I x 3 0 h Y U U I I I I I I I I I f y y X.%.$.$.",
+"$.$.=. .9 7 J R I I A K h 7 0 0 M 0 > p n y . $ L P 7 0 g I R L I R I I I I I I I I F C  .=.$.%.",
+"$.$.=. .9 7 j A L I U U U h 7 B M M 0 > i q $ = l l 6 V 0 g L Y L P I I I I I L L I R P +.=.$.$.",
+"$.$.*.X.9 3 $ # A T I I T G 7 N 0 M V 7 z z $ $ < 3 0 0 0 7 J T R Y I I R I Y I R I Y P +.%.$.$.",
+"$.$.%.X.8 6 - & C P S P P Z 5 9 9 7 9 5 A D $ & 5 0 9 9 9 5 C P P P P S P P P J P P K D  .%.$.$.",
+"$.$.$.$.X.X.X.X.+.X.+.....+.....X.X.o.X.X.+.o.o.X.X.X.X.X.X.X.+.| +. .+.+.| +. .+.X.+.X.$.$.$.$.",
+"&.$.$.$.%.%.*.=.%.=.%.%.%.=.=.=.&.%.%.=.%.%.%.%.=.=.=.=.=.=.%.*.%.*.=.%.=.%.=.=.%.=.%.%.$.%.%.$.",
+"%.$.$.%.$.$.$.$.$.$.$.$.$.$.%.$.$.$.$.$.%.$.$.$.$.$.$.$.$.$.$.$.$.$.$.$.$.$.$.$.$.$.$.$.$.%.$.$.",
+"$.$.$.$.$.$.$.$.$.%.$.$.$.$.$.$.$.$.$.%.$.$.$.$.$.$.%.$.$.$.$.$.$.$.$.$.$.$.$.$.$.&.$.$.$.$.$.$."
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/map-web.png b/icons/map-web.png
new file mode 100644 (file)
index 0000000..4aef7e8
Binary files /dev/null and b/icons/map-web.png differ
diff --git a/icons/map.ico b/icons/map.ico
new file mode 100644 (file)
index 0000000..9063d3e
Binary files /dev/null and b/icons/map.ico differ
diff --git a/icons/map.rc b/icons/map.rc
new file mode 100644 (file)
index 0000000..3efb391
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "map.ico"
diff --git a/icons/map.sav b/icons/map.sav
new file mode 100644 (file)
index 0000000..33863e1
--- /dev/null
@@ -0,0 +1,27 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :3:Map
+PARAMS  :10:20x20n30dn
+CPARAMS :10:20x20n30dn
+SEED    :15:794003990129265
+DESC    :264:dcbakatgcaaedaccabfabadbaaaagaiaaaeaiadcaaaafabccbdcaaecabggedfaebqbadgbngcblabdaadaaaeagabaaaacaacacbcaebabaabaebaafaaakabdhcdanaaceagakacbbajaaadbacbaaccbcbicdafbadgbaccbkcdaafbacbcaaabcddacaaaaddbabcdbbacabbhagajabbobcdjaecaafabahaaaffead,23a01c3c1a3d2a20b01a3a
+AUXINFO :282:738e7a68c5d32445002968f3726646962b3604ef27a3657e0fdc0fd8180d5b747febd4619487bbc8bec5a48c709b154eb8da39c9b49be1e312a381fc2394e53126714079bd82e8444dad92419429635d1c816c53774b8c77b4ce03884c94d12bfb757cd93b5600471cb9726b3f2afe74d9932abeaa2efd6a496cad793ce5b221f943d620e883794f9d56741908
+NSTATES :2:18
+STATEPOS:2:12
+MOVE    :4:3:20
+MOVE    :4:0:24
+MOVE    :4:3:10
+MOVE    :4:1:18
+MOVE    :4:2:11
+MOVE    :4:3:17
+MOVE    :4:1:23
+MOVE    :4:3:27
+MOVE    :4:1:29
+MOVE    :4:0:16
+MOVE    :4:2:13
+MOVE    :3:1:6
+MOVE    :3:2:2
+MOVE    :3:0:7
+MOVE    :3:2:9
+MOVE    :4:0:15
+MOVE    :3:3:5
diff --git a/icons/mines-16d24.png b/icons/mines-16d24.png
new file mode 100644 (file)
index 0000000..00c8cbb
Binary files /dev/null and b/icons/mines-16d24.png differ
diff --git a/icons/mines-16d4.png b/icons/mines-16d4.png
new file mode 100644 (file)
index 0000000..fe0aa64
Binary files /dev/null and b/icons/mines-16d4.png differ
diff --git a/icons/mines-16d8.png b/icons/mines-16d8.png
new file mode 100644 (file)
index 0000000..1d68e72
Binary files /dev/null and b/icons/mines-16d8.png differ
diff --git a/icons/mines-32d24.png b/icons/mines-32d24.png
new file mode 100644 (file)
index 0000000..c1106ad
Binary files /dev/null and b/icons/mines-32d24.png differ
diff --git a/icons/mines-32d4.png b/icons/mines-32d4.png
new file mode 100644 (file)
index 0000000..9a7fd49
Binary files /dev/null and b/icons/mines-32d4.png differ
diff --git a/icons/mines-32d8.png b/icons/mines-32d8.png
new file mode 100644 (file)
index 0000000..4041d6a
Binary files /dev/null and b/icons/mines-32d8.png differ
diff --git a/icons/mines-48d24.png b/icons/mines-48d24.png
new file mode 100644 (file)
index 0000000..ff33772
Binary files /dev/null and b/icons/mines-48d24.png differ
diff --git a/icons/mines-48d4.png b/icons/mines-48d4.png
new file mode 100644 (file)
index 0000000..10bcf2e
Binary files /dev/null and b/icons/mines-48d4.png differ
diff --git a/icons/mines-48d8.png b/icons/mines-48d8.png
new file mode 100644 (file)
index 0000000..df628f5
Binary files /dev/null and b/icons/mines-48d8.png differ
diff --git a/icons/mines-base.png b/icons/mines-base.png
new file mode 100644 (file)
index 0000000..16a6f6c
Binary files /dev/null and b/icons/mines-base.png differ
diff --git a/icons/mines-ibase.png b/icons/mines-ibase.png
new file mode 100644 (file)
index 0000000..c741c1d
Binary files /dev/null and b/icons/mines-ibase.png differ
diff --git a/icons/mines-ibase4.png b/icons/mines-ibase4.png
new file mode 100644 (file)
index 0000000..11b3f01
Binary files /dev/null and b/icons/mines-ibase4.png differ
diff --git a/icons/mines-icon.c b/icons/mines-icon.c
new file mode 100644 (file)
index 0000000..b731186
--- /dev/null
@@ -0,0 +1,732 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 256 2 ",
+"   c #E7E6E6",
+".  c #EBEFEF",
+"X  c #E8FCFC",
+"o  c #EBEEEE",
+"O  c #DFDEDE",
+"+  c #DFDDDF",
+"@  c #E6E0E6",
+"#  c #DADBDA",
+"$  c #DDDCDD",
+"%  c #E5E0E5",
+"&  c #DFDDDF",
+"*  c #DADADA",
+"=  c #E4E4DB",
+"-  c #DDDDDA",
+";  c gray89",
+":  c #E8E8E7",
+">  c #EBEEEE",
+",  c #EFE2E2",
+"<  c #F79E9E",
+"1  c #E3D3D3",
+"2  c #C3C5C7",
+"3  c #93BA92",
+"4  c #8CB78C",
+"5  c #D7D0D7",
+"6  c #B2C5B2",
+"7  c #76AE76",
+"8  c #C4CBC3",
+"9  c #D1CFD0",
+"0  c #7D7DE5",
+"q  c #BCBCD5",
+"w  c #EEEEE8",
+"e  c #EAEAEB",
+"r  c #E9EFEF",
+"t  c #F4DADA",
+"y  c #E65656",
+"u  c #CFBAB9",
+"i  c #CBCCD1",
+"p  c #8EBC8D",
+"a  c #73B072",
+"s  c #E1D8E1",
+"d  c #BECEBE",
+"f  c #56A557",
+"g  c #C4CEC7",
+"h  c #E3E1D5",
+"j  c #7474EE",
+"k  c #A7A7E1",
+"l  c #F6F6EA",
+"z  c #E8E8EB",
+"x  c #EDECEC",
+"c  c #D9E0E0",
+"v  c #648282",
+"b  c #868A8A",
+"n  c #CBC7C9",
+"m  c #79B57A",
+"M  c #9BC39E",
+"N  c #E1D8E1",
+"B  c #9EBE9F",
+"V  c #7AB776",
+"C  c #D0D2CF",
+"Z  c #D4D3D1",
+"A  c #7E81ED",
+"S  c #9FA1E3",
+"D  c #F5F5EB",
+"F  c #E8E9EB",
+"G  c #DFE2E2",
+"H  c #C0B3B3",
+"J  c #C88D8D",
+"K  c #BEB5B6",
+"L  c #DFE2E2",
+"P  c #F8ECF3",
+"I  c #F3E6E9",
+"U  c #CACDCC",
+"Y  c #D6D2D3",
+"T  c #B9B2CD",
+"R  c #CDCDCE",
+"E  c #E9EAEB",
+"W  c #F6EEE3",
+"Q  c #EDE6DF",
+"!  c #E7E8E9",
+"~  c #EBEAEA",
+"^  c #DBDCDC",
+"/  c #D8D5D5",
+"(  c #F85151",
+")  c #E8A7A7",
+"_  c #EAFEFE",
+"`  c #F76866",
+"'  c #F58585",
+"]  c #C5D7D2",
+"[  c #A8A5C0",
+"{  c #373894",
+"}  c #D0D2DA",
+"|  c #F2EBE9",
+" . c #FC5255",
+".. c #EBA2A4",
+"X. c #D8E6E6",
+"o. c #EDEAEA",
+"O. c #DBDFDF",
+"+. c #D8C7C7",
+"@. c #F25D5E",
+"#. c #E1A8A7",
+"$. c #F0FDFD",
+"%. c #B7A8A9",
+"&. c #827273",
+"*. c #C6C8C4",
+"=. c #9D9BBB",
+"-. c #444097",
+";. c #BEBECF",
+":. c #F7F8F4",
+">. c #9D8A8A",
+",. c #897F7F",
+"<. c #E1E3E3",
+"1. c gray92",
+"2. c #DBDDDD",
+"3. c #CAC3C1",
+"4. c #CFBEBC",
+"5. c #D6D5D8",
+"6. c #CDCDCD",
+"7. c #9FA29F",
+"8. c #8E9291",
+"9. c #B8B6B6",
+"0. c #EEF6F2",
+"q. c #E0F3F3",
+"w. c #E6E5E6",
+"e. c #DDDEDE",
+"r. c #A4BCBC",
+"t. c #A4ADAD",
+"y. c #DEDDDD",
+"u. c #ECEDED",
+"i. c #DEDCDE",
+"p. c #C6CFC8",
+"a. c #6BB070",
+"s. c #AAC2AB",
+"d. c #DFDAD9",
+"f. c #999AC1",
+"g. c #8684B8",
+"h. c #F2F9F1",
+"j. c #EDBFC1",
+"k. c #FC7F7F",
+"l. c #DBDADA",
+"z. c #E4E0E0",
+"x. c #FF8A8A",
+"c. c #F0C0C0",
+"v. c #DCE6E6",
+"b. c #ECEAEA",
+"n. c #DFDDDF",
+"m. c #C9CFC8",
+"M. c #55A354",
+"N. c #B5CAB4",
+"B. c #D6D1D7",
+"V. c #4C4D9E",
+"C. c #55549C",
+"Z. c #EDF2ED",
+"A. c #EFCBCC",
+"S. c #C06161",
+"D. c #C8C8C8",
+"F. c #EAE7E7",
+"G. c #D67979",
+"H. c #BA9696",
+"J. c #DEE5E5",
+"K. c #EBEAEA",
+"L. c #E2DEE2",
+"P. c #BFCDBF",
+"I. c #89BB89",
+"U. c #CDD5CD",
+"Y. c #D7D6D6",
+"T. c #C5C5D3",
+"R. c #ADADC8",
+"E. c #EEECEA",
+"W. c #C2CACA",
+"Q. c #647676",
+"!. c #A6A5A5",
+"~. c #E0E1E1",
+"^. c #819393",
+"/. c #787F7F",
+"(. c #DEDDDD",
+"). c #ECECEC",
+"_. c #E8E9E8",
+"`. c #F2F0F2",
+"'. c #FCF3FC",
+"]. c #E6E5E6",
+"[. c gray89",
+"{. c #F4F4F1",
+"}. c #F4F4EF",
+"|. c gray87",
+" X c #E4E3E3",
+".X c #EEEBEB",
+"XX c gainsboro",
+"oX c #E0DFDF",
+"OX c #EBE8E8",
+"+X c #E6E4E4",
+"@X c #DFE0E0",
+"#X c #ECECEC",
+"$X c gray92",
+"%X c gray92",
+"&X c #E6E8E6",
+"*X c #D9DAD9",
+"=X c gray86",
+"-X c #EAEAEA",
+";X c #E3E3E4",
+":X c #D2D2D2",
+">X c gray92",
+",X c #EAEAEA",
+"<X c gray84",
+"1X c gray89",
+"2X c #ECECEC",
+"3X c gray88",
+"4X c gray85",
+"5X c #ECECEC",
+"6X c gray92",
+"7X c #E6E6E6",
+"8X c gray88",
+"9X c LightGray",
+"0X c #D7D7D7",
+"qX c gray89",
+"wX c #DDDDDD",
+"eX c #CECECE",
+"rX c #E2E2E2",
+"tX c #E1E1E1",
+"yX c gray81",
+"uX c gainsboro",
+"iX c #E2E2E2",
+"pX c #D7D7D7",
+"aX c gray84",
+"sX c gray93",
+"dX c #EAEAEA",
+"fX c gray88",
+"gX c #D8D8D8",
+"hX c gray85",
+"jX c #E1E1E1",
+"kX c #DADADA",
+"lX c #D8D8D8",
+"zX c gray87",
+"xX c #DDDDDD",
+"cX c #D8D8D8",
+"vX c #DADADA",
+"bX c gray88",
+"nX c gray85",
+"mX c #D7D7D7",
+"MX c #E7E7E7",
+"NX c gray92",
+"BX c #E6E6E6",
+"VX c gray92",
+"CX c gray93",
+"ZX c gray93",
+"AX c #ECECEC",
+"SX c #ECECEC",
+"DX c gray93",
+"FX c #ECECEC",
+"GX c #ECECEC",
+"HX c gray93",
+"JX c gray93",
+"KX c #ECECEC",
+"LX c #ECECEC",
+"PX c gray93",
+"IX c #E6E6E6",
+"UX c white",
+/* pixels */
+"  . X o O + @ # $ % & * = - ; : ",
+"> , < 1 2 3 4 5 6 7 8 9 0 q w e ",
+"r t y u i p a s d f g h j k l z ",
+"x c v b n m M N B V C Z A S D F ",
+"G H J K L P I U Y T R E W Q ! ~ ",
+"^ / ( ) _ ` ' ] [ { } |  ...X.o.",
+"O.+.@.#.$.%.&.*.=.-.;.:.>.,.<.1.",
+"2.3.4.5.6.7.8.9.0.q.w.e.r.t.y.u.",
+"i.p.a.s.d.f.g.h.j.k.l.z.x.c.v.b.",
+"n.m.M.N.B.V.C.Z.A.S.D.F.G.H.J.K.",
+"L.P.I.U.Y.T.R.E.W.Q.!.~.^./.(.).",
+"_.`.'.].[.{.}.|. X.XXXoXOX+X@X#X",
+"$X%X&X*X=X-X;X:X>X,X<X1X2X3X4X5X",
+"6X7X8X9X0XqXwXeXrXtXyXuXiXpXaXsX",
+"dXfXgXhXjXkXlXzXxXcXvXbXnXmXMXNX",
+"BXVXCXZXAXSXDXFXGXHXJXKXLXPXNXIX"
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 169 2 ",
+"   c #131515",
+".  c #1A1D1D",
+"X  c #2F3434",
+"o  c #2F2F2F",
+"O  c #067F06",
+"+  c #00007D",
+"@  c #4D4D4D",
+"#  c #5E5353",
+"$  c #605C5C",
+"%  c #7C5555",
+"&  c #6F6E6F",
+"*  c #726767",
+"=  c #7B7B7B",
+"-  c #777373",
+";  c #FE0000",
+":  c #FB1515",
+">  c #FA1D1D",
+",  c #F62222",
+"<  c #EC3F3F",
+"1  c #F53333",
+"2  c #F63B3B",
+"3  c #A75C5C",
+"4  c #8D6D6D",
+"5  c #F44B4B",
+"6  c #F25757",
+"7  c #EA6666",
+"8  c #ED6D6D",
+"9  c #F26464",
+"0  c #E97A7A",
+"q  c #F07575",
+"w  c #128512",
+"e  c #1C8B1C",
+"r  c #168A16",
+"t  c #2C8F2C",
+"y  c #2C922C",
+"u  c #349334",
+"i  c #3E9A3E",
+"p  c #489D47",
+"a  c #469E46",
+"s  c #4EA14E",
+"d  c #57A357",
+"f  c #68AB68",
+"g  c #63AA63",
+"h  c #7AB37A",
+"j  c #151586",
+"k  c #24248D",
+"l  c #2E2E93",
+"z  c #343495",
+"x  c #383897",
+"c  c #46469D",
+"v  c #4A4AA0",
+"b  c #5A5AA6",
+"n  c #6161A7",
+"m  c #6D6DAD",
+"M  c #797BAF",
+"N  c #7B7BB3",
+"B  c #0606FF",
+"V  c #2525F9",
+"C  c #3F3FF5",
+"Z  c #6969EF",
+"A  c #858585",
+"S  c #8E8E8E",
+"D  c #939393",
+"F  c #9D9D9D",
+"G  c #A09F9F",
+"H  c #82AF82",
+"J  c #85B585",
+"K  c #99BF99",
+"L  c #9898BD",
+"P  c #8888B8",
+"I  c #A3A3A2",
+"U  c #A7A7A8",
+"Y  c #ACACAC",
+"T  c #A5ACAC",
+"R  c #B1ADAD",
+"E  c #A8BFA8",
+"W  c #B4B4B4",
+"Q  c #BAB6B6",
+"!  c #BABABA",
+"~  c #B5B9B9",
+"^  c #ED8282",
+"/  c #E59C9C",
+"(  c #EC9494",
+")  c #CBBBBB",
+"_  c #E6B9B9",
+"`  c #9FC29F",
+"'  c #A4C4A4",
+"]  c #A8C7A8",
+"[  c #B3C6B3",
+"{  c #B5CBB5",
+"}  c #BDCDBD",
+"|  c #BED0BE",
+" . c #C8C8BF",
+".. c #C4C4BF",
+"X. c #9E9EC2",
+"o. c #8D8DDB",
+"O. c #B9B9CC",
+"+. c #A1A1D7",
+"@. c #ACACDD",
+"#. c #A7A7E2",
+"$. c #AFAFE4",
+"%. c #B3B3E1",
+"&. c #B3C7C7",
+"*. c #C3C4C3",
+"=. c #C9C4C5",
+"-. c #C0C0CF",
+";. c #CAC6CA",
+":. c #C6C8C8",
+">. c #CBCBCB",
+",. c #C3CFC3",
+"<. c #DBC6C6",
+"1. c #D1CFCF",
+"2. c #DACAC9",
+"3. c #CDD1CD",
+"4. c #C5D2C5",
+"5. c #DFDFCF",
+"6. c #D1D1CE",
+"7. c #C4C4D1",
+"8. c #CBCBD5",
+"9. c #C7C7DD",
+"0. c #D2CDD3",
+"q. c #D3CED8",
+"w. c #CED4D4",
+"e. c #CEDBDB",
+"r. c #D3D3D3",
+"t. c #D6D9D6",
+"y. c #DCDCD4",
+"u. c #D5D5D9",
+"i. c #D9D5D9",
+"p. c #D4DBDB",
+"a. c #DCDBDB",
+"s. c #D9D4D3",
+"d. c #EDC7C7",
+"f. c #E4CBCB",
+"g. c #E8C5C5",
+"h. c #EDD6D6",
+"j. c #E0D9D7",
+"k. c #ECDBDB",
+"l. c #DFE0D8",
+"z. c #E5E5D7",
+"x. c #E2E2DC",
+"c. c #DEDEE1",
+"v. c #E2DDE2",
+"b. c #D5E3E3",
+"n. c #DCE3E3",
+"m. c #D7E8E8",
+"M. c #E5E5E5",
+"N. c #EBE3E3",
+"B. c #E7E9E7",
+"V. c #EAEAE4",
+"C. c #E7E7EB",
+"Z. c #EBE2EB",
+"A. c #E4EBEB",
+"S. c #EBEBEB",
+"D. c #F0EFEF",
+"F. c #F6F2E7",
+"G. c #F6F7EC",
+"H. c #F7FBEF",
+"J. c #EFEEF0",
+"K. c #F4EEF2",
+"L. c #F1E5F1",
+"P. c #E3F7F7",
+"I. c #EDF3F3",
+"U. c #EBFCFC",
+"Y. c #E7FCFC",
+"T. c #F4F4F4",
+"R. c #F3FBFB",
+"E. c #FBFBFB",
+"W. c #F8F6FD",
+/* pixels */
+"M.M.M.M.M.M.M.v.C.S.B.C.B.B.C.B.B.B.B.B.B.B.S.B.S.C.B.S.S.Z.M.M.",
+"M.B.I.S.S.D.S.G.l.>.i.v.i.r.r.r.r.i.v.i.r.r.r.0.5.y.r.r.t.S.M.M.",
+"M.T.T.I.U.I.T.B.Y r.[ H E r.1.! r.E H [ r.>.! r.+.o.7.r.u.E.B.V.",
+"M.S.T.g.^ 1 d.p.F v.h d r ' Z.*.a.g s e 4.a.:.M.Z B $.z.c.E.V.M.",
+"M.G.T.^ : ; d.p.I a.v.M.e ` v.*.u.Z.3.w &.j.*.a.l.V #.z.a.E.M.M.",
+"M.S.I.A.E.3 <.M.I a.r.i h Z.i.*.j.} y ' Z.t.:.a.z.V $.z.c.E.B.M.",
+"M.S.T.a.~ X = u.I M.s O a ` v.*.c.y w p { a.*.x.Z B C 9.M.E.B.M.",
+"M.J.I.U # $ $ G I t...[ ` ,.a.&.a.| { ' t.i.*.y.@.@.@.q.a.E.B.M.",
+"M.a.Y T &.&.~ I ! T.D.K.W.D.S.) *.;.s.j.:.*.a.I.F.G.H.D.S.E.M.Z.",
+"S.8.w.g.7 0 1.p.S.T.U.U.k.T.t.R i.z.L M t.i.T.I.Y.S.k.I.:.T.B.M.",
+"B.r.w._ ^ > 8 m.N.S.( 2 , N.*.U B.7.j k a.a.T.f.q > 9 P.W T.B.M.",
+"B.1.1.b.0 : / b.D.S.^ > : N.:.U M.n N x a.i.R.d.6 ; 9 P.Q W.B.M.",
+"S.r.w.f.<.2 6 m.S.D.U.E.% S.>.Y 7.k z + L M.J.A.E.) 4 E.W T.B.V.",
+"S.1.e.^ 2 1 ( b.S.T.*.=   A *.Y a.7.P l 8.l.T.B.Y @ o Q ) T.C.M.",
+"S.r.1.a.<.r.n.u.S.u.D & = = F Y a.a.a.r.a.a.D.! = - - A Y E.C.M.",
+"S.0.! >.0.0.=.*.) Y W .. .! Y u.T.S.T.R.T.B.C.y.Z.A.A.M.u.R.V.M.",
+"B.8.i.] p d 4.v.*.i.V.L c u.a.T.T.Y.k._ I...M.R.Y.h.d.T.*.T.Z.M.",
+"S.r.r.] ] u d Z...i.r.z j -.a.I.h.9 ; 2 Y.Q B.g.5 ; 9 P.W T.B.M.",
+"S.r.>.L.a.u ' M.&.z.b N z O.x.T.N.( 2 < I.Q A.h.^ , 9 Y.Q T.B.Z.",
+"B.1.r.{ w f w.a.;.r.c z + m x.S.B.R.n.# E.! M.J.E.&.* E.~ T.V.M.",
+"S.1.i.J y i h Z...u.i.-.c -.x.T.T.I @   A ! C.B.D X . G ! T.B.M.",
+"S.r.1.M.Z.j.c.a.;.r.l.l.M.a.a.S.*.D S F D U a.Q D D D D W E.B.M.",
+"M.S.T.T.T.T.T.J.M.T.T.T.T.T.S.M.M.S.K.S.G.c.B.C.G.K.D.K.a.E.B.M.",
+"M.S.I.V.B.M.S.w.>.T.M.M.M.S.;.t.T.M.B.C.J.! M.J.B.M.M.B.! T.B.M.",
+"M.S.T.M.M.M.B.r.>.T.M.M.M.S.=.i.S.M.M.M.S.! M.M.M.M.M.B.! T.S.Z.",
+"M.T.S.M.M.M.S.>.:.T.x.M.M.S.*.t.T.M.M.M.J.! M.S.M.M.M.M.! T.C.M.",
+"M.S.T.M.M.M.S.r.:.T.M.V.M.S.:.t.S.M.M.M.S.! M.S.M.M.M.M.Q E.B.M.",
+"M.S.T.M.B.M.B.r.:.T.M.Z.M.S.:.u.T.M.B.M.J.! M.S.M.M.B.S.~ T.B.M.",
+"M.T.r.Q ! Q ! Y 1.>.W ! ! ! Y i.*.W ! ~ ! R a.! Q ! ! ! W E.M.M.",
+"M.S.T.T.T.W.T.R.E.W.T.T.T.T.E.E.T.T.T.T.T.E.E.T.T.T.T.T.E.E.Z.M.",
+"M.M.B.B.B.B.S.B.M.S.B.B.B.S.M.M.S.B.B.S.S.B.M.S.B.B.B.B.B.M.V.M.",
+"M.M.M.M.M.M.M.Z.M.M.M.M.M.M.M.M.M.M.M.M.M.M.M.M.M.M.M.M.M.M.M.A."
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 184 2 ",
+"   c #030101",
+".  c #0C0C0C",
+"X  c #131212",
+"o  c #191A1A",
+"O  c #2A2929",
+"+  c #262626",
+"@  c #323232",
+"#  c #393E3E",
+"$  c #4E2B2B",
+"%  c #007500",
+"&  c #027D02",
+"*  c #0A7E0A",
+"=  c #00007C",
+"-  c #4A4949",
+";  c #5B5B5B",
+":  c #545555",
+">  c #656464",
+",  c #6A6A6A",
+"<  c #7B7B7B",
+"1  c #D20F0F",
+"2  c #D01E1E",
+"3  c #FF0101",
+"4  c #FD0B0B",
+"5  c #FD1212",
+"6  c #FB1D1D",
+"7  c #FB2323",
+"8  c #F83434",
+"9  c #A95252",
+"0  c #B85858",
+"q  c #F84545",
+"w  c #F44B4B",
+"e  c #F64848",
+"r  c #EF5757",
+"t  c #F65858",
+"y  c #EA6666",
+"u  c #F46363",
+"i  c #ED7B7B",
+"p  c #F27676",
+"a  c #9C2323",
+"s  c #0C830C",
+"d  c #028102",
+"f  c #168916",
+"g  c #1B8A1B",
+"h  c #118711",
+"j  c #318F31",
+"k  c #359635",
+"l  c #3B973B",
+"z  c #3D993D",
+"x  c #289128",
+"c  c #439C43",
+"v  c #489E48",
+"b  c #4DA14D",
+"n  c #53A253",
+"m  c #5AA55A",
+"M  c #65A965",
+"N  c #6BAC6B",
+"B  c #77B277",
+"V  c #020281",
+"C  c #0A0A83",
+"Z  c #111184",
+"A  c #131388",
+"S  c #1E1E8B",
+"D  c #22228D",
+"F  c #2C2C8E",
+"G  c #2C2C92",
+"H  c #3B3B99",
+"J  c #363695",
+"K  c #42429B",
+"L  c #48489D",
+"P  c #5757A5",
+"I  c #7373AF",
+"U  c #7676B0",
+"Y  c #7D7DB3",
+"T  c #6565AA",
+"R  c #0F0FFC",
+"E  c #1212FC",
+"W  c #2D2DF7",
+"Q  c #3434F6",
+"!  c #3D3DF5",
+"~  c #4B4BF3",
+"^  c #4242F9",
+"/  c #6767EE",
+"(  c #6363F4",
+")  c #7777F0",
+"_  c #7D8686",
+"`  c #949494",
+"'  c #9C9C9C",
+"]  c #9A9696",
+"[  c #8B8B8B",
+"{  c #8CBA8C",
+"}  c #82B682",
+"|  c #91BD91",
+" . c #98BE98",
+".. c #8C8CBA",
+"X. c #9595BD",
+"o. c #9EA1A1",
+"O. c #A4A4A4",
+"+. c #ABABAB",
+"@. c #ABAAA7",
+"#. c #B5B5B5",
+"$. c #BBBBBB",
+"%. c #BEB2B2",
+"&. c #DD9F9F",
+"*. c #C79B9B",
+"=. c #E78C8C",
+"-. c #F08F8F",
+";. c #E69494",
+":. c #ED9494",
+">. c #EA9D9D",
+",. c #C2BFBF",
+"<. c #DFB2B2",
+"1. c #E4A4A4",
+"2. c #ECA6A6",
+"3. c #EBAAAA",
+"4. c #E1AEAE",
+"5. c #E9B4B4",
+"6. c #E9B9B9",
+"7. c #E0B6B6",
+"8. c #AAC6AA",
+"9. c #AECAAE",
+"0. c #B4CAB4",
+"q. c #BFC1BE",
+"w. c #BBCDBB",
+"e. c #D3D3BF",
+"r. c #9797C0",
+"t. c #A4A4C5",
+"y. c #A8A8C6",
+"u. c #B9B9C9",
+"i. c #B9B9DD",
+"p. c #C5BCC3",
+"a. c #C3C3C3",
+"s. c #C8C5C5",
+"d. c #CCC6CC",
+"f. c #CBCBCB",
+"g. c #C7CEC0",
+"h. c #DAC5C5",
+"j. c #C5D1C5",
+"k. c #CCD4CC",
+"l. c #C3C3D1",
+"z. c #CCCCD4",
+"x. c #C5C5DE",
+"c. c #C8C8DD",
+"v. c #D3C9D3",
+"b. c #CFDADA",
+"n. c #D5D5D5",
+"m. c #D6D9D6",
+"M. c #DADAD7",
+"N. c #D7D7D8",
+"B. c #DCDCDC",
+"V. c #D6D9D9",
+"C. c #E7C2C2",
+"Z. c #E9C6C6",
+"A. c #E7D5D5",
+"S. c #EBD5D5",
+"D. c #E3DBDB",
+"F. c #E9E9D4",
+"G. c #E3E3DD",
+"H. c #E4E4D7",
+"J. c #F4F4D6",
+"K. c #CECEE0",
+"L. c #DFDFE0",
+"P. c #E2DEE2",
+"I. c #E9DEE9",
+"U. c #D7E4E4",
+"Y. c #DBE3E3",
+"T. c #DAEAE9",
+"R. c #D6F4F4",
+"E. c #E5E5E5",
+"W. c #EAE6E5",
+"Q. c #EAEAE3",
+"!. c #EAE3EB",
+"~. c #E3ECEC",
+"^. c #EAEAEA",
+"/. c #F0ECEC",
+"(. c #F2E6F2",
+"). c #FFEAFF",
+"_. c #E3F3F3",
+"`. c #ECF5F5",
+"'. c #ECFAFA",
+"]. c #E6FAFA",
+"[. c #F4F4F4",
+"{. c #F4FDFD",
+"}. c #FDFDFD",
+"|. c #F8F7F7",
+/* pixels */
+"E.E.E.E.~.E.E.E.E.W.E.E.~.E.E.E.E.E.E.E.E.W.E.E.E.E.E.E.E.E.E.E.E.E.W.E.E.E.E.E.E.E.E.E.E.E.E.E.",
+"E.E.E.W.W.E.E.W.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.W.E.E.W.",
+"E.E.E.E.E.E.E.E.E.E.E.E.E.W.^.^.^.E.^.^.W.^.^.^.^.W.^.W.^.E.^.^.^.W.^.^.^.E.^.E.^.W.^.E.~.E.E.E.",
+"W.E.E.E.^.^.^.^.^.^.^.W./.E.N.V.m.n.n.N.N.N.V.V.V.N.m.n.m.m.m.V.N.N.N.N.N.N.N.N.V.N.Y.^.E.W.E.E.",
+"E.E.E.^.}.}.|.|.|.}.}.}.}.#.#.a.d.v.v.f.a.e.a.+.$.e.v.v.v.d.q.a.$.+.s.g.e.h.g.a.a.$.V.}.W.E.W.E.",
+"E.E.E.^.}.E.~._._.Z.D.^.f.` V.E.| b b { L.V.D.u.L.n.B c b 9.E.Y.z.e.E.c./ ^ ) G.E.B.W.}.^.E.~.E.",
+"E.E.E.W.}.E.S.2.t 4 6.`.s.` N.M.c m c % { P.N.#.L.9.k M g s j.P.f.u.G.i.~ E Q G.N.N.E.}.^.E.E.E.",
+"E.E.~.W.}.S.q 3 3 3 6.`.s.` N.V.V.(.k.% B I.V.$.V.N.V.).{ % w.P.k.q.L.B.J.Q Q F.V.N.E.}.W.E.E.E.",
+"~.E.E.^.}.E.A.>.w 3 6.`.s.` N.V.D.V.l g k.B.B.$.B.N.P.w.s n P.B.v.q.Y.N.M.W Q H.B.V.W.}.^.E.E.E.",
+"W.E.E.^.|.E.E._.{.$ %.[.a.` n.L.n.z x N.!.m.B.#.V.P.0.f m (.P.N.k.q.B.B.J.Q ! J.B.V.E.}.^.E.E.E.",
+"E.E.E.^.}.E./.^.n.o ' }.s.` n.L.l % v n | L.V.$.P.w.* * n n 0.P.f.p.G.c./ E E / c.V.E.}.^.E.E.~.",
+"E.E.E.^.{./.#.- o   . , s.` n.B.M b v k { P.B.$.L.w.b b c z 0.P.k.p.G.x.! ~ ~ ~ x.D.E.}.W.E.E.E.",
+"W.E.E.^.|.f.O.' O.O.O.' +.] k.B.P.!.I.I.P.V.N.$.B.E.(.!.(.(.E.P.n.$.V.V.F.F.F.F.M.N.E.}.^.E.E.E.",
+"E.~.E.E.$.` ' o.o.' ' o.] +.^.^.W.E.E.W.W.^./.#.$.a.$.$.$.q.$.a.,.V./.^.~.!.E.!.^.^.[.}.W.E.E.E.",
+"E.E.^.N.$.M.N.n.v.n.b.n.n.W.}.[.|.[.{.{.[.}.V.O.n.f.f.n.d.s.f.n.n.|.|.[.[.{.{.|.|.^.k.}.^.E.W.E.",
+"W.E.^.V.a.T.>.5 5 7 1.Y.M.^.[.E._.~.C.:.E.W.+.O.E.V.Q.r.V P E.V.P.|.E._._.D.:.C.`.a.#.}.^.E.E.E.",
+"E.E.W.N.a.Y.<.1.<.4 8 U.N.^.{.A.-.w 4 6 ~.^.+.O.L.B.l.D V K G.N.B.}.E.3.u 7 3 2.].s.#.}.W.E.E.~.",
+"E.E.^.m.a.B.T.;.w 4 =.U.n.^.{.6.8 3 3 7 E.^.+.@.P.G.L U K H E.N.B.}.E.u 4 3 3 3.'.s.#.}.W.E.E.E.",
+"E.E.^.V.a.B.U.=.e 5 p U.M.^.[.E._.Z.r 2 E.^.+.O.Q...S l.G G n.V.L.}.W._.D.>.1 4.'.s.#.}.~.E.E.W.",
+"E.E.E.n.p.E.h.B.R.8 6 M.N./.[.E.E.{.b.+ [.^.+.O.Q...= Z C = T Q.B.}.E.E.W.}.$ o.}.a.$.}.^.E.E.E.",
+"E.E.^.V.,.T.r 5 6 3 y U.N.^.|.E.E.$.> . ' E.+.O.P.z.u.p.D F z.M.B.}.E.E.f.] X ; B.f.#.}.W.E.E.E.",
+"E.E.W.m.a.Y.h.;.=.4.B.B.V.W.|.W.< @ O @ + [ #.O.L.B.Y.G.t.t.G.V.B.}.[.+.- + @ + : $.$.}.W.E.E.E.",
+"E.E.^.V.a.Y.B.T.T.U.V.B.N.^.N.@.+.#.#.#.#.+.] O.B.N.N.n.G.G.N.N.P.^.+.O.+.#.#.#.+.' #.}.^.E.E.W.",
+"E.E.^.V.+.$.$.p.p.p.#.$.$.#.+.O.O.O.@.@.O.O.O.M.|.[.[.[.[.[.|.|./.^.E.Y.D.B.B.L.E.B.E.}.~.E.E.E.",
+"E.E.^.V.a.P.n.8. .0.P.V.D.$.n.P.B.G.u.y.V.E.B.|.[.^./.[.'.[.^.^.#.|.|.[.[.{.'.[.|.B.s.}.^.E.W.W.",
+"E.E.W.N.q.E.N s f & } P.B.a.k.V.E.t.= Z l.M.N.[.^._.~.C.u =._.B.o.|.E._.E.3.w 5.`.a.#.}.~.E.E.E.",
+"W.E.^.N.u.E.0.j.B.g j D.B.a.z.G.n.J G A l.D.N.[.[.>.8 4 3 u '.D.@.{.E.p 6 3 3 2.'.s.$.}.E.E.E.E.",
+"E.E.^.V.e.B.B.!.j.& N I.B.q.f.Q.P I ..= m.L.N.|.`.5.t 6 3 u ].B.O.|.E.:.e 4 3 3.'.s.#.}.W.E.~.E.",
+"E.E.E.N.$.M.P.w.h n I.V.V.e.n.l.= U K = Y L.m.[.^.~._.W.0 9 '.B.O.|.W.].~.S.a *.'.s.#.}.W.E.E.E.",
+"E.E.^.N.u.E.8.s k 9.0.M.L.$.n.l.H G A = K M.N.[.[.E./.}._ : }.D.O.{.W.W.[.}.# O.}.s.#.}.W.E.E.E.",
+"E.E.^.N.e.^.M % g d k V.L.u.f.B.V.Q.Y C z.D.N.[.`.D.+.> X . < n.+.[.^.N.` -   O ' s.#.}.~.E.E.W.",
+"E.E.W.z.$.L.n.j.j.k.k.V.M.$.n.B.M.B.z.z.N.M.N.[.`.s., ; > > ; O.+.[.^.@.; ; , ; , #.$.}.^.E.E.E.",
+"E.E.^.B.f.E.B.E.P.P.P.M.E.s.n.Y.B.L.D.B.B.L.Y.[.#.O.$.$.$.#.q.@.+.E.O.+.#.$.#.$.$.o.s.}.E.E.E.E.",
+"E.E.E.E.}.|.|.|.}.|.}.}.[.E.}.|.}.}.}.}.}.}.^.^.|.|.[.[.[.[.[.{.E.[.|.{.[.[.|.[.[.[.E.}.W.E.E.E.",
+"E.E.E.^.}.E.E.E.E.W.E.^.a.a.}.E.E.E.E.^.E./.#.B.[.E.E.W.^.W.E.E.+.|.^.E.^.~.^.E./.f.$.}.W.E.E.~.",
+"E.E.E.^.}.E.E.E.E.E.E.`.$.a.|.E.~.E.E.E.E.^.@.L.[.E.E.E.E.E.E.D.O.|.E.E.E.E.E.E.^.d.#.}.^.E.E.W.",
+"E.E.E.^.}.E.E.E.E.E.E.`.a.a.}.E.^.E.E.E.E.^.@.E.[.E.E.E.E.E.W.Y.@.|.^.E.E.E.E.E.^.s.$.}.E.E.E.E.",
+"W.E.E.^.}.E.E.E.E.E.E.^.a.$.|.E.E.E.E.E.E.^.@.D.[.Y.E.E.E.E.E.P.O.|.^.E.E.E.E.E.^.s.#.}.^.E.E.E.",
+"~.E.E.^.}.E.E.E.E.E.E.^.u.a.}.E.E.E.E.E.E.^.+.Y.[.E.E.E.E.E.W.Y.O.|.E.E.E.E.E.E.`.f.#.}.W.E.E.E.",
+"E.W.E.^.{.E.E.E.E.E.P.E.e.q.}.B.E.E.E.E.E.^.O.E.[.L.E.E.E.E.E.E.O.}.E.E.E.E.E.E.W.s.#.}.^.E.~.E.",
+"E.E.E.^.}.^./.^.^./.W.[.a.q.}.^.^.^.^.^.^.[.+.B.{.^.^.^.^.W.^.E.O.{.^.^.^.W.^.^.[.f.#.}.W.E.E.E.",
+"E.E.E.^.{.f.s.s.f.f.f.f.+.q.[.a.f.s.f.f.f.f.' E.D.s.f.s.f.f.f.a.' [.n.s.f.f.f.f.f.#.#.}.W.E.E.E.",
+"E.E.E.^.E.#.#.$.#.#.#.$.#.V.n.#.$.#.#.#.#.#.$.E.a.#.$.#.#.#.$.#.s.E.$.#.#.#.#.#.#.#.n.}.W.E.~.W.",
+"E.E.E.W.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.^.E.E.E.",
+"E.~.E.W.^.W.^.W.^.E.^.^.W.W.^.~.^.W.~.W.W.~.W.^.E.W.~.W.^.E.^.E.W.W.^.E.W.^.W.~.~.W.^.^.E.E.~.E.",
+"E.W.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.E.W.",
+"W.E.E.E.E.E.E.E.E.E.E.E.E.W.E.E.W.E.E.~.E.E.E.E.E.E.E.E.E.E.E.E.W.~.E.E.E.E.E.W.E.E.E.E.~.E.E.E.",
+"E.E.~.W.E.E.~.E.E.E.E.E.W.E.E.E.E.E.E.E.E.W.E.W.~.E.W.E.E.^.E.E.E.E.E.E.~.W.E.E.E.E.W.E.E.E.W.E."
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/mines-web.png b/icons/mines-web.png
new file mode 100644 (file)
index 0000000..cb6d00f
Binary files /dev/null and b/icons/mines-web.png differ
diff --git a/icons/mines.ico b/icons/mines.ico
new file mode 100644 (file)
index 0000000..eaca3e7
Binary files /dev/null and b/icons/mines.ico differ
diff --git a/icons/mines.rc b/icons/mines.rc
new file mode 100644 (file)
index 0000000..fd34f5f
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "mines.ico"
diff --git a/icons/mines.sav b/icons/mines.sav
new file mode 100644 (file)
index 0000000..a827541
--- /dev/null
@@ -0,0 +1,67 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :5:Mines
+PARAMS  :6:9x9n35
+CPARAMS :6:9x9n35
+SEED    :15:698938038698621
+DESC    :26:0,0,me0691ca8a278f3c371688
+PRIVDESC:22:me0691ca8a278f3c371688
+UI      :3:D0C
+TIME    :7:75.2958
+NSTATES :2:56
+STATEPOS:2:41
+MOVE    :4:O0,0
+MOVE    :4:F1,2
+MOVE    :4:F0,2
+MOVE    :4:O2,2
+MOVE    :4:F2,1
+MOVE    :4:F3,1
+MOVE    :4:F3,2
+MOVE    :4:F3,3
+MOVE    :4:F1,3
+MOVE    :4:F2,3
+MOVE    :4:C1,0
+MOVE    :4:C2,0
+MOVE    :4:C3,0
+MOVE    :4:F5,0
+MOVE    :4:F5,1
+MOVE    :4:C4,1
+MOVE    :4:O6,1
+MOVE    :4:O6,2
+MOVE    :4:O6,3
+MOVE    :4:F7,1
+MOVE    :4:O7,4
+MOVE    :4:O5,4
+MOVE    :4:F5,3
+MOVE    :4:F5,5
+MOVE    :4:F6,6
+MOVE    :4:C6,5
+MOVE    :4:F8,6
+MOVE    :4:C6,3
+MOVE    :4:F8,2
+MOVE    :4:C7,2
+MOVE    :4:F8,0
+MOVE    :4:F7,0
+MOVE    :4:F6,0
+MOVE    :4:C4,2
+MOVE    :4:F4,4
+MOVE    :4:F4,5
+MOVE    :4:F3,4
+MOVE    :4:C5,6
+MOVE    :4:F7,7
+MOVE    :4:F8,7
+MOVE    :4:F7,8
+MOVE    :4:O4,8
+MOVE    :4:F3,6
+MOVE    :4:C4,6
+MOVE    :4:F3,8
+MOVE    :4:F5,8
+MOVE    :4:C6,7
+MOVE    :4:C3,7
+MOVE    :4:F2,5
+MOVE    :4:F2,4
+MOVE    :4:F1,8
+MOVE    :4:F1,7
+MOVE    :4:C2,7
+MOVE    :4:C2,6
+MOVE    :4:C1,6
diff --git a/icons/net-16d24.png b/icons/net-16d24.png
new file mode 100644 (file)
index 0000000..97896df
Binary files /dev/null and b/icons/net-16d24.png differ
diff --git a/icons/net-16d4.png b/icons/net-16d4.png
new file mode 100644 (file)
index 0000000..c40fda8
Binary files /dev/null and b/icons/net-16d4.png differ
diff --git a/icons/net-16d8.png b/icons/net-16d8.png
new file mode 100644 (file)
index 0000000..b73fd7e
Binary files /dev/null and b/icons/net-16d8.png differ
diff --git a/icons/net-32d24.png b/icons/net-32d24.png
new file mode 100644 (file)
index 0000000..81201d1
Binary files /dev/null and b/icons/net-32d24.png differ
diff --git a/icons/net-32d4.png b/icons/net-32d4.png
new file mode 100644 (file)
index 0000000..6512c66
Binary files /dev/null and b/icons/net-32d4.png differ
diff --git a/icons/net-32d8.png b/icons/net-32d8.png
new file mode 100644 (file)
index 0000000..a31dec3
Binary files /dev/null and b/icons/net-32d8.png differ
diff --git a/icons/net-48d24.png b/icons/net-48d24.png
new file mode 100644 (file)
index 0000000..7b4aea6
Binary files /dev/null and b/icons/net-48d24.png differ
diff --git a/icons/net-48d4.png b/icons/net-48d4.png
new file mode 100644 (file)
index 0000000..ea4dd32
Binary files /dev/null and b/icons/net-48d4.png differ
diff --git a/icons/net-48d8.png b/icons/net-48d8.png
new file mode 100644 (file)
index 0000000..d21a767
Binary files /dev/null and b/icons/net-48d8.png differ
diff --git a/icons/net-base.png b/icons/net-base.png
new file mode 100644 (file)
index 0000000..e4d52f6
Binary files /dev/null and b/icons/net-base.png differ
diff --git a/icons/net-ibase.png b/icons/net-ibase.png
new file mode 100644 (file)
index 0000000..89c59bd
Binary files /dev/null and b/icons/net-ibase.png differ
diff --git a/icons/net-ibase4.png b/icons/net-ibase4.png
new file mode 100644 (file)
index 0000000..7bcc94f
Binary files /dev/null and b/icons/net-ibase4.png differ
diff --git a/icons/net-icon.c b/icons/net-icon.c
new file mode 100644 (file)
index 0000000..8186ac4
--- /dev/null
@@ -0,0 +1,590 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 242 2 ",
+"   c #E5E4E4",
+".  c #DDDBDB",
+"X  c #DDDEDE",
+"o  c #DDDDDD",
+"O  c #DDDDDD",
+"+  c #DDDDDD",
+"@  c #DDDDDD",
+"#  c gainsboro",
+"$  c gainsboro",
+"%  c #DDDDDD",
+"&  c #DDDDDD",
+"*  c #DDDDDD",
+"=  c #DDDDDD",
+"-  c #DDDDDD",
+";  c gray87",
+":  c gray90",
+">  c #D7CBCB",
+",  c #967A7A",
+"<  c #ADB2B2",
+"1  c #ABAAAA",
+"2  c #ADACAC",
+"3  c #B0AFAF",
+"4  c #DDDCDC",
+"5  c #DDDCDC",
+"6  c #DCDBDB",
+"7  c #E0DFDF",
+"8  c #B5B4B4",
+"9  c #ABAAAA",
+"0  c gray68",
+"q  c #ADACAC",
+"w  c #AEADAD",
+"e  c gray87",
+"r  c #D5C8C8",
+"t  c #927474",
+"y  c #B0B4B4",
+"u  c #AAA9A9",
+"i  c #9FA4A4",
+"p  c #A4A9A9",
+"a  c #D6DCDC",
+"s  c #DEE1E1",
+"d  c #DEE1E1",
+"f  c #D8DDDD",
+"g  c #B3B8B8",
+"h  c #565858",
+"j  c #0F0E0E",
+"k  c #464848",
+"l  c #A7ACAC",
+"z  c gray86",
+"x  c #D5C8C8",
+"c  c #927474",
+"v  c #B2B5B5",
+"b  c #5E8686",
+"n  c #618787",
+"m  c #688B8B",
+"M  c #8CACAC",
+"N  c #73A1A1",
+"B  c #6B9D9D",
+"V  c #8DADAD",
+"C  c #729999",
+"Z  c #2C3C3C",
+"A  c black",
+"S  c #1D2929",
+"D  c #739696",
+"F  c #D9DBDB",
+"G  c #D5C8C8",
+"H  c #937575",
+"J  c #AEB4B4",
+"K  c #668888",
+"L  c #B1ADAD",
+"P  c #B4B1B1",
+"I  c #F8F0F0",
+"U  c #B9CBCB",
+"Y  c #A6BEBE",
+"T  c #FDF5F5",
+"R  c #BDBABA",
+"E  c #807F7F",
+"W  c #5D5E5E",
+"Q  c #7A7979",
+"!  c #B2AFAF",
+"~  c #DDDDDD",
+"^  c #D5C9C9",
+"/  c #8E7070",
+"(  c #A6ABAB",
+")  c #5F8383",
+"_  c #A1A2A2",
+"`  c #A3A5A5",
+"'  c #E0DBDB",
+"]  c #B1BDBD",
+"[  c #9FB3B3",
+"{  c #E4DFDF",
+"}  c #B0B2B2",
+"|  c gray74",
+" . c gray65",
+".. c #B9B9B9",
+"X. c #B2B3B3",
+"o. c #DDDDDD",
+"O. c #D5C8C8",
+"+. c #927373",
+"@. c #ADB2B2",
+"#. c #638686",
+"$. c gray66",
+"%. c #AFAFAF",
+"&. c #DFDEDE",
+"*. c #7FB3B3",
+"=. c #72ABAB",
+"-. c #E0DEDE",
+";. c gray84",
+":. c #EFEFEF",
+">. c gray53",
+",. c gray85",
+"<. c gray88",
+"1. c #DDDDDD",
+"2. c #D5C8C8",
+"3. c #937474",
+"4. c #AEB3B3",
+"5. c #648787",
+"6. c #A8A9A9",
+"7. c #B8B4B4",
+"8. c #BECECE",
+"9. c #00EEEE",
+"0. c #00EEEE",
+"q. c #BECECE",
+"w. c #DDD9D9",
+"e. c #EDEEEE",
+"r. c #898989",
+"t. c gray85",
+"y. c #DDDDDD",
+"u. c gainsboro",
+"i. c #D5C8C8",
+"p. c #937474",
+"a. c #AEB4B4",
+"s. c #648787",
+"d. c #A9AAAA",
+"f. c #B8B4B4",
+"g. c #C1CFCF",
+"h. c #11DBDB",
+"j. c #11DBDB",
+"k. c #C1CFCF",
+"l. c #DDD9D9",
+"z. c #EEEFEF",
+"x. c #898989",
+"c. c #DADADA",
+"v. c gray87",
+"b. c gainsboro",
+"n. c #D5C8C8",
+"m. c #917373",
+"M. c #ABB0B0",
+"N. c #628585",
+"B. c #A7A7A7",
+"V. c gray68",
+"C. c #DFDFDF",
+"Z. c #D5D3D3",
+"A. c #D5D3D3",
+"S. c #DFDFDF",
+"D. c LightGray",
+"F. c #ECEDED",
+"G. c #868989",
+"H. c #D6D8D8",
+"J. c #DDDDDD",
+"K. c #DDDDDD",
+"L. c #D5C9C9",
+"P. c #8F7070",
+"I. c #A6ABAB",
+"U. c #608383",
+"Y. c #A5A5A5",
+"T. c gray63",
+"R. c gray68",
+"E. c #B4B4B4",
+"W. c gray70",
+"Q. c #AFAEAE",
+"!. c #A6A7A7",
+"~. c #B6AFAF",
+"^. c #A79292",
+"/. c #B2A8A8",
+"(. c #AEAFAF",
+"). c #DDDDDD",
+"_. c #D5C8C8",
+"`. c #947575",
+"'. c #AFB4B4",
+"]. c #638787",
+"[. c #ACACAC",
+"{. c #A5A5A5",
+"}. c #A7A7A7",
+"|. c #A9AAAA",
+" X c #A9A9A9",
+".X c #A7A9A9",
+"XX c #ABA1A1",
+"oX c #68A3A3",
+"OX c #28CACA",
+"+X c #58A8A8",
+"@X c #B0A6A6",
+"#X c #DCDEDE",
+"$X c #D5C9C9",
+"%X c #927373",
+"&X c #B2B3B3",
+"*X c #668888",
+"=X c #5F8585",
+"-X c #648787",
+";X c #618888",
+":X c #667A7A",
+">X c #2DA6A6",
+",X c cyan",
+"<X c #33C1C1",
+"1X c #B4A1A1",
+"2X c #DBE0E0",
+"3X c #D4C8C8",
+"4X c #947878",
+"5X c #B2BDBD",
+"6X c #B2B3B3",
+"7X c #AFB4B4",
+"8X c #AEB4B4",
+"9X c #ABB2B2",
+"0X c #ACAAAA",
+"qX c #7AAAAA",
+"wX c #42C0C0",
+"eX c #6CACAC",
+"rX c #B2B0B0",
+"tX c #DCDEDE",
+"yX c #DAD1D1",
+"uX c #8C5D5D",
+"iX c #937676",
+"pX c #947575",
+"aX c #927373",
+"sX c #8E7070",
+"dX c #9A7474",
+"fX c #A06D6D",
+"gX c #9A7070",
+"hX c #967B7B",
+"jX c #DEDBDB",
+"kX c #E6E7E7",
+"lX c #D4C7C7",
+"zX c #D4C8C8",
+"xX c #D5C8C8",
+"cX c #D5C8C8",
+"vX c #D5C8C8",
+"bX c #D5C9C9",
+"nX c #D3C8C8",
+"mX c #D1CACA",
+"MX c #D2C8C8",
+"NX c #D7CBCB",
+"BX c #E5E4E4",
+"VX c white",
+/* pixels */
+"  . X o O + @ # $ % & * = - ; : ",
+"> , < 1 2 3 4 5 6 7 8 9 0 q w e ",
+"r t y u i p a s d f g h j k l z ",
+"x c v b n m M N B V C Z A S D F ",
+"G H J K L P I U Y T R E W Q ! ~ ",
+"^ / ( ) _ ` ' ] [ { } |  ...X.o.",
+"O.+.@.#.$.%.&.*.=.-.;.:.>.,.<.1.",
+"2.3.4.5.6.7.8.9.0.q.w.e.r.t.y.u.",
+"i.p.a.s.d.f.g.h.j.k.l.z.x.c.v.b.",
+"n.m.M.N.B.V.C.Z.A.S.D.F.G.H.J.K.",
+"L.P.I.U.Y.T.R.E.W.Q.!.~.^./.(.).",
+"_.`.'.].[.{.}.|. X.XXXoXOX+X@X#X",
+"$X%X&X*X=XU.N.-X5.;X:X>X,X<X1X2X",
+"3X4X5X6X7XI.M.8X4.9X0XqXwXeXrXtX",
+"yXuXiX%XpXP.m.p.3.aXsXdXfXgXhXjX",
+"kXyXlX$XzXL.n.xXcXvXbXnXmXMXNXBX"
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 93 2 ",
+"   c #010202",
+".  c #111111",
+"X  c #1D1D1D",
+"o  c #292828",
+"O  c #323232",
+"+  c #702727",
+"@  c #732A2A",
+"#  c #7E2B2B",
+"$  c #783333",
+"%  c #1E4444",
+"&  c #296161",
+"*  c #247171",
+"=  c #2E7474",
+"-  c #327474",
+";  c #317A7A",
+":  c #414141",
+">  c #4C5656",
+",  c gray34",
+"<  c #496B6B",
+"1  c #407777",
+"2  c #4E7A7A",
+"3  c #686262",
+"4  c #737272",
+"5  c #747C7C",
+"6  c #268989",
+"7  c #02AEAE",
+"8  c #418383",
+"9  c #498585",
+"0  c #418C8C",
+"q  c #568282",
+"w  c #5B8585",
+"e  c #558E8E",
+"r  c #5E8888",
+"t  c #409797",
+"y  c #5D9292",
+"u  c #628585",
+"i  c #628C8C",
+"p  c #7F8E8E",
+"a  c #729B9B",
+"s  c #7D9E9E",
+"d  c #63A4A4",
+"f  c #00CACA",
+"g  c #11C9C9",
+"h  c #02D4D4",
+"j  c #00DEDE",
+"k  c #00E2E2",
+"l  c #00EAEA",
+"z  c #01F5F5",
+"x  c #03FFFF",
+"c  c #918E8E",
+"v  c #859696",
+"b  c #8D9595",
+"n  c #959595",
+"m  c #9C9393",
+"M  c #969A9A",
+"N  c #9C9C9C",
+"B  c #8EACAC",
+"V  c #92A3A3",
+"C  c #9DA1A1",
+"Z  c #93B1B1",
+"A  c #9BB6B6",
+"S  c #A3A3A3",
+"D  c #AAA1A1",
+"F  c #A6AAAA",
+"G  c #ABACAC",
+"H  c #B4ADAD",
+"J  c #B9AFAF",
+"K  c #A4B3B3",
+"L  c #ACB1B1",
+"P  c #B3B3B3",
+"I  c #BBB2B2",
+"U  c #BBBBBB",
+"Y  c #C3B7B7",
+"T  c #C5BDBD",
+"R  c #CCB8B8",
+"E  c #AFC1C1",
+"W  c #B2C3C3",
+"Q  c #BEC3C3",
+"!  c #B7C8C8",
+"~  c #C5C5C5",
+"^  c #C1CACA",
+"/  c #CCCCCC",
+"(  c #D6CACA",
+")  c #D6D1D1",
+"_  c #DBDBDB",
+"`  c #E4E4E4",
+"'  c #EAEBEB",
+"]  c #F3E7E7",
+"[  c #F3EAEA",
+"{  c #FAEFEF",
+"}  c #F3F2F2",
+"|  c #FAF3F3",
+" . c #FCF8F8",
+/* pixels */
+"` ` ' ' ` ' ' ' ' ' ' ' ' ' ' ' ' ` ' ' ' ' ' ' ` ' ' ' ' ` ` ' ",
+"' ` _ _ _ _ _ _ _ _ _ _ ) _ _ _ ) _ ) _ _ _ _ _ _ _ _ _ _ _ ` ` ",
+"' ) $ C S S S S S S S S ( / / / / / / / G N S S S C C C S N _ ' ",
+"' ) @ G G H G G G G F H ' ' ' ' ' ' ' } ~ S P P I U U H H S _ ' ",
+"' ) @ F H G G G F G F G ` ` ` ` ` ` ` ' U F M O O O o 4 P C _ ' ",
+"' ) @ F G G H H H H H H } ] ] ] ] [ ] | T H m         3 T D _ ' ",
+"' ) @ F G H v = = - = - 6 9 8 0 6 8 8 8 - ; &         % ; - _ ' ",
+"' ) @ F G Y w ; n v v v ! U ^ a 9 / W ! V v 5         > V p _ ' ",
+"' ) @ G G I q w R P H P [ '  .A y  .' | ~ H N X o X . 4 T F _ ' ",
+"' ) @ F G I q q I F F G ` ` | Z e { ` ' T S G P L G L H G S _ ' ",
+"' ) @ F F J q q J F F F ` _ ] B e ] _ ` U N F S F H S S S N _ ' ",
+"' ) @ M N D 2 2 D N m N / ^ ( s 9 ( ^ / L I U ~ N , ~ U ~ H _ ' ",
+"' ) @ K G I q q Y G G H ' '  .K y  .' ' / ` '  .P :  .` [ / _ ' ",
+"' ) @ F G I q q I F F G [ Q d t 6 d Q } ~ _ ` } P : } ` ' / _ ' ",
+"' ) @ F G I q q I G F G } K k x x j K | ^ _ ` } P : } ` ' / _ ` ",
+"' ) @ F G I q q Y F D G } K k x x k K | ^ ` ` } P : } ` ' / ) ' ",
+"' ) @ F G I q q I G F G } K k x x k K | ^ _ ` } P : } ` ' / ) ' ",
+"' ) @ F G I q q I G D G } L 7 g g 7 L } / _ ` } P : } ` ' / _ ' ",
+"' ) @ F G I q q I G F G ' ` ( ( ( ( ` ' ~ _ ` } P : } ` ' / ) ' ",
+"' ) @ F F I q q I G F G ' ' ' } ' ' ' ' / ` ' } P : | ' ' / _ ' ",
+"' ) + N N D 2 2 D N N n H G G G G G G H N F H G N 4 L F L S _ ' ",
+"' ) @ F G I q q I G G N F F F D G F F G N F G J H Y J H F S _ ' ",
+"' ) @ F G I q q J F F N G G F G G G F G N F N u i i w b P S _ ` ",
+"' ) @ F F I q u R I I D I I I I I I I Y D Y m f x z l i J S _ ' ",
+"' ) @ F L I q * w q q 2 q q q q q q q q 2 q < h x x z i I S _ ' ",
+"' ) @ F G P C q q q q 2 q q q q q q q q 2 q < h x x z i J S _ ` ",
+"} ) + F H F P I I I I D I I I I I I I I D I m 7 j h f u I S _ ' ",
+"' ) @ F G H G F G G G M F G G G G G G G C G F b c c c N H S _ ' ",
+"' ) @ C G F F F F F F M F F F F G F F G M C F L L L P G F C _ ' ",
+"' _ # + @ @ @ @ @ @ @ @ @ @ + $ + @ @ @ @ @ @ @ @ + @ @ + $ _ ' ",
+"' ` _ ) ) ) ) ) _ ) ) ) ) ) ) ) ) ) ) ) _ ) ) ) ) ) ) ) ) ) ` ` ",
+"` ' ' ' ' } ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ` ` "
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 132 2 ",
+"   c #010101",
+".  c #090909",
+"X  c #2A2A2A",
+"o  c #313131",
+"O  c #660404",
+"+  c #680000",
+"@  c #670808",
+"#  c #6B0A0A",
+"$  c #721212",
+"%  c #7F2121",
+"&  c #004343",
+"*  c #074C4C",
+"=  c #005252",
+"-  c #006464",
+";  c #006969",
+":  c #0A6B6B",
+">  c #1E6F6F",
+",  c #007373",
+"<  c #0B7070",
+"1  c #007A7A",
+"2  c #1E7171",
+"3  c #1E7878",
+"4  c #226D6D",
+"5  c #217272",
+"6  c #337878",
+"7  c #3F7D7D",
+"8  c gray30",
+"9  c #475E5E",
+"0  c #515151",
+"q  c #5C5E5E",
+"w  c #745C5C",
+"e  c #467373",
+"r  c #497676",
+"t  c #447D7D",
+"y  c #4E7C7C",
+"u  c #517777",
+"i  c #5F7777",
+"p  c #507E7E",
+"a  c #656464",
+"s  c #696969",
+"d  c #7B6D6D",
+"f  c #6F7878",
+"g  c #717272",
+"h  c #7A7777",
+"j  c #777A7A",
+"k  c #7B7F7F",
+"l  c #822F2F",
+"z  c #846C6C",
+"x  c #877E7E",
+"c  c #887E7E",
+"v  c #0B8B8B",
+"b  c #258585",
+"n  c #298484",
+"m  c #268B8B",
+"M  c #298888",
+"N  c #249F9F",
+"B  c #05AEAE",
+"V  c #1FA1A1",
+"C  c #468686",
+"Z  c #4A8B8B",
+"A  c #4D9191",
+"S  c #6E8686",
+"D  c #7E8181",
+"F  c #758D8D",
+"G  c #769292",
+"H  c #789393",
+"J  c #00C2C2",
+"K  c #00DBDB",
+"L  c #00E4E4",
+"P  c #00E9E9",
+"I  c #02FDFD",
+"U  c #08FFFF",
+"Y  c #878B8B",
+"T  c #8B8C8C",
+"R  c #938D8D",
+"E  c #869494",
+"W  c #8E9292",
+"Q  c #8E9A9A",
+"!  c #959494",
+"~  c #999696",
+"^  c #949B9B",
+"/  c #9C9C9C",
+"(  c #A49595",
+")  c #A29D9D",
+"_  c #AC9B9B",
+"`  c #B09B9B",
+"'  c #BD9F9F",
+"]  c #97A3A3",
+"[  c #9CA1A1",
+"{  c #9FAAAA",
+"}  c #A5A5A5",
+"|  c #AEA1A1",
+" . c #A1ADAD",
+".. c #ABACAC",
+"X. c #B2A2A2",
+"o. c #BEA5A5",
+"O. c #B1ADAD",
+"+. c #B9A9A9",
+"@. c #A4B0B0",
+"#. c #ADB1B1",
+"$. c #A7B9B9",
+"%. c #B3B3B3",
+"&. c #BBB2B2",
+"*. c #B3BABA",
+"=. c #BABABA",
+"-. c #C1B7B7",
+";. c #C6BDBD",
+":. c #CDBBBB",
+">. c #BEC1C1",
+",. c #C4C4C4",
+"<. c #C7CACA",
+"1. c #CBCBCB",
+"2. c #D3CECE",
+"3. c #DBCDCD",
+"4. c #D5D5D5",
+"5. c gainsboro",
+"6. c #E0CECE",
+"7. c #E1D6D6",
+"8. c #F9D6D6",
+"9. c #DBE7E7",
+"0. c #DBE8E8",
+"q. c #E5E5E5",
+"w. c #EAE7E7",
+"e. c #E6E9E9",
+"r. c #EBEBEB",
+"t. c #F5E7E7",
+"y. c #F1EEEE",
+"u. c #FAECEC",
+"i. c #ECF1F1",
+"p. c #F2F5F5",
+"a. c #FFF4F4",
+"s. c #FCFBFB",
+/* pixels */
+"q.q.q.q.q.q.q.q.q.e.q.q.q.w.e.q.w.q.q.q.q.w.q.q.q.q.q.q.w.q.q.q.q.w.q.q.q.e.w.q.q.q.e.q.e.w.q.q.",
+"q.q.q.w.q.q.q.q.q.q.q.q.q.q.q.q.q.q.q.q.q.q.q.q.q.q.q.q.q.q.q.q.q.e.q.q.q.q.q.q.q.q.q.q.q.q.q.q.",
+"e.w.q.e.p.r.r.r.r.y.r.r.r.r.r.r.r.r.r.r.y.r.r.r.r.r.p.r.r.r.r.r.r.r.r.y.r.r.r.r.r.r.r.r.e.q.w.q.",
+"q.q.e.4.o.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.=.=.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.<.q.q.q.q.",
+"q.q.p.` + ^ } / / ) [ / / / / / / / 4.1.4.2.4.<.,.4.4.2.4.4.~ ~ / / ~ / / / / ~ / ) / ~ 9.w.q.e.",
+"e.q.p.` @  .%.....O.O.#.%...%.%.....i.w.r.e.r.e.y.e.e.e.e.p.....%.%.%.%.%.%.%.%.%...O.} 7.q.q.q.",
+"q.q.i.` @  .....................} ..w.q.q.q.q.q.q.q.q.q.q.r...} ..} [ / [ ) / } ....../ q.q.q.w.",
+"q.9.p.` +  .O...................} ..e.q.q.q.q.q.q.q.q.q.q.e.} } =.g   . . . .   Y %...~ 9.w.q.q.",
+"q.q.i.` @ { ..........%.%.%.%.%...%.p.r.y.y.y.p.r.p.p.y.r.p.%...>.g             T =.O.} q.w.q.q.",
+"q.q.p.` +  .O.....%.} T W W W W Y ! ,.>.>.>.>.=.;.>.=.,.>.,.T Y / q             g ^ T Y 9.e.q.w.",
+"q.q.p._ @ { O.....+.t , - - ; ; ; ; - ; ; ; - 1 1 - - ; ; - ; - ; &             = : - 3 q.q.q.e.",
+"q.q.p.` @  .O.....+.4 e _ ~ ~ ~ ! ! 2.1.1.<.6.7 7 6.<.1.<.4.~ R } a   .     .   h [ ~ ! 7.q.q.q.",
+"q.q.p.` @  .O.....O.2 p -.#.%.%...%.p.r.e.r.a.Z Z a.e.r.r.p.O...;.g             T =.%.} q.e.q.q.",
+"q.q.p.` @  .O.....+.2 y +.......} } w.q.q.q.u.C C u.q.q.q.e.} } %.Y 8 8 8 8 8 8 ~ #.../ 7.q.w.q.",
+"q.q.i.` + { ......+.2 y &.......} } e.q.q.q.u.C C u.q.q.q.w...} ..%.=.=.=.=.=.=......./ 9.q.e.q.",
+"q.q.i._ @  .%.O...+.2 y &.#...%.....p.r.r.e.a.Z Z a.e.r.e.p.....%...............%...%./ q.q.w.q.",
+"q.q.p.` O ^ ) [ ^ _ > r | / ) [ ~ ) 4.<.1.1.3.n y 6.<.1.1.2./ ~ ~ / / ~ ) / } ~ / / / ~ q.e.q.q.",
+"q.q.p.` O ] ) / / _ > r | / / / ~ / 1.,.,.,.3.7 7 3.,.<.<.<.} <.1.<.<.5.a o 4.,.<.<.1...7.e.e.q.",
+"q.q.p._ @  .%.O.#.+.2 y &.#.O.%.....p.r.r.i.s.Z Z s.r.r.r.y.=.e.r.r.e.s.s X s.e.r.e.p.=.5.e.q.e.",
+"q.9.p._ @ { ......+.> y &.....O.} ..e.q.q.6.8.t t 8.7.q.q.w.%.q.q.q.q.p.s X s.q.w.q.r.=.5.e.w.q.",
+"q.q.p._ O { #.....+.2 y &.......} ..e.p.E v N v v V 5 2.r.q.=.q.e.q.q.s.s X p.7.q.q.r.=.5.e.q.q.",
+"q.q.p.` O @.O...@.+.> y &.......} ..e.u.H P I I I I B :.i.e.%.q.e.q.q.s.s X p.q.q.q.e.=.5.r.e.q.",
+"q.q.p._ + { ......+.> y &.......} ..w.p.H L U I I I B :.i.w.%.q.q.q.q.s.s X p.q.q.q.r.=.7.e.q.q.",
+"q.q.p.` @ { %.....+.4 y &.......} ..e.u.H L U I I I B :.r.e.%.q.q.q.q.s.s X a.q.q.q.r...5.e.q.w.",
+"q.q.p._ @  .......+.> y &.........} e.y.H P U I I I B :.r.e.=.q.e.q.q.s.a X p.q.q.q.r.=.5.e.q.e.",
+"e.7.p.` @  .O.....O.> y +.......} ..e.a.G P I I I I B :.i.q.%.q.q.q.q.s.s X p.q.q.q.r.=.7.e.q.q.",
+"q.q.i.` @  .O.....+.4 p &.......} ..e.y.[ C A Z Z A y 4.e.w.%.q.q.q.q.s.s X p.q.q.q.r.=.9.w.q.q.",
+"q.q.p._ +  .O.....o.> p &.......} ..e.q.r.t.t.t.t.t.u.e.q.e.%.q.q.q.q.s.a X p.q.q.q.e.=.5.w.w.q.",
+"e.q.p._ @  .O.....+.2 p &.....#.} ..r.e.q.e.e.e.e.e.q.e.e.r.=.w.r.r.q.s.s X s.e.e.r.y.=.5.e.w.q.",
+"q.q.p.` O ] } } } | 2 t X.} } } ) } 5.4.5.4.4.5.4.4.5.5.4.5...4.4.4.4.r.s X e.4.4.4.5.%.5.q.q.q.",
+"q.q.p.` + Q ~ ~ ^ ( > r ) ~ ~ / ~ Y / / / / / ~ / ~ ~ / / / T ~ / / ~ / ! Y [ ~ / ~ / ~ q.q.q.e.",
+"q.q.i._ @ @.%.%.#.+.> p -.#...%...~ %.%.O.#.#.%.#.#.%...#.%.~ ..#.#.#.#.%.%.#.#...%.O.[ q.e.q.q.",
+"q.9.p._ @ { ......+.4 y &.........! ........................~ ......+.` ` X.X.+......./ q.q.q.q.",
+"q.q.p.` @  .O.....+.2 y &.........! ........................! ..#.D 2 M n b n 4 R %.../ q.q.q.q.",
+"q.q.p._ + { O.....+.> y %.........^ #.#.......#.#.#.......#.! ..=.f K I I I I J x %...) q.w.e.q.",
+"q.q.p.` @  .O.....O.> u o.| | | | R | | | | X.| | | | | | | R _ +.g K U I I I J c %...[ 5.e.q.q.",
+"q.q.p._ @  .O.....+.6 , : : : : ; : : : : : : : : : : : : : : : : * L I I I I J Y %.../ q.q.q.q.",
+"e.7.p.` @  .%.....%.^ f F S S S S f S S S S S S S S S S S S i S F 9 K I I I I B c %...} q.q.q.q.",
+"e.q.i.` @  .O.........=.+.%.&.%.&.~ &.%.=.&.%.&.=.&.&.%.%.&.) O.;.j K I I I I J x %.../ q.e.q.q.",
+"q.q.i.` + { ....................} ! ......} ....} ..........! } %.D 3 m b b b > R %...[ 7.e.q.q.",
+"q.q.p.` O [ ......} ..} ..} ....| ! ..........} ............! ......X._ X.X._ X...} ../ q.w.q.q.",
+"q.q.p.` # $.=.*.*.*.*.*.*.*.*.%.*.[ *.*.*.%.*.*.%.*.#.*.%.*./ *.*.*.*.*.*.*.*.*.*.*.*.} 9.q.q.w.",
+"q.q.p.` + d z z z z z z z z z z z w z z z z z z z z z z z z w z z z z z z z z z z z z z q.q.q.q.",
+"q.q.e.;.l $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ # l q.w.q.q.",
+"q.q.q.e.9.9.9.0.0.0.0.0.0.9.9.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.q.q.q.w.",
+"q.q.q.q.w.r.w.w.w.w.w.e.r.w.w.w.w.e.w.w.w.w.w.e.e.e.w.w.w.r.w.w.q.e.w.w.w.w.w.w.w.w.w.e.q.q.q.q.",
+"q.q.q.w.q.q.q.q.w.q.q.q.q.q.q.w.q.q.q.w.w.q.w.q.q.q.q.q.q.q.w.e.w.q.w.q.q.e.q.q.w.w.q.q.q.q.q.e.",
+"q.e.q.q.e.q.q.w.q.q.w.q.e.q.q.q.e.q.q.q.q.q.q.q.e.q.q.w.q.q.q.q.q.q.q.q.q.q.w.q.q.q.q.e.q.w.q.q."
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/net-web.png b/icons/net-web.png
new file mode 100644 (file)
index 0000000..116d689
Binary files /dev/null and b/icons/net-web.png differ
diff --git a/icons/net.ico b/icons/net.ico
new file mode 100644 (file)
index 0000000..57d8423
Binary files /dev/null and b/icons/net.ico differ
diff --git a/icons/net.rc b/icons/net.rc
new file mode 100644 (file)
index 0000000..7a2933a
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "net.ico"
diff --git a/icons/net.sav b/icons/net.sav
new file mode 100644 (file)
index 0000000..06a5426
--- /dev/null
@@ -0,0 +1,53 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :3:Net
+PARAMS  :3:5x5
+CPARAMS :3:5x5
+DESC    :25:1115337157375775157135131
+UI      :9:O0,0;C2,2
+NSTATES :2:45
+STATEPOS:2:45
+MOVE    :4:C0,0
+MOVE    :4:L0,0
+MOVE    :4:L0,1
+MOVE    :4:C0,2
+MOVE    :4:L0,2
+MOVE    :4:A0,3
+MOVE    :4:L0,3
+MOVE    :4:L0,4
+MOVE    :4:L1,4
+MOVE    :4:A2,4
+MOVE    :4:A2,4
+MOVE    :4:L2,4
+MOVE    :4:C1,0
+MOVE    :4:L1,0
+MOVE    :4:L3,0
+MOVE    :4:L2,0
+MOVE    :4:A4,0
+MOVE    :4:A4,0
+MOVE    :4:L4,0
+MOVE    :4:A4,1
+MOVE    :4:L4,1
+MOVE    :4:L3,1
+MOVE    :4:A3,2
+MOVE    :4:A3,2
+MOVE    :4:L3,2
+MOVE    :4:L2,2
+MOVE    :4:A2,1
+MOVE    :4:A2,1
+MOVE    :4:A1,1
+MOVE    :4:A1,1
+MOVE    :4:A1,1
+MOVE    :4:A1,1
+MOVE    :4:A1,1
+MOVE    :4:A1,1
+MOVE    :4:A1,2
+MOVE    :4:A1,2
+MOVE    :4:A1,3
+MOVE    :4:C2,3
+MOVE    :4:A3,3
+MOVE    :4:A4,4
+MOVE    :4:A4,4
+MOVE    :4:A4,3
+MOVE    :4:A4,2
+MOVE    :4:A4,2
diff --git a/icons/netslide-16d24.png b/icons/netslide-16d24.png
new file mode 100644 (file)
index 0000000..e55f80c
Binary files /dev/null and b/icons/netslide-16d24.png differ
diff --git a/icons/netslide-16d4.png b/icons/netslide-16d4.png
new file mode 100644 (file)
index 0000000..ee70e8b
Binary files /dev/null and b/icons/netslide-16d4.png differ
diff --git a/icons/netslide-16d8.png b/icons/netslide-16d8.png
new file mode 100644 (file)
index 0000000..4e1a22e
Binary files /dev/null and b/icons/netslide-16d8.png differ
diff --git a/icons/netslide-32d24.png b/icons/netslide-32d24.png
new file mode 100644 (file)
index 0000000..eb140d2
Binary files /dev/null and b/icons/netslide-32d24.png differ
diff --git a/icons/netslide-32d4.png b/icons/netslide-32d4.png
new file mode 100644 (file)
index 0000000..f91af92
Binary files /dev/null and b/icons/netslide-32d4.png differ
diff --git a/icons/netslide-32d8.png b/icons/netslide-32d8.png
new file mode 100644 (file)
index 0000000..5c0a9fa
Binary files /dev/null and b/icons/netslide-32d8.png differ
diff --git a/icons/netslide-48d24.png b/icons/netslide-48d24.png
new file mode 100644 (file)
index 0000000..55bdebf
Binary files /dev/null and b/icons/netslide-48d24.png differ
diff --git a/icons/netslide-48d4.png b/icons/netslide-48d4.png
new file mode 100644 (file)
index 0000000..31c06ca
Binary files /dev/null and b/icons/netslide-48d4.png differ
diff --git a/icons/netslide-48d8.png b/icons/netslide-48d8.png
new file mode 100644 (file)
index 0000000..5dd7771
Binary files /dev/null and b/icons/netslide-48d8.png differ
diff --git a/icons/netslide-base.png b/icons/netslide-base.png
new file mode 100644 (file)
index 0000000..3cc197e
Binary files /dev/null and b/icons/netslide-base.png differ
diff --git a/icons/netslide-ibase.png b/icons/netslide-ibase.png
new file mode 100644 (file)
index 0000000..01ae0e9
Binary files /dev/null and b/icons/netslide-ibase.png differ
diff --git a/icons/netslide-ibase4.png b/icons/netslide-ibase4.png
new file mode 100644 (file)
index 0000000..5b02e2f
Binary files /dev/null and b/icons/netslide-ibase4.png differ
diff --git a/icons/netslide-icon.c b/icons/netslide-icon.c
new file mode 100644 (file)
index 0000000..f537aad
--- /dev/null
@@ -0,0 +1,520 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 237 2 ",
+"   c #E6E6E6",
+".  c #E6E6E6",
+"X  c #E6E6E6",
+"o  c #E6E6E6",
+"O  c #EAEAEA",
+"+  c gray88",
+"@  c #EAEAEA",
+"#  c #E6E6E6",
+"$  c #E6E6E6",
+"%  c #EAEAEA",
+"&  c gray88",
+"*  c #EAEAEA",
+"=  c #E6E6E6",
+"-  c #E6E6E6",
+";  c #E6E6E6",
+":  c #E6E6E6",
+">  c gray91",
+",  c #C8C8C8",
+"<  c gray66",
+"1  c gray82",
+"2  c gray91",
+"3  c gray91",
+"4  c #CECECE",
+"5  c gray66",
+"6  c #CBCBCB",
+"7  c #E9E9E9",
+"8  c gray90",
+"9  c #E6E6E6",
+"0  c #E6E6E6",
+"q  c gray90",
+"w  c #E7E6E6",
+"e  c #B0AFAF",
+"r  c #A2A1A1",
+"t  c #B9B8B8",
+"y  c #E9E8E8",
+"u  c #E8E7E7",
+"i  c #B6B5B5",
+"p  c #A2A1A1",
+"a  c #B2B2B2",
+"s  c #E8E7E7",
+"d  c gray90",
+"f  c #E6E5E5",
+"g  c #E7E9E9",
+"h  c #F1F6F6",
+"j  c #DBDEDE",
+"k  c #B3B7B7",
+"l  c #E4E8E8",
+"z  c #EFF3F3",
+"x  c #EFF3F3",
+"c  c #E1E5E5",
+"v  c #B2B6B6",
+"b  c #DEE2E2",
+"n  c #F0F4F4",
+"m  c #E6E6E6",
+"M  c #E6E6E6",
+"N  c gray90",
+"B  c #E7E9E9",
+"V  c #DAD3D3",
+"C  c #BFA8A8",
+"Z  c #C3AEAE",
+"A  c #C1ABAB",
+"S  c #C3ADAD",
+"D  c #C1ACAC",
+"F  c #C1ACAC",
+"G  c #C3ADAD",
+"H  c #C2ADAD",
+"J  c #C2ACAC",
+"K  c #C4B0B0",
+"L  c #E2E0E0",
+"P  c gray91",
+"I  c #E7E6E6",
+"U  c #F1F5F5",
+"Y  c #C0AAAA",
+"T  c #C3ADAD",
+"R  c #D1C8C8",
+"E  c #908888",
+"W  c #D8CDCD",
+"Q  c #CBC0C0",
+"!  c #CCC1C1",
+"~  c #D5CACA",
+"^  c #8F8888",
+"/  c #CFC4C4",
+"(  c #D4CACA",
+")  c #E4E3E3",
+"_  c #EAEAEA",
+"`  c #C8C8C8",
+"'  c #B0AFAF",
+"]  c #DADEDE",
+"[  c #C3AEAE",
+"{  c #D8D0D0",
+"}  c #E8EEEE",
+"|  c #A0A3A3",
+" . c #F0F3F3",
+".. c #E1E3E3",
+"X. c #D6D9D9",
+"o. c #DEE1E1",
+"O. c #BDC0C0",
+"+. c #DDE0E0",
+"@. c #DFE2E2",
+"#. c gray90",
+"$. c gray88",
+"%. c gray66",
+"&. c #A2A1A1",
+"*. c #B2B6B6",
+"=. c #C5AFAF",
+"-. c #D3C7C7",
+";. c #E3E5E5",
+":. c #9D9C9C",
+">. c #EAEAEA",
+",. c #DDDCDC",
+"<. c #E7E7E7",
+"1. c #F2F1F1",
+"2. c #F1F0F0",
+"3. c gray90",
+"4. c #E6E6E6",
+"5. c #E6E6E6",
+"6. c #EAEAEA",
+"7. c gray82",
+"8. c #B9B8B8",
+"9. c #E4E8E8",
+"0. c #C2ADAD",
+"q. c #D6CBCB",
+"w. c #E6E8E8",
+"e. c gray62",
+"r. c #EEEEEE",
+"t. c gainsboro",
+"y. c #B7B7B7",
+"u. c #B9B9B9",
+"i. c #CACACA",
+"p. c #E9E9E9",
+"a. c gray90",
+"s. c #E6E6E6",
+"d. c #E6E6E6",
+"f. c #E9E8E8",
+"g. c #EFF3F3",
+"h. c #C1ACAC",
+"j. c #CDC2C2",
+"k. c #DCDEE1",
+"l. c #9C9B9F",
+"z. c #E3E3E5",
+"x. c LightGray",
+"c. c gray71",
+"v. c #B7B7B7",
+"b. c #8D8D8D",
+"n. c #E6E6E6",
+"m. c #E6E6E6",
+"M. c #E6E6E6",
+"N. c gray91",
+"B. c #E8E7E7",
+"V. c #EFF3F3",
+"C. c #C1ACAC",
+"Z. c #CDC2C2",
+"A. c #E9EBE0",
+"S. c #E3E2D2",
+"D. c #E4E4DD",
+"F. c #D8D8D9",
+"G. c gray91",
+"H. c #F4F4F4",
+"J. c gray63",
+"K. c gray89",
+"L. c #E7E7E7",
+"P. c #CECECE",
+"I. c #B6B5B5",
+"U. c #E1E5E5",
+"Y. c #C4AEAF",
+"T. c #D0C5C1",
+"R. c #5255DC",
+"E. c #2424E4",
+"W. c #7A7AD4",
+"Q. c #EBEBDF",
+"!. c #CFCFD2",
+"~. c #DADADA",
+"^. c #979797",
+"/. c #D7D7D7",
+"(. c gray86",
+"). c gray90",
+"_. c gray66",
+"`. c #A2A1A1",
+"'. c #B1B5B6",
+"]. c #C7B1B2",
+"[. c #CDC2BC",
+"{. c #2325EC",
+"}. c blue",
+"|. c #3A3ABF",
+" X c #A8A895",
+".X c #DADADE",
+"XX c #E9E9E9",
+"oX c #9D9D9D",
+"OX c #E2E2E2",
+"+X c #E6E6E6",
+"@X c #E6E6E6",
+"#X c #EAEAEA",
+"$X c #CBCBCB",
+"%X c #B2B2B2",
+"&X c #DDE1E1",
+"*X c #C4AEAF",
+"=X c #CFC4C0",
+"-X c #5759D9",
+";X c #2A29E0",
+":X c #7B7BD0",
+">X c #E6E6DA",
+",X c #DCDCDF",
+"<X c #E9E9E9",
+"1X c #9D9D9D",
+"2X c gray89",
+"3X c #E7E7E7",
+"4X c #E6E6E6",
+"5X c #E9E9E9",
+"6X c #E8E7E7",
+"7X c #F0F4F4",
+"8X c #C4AFAF",
+"9X c #D4CACA",
+"0X c #F2F5E8",
+"qX c #EFEEDD",
+"wX c #EEEEE5",
+"eX c #DFDFE0",
+"rX c #DFDFDF",
+"tX c #E9E9E9",
+"yX c gray63",
+"uX c gray89",
+"iX c #E7E7E7",
+"pX c #E6E6E6",
+"aX c gray90",
+"sX c #E6E6E6",
+"dX c #E3E0E0",
+"fX c #E4E3E3",
+"gX c #E5E5E8",
+"hX c #E4E4E8",
+"jX c #E5E5E7",
+"kX c gray90",
+"lX c gray90",
+"zX c #E6E6E6",
+"xX c #DFDFDF",
+"cX c #E6E6E6",
+"vX c #E6E6E6",
+"bX c #E6E6E6",
+"nX c white",
+/* pixels */
+"      . X o O + @ # $ % & * = - ",
+"      ; : > , < 1 2 3 4 5 6 7 8 ",
+"    9 0 q w e r t y u i p a s d ",
+". ; 0 f g h j k l z x c v b n m ",
+"X M N B V C Z A S D F G H J K L ",
+"o P I U Y T R E W Q ! ~ ^ / ( ) ",
+"_ ` ' ] [ { } |  ...X.o.O.+.@.#.",
+"$.%.&.*.=.-.;.:.>.,.<.1.2.3.4.5.",
+"6.7.8.9.0.q.w.e.r.t.y.u.i.p.a.s.",
+"d.3 f.g.h.j.k.l.z.x.c.v.b.n.m.M.",
+"m.N.B.V.C.Z.A.S.D.F.G.H.J.K.L.- ",
+"% P.I.U.Y.T.R.E.W.Q.!.~.^./.(.).",
+"+ _.`.'.].[.{.}.|. X.XXXoXOX+X@X",
+"#X$X%X&X*X=X-X;X:X>X,X<X1X2X3XX ",
+"4X5X6X7X8X9X0XqXwXeXrXtXyXuXiXpX",
+"- 8 aXsXdXfXgXhXjXkXlXzXxXcXvXbX"
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 73 1 ",
+"  c #565656",
+". c #5F5F7A",
+"X c #676161",
+"o c #756F6F",
+"O c #737373",
+"+ c #757878",
+"@ c gray49",
+"# c #8F5D5D",
+"$ c #915D5D",
+"% c #966464",
+"& c #986767",
+"* c #6F6F8B",
+"= c #5555B4",
+"- c #5656BF",
+"; c #5C5CBD",
+": c #6969B5",
+"> c #6C6CBE",
+", c #7474B1",
+"< c #7070BF",
+"1 c #2828C6",
+"2 c #2A2AC8",
+"3 c #0000EA",
+"4 c #0000F1",
+"5 c #0303FE",
+"6 c #5D5DC0",
+"7 c #838383",
+"8 c #8A8282",
+"9 c #8A8A83",
+"0 c #8B8B8B",
+"q c #949494",
+"w c #9B9B9B",
+"e c #9C9393",
+"r c #B99B9B",
+"t c #A3A39D",
+"y c #8383B5",
+"u c #A4A5A5",
+"i c #ADADAD",
+"p c #BAA3A3",
+"a c #B4B4B4",
+"s c #B9B7B7",
+"d c #BBBBBB",
+"f c #C1B5B5",
+"g c #CBBFBF",
+"h c #AFAFCB",
+"j c #BCBCC7",
+"k c #B1B1CC",
+"l c #C3C3C3",
+"z c #CEC2C3",
+"x c #C1C1CA",
+"c c #CBCACA",
+"v c #D2C6C6",
+"b c #D4CBCB",
+"n c #DACECE",
+"m c #D3D3D3",
+"M c #DCD5D5",
+"N c #D5D5D9",
+"B c #DBDBDB",
+"V c #DFDFE0",
+"C c #DEE1E1",
+"Z c #E5E5E5",
+"A c #E8E7E7",
+"S c #E7EAEA",
+"D c #EBEBEB",
+"F c #EFF2E1",
+"G c #EDF0EA",
+"H c #F7F7E7",
+"J c #F9F9E7",
+"K c #F5F5E9",
+"L c #F9F9E9",
+"P c #EDF3F3",
+"I c #F4F5F5",
+"U c #F5FBFB",
+"Y c #FBFBFB",
+/* pixels */
+"ZZZZZZZAZAAZAZAAZZZZAZZZAAZAZZZZ",
+"ZZAZAZZZAZZAZDBZAZZAZZZSZVSZZAZA",
+"AZAAZSZAZAAZDN0uSSZZZZSAwqVDZZZZ",
+"ZAAZZZZZZZZAmwsiuZZSZAZwiawBSZZA",
+"SZZZAAZZZZZSw0da7dDZZDa7sd0uDZZZ",
+"ZAZAZZSZZAAZDidilGZZZZGsisiPZAZA",
+"ZZZZAZZZZZZZDwuqdDZZZZDawtuGZZZA",
+"AZSZZZZAZZZZAcaaBZZZZZAmsacZZZAZ",
+"ZZZAZZAZZPPPPIUUIPPPPPPIUUIPPPAZ",
+"ZSZZZZZZGr$&%%$%%%&%%%%&$$%%%&BS",
+"AZZZZZZZP%pbzMXengvsfvgn8oMzzzZZ",
+"ZZASAZZZP&bIDY+aYSPVCIPYu0YDPPAZ",
+"ZZGNwDDSP%zDCIOiIBSmcmmBq7VmmmVA",
+"ZDNw0uwcU%zPZIOiIZSNmBBBBBBBBBZA",
+"SC0dddusU%zDVIOiIZDmBSAASAASASZA",
+"ZZuusuwsU%zSZIOiGZDmmZZZZZAAAZAZ",
+"ZSDu7dfmP%vDZYOiIVDMDYIIIDZZZZZZ",
+"AZSZdIPZP%zDBIOiIVAm0qqqqMAZZZZZ",
+"ZZZZDZZVP%vIZYOaIZDm700q 0IZZAAZ",
+"ZAZZZZZZP%sBmZOuVmBcSYIYu7YZAAZA",
+"ZZZZZAAZP%fCNNNNNBBcNZBKq7UZZAZZ",
+"ZAZSDZZZP%vPKKKHLDDNBDZYw7YAAAZA",
+"AZSZaDGZP%vD,6;>=jDmmVBDq7SNBVZZ",
+"ZAAw7aamP%zH15553hYZlmmVq@CmmmZA",
+"ZZwisiwsU%zF25553*tqBGZYw7YZADZZ",
+"AVqadsusU%zF25554.97BDVIw7YVZZAZ",
+"ZSVw0iucP%vF15553kYZBAZIw7YZAZAZ",
+"ZZDBtPGAP&zGy>>>:xGmBACYw7UVZAAZ",
+"ZZZADZZVP%zDKKKKLAANNSZIw7UCAZAZ",
+"ZAAZAAZZP%zPZZZZZZSNBSCIw7YZAZZZ",
+"AZZAZAZZABZZZZZAZSZZZZAABBSZZZAZ",
+"ZZAZAZAZAAZZZZZZAZZZAZAAAAZAZAZZ"
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 87 1 ",
+"  c #3C1616",
+". c #2D2D2D",
+"X c #333333",
+"o c #353B3B",
+"O c #4F2626",
+"+ c #752121",
+"@ c #633838",
+"# c #494949",
+"$ c #5B5B47",
+"% c #545454",
+"& c #5A5A5A",
+"* c #7F5050",
+"= c #5D6363",
+"- c #666666",
+"; c #6A6A6A",
+": c #747474",
+"> c #7D7D7D",
+", c #855252",
+"< c #976363",
+"1 c #996666",
+"2 c #9A6A6A",
+"3 c #817F7F",
+"4 c #9E7373",
+"5 c #A16D6D",
+"6 c #A07575",
+"7 c #A37A7A",
+"8 c #AA7E7E",
+"9 c #848470",
+"0 c #111195",
+"q c #19199D",
+"w c #1212A9",
+"e c #1818B8",
+"r c #2B2BA6",
+"t c #2121B5",
+"y c #2B2BB0",
+"u c #2424B8",
+"i c #3333B5",
+"p c #474795",
+"a c #7A7AA5",
+"s c #7B7BA9",
+"d c #0000DF",
+"f c #0202EC",
+"g c #0000F2",
+"h c #0202FE",
+"j c #0808FF",
+"k c #828283",
+"l c #868E8E",
+"z c #8B8B8B",
+"x c #949494",
+"c c #9C9C9C",
+"v c #AE8282",
+"b c #8282A5",
+"n c #8A8AA6",
+"m c #8585A9",
+"M c #8A8AAB",
+"N c #9C9CAE",
+"B c #A3A3A3",
+"V c #ABABAB",
+"C c #B4B4B4",
+"Z c #BBBBBB",
+"A c #BBBBC2",
+"S c #C3C3C3",
+"D c #C0CACA",
+"F c #CBCBCB",
+"G c #CCD7D7",
+"H c #D6D5D5",
+"J c #DCDBDB",
+"K c #E1DEDE",
+"L c #E5E5D3",
+"P c #E8E8D6",
+"I c #E6E6DC",
+"U c #E6E5E5",
+"Y c #E9E7E7",
+"T c #EAE9E4",
+"R c #E7E7EA",
+"E c #E7E8E8",
+"W c #EAEAEA",
+"Q c #F5F4EC",
+"! c #FFFFED",
+"~ c #EDF7F7",
+"^ c #ECF7FA",
+"/ c #EFF9F9",
+"( c #F2F1F1",
+") c #F8F6F6",
+"_ c #F8F8F0",
+"` c #F1FBFB",
+"' c #FBFBFB",
+/* pixels */
+"UUUUUUUUUUUUUEUUUUUUUUEUUUUYUUUUURUUUYUUUUUUEUUU",
+"UUYUUEUEUEEUUUEUUEUEUUUUEUUUUEEUUUUEUUUUEUUEUUYU",
+"UUUUYUUUUUUEUUUUEUUURUERUUUEUUUUUUYUEUUUYUUUEUUE",
+"UEUEUUEUUYUUUEYUUUUYEEJWUUUUUEUUUUUEUUUUUUEUUUUU",
+"UUUUUUUUUUEUUUUUUUEUQB:FWUUEEYUUUUEUkzEYUUUUURUU",
+"UUEUUEEUUUUUEUEUUEUWBzZ3SQUUUUUUUEJkVBzYEUUUUUUE",
+"EUUYUUUUUEUUUUYUUEWVxSZZzFWEUUEUEUzVZSBxERUUUUEU",
+"UUUEUUEUUUYEUUUUUWC-cZCCk;JEUUUUW>:VZZV-xQUUUUUU",
+"UUYUUEUUUEUUUUUEUYKKzCZBBRKRUUUEUUZzSZzFUUUUYUUU",
+"EUUUUUUYUUEUUUUURURWxCSBC(UUUUUUU(FxSZxJEUEUUUEU",
+"UUEUUEEUUUUUUYUUUUUYzVCxV(UUEUUEUWFzCCkHEYUEUUUU",
+"UEUUUUUUYEUUUUUUUUYWBxczSWUUUUUUUWHzxxcUEUUUUEUU",
+"UEUUUUEUUUUUYUUUUUIKWWWWYKIUUUUKUUUWWWWUUIKUUUUY",
+"UUEUUEUUEUUEUUE`````/^~/``/``/````~^(^/````/UUUU",
+"UUEUUUUUUUEUUEH7466647v6666666664644v8466666KYUE",
+"YUUUUEUYUUUUU~8+4<<<1, 1<<<<<,<<<<<5O@5<<<<1KEUU",
+"UUUUUEUUUUUYU`45'^`/'Do'`````G~`/`~'-l'/`^``EUUU",
+"UUEUUUU(YUUUU`4<^KUK(CXWKUKUYSJUUUJ)&>)KUIKUUUUU",
+"EUUUUUWCJEUUU^6</UUU(CX(UUUUWSUYEEU'&k'UEEYYUUUY",
+"UUUUU(B-UWEEU`6<`KUU(ZXWUUUUWZZSSSSF>xFSSSSSUEUU",
+"EYUU(BxckxzBW)6<`KUUWZX(UUUUWSUUUUUJWEIUUUUUUUEU",
+"UUUEVzSZCCVxW/6<`UUU(CXWUUUUESUUYEUURUUUUREEUUUU",
+"UYEJ:ZZCZSZcW/6<`KYU(CX(UUUUESUUEUUUUUUUUURUUUUU",
+"UEEWF>ZCBBxzW/6<`UUU(ZXWUUYUESKUUUUUURUUUUUUUUUU",
+"UEUUQSzkcCBSY`6<^UUU(ZX(UUUUUSJUUUKUUUUUUUUUUUUU",
+"UUEUUWF;U((WU`6<`KUUWCX~UUUUEF)'''`''EUUUEUUUUEU",
+"UUUUUUWJUUUUK`6<`URU(ZXWUUUUWc#%%%%%&FWUEUUEEUUE",
+"UEYUURUWUUUUU`6<`KUUWZXWUUUUWV>zkzkx.k'UUUUYUUUU",
+"EUUEUURUUUUUU`41`YYY(ZX(EEEEWF)'))('-3)UUUUYUEUY",
+"UUUUUUUUUUUYK`6,GSSSFV-FSSSSSCKUUUK)%3'KEUUUUUUU",
+"UUUUUUUUYUUUU`6<~KUKUUWKUUUKUSUUUUU'&3'UUEEUUEEU",
+"UEEUUUUUUUUUU`6<`URRWRRWRREUEDKUUUU`&3)UUUUUUUUU",
+"UEUUYUEWUUUUU/4</UULLLLLLPUUESUEEEU'&3'EEWEWEUUE",
+"UUEUUWJ3U((EI^6<~QnwttttepTUESHJJJHW&>WHHJJHUUUU",
+"UEUUEJz:ZFSJU`6<^QsghhhhhyLKUZSFFFFJ&>JSFFFFUEEU",
+"UUUEUkVVzxkzW`6<^Qsfjhhhhi_''HUWEWE'&k'UWWWWUEUU",
+"UYEU3VZZZSZxW/6<^Qaghhhhh0$&%3WUUUU)&3'UUUUUUUUU",
+"UUEUzBSCZZCcW/6<~Qsghhhhhq3k>xEUUYJ'&3`KUUUUUUUU",
+"EUUEWzBVkx3xW/6<^Qsgjjhjhi!`'FKYUUU'&3'UUUEUEUUE",
+"UUUUEEx:FJHUY`6<^QadffffgrPUUSUUUUU'&3'KUEUUUUUU",
+"UUUEUEUxUWEYU`4</EAmMMmMmNUUESUUUUU'&3)IUUUUEUUE",
+"UYUUUUE(YUUYU`6<`KW_QQQQ_QUUESKRUUU'&k)UUEUEUUUU",
+"EUUUUUUUUURUU`6</UUUUUUUUUUUWSIUUUK`&3)UUEUUEUUE",
+"UUUEUUUUUUUUK`71/UUYEUUUUURUWSUEUUU)=k)UUUUUUUUU",
+"EUUEUEUYUUUUUYJKEUEUUUUUUUUUUUUUURUEHJEYUUUUUEUU",
+"UUUUUUUUEURUUUYYUUUUUUEUUURUUEUUUUUUEEEEEUYUUUUY",
+"UUUYUEEUUUUUUUUUUUUUEUUEUUUUEUUEYUUUUUUUUUUEUUUU",
+"UEUUUUUUUYYUUUUUEUUEUUUUUURUUUUUUUEUUEUUEUUUEUUE"
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/netslide-web.png b/icons/netslide-web.png
new file mode 100644 (file)
index 0000000..5334b83
Binary files /dev/null and b/icons/netslide-web.png differ
diff --git a/icons/netslide.ico b/icons/netslide.ico
new file mode 100644 (file)
index 0000000..6944e56
Binary files /dev/null and b/icons/netslide.ico differ
diff --git a/icons/netslide.rc b/icons/netslide.rc
new file mode 100644 (file)
index 0000000..127702f
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "netslide.ico"
diff --git a/icons/netslide.sav b/icons/netslide.sav
new file mode 100644 (file)
index 0000000..f37178e
--- /dev/null
@@ -0,0 +1,47 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :8:Netslide
+PARAMS  :3:4x4
+CPARAMS :3:4x4
+SEED    :15:344208514520242
+DESC    :16:49b59aca247714b4
+AUXINFO :34:60d28a22f68cdb6078d67a4d6069b9ff54
+NSTATES :2:38
+STATEPOS:2:31
+MOVE    :4:R0,1
+MOVE    :4:C1,1
+MOVE    :4:R1,1
+MOVE    :4:R1,1
+MOVE    :4:C3,1
+MOVE    :4:C1,1
+MOVE    :4:R1,1
+MOVE    :4:R1,1
+MOVE    :4:R3,1
+MOVE    :4:R0,1
+MOVE    :4:C1,1
+MOVE    :5:R3,-1
+MOVE    :5:R0,-1
+MOVE    :4:R0,1
+MOVE    :4:R0,1
+MOVE    :4:C0,1
+MOVE    :5:R0,-1
+MOVE    :5:R0,-1
+MOVE    :5:C1,-1
+MOVE    :4:R1,1
+MOVE    :4:C1,1
+MOVE    :5:R1,-1
+MOVE    :5:C0,-1
+MOVE    :5:C0,-1
+MOVE    :4:R3,1
+MOVE    :4:C0,1
+MOVE    :4:C0,1
+MOVE    :5:R3,-1
+MOVE    :4:C0,1
+MOVE    :5:R3,-1
+MOVE    :5:C1,-1
+MOVE    :4:R0,1
+MOVE    :4:C0,1
+MOVE    :5:R0,-1
+MOVE    :5:C0,-1
+MOVE    :4:C1,1
+MOVE    :4:R3,1
diff --git a/icons/palisade-16d24.png b/icons/palisade-16d24.png
new file mode 100644 (file)
index 0000000..33eb5f8
Binary files /dev/null and b/icons/palisade-16d24.png differ
diff --git a/icons/palisade-16d4.png b/icons/palisade-16d4.png
new file mode 100644 (file)
index 0000000..de73dfe
Binary files /dev/null and b/icons/palisade-16d4.png differ
diff --git a/icons/palisade-16d8.png b/icons/palisade-16d8.png
new file mode 100644 (file)
index 0000000..5f3d1cd
Binary files /dev/null and b/icons/palisade-16d8.png differ
diff --git a/icons/palisade-32d24.png b/icons/palisade-32d24.png
new file mode 100644 (file)
index 0000000..224a616
Binary files /dev/null and b/icons/palisade-32d24.png differ
diff --git a/icons/palisade-32d4.png b/icons/palisade-32d4.png
new file mode 100644 (file)
index 0000000..8e12ba3
Binary files /dev/null and b/icons/palisade-32d4.png differ
diff --git a/icons/palisade-32d8.png b/icons/palisade-32d8.png
new file mode 100644 (file)
index 0000000..48ef984
Binary files /dev/null and b/icons/palisade-32d8.png differ
diff --git a/icons/palisade-48d24.png b/icons/palisade-48d24.png
new file mode 100644 (file)
index 0000000..cdea9da
Binary files /dev/null and b/icons/palisade-48d24.png differ
diff --git a/icons/palisade-48d4.png b/icons/palisade-48d4.png
new file mode 100644 (file)
index 0000000..14f17d1
Binary files /dev/null and b/icons/palisade-48d4.png differ
diff --git a/icons/palisade-48d8.png b/icons/palisade-48d8.png
new file mode 100644 (file)
index 0000000..18a2ec8
Binary files /dev/null and b/icons/palisade-48d8.png differ
diff --git a/icons/palisade-base.png b/icons/palisade-base.png
new file mode 100644 (file)
index 0000000..eb7c1d3
Binary files /dev/null and b/icons/palisade-base.png differ
diff --git a/icons/palisade-ibase.png b/icons/palisade-ibase.png
new file mode 100644 (file)
index 0000000..e01cde2
Binary files /dev/null and b/icons/palisade-ibase.png differ
diff --git a/icons/palisade-ibase4.png b/icons/palisade-ibase4.png
new file mode 100644 (file)
index 0000000..38b9214
Binary files /dev/null and b/icons/palisade-ibase4.png differ
diff --git a/icons/palisade-icon.c b/icons/palisade-icon.c
new file mode 100644 (file)
index 0000000..4c0562d
--- /dev/null
@@ -0,0 +1,677 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 253 2 ",
+"   c gray83",
+".  c #C0C0C0",
+"X  c #BBBBBB",
+"o  c #BBBBBB",
+"O  c #BCBCBC",
+"+  c #BCBCBC",
+"@  c #BCBCBC",
+"#  c #BCBCBC",
+"$  c #BCBCBC",
+"%  c #BCBCBC",
+"&  c #BCBCBC",
+"*  c gray74",
+"=  c gray74",
+"-  c gray",
+";  c LightGray",
+":  c #C0C0C0",
+">  c gray58",
+",  c #B1B1B1",
+"<  c gray69",
+"1  c gray67",
+"2  c #AAAAAA",
+"3  c #AEAEAE",
+"4  c gray68",
+"5  c gray67",
+"6  c #AAAAAA",
+"7  c gray68",
+"8  c #B1B1B1",
+"9  c gray61",
+"0  c gray56",
+"q  c #B6B6B6",
+"w  c gray82",
+"e  c gray73",
+"r  c #B9B9B9",
+"t  c #C0C0C0",
+"y  c #959595",
+"u  c #E2E2E2",
+"i  c gray84",
+"p  c #DDDDDD",
+"a  c gainsboro",
+"s  c gray85",
+"d  c #D7D7D7",
+"f  c gainsboro",
+"g  c #E1E1E1",
+"h  c gray77",
+"j  c #B3B3B2",
+"k  c #E4E4E3",
+"l  c gray83",
+"z  c gray73",
+"x  c #B6B6B6",
+"c  c #AAAAAA",
+"v  c gray54",
+"b  c gray86",
+"n  c #D2D2D2",
+"m  c #DADADA",
+"M  c gray85",
+"N  c #D7D7D6",
+"B  c #D5D5D4",
+"V  c gray85",
+"C  c gray87",
+"Z  c #C2C2C1",
+"A  c #ACACB0",
+"S  c #DBDBE0",
+"D  c #D3D3D4",
+"F  c #BCBCBC",
+"G  c gray67",
+"H  c #DADADA",
+"J  c #D2D2D2",
+"K  c #D0D0D0",
+"L  c gray74",
+"P  c gray",
+"I  c gray74",
+"U  c #BBBBBC",
+"Y  c #BABABB",
+"T  c #BEBEBD",
+"R  c #C3C3C2",
+"E  c #A8A8A9",
+"W  c #A9A999",
+"Q  c #DBDBC6",
+"!  c #D3D3D2",
+"~  c #BCBCBC",
+"^  c #AAAAAA",
+"/  c #D7D7D7",
+"(  c #D7D7D7",
+")  c gray74",
+"_  c #909090",
+"`  c #B1B1B1",
+"'  c #B1B1B4",
+"]  c #AAAA9B",
+"[  c #A9A98E",
+"{  c #ADADB1",
+"}  c #ADADB0",
+"|  c #A8A89A",
+" . c #C1C18B",
+".. c #D4D4B6",
+"X. c #D4D4D1",
+"o. c #BBBBBB",
+"O. c #B4B4B4",
+"+. c gray72",
+"@. c gray58",
+"#. c #C5C5C5",
+"$. c #B8B8B7",
+"%. c #BFBFBD",
+"&. c #8D8D91",
+"*. c #E3E3CC",
+"=. c #D6D6B0",
+"-. c #DDDDE3",
+";. c #DDDDE0",
+":. c #DBDBC4",
+">. c #D4D4B2",
+",. c #D6D6E3",
+"<. c #D5D5D4",
+"1. c gray73",
+"2. c #B6B6B6",
+"3. c gray67",
+"4. c gray57",
+"5. c #C6C6C6",
+"6. c #B1B1B5",
+"7. c #B4B4B8",
+"8. c #84848E",
+"9. c #DADAC9",
+"0. c #CFCFAE",
+"q. c #D5D5E0",
+"w. c #D4D4DD",
+"e. c #D2D2C1",
+"r. c #D2D2B0",
+"t. c #D6D6E0",
+"y. c #D5D5D4",
+"u. c #BCBCBC",
+"i. c gray67",
+"p. c gray85",
+"a. c #D7D7D7",
+"s. c #BBBBBC",
+"d. c #ABAB9B",
+"f. c #D9D9C3",
+"g. c #D6D6C5",
+"h. c #CECEA9",
+"j. c #CBCB9A",
+"k. c #D4D4C4",
+"l. c #D3D3C1",
+"z. c #CECEA9",
+"x. c #CBCB99",
+"c. c #D4D4C6",
+"v. c #D5D5D2",
+"b. c #AAAAAA",
+"n. c #D7D7D7",
+"m. c #DADAD9",
+"M. c #BABABB",
+"N. c #A9A98E",
+"B. c #D8D8B1",
+"V. c #D5D5B4",
+"C. c #CBCB99",
+"Z. c #C7C78C",
+"A. c #D3D3B2",
+"S. c #D2D2B0",
+"D. c #CBCB9A",
+"F. c #C6C68B",
+"G. c #D3D3B6",
+"H. c #D4D4D1",
+"J. c #BBBBBB",
+"K. c #B4B4B4",
+"L. c #B6B6B6",
+"P. c gray54",
+"I. c #C5C5C5",
+"U. c #B2B2B7",
+"Y. c #B7B7BD",
+"T. c #858591",
+"R. c #DCDCCC",
+"E. c #D1D1B1",
+"W. c #D6D6E4",
+"Q. c #D6D6E1",
+"!. c #D4D4C4",
+"~. c #D3D3B2",
+"^. c #D6D6E3",
+"/. c #D5D5D4",
+"(. c gray73",
+"). c gray72",
+"_. c gray73",
+"`. c #909090",
+"'. c gray79",
+"]. c #B7B7B6",
+"[. c #BBBBB8",
+"{. c #8B8B8F",
+"}. c #E0E0C9",
+"|. c #D0D0AA",
+" X c #D6D6DB",
+".X c #D6D6D8",
+"XX c #D3D3BD",
+"oX c #D2D2B0",
+"OX c #D6D6E0",
+"+X c #D5D5D4",
+"@X c gray74",
+"#X c gray61",
+"$X c #C3C3C3",
+"%X c #C6C6C6",
+"&X c #A9A9A9",
+"*X c #9A9A9A",
+"=X c #C4C4C3",
+"-X c #C1C1C6",
+";X c #BBBBA8",
+":X c #CBCBA9",
+">X c #D4D4DA",
+",X c #D3D3D6",
+"<X c #CECEBC",
+"1X c #CBCB9A",
+"2X c #D4D4C6",
+"3X c #D5D5D2",
+"4X c gray",
+"5X c gray56",
+"6X c #B2B2B2",
+"7X c gray67",
+"8X c #A9A9A9",
+"9X c gray65",
+"0X c gray68",
+"qX c gray69",
+"wX c #999998",
+"eX c #A8A8A7",
+"rX c #D7D7D7",
+"tX c #D2D2D2",
+"yX c #CBCBCB",
+"uX c #C6C6A8",
+"iX c #D3D3B0",
+"pX c #D4D4D2",
+"aX c #B6B6B6",
+"sX c #E4E4E4",
+"dX c gray86",
+"fX c gray86",
+"gX c gray85",
+"hX c gainsboro",
+"jX c #E2E2E2",
+"kX c #C5C5C6",
+"lX c #B6B6B7",
+"zX c gray74",
+"xX c gray59",
+"cX c gray86",
+"vX c #D2D2D8",
+"bX c #D6D6DD",
+"nX c #D5D5D5",
+"mX c gray82",
+"MX c gray83",
+"NX c LightGray",
+"BX c LightGray",
+"VX c LightGray",
+"CX c LightGray",
+"ZX c gray83",
+"AX c #D2D2D2",
+"SX c #D2D2D2",
+"DX c #D5D5D5",
+"FX c #CBCBCB",
+"GX c #D5D5D5",
+"HX c #D4D4D3",
+"JX c #D5D5D4",
+"KX c #D5D5D5",
+"LX c white",
+/* pixels */
+"  . X o O + @ # $ + % & * = - ; ",
+": > , < 1 2 3 4 5 6 7 8 9 0 q w ",
+"e r t y u i p a s d f g h j k l ",
+"z x c v b n m M N B V C Z A S D ",
+"F G H J K L P I U Y T R E W Q ! ",
+"~ ^ / ( ) _ ` ' ] [ { } |  ...X.",
+"o.O.+.@.#.$.%.&.*.=.-.;.:.>.,.<.",
+"1.2.3.4.5.6.7.8.9.0.q.w.e.r.t.y.",
+"u.i.p.a.s.d.f.g.h.j.k.l.z.x.c.v.",
+"+ b.n.m.M.N.B.V.C.Z.A.S.D.F.G.H.",
+"J.K.L.P.I.U.Y.T.R.E.W.Q.!.~.^./.",
+"(.)._.`.'.].[.{.}.|. X.XXXoXOX+X",
+"@X#X$X%X&X*X=X-X;X:X>X,X<X1X2X3X",
+"4X5X6X7X8X9X0XqXwXeXrXtXyXuXiXpX",
+"- aXsXdXfXgXhXjXkXlXzXxXcXvXbXnX",
+"; mXMXNXBXVXCXZXAXSXDXFXGXHXJXKX"
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 221 2 ",
+"   c #252525",
+".  c #323232",
+"X  c gray26",
+"o  c gray27",
+"O  c gray29",
+"+  c #4C4C4C",
+"@  c gray31",
+"#  c #505050",
+"$  c #73734A",
+"%  c #636362",
+"&  c #686868",
+"*  c DimGray",
+"=  c #6F6F68",
+"-  c #6D6D6D",
+";  c gray43",
+":  c #757568",
+">  c #777769",
+",  c #77776A",
+"<  c #73736E",
+"1  c gray45",
+"2  c #747474",
+"3  c gray46",
+"4  c #767676",
+"5  c #777777",
+"6  c #787877",
+"7  c #797977",
+"8  c gray47",
+"9  c #797978",
+"0  c #797979",
+"q  c #7B7B78",
+"w  c gray48",
+"e  c #7A7A7B",
+"r  c #7B7B7B",
+"t  c #79797C",
+"y  c #79797D",
+"u  c #7B7B7D",
+"i  c #79797F",
+"p  c #7C7C7C",
+"a  c gray49",
+"s  c #7E7E7D",
+"d  c #7E7E7E",
+"f  c gray50",
+"g  c #979744",
+"h  c #A2A244",
+"j  c #BEBE68",
+"k  c #BEBE6A",
+"l  c #A2A273",
+"z  c #C2C275",
+"x  c #C8C875",
+"c  c #C9C977",
+"v  c #CACA76",
+"b  c #CACA77",
+"n  c #C9C978",
+"m  c #CBCB78",
+"M  c #CBCB79",
+"N  c #CECE7A",
+"B  c #CFCF7D",
+"V  c #CCCC7E",
+"C  c #CFCF7E",
+"Z  c #D1D179",
+"A  c #D7D77E",
+"S  c #808080",
+"D  c #848484",
+"F  c gray53",
+"G  c #888888",
+"H  c #898988",
+"J  c #929292",
+"K  c #949492",
+"L  c gray58",
+"P  c #9B9B9D",
+"I  c #9D9D9D",
+"U  c #9E9E9D",
+"Y  c #9F9F9F",
+"T  c #B4B49B",
+"R  c gray63",
+"E  c #A4A4A4",
+"W  c #AAAAAA",
+"Q  c #AEAEAE",
+"!  c #AFAFB5",
+"~  c #B4B4B4",
+"^  c gray71",
+"/  c #B4B4B6",
+"(  c #B6B6B6",
+")  c #B7B7B7",
+"_  c #B8B8B7",
+"`  c gray72",
+"'  c #B9B9B9",
+"]  c gray73",
+"[  c #BBBBBB",
+"{  c #B8B8BE",
+"}  c #BCBCBC",
+"|  c gray74",
+" . c #BEBEBD",
+".. c gray",
+"X. c #D1D180",
+"o. c #D3D380",
+"O. c #D7D780",
+"+. c #CBCB9D",
+"@. c #CBCBB6",
+"#. c #CFCFB4",
+"$. c #CFCFB5",
+"%. c #D1D1B4",
+"&. c #D0D0B5",
+"*. c #D1D1B5",
+"=. c #D1D1B6",
+"-. c #D2D2B6",
+";. c #D2D2B7",
+":. c #D3D3B8",
+">. c #D2D2B9",
+",. c #D3D3BD",
+"<. c #D4D4BD",
+"1. c #D9D9BE",
+"2. c #BEBEC2",
+"3. c #C0C0C0",
+"4. c #C1C1C1",
+"5. c gray76",
+"6. c #C6C6C5",
+"7. c gray78",
+"8. c #C8C8C5",
+"9. c #C8C8C7",
+"0. c #C9C9C7",
+"q. c #C8C8C8",
+"w. c gray79",
+"e. c #CACAC9",
+"r. c #CACACA",
+"t. c #CBCBCB",
+"y. c gray80",
+"u. c #CECECE",
+"i. c #CFCFCE",
+"p. c gray81",
+"a. c #D4D4C1",
+"s. c #D5D5C1",
+"d. c #D5D5C2",
+"f. c #DEDEC1",
+"g. c #DFDFC1",
+"h. c #DEDEC2",
+"j. c #D1D1CF",
+"k. c #D4D4CF",
+"l. c #D0D0D0",
+"z. c #D1D1D0",
+"x. c gray82",
+"c. c #D2D2D0",
+"v. c #D2D2D1",
+"b. c #D3D3D1",
+"n. c #D2D2D2",
+"m. c LightGray",
+"M. c #D4D4D0",
+"N. c #D5D5D1",
+"B. c #D4D4D2",
+"V. c #D5D5D2",
+"C. c #D5D5D3",
+"Z. c #D6D6D3",
+"A. c gray83",
+"S. c #D5D5D4",
+"D. c #D5D5D5",
+"F. c #D6D6D4",
+"G. c #D6D6D5",
+"H. c #D5D5D6",
+"J. c #D5D5D7",
+"K. c gray84",
+"L. c #D7D7D7",
+"P. c #D1D1D8",
+"I. c #D3D3D9",
+"U. c #D3D3DA",
+"Y. c #D4D4DA",
+"T. c #D6D6DA",
+"R. c #D6D6DB",
+"E. c #D3D3DE",
+"W. c #D6D6DC",
+"Q. c #D6D6DD",
+"!. c #D4D4DE",
+"~. c #D6D6DE",
+"^. c #D6D6DF",
+"/. c #D8D8D8",
+"(. c #D8D8D9",
+"). c gray85",
+"_. c #DADADA",
+"`. c gray86",
+"'. c #D8D8DC",
+"]. c #D8D8DE",
+"[. c #D9D9DE",
+"{. c gainsboro",
+"}. c #DDDDDD",
+"|. c gray87",
+" X c #DFDFDE",
+".X c #DFDFDF",
+"XX c #E0E0DE",
+"oX c #E1E1DE",
+"OX c #D6D6E0",
+"+X c #D6D6E1",
+"@X c #D7D7E2",
+"#X c #D6D6E3",
+"$X c #D6D6E4",
+"%X c #D9D9E3",
+"&X c #DCDCE3",
+"*X c #DFDFE5",
+"=X c #D6D6E8",
+"-X c #D7D7E9",
+";X c #D7D7EE",
+":X c #DFDFEA",
+">X c gray88",
+",X c #E1E1E0",
+"<X c #E1E1E1",
+"1X c #E2E2E2",
+"2X c gray89",
+"3X c #E4E4E3",
+"4X c #E1E1E7",
+"5X c #E4E4E4",
+"6X c gray90",
+"7X c #E7E7E7",
+"8X c #E1E1E8",
+"9X c #E2E2E9",
+"0X c #E3E3EA",
+"qX c #E2E2EE",
+"wX c #E4E4EF",
+"eX c gray91",
+"rX c #E9E9E9",
+"tX c #EAEAEA",
+"yX c gray93",
+"uX c #EEEEEE",
+"iX c #EFEFEF",
+/* pixels */
+"D.D.`.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.}.`.}.}.}.D.D.",
+"D.D...) ` ` ` ` ) ` ` ) ` ` ` ` ` ` ` ) ` ` ` ` ` ` ..` ) ` D.D.",
+"`.3.# d 8 3 3 8 8 8 3 8 8 3 3 3 8 8 3 8 3 3 8 3 d * O d 3 d p.D.",
+"}.) d uX}.uXuX2X2X}.D.2X2X2X2X2X2X}.D.2X2X2X2X}.tX3.S uX}.2XD.D.",
+"}.` 8 }.m.D 8 w.m.p.w.D.m.m.m.m.D.p.w.m.m.m.m.m.`.) 6 }.m.m.D.D.",
+"}.` 8 }.p.3.X R 2Xp.w.D.m.D.D.D.D.m.p.D.D.D.D.D.`._ 8 2Xm.D.D.D.",
+"}.` 8 2X}.8 + p.D.m.p.D.D.D.D.D.D.m.w.D.D.D.D.m.`.) 8 2Xm.D.D.D.",
+"}.` 3 2Xm.1 - ..`.m.w.D.m.m.m.D.D.m.w.D.m.m.D.m.`.) 8 }.m.B.D.D.",
+"}.` 8 2Xm.5X5X`.D.m.p.}.}.`.}.`.{.R.m.}.`.}.`.`.2X..u 0XU.W.D.D.",
+"}.` 8 }.p.p.p.m.m.p.) ) ) _ ) ) _ ) ! ` ) ) ) ) ..P : h.$.>.D.D.",
+"}.` 1 D.w.w.w.w.m.) + d 8 8 3 6 8 , $ i 6 8 8 8 8 < g o.c V k.D.",
+"}.` 8 2Xm.2X5X`.}.` S uX}.tXuX3X0Xh.A qXoX2X2XoX0Xg.Z ;XW.+XD.D.",
+"}.` 8 2XD.D 8 w.}.) 8 }.m.J 6 6.R.$.c W.k.B.m.m.U.&.m W.m.B.D.D.",
+"}.` 3 2Xm.3.X Y 5X) 8 }.`.E   Q *X&.m +XB.D.D.B.R.>.M +XB.D.D.D.",
+"}.` 8 }.`.8 + p.}.) 3 2Xm.W . Y 0X$.m +XZ.D.D.D.W.&.M +XB.D.D.D.",
+"}.` 8 2XD.1 - ..2X) 8 2Xp.G d 8.W.>.m +XB.B.D.B.].&.M ].B.D.D.D.",
+"}.) 8 3Xm.3X3XR.].) t 0XR.4X0X].+X,.C -XR.W.W.R.$X<.X.-XW.W.D.D.",
+"}.` 8 }.p.p.p.m.`./ : h.$.&.%.&.,.+.j a.&.>.>.&.<.+.j d.$.>.D.D.",
+"}.` 2 D.w.w.8.w.k.! $ A c c c m C j h o.m m M M C j h o.c V k.D.",
+"}.) 8 2XD.}.2X`.}._ u qXE.:XwX%X-Xa.o.;XW.W.%X].-Xa.Z ;XW.+XD.D.",
+"}.` 8 2Xm.J u 6.}.) 6 oXB.K 6 8.W.&.m W.m.B.B.B.R.&.m W.B.D.D.D.",
+"}.` 8 }.`.R   Q 5X) 8 }.`.R   Q *X$.m +XB.D.L.B.W.>.m W.B.D.D.D.",
+"}.` 8 2Xm.W . Y 5X) 8 2Xm.W . Y 4X&.m +XB.D.D.D.W.&.M %XB.D.D.D.",
+"}.` 3 2Xm.D d w.}.) 8 2Xm.D d 8.].&.m +XB.D.D.D.W.>.c %XB.B.D.D.",
+"}.) u tX].5XtX}.2X..d 7X`.5XtX}.&X1.M +XB.D.D.D.W.:.C -XR.R.D.D.",
+"}.` * 5.) ) ~ ) ..Y & 2.) ) ~ ) { T z E.k.m.m.m.U.$.j a.&.>.D.D.",
+"].@.O u 8 8 8 8 8 3 - 8 3 8 8 3 d % < `.w.w.8.y.p.2.l Z m C k.D.",
+"}._ i uX}.}.2X2X2X}.`.2X2XoX2X2XtX3.8 2Xm.2X3XR.D.D.U.+X+X+XD.D.",
+"}.` 3 }.m.m.m.m.m.p.q.D.m.m.m.m.{.) 6 2XD.D 8 p.D.m.w.D.m.B.D.D.",
+"}.` d 2Xm.D.D.m.D.m.y.D.A.D.B.B.{.` d oXl._ o Y 2Xp.p.D.A.D.D.D.",
+"D.D.l.D.D.D.D.D.D.D.m.D.D.D.D.D.D.m.p.D.D.}.m.m.D.D.A.D.D.D.D.D.",
+"D.D.D.D.D.D.D.D.D.D.D.D.D.D.D.D.D.D.R.D.D.m.D.D.D.D.D.D.D.D.D.D."
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 80 1 ",
+"  c #050505",
+". c gray6",
+"X c #161616",
+"o c #1D1D1D",
+"O c #303019",
+"+ c #252525",
+"@ c #2C2C2C",
+"# c #343426",
+"$ c #323232",
+"% c #3B3B3B",
+"& c #464646",
+"* c #484845",
+"= c #4B4B4B",
+"- c #555555",
+"; c #5E5E5E",
+": c #6D6D42",
+"> c #646464",
+", c #6A6A6A",
+"< c #7D7D67",
+"1 c #757575",
+"2 c #828223",
+"3 c #99992F",
+"4 c #939334",
+"5 c #BBBB3F",
+"6 c #A6A646",
+"7 c #BFBF46",
+"8 c #B1B156",
+"9 c #B2B25D",
+"0 c #B4B47B",
+"q c #C6C647",
+"w c #C6C649",
+"e c #C9C94A",
+"r c #D1D14D",
+"t c #C8C856",
+"y c #C8C859",
+"u c #C2C261",
+"i c #C9C967",
+"p c #CACA69",
+"a c #D0D06D",
+"s c #CBCB74",
+"d c #D3D371",
+"f c #828282",
+"g c #8E8E8E",
+"h c #939393",
+"j c #9C9C9C",
+"k c #A4A4A4",
+"l c gray67",
+"z c gray71",
+"x c #BABAB7",
+"c c #BBBBBB",
+"v c #C8C884",
+"b c #CECE8E",
+"n c #D0D08E",
+"m c #CFCF93",
+"M c #D4D493",
+"N c #D4D499",
+"B c #D1D1AD",
+"V c #D3D3BC",
+"C c #BDBDC1",
+"Z c #BCBCCA",
+"A c #C5C5C5",
+"S c #C7C7CC",
+"D c #CCCCCC",
+"F c #D3D3C6",
+"G c #D4D4CE",
+"H c #D5D5D4",
+"J c #D9D9D7",
+"K c #D5D5DA",
+"L c #DBDBDB",
+"P c #E2E2DF",
+"I c #D7D7E5",
+"U c #DADAE1",
+"Y c #D7D7E9",
+"T c #DDDDED",
+"R c #D7D7F6",
+"E c #D8D8FA",
+"W c #E3E3E3",
+"Q c #E9E9E9",
+"! c gray95",
+"~ c #F8F8F8",
+/* pixels */
+"HHHHHHHHJHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH",
+"HHHJHHHHDHHHHHHHHHDHHHHHHHHHHHHHHHHHDHHHHHDHHHHH",
+"HHJJUWUWWWWWUWWWWWWWWUWWWUWWWWLWWWWUWWUWWLWWHHHH",
+"HHJDkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjljjjjkHJHH",
+"HHPk+>;;;;;;;;;-;;;;;;;;;;;-;>;;;;;;;;o-;;;,DHHH",
+"HHWj>~WQWWWWQWQHWWQWWQWWWWLLWWWWWWQQQQ=HQWQWJHHJ",
+"HHUj;WDHLWLHHDHADHDHHDHHHHDAHHHHHHDHHH&AHHDHHHHH",
+"HHWj;WGLh&1HJHJAHHJHHHJHHJDDHHHHHHHHHH=ALHHHHJHH",
+"HHLj;WHLgjXgWHHAHJHHHHHHHJDDJHHHHHHJHJ&AKHJHKHHH",
+"HHWj;QHD!z$CLHHADJHJHHHHHJDDJHHHHHHHHH=AKHHHHHHH",
+"HHWj;WGLg fDHHHAHJHHHJHHHJDSKHHHHHHHHH&ALHHHJHHH",
+"HHWj;QDL,%&jWHHAHHHHHHHHHJDDJJHHHHHHJH&AKHHHHHJH",
+"HHUj;QHHWULLHHHDDHHHHJHHHHDDJHHHHHHHHH&ALHHHHHHH",
+"HDWj;WHHHHHHHHHAHHHHHHHHHHDAHHHHHHHHHH=ALHHHHJHH",
+"HHWj;QHHHJHHHHHDLLLULULPLLJHULKULWLLLU=AIIJKHHHH",
+"HHWj-JAAAAADDAD>&=========**==========O7wwqyHKHH",
+"HHWj;WDHHHDHHHL=kcccxcccxZ09Zccccccccc5BVVVVHHJH",
+"HHWj;WHHJULHHHL=CWLLWWLLLTNpTLLLKLLLLUeGKKKKHHHH",
+"HHWj;QDHAkALHHL=xLHHllHHHIviIGHHHHHHHHwFJHHHHHHK",
+"HHWj;WHU,%olLGL=cLLc;$=WGImiIHHHHHHHHHwFHHHJHHJH",
+"HDWj;QHHJWohUHL=cLHKk@;UHInpYHHHHHHHHHqFKHHHHHHH",
+"HHWj;WDJH&1WHHL=xLHLA-%LGIbpIHHHJHHJHJqFKHJHKHHH",
+"HDWj;QDW- =kLHL=cLLk-%&LHIbiIHJHHHHHHHqFKHHHHHJH",
+"HHWj;WHJxljcJHP=cLHDkkJJGInpIHHHHJHJHHqFHHHHHHHH",
+"HHWj;WHHLWWLHHL=xJHHPLHHGIbiIHHHHHGHHHqFHHHHHHHH",
+"HHWj;QHHHHHHHHL=ZTIIIKIIKRNdRKIIIIUIIUeHIIIIHHJH",
+"HHWj;LDDDDDDDDH&0MmbbbmbbN96NbbbmbbbbM3vmbmmHHHH",
+"HHUj-LADADDDDAH&8aipiiiiid64diiipiiiii2upiisHHHH",
+"HHLj;QHHHHHHJHW=ZTIIIIIIURNdEIIIIIYIIYrHTIIIHHHH",
+"HHWj;WHHLWWLHHL=xJGJPPLGGIbiUGHHHHHHHHqFHHHHJHJH",
+"HHWj>WDLh=>AHHU=xLLA;=jLHIniIHHHHJJHHHqFJHHHHHHH",
+"HHWj;WHLlf.jLHL=cLHAj%%UGInpIHHHHHHHHHqFJHHHHJHH",
+"HHUj;QHJU>+zLHL=cLDQk+,UHIbiIHHHJHHHHJwFJHHHHHHJ",
+"HHWj;WDHjkXfWGL=xLLzj,oLHIbiIHHHHHHHHJqFHHHJHHHH",
+"HHPj;QDLf&,DHHL=cLLz--kLHIbiYHHJHHHHHHqFKHHHKHHH",
+"HHWj;WHHWWWLHHL=cLHKWWLHHImpIHHHHHHJHHqFKHHHHHHH",
+"HHWj;WHHHHHHHHL=cLJHDHHHGUbiYHHHJHHHHJqFHHHHJHHH",
+"HHWj>WHHHHHHJHU=cLHJJHJHHInpIHHHHHHHHHqFKHJHHHHH",
+"HHLko=&&&&&=&&=+%==&&&=&*=#<HAAAAAAAAS:5wwqtHKHH",
+"HHWj-HAAAAAAAAAAAAASAAAAAHf,WHHDHHHHHHCDFFFFHHHJ",
+"HHWj;QHKLJLJJKLAHKKLJJKKJQg,QDHLWLHHJHSHKKKHHJHH",
+"HHWj;WHHHHHHHHJAHHHHHHHHHWg,QHJchCLHHHAHHJHHHHHH",
+"HDWj;QHHHHHHHHJAHHKHHHHHDWg,QDL,&XzLHHAHHHJHJHHK",
+"HHUk,WHHHHHHHHJAHJHHHHHJHWh1WHHKL@kUHHSHHHHHHHHH",
+"HHJHHHHHHJHHHJHHJHHHHHHJHJHHJHHKDDLHHHHHHHHHHHHH",
+"HHHHKHHHHKHHHHHHHHHHHHHHHHHHHHHHHLHHJHHHHHHJHHHK",
+"HHHHHHHHHHHHJHHHHHHHHHHKHHHHHHHJHHHHHHHHHHHKHHHJ",
+"HHHJHHJHHHHHHHHKKHHHHJHHHHHHHHHHHHHHKHHHHJHHHHHH"
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/palisade-web.png b/icons/palisade-web.png
new file mode 100644 (file)
index 0000000..35e8e27
Binary files /dev/null and b/icons/palisade-web.png differ
diff --git a/icons/palisade.ico b/icons/palisade.ico
new file mode 100644 (file)
index 0000000..b6269ef
Binary files /dev/null and b/icons/palisade.ico differ
diff --git a/icons/palisade.rc b/icons/palisade.rc
new file mode 100644 (file)
index 0000000..62e613b
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "palisade.ico"
diff --git a/icons/palisade.sav b/icons/palisade.sav
new file mode 100644 (file)
index 0000000..a935e89
--- /dev/null
@@ -0,0 +1,50 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :8:Palisade
+PARAMS  :5:5x5n5
+CPARAMS :5:5x5n5
+SEED    :15:930059588777257
+DESC    :13:2d23c33e2c1b2
+AUXINFO :52:14a191be1282597737537139d11d87fb4f21ad4a8f31e67b4441
+NSTATES :2:41
+STATEPOS:2:27
+MOVE    :14:F0,1,16F0,0,64
+MOVE    :15:F0,0,32F1,0,128
+MOVE    :12:F1,1,8F0,1,2
+MOVE    :12:F1,0,4F1,1,1
+MOVE    :14:F0,2,16F0,1,64
+MOVE    :12:F1,2,8F0,2,2
+MOVE    :12:F0,3,1F0,2,4
+MOVE    :15:F2,0,128F1,0,32
+MOVE    :12:F2,0,4F2,1,1
+MOVE    :12:F3,0,8F2,0,2
+MOVE    :15:F1,4,128F0,4,32
+MOVE    :14:F1,4,16F1,3,64
+MOVE    :15:F2,4,128F1,4,32
+MOVE    :14:F0,3,64F0,4,16
+MOVE    :15:F1,3,128F0,3,32
+MOVE    :12:F1,3,1F1,2,4
+MOVE    :15:F4,4,128F3,4,32
+MOVE    :14:F4,4,16F4,3,64
+MOVE    :12:F3,4,8F2,4,2
+MOVE    :12:F2,4,1F2,3,4
+MOVE    :12:F2,3,8F1,3,2
+MOVE    :14:F2,2,64F2,3,16
+MOVE    :15:F2,3,32F3,3,128
+MOVE    :12:F3,3,4F3,4,1
+MOVE    :12:F4,3,8F3,3,2
+MOVE    :14:F4,3,16F4,2,64
+MOVE    :12:F1,2,1F1,1,4
+MOVE    :15:F2,1,128F1,1,32
+MOVE    :15:F2,2,128F1,2,32
+MOVE    :12:F2,2,1F2,1,4
+MOVE    :15:F3,2,128F2,2,32
+MOVE    :14:F3,2,64F3,3,16
+MOVE    :12:F4,2,8F3,2,2
+MOVE    :12:F3,2,1F3,1,4
+MOVE    :15:F2,1,32F3,1,128
+MOVE    :14:F4,2,16F4,1,64
+MOVE    :12:F4,1,8F3,1,2
+MOVE    :14:F3,0,64F3,1,16
+MOVE    :15:F4,0,128F3,0,32
+MOVE    :12:F4,1,1F4,0,4
diff --git a/icons/pattern-16d24.png b/icons/pattern-16d24.png
new file mode 100644 (file)
index 0000000..1d6e463
Binary files /dev/null and b/icons/pattern-16d24.png differ
diff --git a/icons/pattern-16d4.png b/icons/pattern-16d4.png
new file mode 100644 (file)
index 0000000..8c96b62
Binary files /dev/null and b/icons/pattern-16d4.png differ
diff --git a/icons/pattern-16d8.png b/icons/pattern-16d8.png
new file mode 100644 (file)
index 0000000..1d6e463
Binary files /dev/null and b/icons/pattern-16d8.png differ
diff --git a/icons/pattern-32d24.png b/icons/pattern-32d24.png
new file mode 100644 (file)
index 0000000..71a2aae
Binary files /dev/null and b/icons/pattern-32d24.png differ
diff --git a/icons/pattern-32d4.png b/icons/pattern-32d4.png
new file mode 100644 (file)
index 0000000..52472f5
Binary files /dev/null and b/icons/pattern-32d4.png differ
diff --git a/icons/pattern-32d8.png b/icons/pattern-32d8.png
new file mode 100644 (file)
index 0000000..71a2aae
Binary files /dev/null and b/icons/pattern-32d8.png differ
diff --git a/icons/pattern-48d24.png b/icons/pattern-48d24.png
new file mode 100644 (file)
index 0000000..70b5d25
Binary files /dev/null and b/icons/pattern-48d24.png differ
diff --git a/icons/pattern-48d4.png b/icons/pattern-48d4.png
new file mode 100644 (file)
index 0000000..ab51779
Binary files /dev/null and b/icons/pattern-48d4.png differ
diff --git a/icons/pattern-48d8.png b/icons/pattern-48d8.png
new file mode 100644 (file)
index 0000000..70b5d25
Binary files /dev/null and b/icons/pattern-48d8.png differ
diff --git a/icons/pattern-base.png b/icons/pattern-base.png
new file mode 100644 (file)
index 0000000..afe2f78
Binary files /dev/null and b/icons/pattern-base.png differ
diff --git a/icons/pattern-ibase.png b/icons/pattern-ibase.png
new file mode 100644 (file)
index 0000000..7763ce4
Binary files /dev/null and b/icons/pattern-ibase.png differ
diff --git a/icons/pattern-ibase4.png b/icons/pattern-ibase4.png
new file mode 100644 (file)
index 0000000..ac8094d
Binary files /dev/null and b/icons/pattern-ibase4.png differ
diff --git a/icons/pattern-icon.c b/icons/pattern-icon.c
new file mode 100644 (file)
index 0000000..384a508
--- /dev/null
@@ -0,0 +1,891 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"lXlXlXlXlXxXpXsXhXuXzXtXzXyXhXzX",
+"lXlXlXlXkXmX%X<XtX XxXW.bX].yXcX",
+"lXlXlXlXlXlXxXxXkXlXlXcXlXlXlXlX",
+"lXlXlXlXkXmXoX-XrX{.zX`.bX).wXvX",
+"lXlXlXkXkXcXlXxXcXhXMXgXMXjXvXzX",
+"lXlXlXnXcXiX;X>X:X,X=X;X=X=X<XlX",
+"lXlXzX XbXoX;.7.2.r.j   ;   z xX",
+"lXlXzX-XvX@Xw.f.i.v.z   =   k xX",
+"lXlXlX8XNX{.3 w t t 2 O , X n xX",
+"lXzXzX).bX[.  O +   #   %   j xX",
+"kXgXlXjXVX[.+ - q.Z.M   G v.M.hX",
+"iX|.nX_.nX[.  o eXUX`   =.UXvXsX",
+"kXgXlXjXNX}.p k (.rXY   E (.E.gX",
+"lXzXxX^.zX+Xw.g.4.r.f   &   j xX",
+"lXlXzX0XbX%Xr.g.d.n.J 2 s 6 P xX",
+"lXlXlXvXzXhXpXaXaXaXaXpXaXpXsXlX"
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"lXlXlXlXlXlXlXlXlXlXlXlXkXnXcXlXlXxXmXlXlXkXxXcXkXlXxXmXlXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXlXxX6XuXxXzXaX3XlXlXcXdXiXxXzXpX3XlXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXjXZXl.l.AXzX8X;.aXzXBX:.v.AXxX6X;.dXxXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXkXbX~. XmXvX2Xm.lXlXvX(.{.mXvX<XM.zXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXlXlXvXnXkXlXcXmXkXlXlXNXmXkXlXcXmXkXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXlXkXmXvXkXlXkXvXzXlXkXxXzXlXlXkXnXzXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXjXNXW.`.NXzXrXB.fXlXmXW.1XnXcXwXx.hXzXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXkXNX2.k.ZXcX1X*.gXkXZXx.T.VXxX8X@.iXcXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXlXzXzXfXzXxXiX4XlXlXxX9XeXxXzXpXeXzXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXkXhXjXkXhXhXlXvXjXjXhXxXzXhXhXlXxXhXjXlXlX",
+"lXlXlXlXlXlXlXlXlXlXkXnXCXBXBXVXVXVXBXVXVXVXBXBXVXVXBXBXVXMXlXlX",
+"lXlXlXlXlXlXzXzXlXkXvX&XY.Q.W.W.W.W.W.W.Q.!.!.!.!.!.!.~.E..XxXkX",
+"lXlXlXlXlXzXgXgXzXhXVXk.X.1.>.-.%.,.;.4.E   O X $ - . #   U VXhX",
+"lXlXlXlXjXCXG.i.BXgXBXM.9.j.f.p.r.g.a.x.|   X   @ =   O   Y CXhX",
+"lXlXlXlXkXMX|.g.bXhXBXn.2.p.y.w.8.u.r.f.]   X   @ =   O   U CXhX",
+"lXlXlXlXlXlXjXxXzXjXBXm.w.z.k.d.u.l.g.n.X.  o   # -   +   Y CXhX",
+"lXlXlXlXlXkXMXmXkXjXCXl.f m v n M v v n l - ; - < 5 = , . ~ VXhX",
+"lXlXlXlXkXmXOX}.nXgXZXg.  X   + =   .   - o     + =   O   U CXhX",
+"lXlXlXlXjXCXJ.=.MXgXCXg.  O . # : X + X > o     @ -   @   Y CXhX",
+"lXlXlXlXkXcX0X*XxXhXZXg.  X   + =   .   - o     + =   O   U CXhX",
+"lXlXxXlXlXkXbXNXkXhXCXh.o - * ; a y u u i = * & : a y p 5 | NXjX",
+"lXkXsXlXlXzXsXfXzXhXCXg.  $ $   #XUXAXUXP.  & #   *XUXFXUXwXdXxX",
+"kXvXg.qXcXNX|.D.ZXfXZXg.  X X   &XUXGXUXU.  O .   ;XUXKXUXtXdXxX",
+"kXnXd.-XnXmXOXl.MXgXCXg.  O O   %XUXFXUXI.  O .   *XUXGXUXwXdXxX",
+"lXkXkXkXlXlXjXjXlXhXZXg.  X X   &XUXGXUXU.  @ o   ;XUXKXUXtXdXxX",
+"lXlXlXlXlXkXcXNXkXjXVXc.T ] ` ^ d.H.C.P.;.@ > - : T _ _ E 8.nXkX",
+"lXlXlXlXkXvXwX}.bXhXBXm.w.z.k.f.7.e.0.p.`   .   + =   O   U CXhX",
+"lXlXlXlXjXCXV.} BXgXBXn.3.a.u.e.q.s.i.k.}   O . # > o %   R VXhX",
+"lXlXlXlXlXzXaX1XzXjXBXv.3.a.i.e.8.i.t.g.]   X   + *   o   I CXhX",
+"lXlXlXlXlXlXxXnXlXjXNXA.9.f.s.i.r.s.p.j.%.w i y a h t p 5 X.BXjX",
+"lXlXlXlXlXlXlXkXlXlXlXhXsXsXsXsXsXsXsXsXfXjXjXjXjXjXjXjXjXjXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXzXxXxXxXxXxXxXxXxXxXzXzXzXzXzXzXzXzXzXlXlX"
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"lXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXkXkXlXlXlXlXlXkXlXlXlXlXlXlXkXlXlXlXlXlXkXlXlXlXlXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXvXcXlXlXlXlXxXbXlXlXlXlXlXlXcXlXlXlXkXvXvXkXlXlXlXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXzXqXiXzXlXlXxXiX5XcXlXlXlXxXkXuXzXlXlXcXqXqXvXkXlXlXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXjXNXG.` zXlXkXvX9X@./.CXjXjXBXi.6.SXhXkXvX%X{ 1XmXkXlXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXkXbX(.P pXcXjXbX6X/ $XMXhXvXeXU / nXkXjXMX{._ tXcXkXlXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXxX~.~.zXlXjXmX%Xs.7XvXkXlXcX9X[.vXkXjXMX^.x.pXxXkXlXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXBXVXlXlXlXkXmXFXcXkXlXlXlXvXNXkXlXlXkXBXSXzXlXlXlXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXhXfXlXlXlXlXjXdXjXlXlXlXlXhXhXlXlXlXlXhXdXkXlXlXlXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXkXvXVXkXlXlXlXcXVXnXkXlXlXkXBXbXkXlXlXlXvXCXxXkXlXlXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXkXMX3Xl.nXkXkXbX<Xw.;XmXjXkXvXl..XNXjXjXmX.Xe.pXcXkXlXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXkXvX$.V fXxXkXzXfX$.).VXjXhXSXz.Z.DXhXkXvX6XD *XMXkXlXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlX^.>.pXxXhXBX[.E 2XbXkXkXMXe.e.MXkXjXnX_.=.6XbXkXlXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXVXBXxXlXlXlXlXlXgXzXlXlXkXjXkXkXlXlXzXgXxXvXkXlXlXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXjXjXlXlXlXlXlXlXzXlXlXlXlXzXzXlXlXlXlXzXlXkXlXlXlXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXlXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXxXmXnXnXnXnXnXnXnXnXnXnXnXnXbXbXbXbXbXbXbXbXbXbXvXbXvXlXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXkXlXlXlXlXxXpX<X1X<X<X<X<X1X<X<X<X<X<X2X9X9X9X9X9X8X8X9X9X9X9X8XeXzXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXzXbXzXlXlXjXVXE.E O. ..... .[ .. ... .X._ c v v v c n B c v v n s -.BXjXlXlX",
+"lXlXlXlXlXlXlXlXlXzXgX7XdXzXlXjXCXT.@.l.a.s.d.a.7.d.s.s.a.k.-.          # ,   .   o   ( ZXhXlXlX",
+"lXlXlXlXlXlXlXlXhXCXQ.J 6XbXkXjXCXT.O.g.u.i.i.y.4.p.u.i.y.f.&.  . X X   $ <   X . +   ) ZXhXlXlX",
+"lXlXlXlXlXlXlXlXjXnX,X~ D.AXhXjXCXT.O.g.u.i.i.u.4.p.i.i.u.f.&.          # ,   .   o   ( ZXhXlXlX",
+"lXlXlXlXlXlXlXlXkXvX7XF.0XvXkXjXCXT.o.d.y.y.u.t.3.u.y.y.t.d.%.  . . .   # <   . . O   ) ZXhXlXlX",
+"lXlXlXlXlXlXlXlXlXkXvXZXvXkXlXjXCXY.%.v.g.h.j.g.0.k.h.h.g.c.:.          # ,   .   o   ( ZXhXlXlX",
+"lXlXlXlXlXlXlXlXlXlXjXdXkXlXlXjXVXW.Y | ] [ [ ] ` [ [ [ ] } ! 2 1 1 1 < 6 r , 2 1 4 @ | CXhXlXlX",
+"lXlXlXlXlXlXlXlXlXkXnXCXlXlXlXjXBX(.1   .     X 2       .   < & X O O X & 2 . O o #   _ ZXhXlXlX",
+"lXlXlXlXlXlXlXlXjXNXXX<.rXcXkXjXBX(.2   o . . o 3   X . X   < #         # ,   .   o   ( ZXhXlXlX",
+"lXlXlXlXlXlXlXlXjXNX.XK /.VXjXjXBX(.1   .     X 2       .   , #         # ,   .   o   ( ZXhXlXlX",
+"lXlXlXlXlXlXlXlXkXbX4X>._.BXjXjXBX(.2   X     X 2   . . .   < #   . .   # <   . . O   ) ZXhXlXlX",
+"lXlXlXlXlXlXlXlXlXzXhXkXmXkXlXjXBX(.1   .     X 2       .   , #         # ,   .   o   ( ZXhXlXlX",
+"lXlXlXlXkXkXlXlXlXlXlXjXkXlXlXjXBX/.8 $ = * & - 6 . o o o   2 , % * * % , 2   o o @   _ ZXhXlXlX",
+"lXlXlXkXMXnXkXlXlXkXcXNXlXlXlXjXBX/.0 = : ; , . r.kX0XeXqXgXI.# > ; ; > # I.gXqXeXeXtX|.hXzXlXlX",
+"lXlXkXvX&X<XvXkXkXcXeXXXkXlXlXjXBX(.1   .   O   z.UXKXUXLXUXoX  X     X   oXUXLXUXIXUX,XdXzXlXlX",
+"lXlXjXCXF.y.FXhXkXcXrX..aXcXlXjXBX(.2   X . O   l.UXHXPXJXUXXX  X . . X   XXUXJXPXLXUX>XdXzXlXlX",
+"lXlXjXCXP.| nXkXkXxXpXW #XmXkXjXBX(.1   .   O   z.UXKXUXLXUXoX  X     X   oXUXLXUXIXUX,XdXzXlXlX",
+"lXlXkXcX*X'.gXzXkXcXrX^.6XvXkXjXBX(.2   o X @   z.UXHXPXJXUXXX  X     X   .XUXGXKXJXUX:XfXzXlXlX",
+"lXlXlXkXmXVXzXlXlXkXcXCXbXkXlXjXBX(.1   .   O   z.UXKXUXLXUXoX  o . . o   oXUXLXUXIXUX,XdXzXlXlX",
+"lXlXlXlXkXjXlXlXlXlXkXgXkXlXlXjXVXQ.D R U U T H 2.U.F.H.F.U.r., 3 2 2 2 2 } ,.-.-.:.#.s.bXkXlXlX",
+"lXlXlXlXlXlXlXlXlXlXzXnXlXlXlXjXCXY.%.b.j.k.k.j.3.0.0.0.9.t.+.          # ,   .   o   ( ZXhXlXlX",
+"lXlXlXlXlXlXlXlXlXlXxX:.*XMXkXjXCXT.o.d.t.y.y.t.5.s.p.a.p.h.=.          # 2 . O O #   _ ZXhXlXlX",
+"lXlXlXlXlXlXlXlXhXVXU.e K.ZXhXjXCXT.O.g.u.i.i.u.4.p.i.i.u.f.&.          # ,   .   o   ( ZXhXlXlX",
+"lXlXlXlXlXlXlXlXlXzXsXC.oXMXkXjXCXT.+.g.u.i.i.u.5.p.i.i.u.f.*.  X X X   $ 1   X X +   ) ZXhXlXlX",
+"lXlXlXlXlXlXlXlXlXlXcXZXnXkXlXhXCXY.o.h.u.i.p.u.4.p.i.i.u.g.&.          # ,   .   o   ( ZXhXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXhXkXlXlXjXVX!.@.p.e.r.r.e.4.t.r.r.e.i.-.w e e e q u h 0 e e t < #.VXjXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXzXgXyXtXyXyXyXyXyXtXyXyXyXtXuXdXdXdXdXdXdXsXdXdXdXdXdXfXlXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXzXcXcXcXcXcXcXcXcXcXcXcXcXcXxXxXxXxXxXxXxXxXxXxXxXxXxXlXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXlXlXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlX"
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/pattern-web.png b/icons/pattern-web.png
new file mode 100644 (file)
index 0000000..c957409
Binary files /dev/null and b/icons/pattern-web.png differ
diff --git a/icons/pattern.ico b/icons/pattern.ico
new file mode 100644 (file)
index 0000000..568dcf7
Binary files /dev/null and b/icons/pattern.ico differ
diff --git a/icons/pattern.rc b/icons/pattern.rc
new file mode 100644 (file)
index 0000000..965dbd8
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "pattern.ico"
diff --git a/icons/pattern.sav b/icons/pattern.sav
new file mode 100644 (file)
index 0000000..97c2396
--- /dev/null
@@ -0,0 +1,29 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :7:Pattern
+PARAMS  :5:10x10
+CPARAMS :5:10x10
+DESC    :67:3.4/2.2/4.1/2.3/2.3.2/4/6/6/3.1/1/5/5/1.1/4/5/6/2.3/3.3/1.1.3/1.1.4
+NSTATES :2:22
+STATEPOS:2:22
+MOVE    :8:F6,4,1,2
+MOVE    :8:F7,4,1,2
+MOVE    :8:F4,5,2,1
+MOVE    :8:F5,4,1,1
+MOVE    :8:E0,5,2,1
+MOVE    :8:E0,4,3,1
+MOVE    :8:F0,6,1,4
+MOVE    :8:F0,1,1,2
+MOVE    :8:F0,1,5,1
+MOVE    :8:E1,2,1,1
+MOVE    :8:F2,0,1,4
+MOVE    :8:E3,2,1,1
+MOVE    :8:F3,0,1,1
+MOVE    :8:F4,0,1,1
+MOVE    :8:F3,3,1,1
+MOVE    :8:E6,3,4,1
+MOVE    :8:F6,6,1,4
+MOVE    :8:F7,6,1,4
+MOVE    :8:E6,0,1,4
+MOVE    :8:E7,0,1,3
+MOVE    :8:E5,1,1,1
diff --git a/icons/pearl-16d24.png b/icons/pearl-16d24.png
new file mode 100644 (file)
index 0000000..f99ad2b
Binary files /dev/null and b/icons/pearl-16d24.png differ
diff --git a/icons/pearl-16d4.png b/icons/pearl-16d4.png
new file mode 100644 (file)
index 0000000..97da5db
Binary files /dev/null and b/icons/pearl-16d4.png differ
diff --git a/icons/pearl-16d8.png b/icons/pearl-16d8.png
new file mode 100644 (file)
index 0000000..f99ad2b
Binary files /dev/null and b/icons/pearl-16d8.png differ
diff --git a/icons/pearl-32d24.png b/icons/pearl-32d24.png
new file mode 100644 (file)
index 0000000..f96b218
Binary files /dev/null and b/icons/pearl-32d24.png differ
diff --git a/icons/pearl-32d4.png b/icons/pearl-32d4.png
new file mode 100644 (file)
index 0000000..94b19e1
Binary files /dev/null and b/icons/pearl-32d4.png differ
diff --git a/icons/pearl-32d8.png b/icons/pearl-32d8.png
new file mode 100644 (file)
index 0000000..f96b218
Binary files /dev/null and b/icons/pearl-32d8.png differ
diff --git a/icons/pearl-48d24.png b/icons/pearl-48d24.png
new file mode 100644 (file)
index 0000000..249a7f1
Binary files /dev/null and b/icons/pearl-48d24.png differ
diff --git a/icons/pearl-48d4.png b/icons/pearl-48d4.png
new file mode 100644 (file)
index 0000000..063b998
Binary files /dev/null and b/icons/pearl-48d4.png differ
diff --git a/icons/pearl-48d8.png b/icons/pearl-48d8.png
new file mode 100644 (file)
index 0000000..249a7f1
Binary files /dev/null and b/icons/pearl-48d8.png differ
diff --git a/icons/pearl-base.png b/icons/pearl-base.png
new file mode 100644 (file)
index 0000000..9502aed
Binary files /dev/null and b/icons/pearl-base.png differ
diff --git a/icons/pearl-ibase.png b/icons/pearl-ibase.png
new file mode 100644 (file)
index 0000000..9f8dd60
Binary files /dev/null and b/icons/pearl-ibase.png differ
diff --git a/icons/pearl-ibase4.png b/icons/pearl-ibase4.png
new file mode 100644 (file)
index 0000000..d32a0ac
Binary files /dev/null and b/icons/pearl-ibase4.png differ
diff --git a/icons/pearl-icon.c b/icons/pearl-icon.c
new file mode 100644 (file)
index 0000000..d238f3f
--- /dev/null
@@ -0,0 +1,891 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"&.2.8.q.6.5.7.5.4.4.2.4.4.7.6.*.",
+":.rXtX;XtX%X4XuXjXfX9XkXdXeX=X;.",
+"O.>.C % ^.3XeXL.:.;.#.>.9.1X2X-.",
+" .K 8   F.3XsX^ 3 Y A F @ [.6X=.",
+"=.xXB.i hX$XjXY 5.FX0XhXq `.7X=.",
+"*.*Xd.p 5X'.9XJ X.6X'.+X3 I.%X=.",
+"*.wXn.a dX%XaX .8.sX=X7X5 _.7X=.",
+"*.wXn.a dX>XoXmXdX$X1X6X5 _.7X=.",
+"*.rXm.s gX:X<X}.|.4X,X8X6 '.8X=.",
+"*.7Xx.p tXXXaXJ #.sX@X1X8 ^.1X=.",
+"*.,Xg.s tXoXiXI +.qX X-X4 R.=X=.",
+"*.rXN.r -XW.>XZ ;.zX;X,Xe.XX4X-.",
+"*.7XJ.@ 5 1 6 o e.sX%X+XIX>X$X>.",
+";.4XuX3X5X+X1X4XwXrX3X4X#.XX9X-.",
+"-.`.@X@X$X'.+X#X+X X_..X4 G..X=.",
+"&.#.@.+.+.@.@.+.@.#.#.@.&.#.#.&."
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"&.&.+.O.+.+.+.+.+.+.+.@.O.+.+.+.+.+.+.O.@.+.+.+.+.+.+.+.O.+.&.&.",
+"&.&.q.t.r.r.e.r.r.r.e.8.t.r.r.r.r.r.r.t.8.e.r.r.r.r.r.r.t.q.&.&.",
+"&.&.>XiXwXtXsXtXeXyX8X XiXrXrXrXrXrXeXiX X8XyXrXrXrXrXrXiX,X&.&.",
+"&.&.;XeXuXqXoXwXyXqX5X{.rX0X0XqXqXqX0XrX{.4XwX0XqXqX0X0XrX-X&.&.",
+"&.&.-XsX Xj . z @XiX3X{.tX0XqXwXwXwXqXyX}.6XrXwXwXwXqX0XtX;X&.&.",
+";.` q s 1   o   ^ hX2X{.eXpX(.9 u u u i w y u u u 6 T.aXwX;X&.&.",
+":.(   .   . o   Z fX3X{.wXaXE.  .               X   G.dXwX;X&.&.",
+"*.+.N.^.0..     N.fX2X{.wXaXW.  5 L.P.Y.k.G.I.U.e   J.dXwX;X&.&.",
+"%.*.6XfXbX'   | zX8X4X{.wXaXW.  w pXdXhX@XtXfXhXd   H.sXqX-X&.&.",
+"&.&.:XeXzX$.  =.cXeX9X XuXhX^.  q qXeXyX}.6XrXyXs   P.kXuX<X&.&.",
+"&.&.{.%X5X'   } 6X#XOXE.&X<XS.  8 #X&X=XE.OX&X=Xu   b.2X&X{.&.&.",
+"&.&.].#X3X'   } 4X@XoXR.%X>XZ.  0 +X$X&XR.oX%X&Xu   v.<X$X].&.&.",
+"&.&.<XyXcX@.  &.vXrX9X.XuXhX'.  e iXtXpX.X9XuXpXs   P.kXuX<X&.&.",
+"&.&.-X0XhXo.  #.jX8X4X{.wXiXn.' { [.yXwX{.4XqXeXa   H.sXqX-X&.&.",
+"&.&.;XqXjXO.  $.kX9X5X[.dX).oXUXPXP.:XiX{.5XwXrXs   J.dXwX;X&.&.",
+"&.&.;XqXjXO.  $.kX9X5X[.dXH.cXKXUX-X).fX[.5XwXrXs   J.dXwX;X&.&.",
+"&.&.;XqXjXO.  $.kX9X5X[.dX^.%XUXUXT.*XpX{.5XwXrXs   J.dXwX;X&.&.",
+"&.&.;XqXjXO.  $.kX9X5X{.rXyXb.1.:.).uXeX{.5XwXrXs   J.dXwX;X&.&.",
+"&.&.:XwXkXO.  $.lX0X6X}.eXdX).  w tXqXtX}.6XeXtXs   J.fXeX:X&.&.",
+"&.&.&X7XdXX.  @.fX5X1X'.8XyXY.  e 4X7X0X'.1X8X0Xa   F.iX8X&X&.&.",
+"&.&.!.[.%X/   ` &X'._.J.{.OXv.  6 '.{.|.J._.{.}.t   f.@X{.!.&.&.",
+"&.&.>XrXzX+.  $.lX0X6X}.eXsXQ.  q wXrXuX|.7XtXiXf   I.gXtX>X&.&.",
+"&.&.-X0XhXO.  *.mXuXwXOXaXlX(.  0 8X0XrX{.3XrX7Xj O B.gXqX-X&.&.",
+"&.&.;XqXjXO.  ` #X_./.Z.].XXc.  q 9XqXrX{.5XrXS.wXDXP.+XaX-X&.&.",
+"&.&.;XqXjX .  O             .   9 9XqXrX[.wXXXoXUXUXvXJ.dX=X&.&.",
+"&.&.;XqXhX*.O 4 1 1 < > 1 1 2 - h 9XqXrX{.qX@X[.UXUXgXL.dX=X&.&.",
+"&.&.;XtXqX6X2X3X2X4X:X(.6X2X3X2X3XqX0XtX{.4XuXK.].6XB.>XiX-X&.&.",
+"&.&.=XeX8XqXwXwXwXeX6X}.tXqXwXwXqX9X8XeX[.3XqXrXi   G.fX0X=X&.&.",
+"&.&.3XfXiXpXiXiXiXaXwXXXdXuXiXiXiXpXiXfXoXwXaXdXf   Y.xXaX3X&.&.",
+"&.&.J.!.E.E.E.E.E.W.Y.V.!.E.E.E.E.E.E.!.V.U.W.Q.g . u.).W.J.&.&.",
+"&.&.o.X.X.X.X.X.X.X.X.O.X.X.X.X.X.X.X.X.O.X.X.X.$.&.O.X.X.o.&.&.",
+"&.&.=.=.=.=.=.=.=.=.=.*.=.=.=.=.=.=.=.=.*.=.=.=.*.&.=.=.=.=.&.&."
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.",
+"&.&.&.&.*.*.*.*.*.*.*.*.*.*.*.*.*.&.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.&.&.&.&.",
+"&.&.&.&.$.@.@.@.@.@.@.@.@.@.@.@.@.$.@.@.@.@.@.@.@.@.@.@.@.@.#.#.@.@.@.@.@.@.@.@.@.@.@.@.&.&.&.&.",
+"&.&.&.%.2.8.6.7.7.7.7.7.7.7.7.7.7.<.7.7.7.7.7.7.7.7.7.7.6.8.2.4.7.7.7.7.7.7.7.7.7.7.7.7.=.&.&.&.",
+"&.&.=.X.`.iX9XqXqX0X0X0XqXqXqXqXwXT.qXqXqXqXqXqXqXqXqXqX0XiX].+XuX0XqXqXqXqXqXqXqXqXqXqX7.@.*.&.",
+"&.&.=.X.`.iX0XqX9XeXiXrX9XqXqXqXwXT.qXqXqXqXqXqXqXqXqXqX0XiX].+XuX0XqXqXqXqXqXqXqXqXqXqX7.@.*.&.",
+"&.&.=.X.(.tX6X7XsX7XoX1XdX0XqXqXqXT.0XqXqXqX9X7X8X8X8X8X6XrX_.XXeX7X8X8X8X7X8XqXqXqXqXqX7.@.*.&.",
+"&.&.=.o.+XbXfXxXN.t o 4 w.uX0XqXwXT.0XqXqX0XpXkXhXjXjXjXgXbX#X<XvXhXjXjXjXjXgXqXqXqXqXqX7.@.*.&.",
+"&.&.=. .5.v.v.q.$   . .   l.dX8XwXT.0XqX0XuX).f.x.z.z.z.l.b.6.e.v.l.z.z.z.k.N.0XqXqXqXqX7.@.*.&.",
+"&.%.-.{         X X   O   m iX0XqXT.0XqX7XlX=.  O                       .   6 8XwX0XqXqX7.@.*.&.",
+"&.%.-.{         X     X   d yX0XqXT.0XqX7XlX-.  $ X                     +   9 8XwX0XqXqX7.@.*.&.",
+"&.&.-.} V I U N       o   *.fX8XqXT.0XqX7XlX=.    t R K L U C S I L U A .   6 8XwX0XqXqX7.@.*.&.",
+"&.&.=.o.+XbXgXfX]   o   C 4XrX0XwXT.0XqX7XlX=.    1.DXsXgXbX#X<XcXfXbX-X.   6 8XwX0XqXqX7.@.*.&.",
+"&.&.=.X.).tX5XwXrX4     +XsX8XqXwXT.0XqX7XlX=.    &.hX4X6XtX_.XXeX5XtX}..   6 8XwX0XqXqX7.@.*.&.",
+"&.&.=.o.`.uX8X0X6X7   X |.yX9X0XqXY.0X0X6XkX*.    *.kX6X9XuX'.OXyX8XuX X.   6 8XqX9X0X0X7.@.*.&.",
+"&.&.=.X.{.gXrXiXrX7   . +XgXtXuXuXW.yXuXeXnX;.    ;.nXeXtXgX|.%XfXrXgX@X.   7 rXiXyXuXuX7.@.*.&.",
+"&.&.*.O.P.*XoX@XoX3   . Y.*XOX+X+XZ.+X+XXX2X .     .2XXXOX*XI.!.%XoX*XY..   3 oX@XOX+X+X3.#.*.&.",
+"&.&.*.+.D. X`.[.`.2   . F..X`.].].m.'.].).%X]     ] %X).'. XF.I.|.`..XF..   2 `.[.'.].].2.#.*.&.",
+"&.&.=.X.}.jXyXpXtX7   . #XhXyXiXpXQ.uXiXrXmX:.    :.mXrXuXhX X*XgXtXhX#X.   7 tXpXuXiXiX8.@.*.&.",
+"&.&.=.o._.uX8XqX7X6   .  XuX8X9X0XY.9X0X5XlX>.    >.lX5X8XuX'.OXtX7XuX X.   6 7XqX9X0X0X6.@.*.&.",
+"&.&.=.X.`.iX9XwX8X6   . .XiX9XqXwXT.0XqX0XdX` l l ` fX0X9XiX].+XuX9XiX.X.   6 8XwX0XqXqX7.@.*.&.",
+"&.&.=.X.`.iX9XwX8X6   . .XiX9XqXwXT.0X0XuXd.[.UXUX[.f.iX9XiX].+XuX9XiX.X.   6 8XwX0XqXqX7.@.*.&.",
+"&.&.=.X.`.iX9XwX8X6   . .XiX9XqXwXT.9XpX_.D.UXLXLXUXS._.iXuX].+XuX9XiX.X.   6 8XwX0XqXqX7.@.*.&.",
+"&.&.=.X.`.iX9XwX8X6   . .XiX9XqXwXT.8XgXS.&XUXLXLXUX&XS.fXyX].+XuX9XiX.X.   6 8XwX0XqXqX7.@.*.&.",
+"&.&.=.X.`.iX9XwX8X6   . .XiX9XqXwXT.9XaX~.P.UXJXJXUXL.~.pXuX].+XuX9XiX.X.   6 8XwX0XqXqX7.@.*.&.",
+"&.&.=.X.`.iX9XwX8X6   . .XiX9XqXwXT.0XqXrXt.<XUXUX,Xt.rX9XiX].+XuX9XiX.X.   6 8XwX0XqXqX7.@.*.&.",
+"&.&.=.X.`.iX9XwX8X6   . .XiX9XqXwXT.0X0XwXrX' ! ! ' rXwX9XiX].+XuX9XiX.X.   6 8XwX0XqXqX7.@.*.&.",
+"&.&.=.X.`.iX9XwX8X6   . .XiX9XqXwXT.0XqX6XcX<.    <.cX6X0XiX].+XuX9XiX.X.   6 8XwX0XqXqX7.@.*.&.",
+"&.&.=.X.`.iX9XwX9X6   . .XiX0XqXwXT.qXqX7XkX*.    *.kX7X0XiX].+XuX9XiX.X.   6 9XwXqXqXqX7.@.*.&.",
+"&.&.=.o.`.iX8XqX8X6   .  XuX9X0XqXT.0XqX7XkX*.    *.kX7X9XuX'.+XyX8XuX X.   6 8XqX0XqXqX7.@.*.&.",
+"&.&.*.@.m.Q.Y.R.U.1   . b.!.Y.T.T.l.T.T.I.'.^     ^ '.I.Y.Q.m.Z.W.U.!.b..   1 U.R.Y.T.T.<.$.&.&.",
+"&.&.=.X.'.pX9XeX9X6   . .XpX0XqXwXT.qXwX8XzX=.    =.zX8X0XpX].+XuX9XpX.X.   6 9XeXqXwXwX7.@.*.&.",
+"&.&.=.X.`.iX9XwX8X6   . .XiX9XqXqXT.0XqX7XlX*.    *.lX7X9XiX].+XyX9XuX.Xo   9 8XqX0XqXqX7.@.*.&.",
+"&.&.=.X.`.iX9XwX8X6   . {.rX6X7X8XI.7X7X3XgX&.    =.lX7X0XiX].+XuX8XsXOX    5 eXeX0XqXqX7.@.*.&.",
+"&.&.=.X.`.iX9XwX8X6   . >XMXjXlXzX'.kXlXgXGX2.    =.lX7X0XiX].+XyXeX4X9.Y.wXM.s.rXqXqXqX7.@.*.&.",
+"&.&.=.X.`.iX9XwX8X6   . ` %.+.@.@.W +.@.O.-.z     =.lX7X0XiX].+XtXiXg.<XUXKXUX/.A.sX9XqX7.@.*.&.",
+"&.&.=.X.`.iX9XwX8X9   + .                   X $   ;.lX7X0XiX].+XpX<Xl.UXKXLXUXCXd.tX0XqX7.@.*.&.",
+"&.&.=.X.`.iX9XwX8X6   .                       O   =.lX7X0XiX].+XpX<Xj.UXKXKXUXNXd.tX0XqX7.@.*.&.",
+"&.&.=.X.`.iX9XqX0Xq.:.<.,.,.,.,.<.` ,.<.,.,.,.1.%.L.pX9X0XiX].+XtXpXk.*XUXKXUXR.F.sX9XqX7.@.*.&.",
+"&.&.=.X.`.iX9XqXqXjXlXlXlXlXlXlXlX'.kXlXlXlXlXlXzXaX0XqX0XiX].+XyXwX7X0.C.<Xf.h.yXqXqXqX7.@.*.&.",
+"&.&.=.X.`.iX9XqXqX8X7X7X7X7X7X7X8XI.7X7X7X7X7X7X7X9XqXqX0XiX].+XuX8XaX@X    6 tXeX0XqXqX7.@.*.&.",
+"&.&.=.o._.uX8X9X9X9X9X9X9X9X9X9X0XY.9X0X9X9X9X9X9X9X9X9X8XyX'.OXtX7XyX|.O   9 6X0X9X0X9X6.@.*.&.",
+"&.&.=.X.}.jXuXiXiXiXiXiXiXiXiXiXpXQ.iXiXiXiXiXiXiXiXiXiXuXjX X*XgXyXhX#X.   7 yXpXiXiXiX8.@.*.&.",
+"&.&.*.+.S.}._.`.`.`.`.`.`.`.`.`.'.m.`.`.`.`.`.`.`.`.`.`._.}.D.P.{.).|.D..   2 ).'.`.`.`.2.$.*.&.",
+"&.&.&.*.+.X.o.X.X.X.X.X.X.X.X.X.X.@.o.X.X.X.X.X.X.X.X.X.o.X.+.O.X.X.o.X.{ [ { X.o.X.X.X.%.&.&.&.",
+"&.&.&.&.*.=.=.=.=.=.=.=.=.=.=.=.=.*.=.=.=.=.=.=.=.=.=.=.=.=.*.*.=.=.=.=.-.-.-.=.=.=.=.=.&.&.&.&.",
+"&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.%.%.%.&.&.&.&.&.&.&.&.&.",
+"&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&.&."
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/pearl-web.png b/icons/pearl-web.png
new file mode 100644 (file)
index 0000000..df8f8d1
Binary files /dev/null and b/icons/pearl-web.png differ
diff --git a/icons/pearl.ico b/icons/pearl.ico
new file mode 100644 (file)
index 0000000..642695b
Binary files /dev/null and b/icons/pearl.ico differ
diff --git a/icons/pearl.rc b/icons/pearl.rc
new file mode 100644 (file)
index 0000000..8b7eca9
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "pearl.ico"
diff --git a/icons/pearl.sav b/icons/pearl.sav
new file mode 100644 (file)
index 0000000..730ca85
--- /dev/null
@@ -0,0 +1,23 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :5:Pearl
+PARAMS  :5:6x6dt
+CPARAMS :5:6x6dt
+SEED    :15:901944054393278
+DESC    :17:BbBfWcWbWBaBeWgWa
+AUXINFO :72:f8bbe71b9be753d5fa143df207d7797ba62a9b3996eb8b8889487e1a2bd659d91a5e73e1
+NSTATES :2:14
+STATEPOS:1:7
+MOVE    :55:F4,2,0;F1,1,0;F4,1,0;F1,0,0;F8,0,0;F2,0,1;F8,0,1;F2,0,2
+MOVE    :27:F1,0,3;F4,1,3;F1,1,3;F4,2,3
+MOVE    :27:F8,3,0;F2,3,1;F8,3,1;F2,3,2
+MOVE    :97:F2,4,2;F8,4,1;F2,4,1;F8,4,0;F1,4,0;F4,5,0;F8,5,0;F2,5,1;F8,5,1;F2,5,2;F8,5,2;F2,5,3;F4,5,3;F1,4,3
+MOVE    :13:F4,4,2;F1,3,2
+MOVE    :13:F4,3,0;F1,2,0
+MOVE    :69:F2,2,3;F8,2,2;F2,2,2;F8,2,1;F4,2,1;F1,1,1;F8,1,1;F2,1,2;F4,1,2;F1,0,2
+MOVE    :41:F8,0,3;F2,0,4;F8,0,4;F2,0,5;F1,0,5;F4,1,5
+MOVE    :27:F1,1,4;F4,2,4;F1,2,4;F4,3,4
+MOVE    :13:F8,1,4;F2,1,5
+MOVE    :55:F1,3,5;F4,4,5;F1,4,5;F4,5,5;F2,5,5;F8,5,4;F4,5,4;F1,4,4
+MOVE    :13:F2,3,5;F8,3,4
+MOVE    :13:F2,4,4;F8,4,3
diff --git a/icons/pegs-16d24.png b/icons/pegs-16d24.png
new file mode 100644 (file)
index 0000000..0151991
Binary files /dev/null and b/icons/pegs-16d24.png differ
diff --git a/icons/pegs-16d4.png b/icons/pegs-16d4.png
new file mode 100644 (file)
index 0000000..397c712
Binary files /dev/null and b/icons/pegs-16d4.png differ
diff --git a/icons/pegs-16d8.png b/icons/pegs-16d8.png
new file mode 100644 (file)
index 0000000..13845b1
Binary files /dev/null and b/icons/pegs-16d8.png differ
diff --git a/icons/pegs-32d24.png b/icons/pegs-32d24.png
new file mode 100644 (file)
index 0000000..1f63698
Binary files /dev/null and b/icons/pegs-32d24.png differ
diff --git a/icons/pegs-32d4.png b/icons/pegs-32d4.png
new file mode 100644 (file)
index 0000000..cbfc0ef
Binary files /dev/null and b/icons/pegs-32d4.png differ
diff --git a/icons/pegs-32d8.png b/icons/pegs-32d8.png
new file mode 100644 (file)
index 0000000..841d5d2
Binary files /dev/null and b/icons/pegs-32d8.png differ
diff --git a/icons/pegs-48d24.png b/icons/pegs-48d24.png
new file mode 100644 (file)
index 0000000..8fcae14
Binary files /dev/null and b/icons/pegs-48d24.png differ
diff --git a/icons/pegs-48d4.png b/icons/pegs-48d4.png
new file mode 100644 (file)
index 0000000..51b3433
Binary files /dev/null and b/icons/pegs-48d4.png differ
diff --git a/icons/pegs-48d8.png b/icons/pegs-48d8.png
new file mode 100644 (file)
index 0000000..f320782
Binary files /dev/null and b/icons/pegs-48d8.png differ
diff --git a/icons/pegs-base.png b/icons/pegs-base.png
new file mode 100644 (file)
index 0000000..0b28a99
Binary files /dev/null and b/icons/pegs-base.png differ
diff --git a/icons/pegs-ibase.png b/icons/pegs-ibase.png
new file mode 100644 (file)
index 0000000..78e009d
Binary files /dev/null and b/icons/pegs-ibase.png differ
diff --git a/icons/pegs-ibase4.png b/icons/pegs-ibase4.png
new file mode 100644 (file)
index 0000000..9d0e317
Binary files /dev/null and b/icons/pegs-ibase4.png differ
diff --git a/icons/pegs-icon.c b/icons/pegs-icon.c
new file mode 100644 (file)
index 0000000..de73396
--- /dev/null
@@ -0,0 +1,656 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 225 2 ",
+"   c gray84",
+".  c #DADADA",
+"X  c gray86",
+"o  c #DADADA",
+"O  c #D9D9DA",
+"+  c #E3E3D8",
+"@  c #E5E5D8",
+"#  c #D8D8D9",
+"$  c #D5D5D5",
+"%  c #D5D5D5",
+"&  c #D5D5D5",
+"*  c #D5D5D5",
+"=  c #D5D5D5",
+"-  c gray86",
+";  c #DBDBDA",
+":  c #DADADC",
+">  c #E1E1DA",
+",  c #B5B5E2",
+"<  c #B0B0E5",
+"1  c #DADAD2",
+"2  c #D0D0D1",
+"3  c gray84",
+"4  c #D5D5D5",
+"5  c #D7D7D7",
+"6  c #CBCBCB",
+"7  c #A6A6A8",
+"8  c #CBCBC4",
+"9  c #BEBEDB",
+"0  c #0808FC",
+"q  c blue",
+"w  c #A4A4D5",
+"e  c #DADACF",
+"r  c #D4D4D6",
+"t  c #D5D5D5",
+"y  c #D7D7D7",
+"u  c gray81",
+"i  c #B1B1B2",
+"p  c #CECEC9",
+"a  c #C9C9DA",
+"s  c #1D1DF8",
+"d  c #1111FD",
+"f  c #B4B4D4",
+"g  c #D7D7D0",
+"h  c #D4D4D6",
+"j  c #D5D5D5",
+"k  c #D5D5D5",
+"l  c #D7D7D7",
+"z  c gray86",
+"x  c #D6D6D8",
+"c  c #DADAD3",
+"v  c #CDCDD9",
+"b  c #CBCBDC",
+"n  c #D5D5CD",
+"m  c #D0D0D1",
+"M  c gray84",
+"N  c #D5D5D5",
+"B  c gray84",
+"V  c #D2D2D2",
+"C  c #C0C0C0",
+"Z  c gray81",
+"A  c #D6D6D8",
+"S  c #C9C9C6",
+"D  c #CACAC6",
+"F  c #CECED0",
+"G  c gray82",
+"H  c gray84",
+"J  c #D5D5D5",
+"K  c #D5D5D5",
+"L  c #D5D5D5",
+"P  c #D5D5D5",
+"I  c #D5D5D5",
+"U  c #D5D5D5",
+"Y  c #D8D8D7",
+"T  c #CACACB",
+"R  c #9E9EA2",
+"E  c #C2C2C3",
+"W  c #DBDBDA",
+"Q  c #ACACB0",
+"!  c #AAAAAE",
+"~  c #CFCFCE",
+"^  c #CFCFD0",
+"/  c #D2D2D5",
+"(  c #D3D3D4",
+")  c gray83",
+"_  c #D1D1D4",
+"`  c #D1D1D4",
+"'  c gray83",
+"]  c #D5D5D5",
+"[  c #D5D5D5",
+"{  c #D7D7D4",
+"}  c #DBDBCA",
+"|  c #D7D7D2",
+" . c #D4D4D7",
+".. c #D9D9CE",
+"X. c #DBDBCE",
+"o. c #CFCFD1",
+"O. c #D9D9D6",
+"+. c #E9E9D9",
+"@. c #DEDEDA",
+"#. c #D9D9DB",
+"$. c #E5E5D8",
+"%. c #E5E5D9",
+"&. c #D7D7D9",
+"*. c #D5D5D4",
+"=. c #D7D7D4",
+"-. c #CECED6",
+";. c #8F8FE5",
+":. c #C3C3D9",
+">. c #E0E0D3",
+",. c #A7A7E0",
+"<. c #9F9FE1",
+"1. c #DEDED4",
+"2. c #CFCFDC",
+"3. c #9292E9",
+"4. c #CCCCDD",
+"5. c #E4E4D8",
+"6. c #A6A6E4",
+"7. c #A9A9E6",
+"8. c #D8D8D1",
+"9. c #D0D0D1",
+"0. c #E5E5D2",
+"q. c #6464EB",
+"w. c #4949F1",
+"e. c #C5C5D8",
+"r. c #0505FE",
+"t. c #B8B8DB",
+"y. c #6060EB",
+"u. c #5454ED",
+"i. c #BEBED8",
+"p. c blue",
+"a. c #0101FF",
+"s. c #ABABD3",
+"d. c #D9D9D0",
+"f. c #E2E2D2",
+"g. c #8383E5",
+"h. c #6868EA",
+"j. c #D3D3D5",
+"k. c #2323F8",
+"l. c #1515FB",
+"z. c #C8C8D8",
+"x. c #7F7FE6",
+"c. c #7575E8",
+"v. c #CECED7",
+"b. c #1C1CF9",
+"n. c #1F1FFB",
+"m. c #BCBCD2",
+"M. c #D6D6D0",
+"N. c #D4D4D5",
+"B. c #E2E2D2",
+"V. c #CACAD7",
+"C. c #DFDFD3",
+"Z. c #DEDED3",
+"A. c #D6D6D5",
+"S. c #D2D2D6",
+"D. c #E0E0D3",
+"F. c #D9D9D5",
+"G. c #CACADB",
+"H. c #D9D9D5",
+"J. c #DFDFD3",
+"K. c #D4D4D5",
+"L. c #D5D5D6",
+"P. c #D7D7CC",
+"I. c #CFCFD2",
+"U. c #DFDFD3",
+"Y. c #9898E1",
+"T. c #1B1BFA",
+"R. c #7F7FE6",
+"E. c #D8D8D4",
+"W. c #4242F2",
+"Q. c #3737F4",
+"!. c #C7C7D9",
+"~. c #D3D3D0",
+"^. c #BFBFBD",
+"/. c #D1D1CF",
+"(. c #CBCBD8",
+"). c #3D3DF2",
+"_. c #3E3EF4",
+"`. c #C4C4D0",
+"'. c #D4D4D1",
+"]. c #E5E5D2",
+"[. c #5F5FEC",
+"{. c #4545F1",
+"}. c #C0C0D9",
+"|. c #0202FF",
+" X c #A8A8E1",
+".X c #D2D2C6",
+"XX c #9D9DA3",
+"oX c #CECEC4",
+"OX c #B2B2DF",
+"+X c #0000FE",
+"@X c #A8A8D5",
+"#X c #DADACF",
+"$X c #D9D9D4",
+"%X c #C2C2D9",
+"&X c #6B6BEA",
+"*X c #B2B2DC",
+"=X c #E0E0D3",
+"-X c #8A8AE4",
+";X c #8282E5",
+":X c #D8D8D5",
+">X c #D5D5D5",
+",X c #CECECF",
+"<X c #D4D4D5",
+"1X c #D9D9D4",
+"2X c #8686E4",
+"3X c #8787E6",
+"4X c #D3D3CE",
+"5X c #D1D1D2",
+"6X c #D4D4D5",
+"7X c #D9D9D4",
+"8X c #E7E7D1",
+"9X c #DCDCD4",
+"0X c #D4D4D5",
+"qX c #E3E3D2",
+"wX c #E4E4D2",
+"eX c #D5D5D5",
+"rX c #D5D5D5",
+"tX c #D7D7D7",
+"yX c #D5D5D5",
+"uX c #D5D5D5",
+"iX c #E3E3D2",
+"pX c #E3E3D2",
+"aX c #D5D5D5",
+"sX c #D5D5D5",
+"dX c white",
+/* pixels */
+"  . X o O + @ # $ % & * * * * * ",
+"= - ; : > , < 1 2 3 4 * * * * * ",
+"5 6 7 8 9 0 q w e r t * * * * * ",
+"y u i p a s d f g h j * * * * * ",
+"k l z x c v b n m M N * * * * * ",
+"B V C Z A S D F G H J K L P I U ",
+"Y T R E W Q ! ~ ^ / ( ) _ ` ' ] ",
+"[ { } |  ...X.o.O.+.@.#.$.%.&.*.",
+"=.-.;.:.>.,.<.1.2.3.4.5.6.7.8.9.",
+"0.q.q w.e.r.q t.y.q u.i.p.a.s.d.",
+"f.g.q h.j.k.l.z.x.q c.v.b.n.m.M.",
+"N.B.V.C.Z.A.S.D.F.G.H.J.K.L.P.I.",
+"U.Y.T.R.E.W.Q.!.~.^./.(.)._.`.'.",
+"].[.q {.}.|.q  X.XXXoXOX+Xq @X#X",
+"$X%X&X*X=X-X;X:X>X,X<X1X2X3X4X5X",
+"6X7X8X9X0XqXwXeXrXtXyXuXiXpXaXsX"
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 54 1 ",
+"  c #0202FF",
+". c #0A0AFD",
+"X c #1313FC",
+"o c #1B1BFA",
+"O c #2323F9",
+"+ c #3434F5",
+"@ c #3D3DF2",
+"# c #4343EF",
+"$ c #4F4FEF",
+"% c #5454ED",
+"& c #5C5CEC",
+"* c #4141F1",
+"= c #4B4BF1",
+"- c #6565EB",
+"; c #6C6CEB",
+": c #7B7BE7",
+"> c #7171E9",
+", c #A6A6A6",
+"< c #ABABAB",
+"1 c #B3B3B3",
+"2 c #BBBBBB",
+"3 c #9F9FDF",
+"4 c #A4A4DE",
+"5 c #ABABDC",
+"6 c #BEBED7",
+"7 c #B4B4DC",
+"8 c #BCBCDA",
+"9 c #8383E5",
+"0 c #8B8BE3",
+"q c #8181E8",
+"w c #9393E2",
+"e c #9C9CE1",
+"r c #A1A1E1",
+"t c #C4C4C4",
+"y c #CDCDC3",
+"u c #CDCDCE",
+"i c #D4D4C2",
+"p c #D0D0CF",
+"a c #CDCDD3",
+"s c #C3C3D8",
+"d c #C8C8D8",
+"f c #D5D5D5",
+"g c #DCDCD3",
+"h c #D5D5D8",
+"j c #DADADA",
+"k c #F2F2CF",
+"l c #E2E2D2",
+"z c #EBEBD1",
+"x c #E7E7DE",
+"c c #EBEBDD",
+"v c #F1F1DF",
+"b c #DEDEE0",
+"n c #E2E2E2",
+"m c #E9E9E0",
+/* pixels */
+"ffffffffffffffffffffffffffffffff",
+"ffjjjjjjjjjjjjjhffffffffffffffjg",
+"fjnxnnnnxncvmmnfpfffffffffffffff",
+"ffffffffff6&%5gtpjffffffffffffff",
+"fffu<<tjfaO  .7yuffjfffjffffffff",
+"ffj2,<<ff3    ;iafffffffjfffffff",
+"ffht,,1hg5    qiuhffffffffffffff",
+"fffft2fffj-  *hyufffffffffffffff",
+"ffffjjffffg75jjtujffffffffgfffff",
+"fffffffffffggfftpfffffffffffffff",
+"fffjffjfffffffhyufffffffffffjfff",
+"fffu<<tffju<<tjtujffjfffffffffff",
+"ffj2,,<jfj1,,<ftufffffffffffffff",
+"ffft,,1ffft,,2jtpjffffffffffffff",
+"fffjttpffffttfftuffpffffffffffff",
+"fffffhffffffjffujbnjbnjbbjbjxgff",
+"fffjllgfffjglgffbxcccjnjmcccnuuf",
+"ffg8$@wgfg7=@3gfg5##3gag5*#4gtff",
+"ffao   3lso   5l8X   7l7.  .8yaf",
+"fl4    &z3 .  -k9 .  >k:  . qiaf",
+"fj7.   :z5    9z4    0ze    eiaf",
+"ffg:. $faf:. %jfg>. -ffg-  ;gtff",
+"fffgd8gfffgs8gfffgs6gfffgs6fftff",
+"fffflllffffllgffffjgffffglzfftff",
+"fff7*+0ffg5@+wgffjaufffl4++4gtff",
+"ffao   elsX   4lft<<tjf7.   7yff",
+"fl3 .  &k0    -lf1<<<hl:  . qiaf",
+"fg8.   :z7    0lft,,2jlr    4yaf",
+"ffl0X &ffg9..-jfffttfffg;..>gtff",
+"ffflasgjfflaslffffjjfffflaagjtff",
+"ffffffffffffffffffffffffffffffff",
+"ffffffffffffffffffffffffffffffff"
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 254 2 ",
+"   c blue",
+".  c #0101FF",
+"X  c #0303FE",
+"o  c #0202FF",
+"O  c #0404FE",
+"+  c #0505FE",
+"@  c #0606FE",
+"#  c #0808FD",
+"$  c #0909FD",
+"%  c #0A0AFC",
+"&  c #0B0BFD",
+"*  c #0A0AFE",
+"=  c #0C0CFD",
+"-  c #0F0FFC",
+";  c #1212FB",
+":  c #1414FA",
+">  c #1414FB",
+",  c #1717FA",
+"<  c #1616FB",
+"1  c #1010FC",
+"2  c #1212FC",
+"3  c #1313FC",
+"4  c #1919FA",
+"5  c #1A1AFA",
+"6  c #1B1BFA",
+"7  c #1C1CF9",
+"8  c #1D1DF9",
+"9  c #1E1EF9",
+"0  c #1F1FF9",
+"q  c #1E1EFA",
+"w  c #1F1FFA",
+"e  c #2323F7",
+"r  c #2727F7",
+"t  c #2828F7",
+"y  c #2929F7",
+"u  c #2A2AF6",
+"i  c #2B2BF7",
+"p  c #2C2CF6",
+"a  c #2D2DF6",
+"s  c #2F2FF6",
+"d  c #2121F8",
+"f  c #2020F9",
+"g  c #2121F9",
+"h  c #2222F8",
+"j  c #2323F8",
+"k  c #2525F8",
+"l  c #2626F8",
+"z  c #2929F8",
+"x  c #3737F3",
+"c  c #3131F5",
+"v  c #3232F5",
+"b  c #3333F5",
+"n  c #3131F6",
+"m  c #3535F4",
+"M  c #3434F5",
+"N  c #3636F4",
+"B  c #3737F4",
+"V  c #3B3BF3",
+"C  c #3C3CF3",
+"Z  c #3D3DF3",
+"A  c #3F3FF2",
+"S  c #3E3EF3",
+"D  c #3F3FF3",
+"F  c #3838F4",
+"G  c #3939F4",
+"H  c #3A3AF4",
+"J  c #4242F2",
+"K  c #4343F2",
+"L  c #4646F1",
+"P  c #4747F1",
+"I  c #4A4AF0",
+"U  c #7878E7",
+"Y  c #7979E7",
+"T  c #7D7DE6",
+"R  c #7E7EE6",
+"E  c #7171E9",
+"W  c #7676E8",
+"Q  c gray65",
+"!  c #A7A7A7",
+"~  c #A9A9A9",
+"^  c #AAAAAA",
+"/  c gray67",
+"(  c #ACACAC",
+")  c gray68",
+"_  c #AEAEAE",
+"`  c #AFAFAF",
+"'  c gray69",
+"]  c #B1B1B1",
+"[  c #B2B2B2",
+"{  c gray70",
+"}  c #B4B4B4",
+"|  c gray71",
+" . c #B6B6B6",
+".. c gray74",
+"X. c gray",
+"o. c #BEBEBF",
+"O. c gray75",
+"+. c #C2C2BF",
+"@. c #9C9CDF",
+"#. c #BCBCC0",
+"$. c #BDBDC0",
+"%. c #BEBEC2",
+"&. c #A1A1DF",
+"*. c #A2A2DF",
+"=. c #A4A4DF",
+"-. c #A6A6DE",
+";. c #A7A7DE",
+":. c #AAAADD",
+">. c #ABABDD",
+",. c #ACACDD",
+"<. c #ADADDD",
+"1. c #BEBED6",
+"2. c #B0B0DC",
+"3. c #B1B1DC",
+"4. c #BBBBDA",
+"5. c #BCBCDA",
+"6. c #8383E5",
+"7. c #8181E6",
+"8. c #8585E4",
+"9. c #8484E5",
+"0. c #8686E4",
+"q. c #8686E5",
+"w. c #8484E6",
+"e. c #8B8BE3",
+"r. c #8E8EE3",
+"t. c #8F8FE3",
+"y. c #8888E4",
+"u. c #8989E4",
+"i. c #8989E5",
+"p. c #8A8AE4",
+"a. c #8B8BE4",
+"s. c #8D8DE4",
+"d. c #9191E2",
+"f. c #9090E3",
+"g. c #9393E2",
+"h. c #9696E1",
+"j. c #9494E2",
+"k. c #9595E2",
+"l. c #9898E1",
+"z. c #9999E1",
+"x. c #9A9AE1",
+"c. c #9C9CE0",
+"v. c #9D9DE0",
+"b. c #9D9DE1",
+"n. c #9E9EE0",
+"m. c #9F9FE0",
+"M. c #C0C0C0",
+"N. c #C1C1C1",
+"B. c #C2C2C1",
+"V. c #C1C1C2",
+"C. c #C6C6C0",
+"Z. c gray77",
+"A. c #C8C8C8",
+"S. c #C9C9CB",
+"D. c #CACACA",
+"F. c #CACACB",
+"G. c #CBCBCB",
+"H. c gray80",
+"J. c #CCCCCD",
+"K. c #CDCDCD",
+"L. c #CECECE",
+"P. c gray81",
+"I. c #DDDDCF",
+"U. c #DEDECF",
+"Y. c #CFCFD2",
+"T. c #C8C8D4",
+"R. c #C9C9D7",
+"E. c #CACAD7",
+"W. c #CBCBD7",
+"Q. c #CCCCD7",
+"!. c #CDCDD7",
+"~. c #CFCFD6",
+"^. c #C1C1D9",
+"/. c #C2C2D9",
+"(. c #C6C6D8",
+"). c #C5C5DC",
+"_. c #CFCFDA",
+"`. c #D0D0D0",
+"'. c #D0D0D1",
+"]. c gray82",
+"[. c #D0D0D2",
+"{. c #D1D1D2",
+"}. c #D2D2D2",
+"|. c LightGray",
+" X c #D5D5D2",
+".X c #D2D2D5",
+"XX c #D3D3D5",
+"oX c #D0D0D6",
+"OX c #D1D1D6",
+"+X c #D2D2D6",
+"@X c #D3D3D6",
+"#X c gray83",
+"$X c #D4D4D5",
+"%X c #D5D5D5",
+"&X c #D6D6D4",
+"*X c #D6D6D5",
+"=X c #D7D7D5",
+"-X c #D4D4D6",
+";X c #D5D5D6",
+":X c gray84",
+">X c #D6D6D7",
+",X c #D7D7D7",
+"<X c #D8D8D1",
+"1X c #DCDCD0",
+"2X c #DEDED0",
+"3X c #DCDCD3",
+"4X c #DDDDD3",
+"5X c #DEDED2",
+"6X c #DEDED3",
+"7X c #DFDFD3",
+"8X c #D8D8D4",
+"9X c #D9D9D4",
+"0X c #DADAD4",
+"qX c #DBDBD4",
+"wX c #DCDCD4",
+"eX c #DDDDD4",
+"rX c #DEDED4",
+"tX c #DFDFD4",
+"yX c #DDDDD7",
+"uX c #DEDED7",
+"iX c #D6D6D8",
+"pX c #D7D7D8",
+"aX c #D6D6D9",
+"sX c #D7D7D9",
+"dX c #D8D8D8",
+"fX c #D9D9D8",
+"gX c #D8D8D9",
+"hX c gray85",
+"jX c #DADAD8",
+"kX c #DADADA",
+"lX c #DCDCDD",
+"zX c #DDDDDD",
+"xX c #DFDFDF",
+"cX c #E0E0D3",
+"vX c #E1E1D3",
+"bX c #E2E2D3",
+"nX c #E2E2D6",
+"mX c #E4E4D6",
+"MX c #DEDEE0",
+"NX c gray88",
+"BX c #E2E2E2",
+"VX c #E6E6E9",
+"CX c gray91",
+"ZX c #E9E9E8",
+"AX c #E8E8E9",
+"SX c #E9E9E9",
+"DX c #EAEAE8",
+"FX c #E8E8EB",
+"GX c #E9E9EB",
+"HX c #EAEAEA",
+"JX c #EEEEE8",
+"KX c #EDEDEA",
+"LX c gray93",
+"PX c #EFEFEF",
+/* pixels */
+"%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X",
+"%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X",
+"%X%X%X%X%X}.%X}.}.%X}.%X}.}.%X}.}.}.}.%X}.XX%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X",
+"%X%X%X%XzXzXzXzXzXzXzXzXzXzXzXzXzXzXlXzXzXzXzXdX X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X",
+"%X%X%XdXSXSXSXSXSXSXLXSXSXSXHXSXHXHXKXSXHXSXLX}.G.%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X",
+"%X%X%X%X}.}.}.%X}.}.}.}. X}.XX}.7X XT. X7X}.%Xo.G.pX%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X",
+"%X%X%X%X%X%X%X%X}.}.%X%X%XpX%XwXv.n 3 z e.7XpXV.G.%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X",
+"%X%X%X%X%X%X}. ._ _ L.pX%XXXcXv.          0.mX%.G.pX%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X",
+"%X%X%X%X%X%X .Q ^ Q _ }.%X%XwXH   @   @   e pXV.G.%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X",
+"%X%X%X%X%X%X_ ^ ^ _ ^ L.%X%X+Xq           % ).C.G.pX%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X",
+"%X%X%X%X%X%X .Q _ Q _ }.%XXXwXH   @   @   e pXV.G.pX%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X",
+"%X%X%X%X%X%XL. .^ ' G.%X%XXX7Xv.          0.mX%.G.%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X",
+"%X%X%X%X%X%X%X%XL.%XdX%X%X%XXX7Xx.a 3 k p.wXdXV.G.pX%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X",
+"%X%X%X%X%X%X%X%X%X%X%X%X%X%X%XXX7X8XT.%XcXXXdXV.G.%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X",
+"%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%XXX%X8X%XXXXX8XV.G.%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X",
+"%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%XpX%X%X%XdXV.G.%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X",
+"%X%X%X%X%X%X%X%XL.%XdX%X%X%X%X%XpX%XY.%X8X%XpXV.G.%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X",
+"%X%X%X%X%X%XY.' ^ _ G.%X%X%X%X%XL.' ^ _ G.%XdXV.G.dX%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X",
+"%X%X%X%X%X%X .Q ( Q _ }.%X%X%XpX .Q _ Q `  XdXV.G.%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X",
+"%X%X%X%X%X X_ ^ ^ ( ^ L.pX%X%X}.^ ^ ^ ^ / L.kXV.G.%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X",
+"%X%X%X%X%XdX .Q ^ ^ _ }.%X%X%X%X .Q ^ Q ' XXdXV.G.dX%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X",
+"%X%X%X%X%X%X}. .^ ' L.pX%X%X%X%X}.' _ ' L.%X%XV.G.dX%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X+X%X%X%X%X",
+"%X%X%X%X%X%X%X%X}.%X%X%X%X%X%X%X%X%X}.%X%X%XdXV.Z.%X}.}.%X}.%X}.}.%X}.%X}. X XXX}.%X}.XX%X%X%X%X",
+"%X%X%X%X%X%X%XXX%X%X%X%X%X%X%X%X%X%X%X%X%X%XdXZ.dXBXNXMXzXNXNXzXNXNXzXNXzXMXMXzXzXNXnXlXXX%X%X%X",
+"%X%X%X%X%X%XXX%X8X8XXX%X%X%X%X%XXX%X8X%XXX%X%XdXSXSXVXSXKXDXVXSXSXSXSXVXVXSXKXDXSXBXLXG.L.dX%X%X",
+"%X%X%X%X%XXXcX%X^.!.cX%X%X%X%XXXcX%X^.XXcX%X%X%X}.}.I.}.1.Y.I.}.}.}.}.}.7XY.1.}.7X}.%Xo.L.%X%X%X",
+"%X%X%X%XXX7Xk.k % 5 Y 8X%X%XXX7Xf.g % q T wX%X%XXXwXt.g % q 6.7XpX%X%XwX8.q % g p.wXdXV.L.%X%X%X",
+"%X%X%X%X7Xv.      @   W mXXX7Xh.          T cXXXcXt.          6.cXXXcXp.          e.cX%.L.%X%X%X",
+"%X%X%XXX7XA   @   @   5 !.%X9Xm   @   @   g XX%X8Xa   @   @   k %X%X%Xz   @   @   t 8XV.L.%X%X%X",
+"%X%X%X%X%Xg           @ 5.8XXXq           % ^.8X!.,           3 (.8XT.3           , +X+.L.pX%X%X",
+"%X%X%XXXwXL   @   @   g XX%X7XA   @   @   t XXXXwXH   @   @   a %X%X9Xn   @   %   m kX%.L.%X%X%X",
+"%X%X%XXX7X>.%         p.wXXX7X;.@         t.cXXX7Xv.@         h.7XXX7Xx.          v.cXo.L.%X%X%X",
+"%X%X%X%XXXwX,.A 5 n g.7X%X%XXX7X;.V 5 n x.7X%X%XXX7X*.H 5 m v.7XXX%XXX7Xm.H 5 H m.7XpXo.L.%X%X%X",
+"%X%X%X%X%XXX7X7XXX8XcX%X%X%X%XXX7XwXXX8X7XXX%X%X%XXX7X7XXXwXcXXX%X%X%XXXwXwXXX8X7XXXpX+.L.%X%X%X",
+"%X%X%X%X%X%XXX%XwX%X!.%X%X%X%X%XXXXXwX%XXX%X%X%X%X%XXXXX%XXXXX%X%X%X%X%XXX%X8X%XXXXX8Xo.L.pX%X%X",
+"%X%X%X%X%XXXwXXX4._.cX X%X%X%XXXcXXX4.!.wX%X%X%X%X%X%XXXpX%X%X%X%X%X%X%XcX!.5.XX7XXXdX%.L.dX%X%X",
+"%X%X%X%XXX7Xe.g @ , E 9X%X%XXXwXp.q @ , W 8X%X%X%X%XpXXXL.}.dX%X%X%X%XwXT 5 @ 5 6.wX%Xo.L.dX%X%X",
+"%X%X%XXXcXh.          E cXXXcXg.          U cXXX%X%XF.' ^ _ G.%X%XXXcX6.          0.mX%.G.%X%X%X",
+"%X%X%XXX7XA   @       5 _.%X8Xm   @   @   q XX%X%X%X' Q ^ Q ' %X%X%X%Xz   @   @   t 8X%.L.dX%X%X",
+"%X%X%X%X%Xg           @ 4.8XXXq           = (.8X%X}.( ^ ^ ^ ^ L.pX%XT.3           , _.+.L.%X%X%X",
+"%X%X%XXX7XI   @   @   k XXXXwXJ   @   @   t %X%X%X%X .Q ^ Q { %X%X%X8Xn   @   @   m kX+.L.8X%X%X",
+"%X%X%XXX7X3.=         e.cXXXwX>.%         h.wXXX%X%X}. ./  .Y.%X%X%XcXv.        @ *.cXo.L.%X%X%X",
+"%X%X%X%XXX7X,.L e H x.7XXX%X%X7X,.L g H v.7XXX%X%X%X8XdX}.%X%X%X%X%XXX7X;.A g A ;.7X%Xo.L.pX%X%X",
+"%X%X%X%X%XXX7X7X8X7X7XXX%X%X%XXX7XcX%XwXcX%X%X%X%X%X%X%X%X%X%X%X%X%X%XXX7X7X%X7X7XXXpX%.L.pX%X%X",
+"%X%X%X%X%X%XXXXX%XXXXX%X%X%X%X%XXXXX%XXXXX%X%X%X%X%X%X%X%X%X%X%X%X%X%X%XXXXX%XXXXX%X%X%X}.%X%X%X",
+"%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X",
+"%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X",
+"%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X%X"
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/pegs-web.png b/icons/pegs-web.png
new file mode 100644 (file)
index 0000000..662c7ea
Binary files /dev/null and b/icons/pegs-web.png differ
diff --git a/icons/pegs.ico b/icons/pegs.ico
new file mode 100644 (file)
index 0000000..c0fca9f
Binary files /dev/null and b/icons/pegs.ico differ
diff --git a/icons/pegs.rc b/icons/pegs.rc
new file mode 100644 (file)
index 0000000..c1df80c
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "pegs.ico"
diff --git a/icons/pegs.sav b/icons/pegs.sav
new file mode 100644 (file)
index 0000000..22b8a0d
--- /dev/null
@@ -0,0 +1,16 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :4:Pegs
+PARAMS  :8:7x7cross
+CPARAMS :8:7x7cross
+SEED    :15:103342250484448
+DESC    :49:OOPPPOOOOPPPOOPPPPPPPPPPHPPPPPPPPPPOOPPPOOOOPPPOO
+NSTATES :1:8
+STATEPOS:1:8
+MOVE    :7:3,1-3,3
+MOVE    :7:5,2-3,2
+MOVE    :7:5,4-5,2
+MOVE    :7:3,4-5,4
+MOVE    :7:6,4-4,4
+MOVE    :7:4,0-4,2
+MOVE    :7:2,0-4,0
diff --git a/icons/range-16d24.png b/icons/range-16d24.png
new file mode 100644 (file)
index 0000000..ae0a145
Binary files /dev/null and b/icons/range-16d24.png differ
diff --git a/icons/range-16d4.png b/icons/range-16d4.png
new file mode 100644 (file)
index 0000000..f704222
Binary files /dev/null and b/icons/range-16d4.png differ
diff --git a/icons/range-16d8.png b/icons/range-16d8.png
new file mode 100644 (file)
index 0000000..ae0a145
Binary files /dev/null and b/icons/range-16d8.png differ
diff --git a/icons/range-32d24.png b/icons/range-32d24.png
new file mode 100644 (file)
index 0000000..a5acf61
Binary files /dev/null and b/icons/range-32d24.png differ
diff --git a/icons/range-32d4.png b/icons/range-32d4.png
new file mode 100644 (file)
index 0000000..c7c5e04
Binary files /dev/null and b/icons/range-32d4.png differ
diff --git a/icons/range-32d8.png b/icons/range-32d8.png
new file mode 100644 (file)
index 0000000..a5acf61
Binary files /dev/null and b/icons/range-32d8.png differ
diff --git a/icons/range-48d24.png b/icons/range-48d24.png
new file mode 100644 (file)
index 0000000..b388f91
Binary files /dev/null and b/icons/range-48d24.png differ
diff --git a/icons/range-48d4.png b/icons/range-48d4.png
new file mode 100644 (file)
index 0000000..10d4453
Binary files /dev/null and b/icons/range-48d4.png differ
diff --git a/icons/range-48d8.png b/icons/range-48d8.png
new file mode 100644 (file)
index 0000000..b388f91
Binary files /dev/null and b/icons/range-48d8.png differ
diff --git a/icons/range-base.png b/icons/range-base.png
new file mode 100644 (file)
index 0000000..7f95faa
Binary files /dev/null and b/icons/range-base.png differ
diff --git a/icons/range-ibase.png b/icons/range-ibase.png
new file mode 100644 (file)
index 0000000..0a8a345
Binary files /dev/null and b/icons/range-ibase.png differ
diff --git a/icons/range-ibase4.png b/icons/range-ibase4.png
new file mode 100644 (file)
index 0000000..985dddb
Binary files /dev/null and b/icons/range-ibase4.png differ
diff --git a/icons/range-icon.c b/icons/range-icon.c
new file mode 100644 (file)
index 0000000..8e696a6
--- /dev/null
@@ -0,0 +1,891 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+". @ + + + @ @ O o + # + + + @ . ",
+"+ l._.E./.Z.I.).[.R.V.^.Q.`.c.. ",
+"+ `.zXuXgX&XtX~.g.iX#XfXqXzX}.  ",
+"+ E.uX5XwX|.8XU.1.tX].wXm.qX(.  ",
+"+ /.gXwXaX+XqXoX,XyXXXiXrXhX'.  ",
+"O V.*X|.#XY.`.&X#X}.P.+X|.*XS.  ",
+"+ I.qX7X5X'.&X2X1X:X).4X7XqXR.  ",
+"+ ^.pX/.pX+X4XyXrX9X.XuX/.pX`.  ",
+"+ Q.eXW.eX.X2XrXeX7X XyX^.uX_.  ",
+"O `.vXkXkX&X;X9X7X3X[.9X9XyX~.  ",
+"* S D S C E }. X X[.J.OX&X%XC.  ",
+", 3   o   i tXtXyX9XOX9Xd.8X[.  ",
+"> 5   +   i iX~.^.wX|.3XG Z..X  ",
+", 4   o   p dXyXuXyX%XpXM.*XOX  ",
+"; ,   X   q ).)._.~.C.`.].OXv.  ",
+"                                "
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"                                                                ",
+"  . - % % % % % % % % ; & % % % % % & % - & % % % % % & % = X   ",
+"  # -.b.z.x.x.x.x.z.c.4.l.x.z.l.l.l.z.b.6.g.c.z.x.x.x.l.M.1.    ",
+"  & n.hXyXiXiXiXiXuXsXH.rXiXdXxXlXfXyXjXI.4XsXuXiXiXiXtXcXH.  o ",
+"  % z.yX8X9X9X9X9X9XwXA.5XeX&XK.Q.OX9XyXG.;XeX9X8X7X9X7XdXZ.  o ",
+"  % x.iX9XqXqXqXqX0XrXS.5XsXU.~ 7 [ gXtXH.:XtX9XiXfX0X8XgXA.  o ",
+"  % x.iX9XqXqXqXqX0XrXS.7XqXyXlXb Y.aXuXH.:XwXhXd./ fX6XgXA.  o ",
+"  % x.iX9XqXqXqXqX0XrXS.7X9XhXa.V gX7XiXH.:XeXaXU.d.iX7XgXA.  o ",
+"  % x.iX9XqXqXqXqX0XrXS.7XqXaX~ A.gX7XpXH.:XtX9XaXhX0X8XgXA.  o ",
+"  % z.uX9X0X0X0X0X9XeXA.6XqX9X0XrX9X9XiXG.:XrX0X8X7X0X7XfXA.  o ",
+"  & v.sXeXrXrXrXrXeXiXF.0XtXrXtXeXrXwXdXK.<XiXrXrXrXrXqXkXF.  o ",
+"  # $.H.Z.S.S.S.S.A.F.,.C.S.S.A.A.S.Z.J.2.b.F.A.S.S.S.Z.I.,.  X ",
+"  % k.rX5X7X7X7X7X6X0XC.3X8X7X7X7X7X5XrXD.=X0X7X7X7X7X4XpXV.  o ",
+"  % x.pX0XwX9X0XwXqXtXS.8XeXwXwXwXwX0XaXJ.>XtXwX0X9XwX9XhXS.  o ",
+"  % x.iX9X0XjXaX9X0XrXS.7XwXqXqXqXqX9XiXH.:XtX9XaXjX0X8XgXA.  o ",
+"  % x.iX8XsX-.M.fX8XrXS.7XwXqXqXqXqX9XiXH.:XwXfXM.-.sX7XgXA.  o ",
+"  % x.iX8XsX-.M.fX8XrXS.7XwXqXqXqXqX9XiXH.:XwXfXM.-.sX7XgXA.  o ",
+"  % x.iX0X0XjXaX9XqXrXS.7XwXqXqXqXqX9XiXH.:XtX9XaXjX0X8XgXA.  o ",
+"  % l.rX6X8X5X6X8X7XqXZ.5X0X9X9X9X9X8XuXG.;XeX9X8X6X0X6XdXZ.  o ",
+"  % B.nXhXkXkXkXkXjXcXP.rXaXiXiXiXiXuXjXI.4XdXiXiXiXiXyXcXJ.  o ",
+"  % [ r.q.w.w.w.w.w.w.+.H.H.H.H.H.H.G.I.6.N.L.H.H.H.H.F.R.2.  X ",
+"  5 k   X         o   V 8X-X:X:X:X:X;X4XN.XX1X:X:X,X>X-X9Xv.  o ",
+"  7 v   + O O O O @   G jXwXtXwXwXtXeXdXK.1XiXtXtX7X0XwXkXF.  o ",
+"  7 x   X         o   S dX8X9XfXfX9X9XiXH.;XuX>XN S c.iXsXA.  o ",
+"  7 x   X         o   S fX7XaXM.M.aX8XiXH.;XpX-X0 x A.uXdXA.  o ",
+"  7 x   X         o   S fX6XjX-.-.jX6XiXH.:XyX2X3Xr.s yXfXA.  o ",
+"  7 x   X         o   S fX9X0XsXsX0X0XiXH.;XsX|.| N O.iXdXA.  o ",
+"  6 z   X         o   S aX6X8X7X7X8X6XyXF.-XwX7X^.Q.eX6XaXC.  o ",
+"  8 b   X         o   H MXsXgXgXgXgXdXcXR.9XkXfXvXbXfXaXNXP.  o ",
+"  < y   .         X   j P.C.A.A.A.A.Z.J.2.v.F.A.Z.Z.A.C.P.,.  X ",
+"                                                                ",
+"    .                 . o o o o o o o o X o o o o o o o o X     "
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"                                                                                                ",
+"      . o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o X         ",
+"                                                                                                ",
+"  .   > V l x x x x x x x x x x x N x x x x x x x x x x x x N x x x x x x x x x x x c x X       ",
+"  X   i 4.B.n.m.m.m.m.m.m.m.m.m.v.*.N.n.m.m.m.m.m.m.m.m.n.N.*.v.m.m.m.m.m.m.m.m.m.b.D.'   X     ",
+"  o   v C.hXyXiXiXiXiXiXiXiXiXpXeXa.dXuXiXiXyXyXyXyXiXiXuXdXa.eXpXiXiXiXiXiXiXiXiXrXMX:.  O     ",
+"  o   x n.yX6X9X9X9X9X9X9X9X9X0X4Xt.eX8X8XqXaXsXaXpXqX8X8XeXt.4X0X9X9X9X9X9X9X9X9X5XhX*.  O     ",
+"  o   x m.iX9XqXqXqXqXqXqXqXqXwX6Xy.tX9XeX1XI.P.Y.Q.2XeX0XtXy.6XwXqXqXqX9X0XqXqXqX7XlX=.  O     ",
+"  o   x m.iX9XqXqXqXqXqXqXqXqXwX6Xy.tX8XpX'.5 6 ; O ].iX8XtXy.6XwXqXqXqXpXiX0XqXqX7XlX=.  O     ",
+"  o   x m.iX9XqXqXqXqXqXqXqXqXwX6Xy.tX0XqX0X4XiXA c iX0X0XtXy.6XwXqXqXqX|.OXeXqXqX7XlX=.  O     ",
+"  o   x m.iX9XqXqXqXqXqXqXqXqXwX6Xy.tX0XqX0XsX{.  B.gX8X0XtXy.6XwXqXwXeX> x gX9XqX7XlX=.  O     ",
+"  o   x m.iX9XqXqXqXqXqXqXqXqXwX6Xy.tX0XqX8XlX` q rXqXqX0XtXy.6XwXqXqXwX8.f.uX0XqX7XlX=.  O     ",
+"  o   x m.iX9XqXqXqXqXqXqXqXqXwX6Xy.tX0X9XiX|.  i.hX8XqX0XtXy.6XwXqXqXqXkXgX0XqXqX7XlX=.  O     ",
+"  o   x m.iX9XqXqXqXqXqXqXqXqXwX6Xy.tX0X9XpXR.+.2XrX0XqX0XtXy.6XwXqXqXqX7X8XqXqXqX7XlX=.  O     ",
+"  o   x m.iX9XqXqXqXqXqXqXqXqXwX6Xy.tX0XqX0XiXzXwX0XqXqX0XtXy.6XwXqXqXqXqXqXqXqXqX7XlX=.  O     ",
+"  o   x M.pX0XwXwXwXwXwXwXwXwXrX7Xu.uXqXwXeXqX8XwXwXwXwXqXuXu.7XrXwXwXwXwXwXwXwXwX8XzX=.  O     ",
+"  X   z c.eX4X6X6X6X6X6X6X6X6X7X1Xe.0X5X6X6X6X6X6X6X6X6X5X0Xe.1X7X6X6X6X6X6X6X6X6X2XdX%.  O     ",
+"  X   0 ' a.t.y.y.y.y.y.y.y.y.u.e.E i.t.y.y.y.y.y.y.y.y.t.i.E e.u.y.y.y.y.y.y.y.y.r.j.S   X     ",
+"  o   c B.dXeXtXtXtXtXtXtXtXtXuX0Xi.aXrXtXtXtXtXtXtXtXtXrXaXi.0XuXtXtXtXtXtXtXtXtXqXvX;.  O     ",
+"  o   x n.uX8X0X0X0X0X0X0X0X0XqX5Xt.rX9X0X0X0X0X0X0X0X0X9XrXt.5XqX0X0X0X0X0X0X0X0X6XkX*.  O     ",
+"  o   x m.iX9XqXqXqX0XqXqXqXqXwX6Xy.tX0XqXqXqXqXqXqXqXqX0XtXy.6XwXqXqXqX0X0XqXqXqX7XlX=.  O     ",
+"  o   x m.iX9XqXqXwXyXeXqXqXqXwX6Xy.tX0XqXqXqXqXqXqXqXqX0XtXy.6XwXqXqXqXtXrXqXqXqX7XlX=.  O     ",
+"  o   x m.iX9XqXqX9X:X5XwXqXqXwX6Xy.tX0XqXqXqXqXqXqXqXqX0XtXy.6XwXqXqXqX,X1XwXqXqX7XlX=.  O     ",
+"  o   x m.iX9X9XpX{.  <.lX7XqXwX6Xy.tX0XqXqXqXqXqXqXqXqX0XtXy.6XwXqXwXeX> x gX9XqX7XlX=.  O     ",
+"  o   x m.iX9X0XyX%XJ B.dX9XqXwX6Xy.tX0XqXqXqXqXqXqXqXqX0XtXy.6XwXqXqXwX{ ,.pX9XqX7XlX=.  O     ",
+"  o   x m.iX9XqX0XrXzXpX0XqXqXwX6Xy.tX0XqXqXqXqXqXqXqXqX0XtXy.6XwXqXqXqXjXgX0XqXqX7XlX=.  O     ",
+"  o   x m.iX9XqXqX0X7X9XqXqXqXwX6Xy.tX0XqXqXqXqXqXqXqXqX0XtXy.6XwXqXqXqX8X8XqXqXqX7XlX=.  O     ",
+"  o   x m.iX9XqXqXqXqXqXqXqXqXwX6Xy.tX0XqXqXqXqXqXqXqXqX0XtXy.6XwXqXqXqXqXqXqXqXqX7XlX=.  O     ",
+"  o   x b.rX5X8X8X8X8X8X8X8X8X9X2Xt.rX9X0X0X0X0X0X0X0X0X9XrXt.5XqX0X0X0X0X0X0X0X0X6XkX*.  O     ",
+"  o   x S.nXgXjXjXjXjXjXjXjXjXkXdXs.pXrXtXtXtXtXtXtXtXtXrXaXi.0XuXtXtXtXtXtXtXtXtXqXvX;.  O     ",
+"  X   r S W E W W W W W W W W Q U F s.t.y.y.y.y.y.y.y.y.t.i.E e.u.y.y.y.y.y.y.y.y.r.j.S   X     ",
+"  X   F d   .                 o   R sX3X6X6X6X6X6X6X6X6X5X0Xe.1X7X6X6X6X5X5X5X6X6X2XdX%.  O     ",
+"  o   H h   + o o o o o o o o #   Q kX9XwXwXwXwXwXwXwXwXqXuXu.7XrXwXwXrXtXtXrXwXwX8XzX=.  O     ",
+"  o   H f   X                 o   W jX8XqXqXqXqXqXqXqXqX0XtXy.6XwXqXqX7X3X1X4XqXqX7XlX=.  O     ",
+"  o   H f   X                 o   W jX8XqXqXqXwXqXqXqXqX0XtXy.6XwX8XdXE 1 r j 4XeX7XlX=.  O     ",
+"  o   H f   X                 o   W jX8XqXqXqXeXwXqXqXqX0XtXy.6XwX8XhXm y <.J.tX0X7XlX=.  O     ",
+"  o   H f   X                 o   W jX8X0XtX,X& { jX8XqX0XtXy.6XwX9XsXW h 1 h :XrX6XlX=.  O     ",
+"  o   H f   X                 o   W jX8X0XrX1Xh ,.gX8XqX0XtXy.6XwXqXqXtXZX_.  b.hX4XlX=.  O     ",
+"  o   H f   X                 o   W jX8XqXqXwXgXpX0XqXqX0XtXy.6XwXrX>X%.m.`   W.aX5XlX=.  O     ",
+"  o   H f   X                 o   W jX8XqXqXqX9X9XqXqXqX0XtXy.6XwXwX9XO.g h j.uX0X7XlX=.  O     ",
+"  o   H f   X                 o   W jX8XqXqXqXqXqXqXqXqX0XtXy.6XwXqXwXgXsXdXsXqXqX7XlX=.  O     ",
+"  o   G d   .                 o   E dX4X7X7X7X7X7X7X7X7X6XqXr.2X8X7X7X5X6X5X5X7X7X3XgX&.  O     ",
+"  o   I j   X                 O   ( DXgXlXlXlXlXlXlXlXlXkXvXj.dXzXlXlXlXlXlXlXlXlXgXGX2.  O     ",
+"  .   e 2   .                 X   p 1.&.=.=.=.=.=.=.=.=.*.;.S %.=.=.=.=.=.=.=.=.=.&.2.v   X     ",
+"                                                                                                ",
+"      . .                         . O O O O O O O O O O O O X O O O O O O O O O O O O X         ",
+"                                                                                                ",
+"                                                                                                "
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/range-web.png b/icons/range-web.png
new file mode 100644 (file)
index 0000000..d772d83
Binary files /dev/null and b/icons/range-web.png differ
diff --git a/icons/range.ico b/icons/range.ico
new file mode 100644 (file)
index 0000000..e1012c8
Binary files /dev/null and b/icons/range.ico differ
diff --git a/icons/range.rc b/icons/range.rc
new file mode 100644 (file)
index 0000000..9f7672d
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "range.ico"
diff --git a/icons/range.sav b/icons/range.sav
new file mode 100644 (file)
index 0000000..708e7db
--- /dev/null
@@ -0,0 +1,36 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :5:Range
+PARAMS  :3:7x7
+CPARAMS :3:7x7
+SEED    :15:989032078841515
+DESC    :22:d7b3e8e5c7a7c13e4e8b4d
+UI      :1:0
+NSTATES :2:27
+STATEPOS:2:27
+MOVE    :5:W,4,2
+MOVE    :5:W,4,3
+MOVE    :5:W,4,4
+MOVE    :5:W,4,5
+MOVE    :5:W,4,6
+MOVE    :5:W,4,0
+MOVE    :5:W,3,1
+MOVE    :5:W,2,1
+MOVE    :5:W,1,1
+MOVE    :5:W,0,1
+MOVE    :5:W,6,1
+MOVE    :5:W,5,1
+MOVE    :5:W,5,5
+MOVE    :5:W,1,5
+MOVE    :5:B,5,2
+MOVE    :5:W,5,3
+MOVE    :5:W,6,3
+MOVE    :5:W,3,6
+MOVE    :5:W,2,6
+MOVE    :5:B,3,5
+MOVE    :5:W,2,4
+MOVE    :5:W,2,2
+MOVE    :5:B,2,3
+MOVE    :5:W,1,3
+MOVE    :5:W,3,3
+MOVE    :5:W,0,5
diff --git a/icons/rect-16d24.png b/icons/rect-16d24.png
new file mode 100644 (file)
index 0000000..58cc006
Binary files /dev/null and b/icons/rect-16d24.png differ
diff --git a/icons/rect-16d4.png b/icons/rect-16d4.png
new file mode 100644 (file)
index 0000000..33c8d7c
Binary files /dev/null and b/icons/rect-16d4.png differ
diff --git a/icons/rect-16d8.png b/icons/rect-16d8.png
new file mode 100644 (file)
index 0000000..58cc006
Binary files /dev/null and b/icons/rect-16d8.png differ
diff --git a/icons/rect-32d24.png b/icons/rect-32d24.png
new file mode 100644 (file)
index 0000000..ca213c9
Binary files /dev/null and b/icons/rect-32d24.png differ
diff --git a/icons/rect-32d4.png b/icons/rect-32d4.png
new file mode 100644 (file)
index 0000000..c32317b
Binary files /dev/null and b/icons/rect-32d4.png differ
diff --git a/icons/rect-32d8.png b/icons/rect-32d8.png
new file mode 100644 (file)
index 0000000..ca213c9
Binary files /dev/null and b/icons/rect-32d8.png differ
diff --git a/icons/rect-48d24.png b/icons/rect-48d24.png
new file mode 100644 (file)
index 0000000..b2587b1
Binary files /dev/null and b/icons/rect-48d24.png differ
diff --git a/icons/rect-48d4.png b/icons/rect-48d4.png
new file mode 100644 (file)
index 0000000..893b160
Binary files /dev/null and b/icons/rect-48d4.png differ
diff --git a/icons/rect-48d8.png b/icons/rect-48d8.png
new file mode 100644 (file)
index 0000000..b2587b1
Binary files /dev/null and b/icons/rect-48d8.png differ
diff --git a/icons/rect-base.png b/icons/rect-base.png
new file mode 100644 (file)
index 0000000..d2a732f
Binary files /dev/null and b/icons/rect-base.png differ
diff --git a/icons/rect-ibase.png b/icons/rect-ibase.png
new file mode 100644 (file)
index 0000000..f10b320
Binary files /dev/null and b/icons/rect-ibase.png differ
diff --git a/icons/rect-ibase4.png b/icons/rect-ibase4.png
new file mode 100644 (file)
index 0000000..30246e6
Binary files /dev/null and b/icons/rect-ibase4.png differ
diff --git a/icons/rect-icon.c b/icons/rect-icon.c
new file mode 100644 (file)
index 0000000..a65c646
--- /dev/null
@@ -0,0 +1,891 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"jX>X*X=X-X=X*X,X1X>X>X,X,X:X5XlX",
+"uXV.B.B.z.N.G.<...q.4.1.4.8.| 5X",
+"pXkXSXCXdXNXUX~.j.+X).`.x.l.i.;X",
+"pXyXzXjXqXgXBXI.h. X/.).N.G.e.:X",
+"pX4XrXwX:XwXvXZ.R 5.%.' :.,.W 4X",
+"pXtXlXgXeX3X&XR.w.k.c.@.U.R.,.,X",
+"pXpXbXzXsX*XK./.f.7.C.,.].|.8.>X",
+"pX0XdXpX5XtXnXH.u._.F.$.~.(.3.,X",
+"pX7XiXyX1XtXzXD.u.].D.#.!./.3.,X",
+"pXpXbXxXeXkXSXY.f..XP.*.`.[.6.>X",
+"pXyXxXkXyXkXAXU.a.|.J.&.).'.6.>X",
+"pX3XwXyX9.1.u.o.] -.[ ' /.).<.,X",
+"pXyXxXzX9.b.y.Q.R./.J.:.N.z.w.:X",
+"pXiXbXxXt.M.u.].^.}.T.2.N.k.y.;X",
+"aX8XiXaX2.g.b.h.j.v.s.} b.N.*.4X",
+"kXaXpXaXqX1X2X2X2X2X3X6X2X<X8XlX"
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"lXlXnXnXnXnXnXnXnXnXnXnXnXnXnXbXbXnXnXnXnXnXnXnXnXnXnXnXnXvXlXlX",
+"lXkX7X4X5X5X5X5X6X6X5X5X5X5X4X0X0X7X7X7X7X7X8X7X7X7X7X7X7XeXkXlX",
+"vXtXC v n n n n x x n n n b N 0 e j g g g g d g g g f h s e eXvX",
+"vXrX<XcXhXjXjXcX6X6XcXjXjXfXFX[ =. XW.!.Q.^.F.R.~.~.`.^.E.g 7XnX",
+"vXtX3XnXlXzXlXbX9X9XbXlXzXhXJX} ;.oX~./.^.).J.Q.(.:.} /.~.h 7XnX",
+"vXtX2XvXkXlXkXvX8X8XvXkXlXgXHX{ -.XXQ.^.~.(.H.E.`.g.U /.!.g 7XnX",
+"vXtX2XbXkXlXkXvX8X8XvXkXlXgXHX{ =. XW.~.!./.F.R.).F ` /.W.g 7XnX",
+"vXtX3XmXzXxXzXnX0X0XnXzXxXjXKX{ 2.*X'.{.[.|.Y.`.{.].).[.].j 7XnX",
+"cXtX:XhXaXsXaXhX2X2XhXaXaXuXVX[ ~ m.d.f.f.h.i.f.g.g.h.h.s.i 9XbX",
+"cXyX}.1X:X>X:X1X.X.X1X,X>X=XeX) 1 M c x z b - p v z x c k 4 wXbX",
+"vXrX5XNXcXvXcXNXqXqXNXjXcXcXPX} 2.-X/.}.|.#Xb c.#X[.{.}.].j 7XnX",
+"vXtX1XvXjXkXjXcX7X5XVX).~ ,XIX] =.oX6.K H.|.z d.].Q.!.~.W.g 7XnX",
+"vXtX2XbXkXlXkXvX8X7XmX0XH _.UX] -.@Xz.P U. Xz g.{.~.^./.!.g 7XnX",
+"vXtX2XvXkXlXkXvX8X6XBXW.,.>XIX[ =.%X_ ! T.|.z f.{.!.~./.!.g 7XnX",
+"vXrX4XMXxXcXxXMXqXqXMXcXBXvXLX} :.oX`.`.~.|.x h.|.^./.).~.g 7XnX",
+"cXtX&XiXrXrXrXiX:X:XiXeXeX0XbX) &.].I.Y.Y.(.l i.(.Y.T.R.U.f 7XnX",
+"cXyXXX5X1X2X2X6X+X+X6X2X2X,XpX~ @.^.H.K.J.W.j r.E.J.K.L.H.d 8XnX",
+"vXrX6XBXvXbXvXNXwXwXNXvXbXzXIX| :.+X^.(./. Xc h.|./.(.).^.h 7XnX",
+"vXtX1XvXjXkXjXcX7X7XcXjXkXfXGX{ -..XQ.~.!.{.x f.[.!.~.^.Q.g 7XnX",
+"vXtX2XbXkXlXkXvX8X8XvXkXlXgXHX{ ;.XX!.^.!.}.x g.{.~.^./.!.g 7XnX",
+"vXtX1XvXjXkXjXvX6X4XzXfXgXaXSX] =.|.E.Q.E.'.z f.[.!.~.^.Q.g 7XnX",
+"vXrX5XBXcXvXvXMXyXjXUXHXJXDXUX@.6.1X.XoXXX;Xn g.|./.(.).^.g 7XnX",
+"cXyX@XwX7X8X6XyXR.:.w.8.8.7.p.l n ..` ' ` } 5 a.Q.L.P.U.L.d 8XnX",
+"cXyX@XwX7X8X4XhX4.e } ` [ / / ` ] ' ' ' ` } 5 a.Q.R.R.U.L.d 8XnX",
+"vXrX5XBXcXvXzXUXw.| 7XY.L.+X#X^.].@XoXoXXX;Xn g. XB.G.`.^.g 7XnX",
+"vXtX1XvXjXkXfXHX9.~ OXc.B j.].F.I.~.Q.Q.E.'.z d.XX=.M (.!.g 7XnX",
+"vXtX2XbXkXlXgXJX9.^ *Xp.A K.`.J.T.(.^.^.!.}.x f.|.w.v E.^.g 7XnX",
+"vXtX1XvXjXkXfXHX9.~ %X8.o.H.).H.U.^.!.!.W.].x f.`.5.e.^.W.g 7XnX",
+"vXrX5XBXvXbXzXUXq.' -XXX%X}.|.E.(.|.{.{.].#Xv x.#XOX+X}.].j 7XnX",
+"cXuXoX5X1X2X,XuX7.g O.` ` ] { ( ` [ ] [ ]  .y U | ` ' { ` s wXbX",
+"lXlXuXrXtXtXtXrXtX6X<X1X1X1X1X2X2X1X1X1X1X1X0X4X1X1X1X1X1XwXlXlX",
+"lXlXcXvXvXvXvXvXvXnXmXmXmXmXmXmXmXmXmXmXmXmXbXmXmXmXmXmXmXvXlXlX"
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"lXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlX",
+"lXlXlXkXhXhXhXhXhXhXhXhXhXhXhXhXhXhXhXhXhXhXhXhXhXhXhXhXhXhXhXhXhXhXhXhXhXhXhXhXhXhXhXhXlXlXlXlX",
+"lXlXkXbXFXDXDXDXDXDXDXDXDXDXDXDXDXDXDXDXDXDXDXSXSXDXDXDXDXDXDXDXDXSXDXDXDXDXDXDXDXDXDXZXlXlXlXlX",
+"lXkXbX5XV.D.S.S.S.S.S.S.S.D.S.S.S.S.S.S.S.S.D.K.G.D.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.F.D.Q.lXlXlXlX",
+"lXhXSXL.  ; * * * * * * * # & * * * * * * * &   o + O O O O O O O o O O O O O O O @   q kXzXlXlX",
+"lXkXmX=X6XbXkXlXlXlXlXjXnXOXyXcXkXlXlXlXkXvXwX# g.}.~./././././.~.C.(.^./.`._.^.!..X,.3 xXzXlXlX",
+"lXkXmX*X7XmXlXzXzXzXzXlXMX+XuXvXzXzXzXzXlXnXeXo f.}.~./././././.^.C.(././.F.L.'.!.XX>.< xXzXlXlX",
+"lXkXmX*X5XbXkXkXkXkXkXjXnXoXtXcXkXkXkXkXjXvXwXo d.{.!.^.^.^.^.^.!.B.^.).G.L d 7.'.|.:.< xXzXlXlX",
+"lXkXmX*X6XbXkXlXlXlXlXkXnXOXyXcXlXlXlXlXkXbXwXo f.{.!.^.^.^.^.^.~.V./.^.!.OXS =.}.|.>.< xXzXlXlX",
+"lXkXmX*X6XbXkXlXlXlXlXkXnXOXyXcXlXlXlXlXkXbXwXo f.{.!.^.^.^.^.^.~.V./.^.(.S N T.!. X>.< xXzXlXlX",
+"lXkXmX*X6XbXkXlXlXlXlXkXnXOXyXcXlXlXlXlXkXbXwXo f.{.!.^.^.^.^.^.~.V.^.).H.M H 9._. X>.< xXzXlXlX",
+"lXkXmX*X5XbXkXkXkXkXkXjXnXoXtXxXkXkXkXkXjXvXqXO a.'.E.Q.Q.Q.Q.Q.W.M.!.W.~.}.'./.R.}.;.< xXzXlXlX",
+"lXkXmX*X0XBXvXbXbXbXbXcXVX#XaXMXvXbXbXbXcXNXyXo m.-X.XoXoXoXoXOX.XL.OXoXoX.X.XXX.X,X8., xXzXlXlX",
+"lXkXmX*X#XrX8X9X9X9X9X8XrX_.,XwX9X9X9X9X8XeX=X@ F { ) _ _ _ _ _ ` ^ ` _ _ _ _ _ ) | c 6 zXzXlXlX",
+"lXkXmX*X).=X$X$X$X$X$X#X=XY. X&X$X#X@X$X#X=X'.# 1 q 9 8 7 9 9 0 3   7 9 9 9 9 9 8 w * 0 lXzXlXlX",
+"lXkXmX*XwXCXnXmXmXmXmXbXAX$XdXBXnXSXHXNXbXCXpXo c.%X{.@X#X|.{.#XN.o E.oX}.|.|.|.[.-X5., xXzXlXlX",
+"lXkXmX*X5XvXjXkXkXkXkXhXbXoXrXzXzX*X(.iXkXcXqXo s.].Q.z.f.~.Q.'.j.  G.).Q.!.!.!.W.|.:.< xXzXlXlX",
+"lXkXmX*X6XbXkXlXlXlXlXkXnXOXyXxXvXV.Z ` VXxXwXo d.}.T.[ h ^ }.'.l.  H._.~.^.^.^.Q..X>.< xXzXlXlX",
+"lXkXmX*X6XbXkXlXlXlXlXkXnXOXyXxXbXfXQ +.VXxXwXo f.{.!.&X) +.}.'.l.  H._.~.^.^.^.Q..X>.< xXzXlXlX",
+"lXkXmX*X6XbXkXlXlXlXlXkXnXOXtXvXhX/.-.B vXvXwXo d.[.).Z t M.^.].l.  H._.~.^.^.^.Q..X>.< xXzXlXlX",
+"lXkXmX*X6XbXkXlXlXlXlXkXnXOXtXcXlXb.1..XvXcXwXo d.}.E.] / 3.(.].l.  H._.~.^.^.^.Q..X>.< xXzXlXlX",
+"lXkXmX*X5XvXjXkXkXkXkXhXbXoXtXxXkXZXFXmXhXvXqXo d.[.!..X.X[.Q.[.k.  H._.!.~.~.~.Q. X:.< xXzXlXlX",
+"lXkXmX*XqXCXnXnXnXnXnXbXZX$XsXBXnXvXxXbXbXVXiXO h. X/.^.^./.(.|.x.  L.].(.).).)./.OX<.< xXzXlXlX",
+"lXkXmX*X|.4X,X,X,X,X,X>X4XQ.$X2X,X,X,X,X>X3XoXo w.R.G.J.J.J.G.T.y.  n.I.H.J.J.J.F.!.@.1 xXzXlXlX",
+"lXkXmX*X|.4X,X,X,X,X,X>X4XQ.$X2X,X,X,X,X>X3XoXo w.R.G.J.J.J.G.T.y.  n.I.H.J.J.J.F.!.@.1 xXzXlXlX",
+"lXkXmX*XqXCXnXnXnXnXnXbXZX$XsXBXnXnXnXnXbXVXiXO h. X/.).).).(.|.x.  L.].(.).).)./.OX<.< xXzXlXlX",
+"lXkXmX*X5XvXjXkXkXkXkXhXbXoXtXxXkXkXkXkXjXcXqXo d.[.!.~.~.~.!.].k.  H._.!.~.~.~.Q. X:.< xXzXlXlX",
+"lXkXmX*X6XbXkXlXlXlXlXkXnXOXyXcXlXlXlXlXkXbXwXo f.{.!.^.^.^.!.[.l.  H._.~.^.^.^.Q..X>.< xXzXlXlX",
+"lXkXmX*X6XbXkXlXlXlXlXkXnXOXyXcXlXlXlXlXkXbXwXo f.{.!.^.^.^.!.[.l.  H._.~.^.^.^.Q..X>.< xXzXlXlX",
+"lXkXmX*X6XbXkXlXlXlXlXkXnXOXyXcXlXlXlXlXkXbXwXo f.{.!.^.^.^.!.[.l.  H._.~.^.^.^.Q..X>.< xXzXlXlX",
+"lXkXmX*X6XbXkXlXlXlXlXkXnXOXyXcXlXlXlXlXkXbXwXo f.{.!.^.^.^.!.[.l.  H._.~.^.^.^.Q..X>.< xXzXlXlX",
+"lXkXmX*X5XvXjXkXkXkXkXhXbXXXeXzXhXjXjXjXhXxX0Xo s.].Q.!.!.!.Q.'.j.  G.).!.~.~.~.Q. X:.< xXzXlXlX",
+"lXkXmX*XwXCXnXmXmXmXmXbXZX*XlXFXZXAXAXAXCXGXhX@ v.$X{.|.|.|.{.#XN.  L.[.(.).).)./.+X<.< xXzXlXlX",
+"lXkXmX*X).=X$X$X$X$X$X$X*XJ p f f d d d d f a . 3 0 9 9 9 9 9 0 2 O z.H.A.S.S.S.Z.T.o.1 xXzXlXlX",
+"lXkXmX*X#XrX8X9X9X9X9X9XwX8 j [ ^ ~ ! ^ / ^ ) ^ ` _ _ _ _ _ ) [ J X B.E.I.L.L.I.L.).%.1 xXzXlXlX",
+"lXkXmX*X0XBXvXbXbXbXvXvXBX5 3.1X.X;X1X%XoXOX+XP..XOXoXoXoXoXXX=XC.  K.'.(.XXoX`.~.OX,.< xXzXlXlX",
+"lXkXmX*X5XbXkXkXkXkXkXkXvX4 $.}.!.1.~ c./.W.!.M.E.Q.Q.Q.Q.Q.E.`.h.  G.`.T.*.+.F.^. X:.< xXzXlXlX",
+"lXkXmX*X6XbXkXlXlXlXlXlXbX4 &.XXQ.u.) h [.!./.V.!.^.^.^.^.^.!.[.l.  H.`.E.:.6 ;.{.|.>.< xXzXlXlX",
+"lXkXmX*X6XbXkXlXlXlXlXlXbX4 &..X!.}.) 7.[.!.(.V.!.^.^.^.^.^.!.[.l.  H.(.'.A.g <.[.|.>.< xXzXlXlX",
+"lXkXmX*X6XbXkXlXlXlXlXlXbX4 &. X'.M : ,.~.^./.V.!.^.^.^.^.^.!.[.l.  G.[.V. .j O.{.|.>.< xXzXlXlX",
+"lXkXmX*X6XbXkXlXlXlXlXlXbX4 &..X!.x.p.f.^.~./.V.!.^.^.^.^.^.!.[.k.  G.`.R.y.j.(.Q. X:.< xXzXlXlX",
+"lXkXmX*X5XvXjXkXkXkXjXkXvX5 -.OX^.}. X|.).(._.Z./.).).).).)./.|.x.X L.].). X}.(.^.OX<.1 xXzXlXlX",
+"lXkXmX&X0XVXbXbXbXbXbXbXBX5 O.).L.P.L.P.I.I.Y.z.L.U.I.I.I.I.P.!.a.  B.R.P.L.P.I.L.(.%.1 xXzXlXlX",
+"lXkXnX:X^.+X.XXXXXXXXXXXOXn i b x c c c c c c l x c c c c c x v k w z c c c c c x b i M lXzXlXlX",
+"lXlXlXlXaXiXpXpXpXpXpXpXiXjXhXdXfXfXfXfXfXfXfXfXfXfXfXfXfXfXfXdXgXkXfXdXfXfXfXfXfXdXgXlXlXlXlXlX",
+"lXlXlXlXxXxXxXxXxXxXxXxXxXzXzXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXzXzXxXxXxXxXxXxXxXxXzXlXlXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlX"
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/rect-web.png b/icons/rect-web.png
new file mode 100644 (file)
index 0000000..29d5623
Binary files /dev/null and b/icons/rect-web.png differ
diff --git a/icons/rect.ico b/icons/rect.ico
new file mode 100644 (file)
index 0000000..b91f5f9
Binary files /dev/null and b/icons/rect.ico differ
diff --git a/icons/rect.rc b/icons/rect.rc
new file mode 100644 (file)
index 0000000..13f0a8b
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "rect.ico"
diff --git a/icons/rect.sav b/icons/rect.sav
new file mode 100644 (file)
index 0000000..17264da
--- /dev/null
@@ -0,0 +1,17 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :10:Rectangles
+PARAMS  :3:7x7
+CPARAMS :3:7x7
+DESC    :33:a3d2b2a3_2a4a8h2a3c4_2b2c3a3_3c3b
+NSTATES :2:10
+STATEPOS:2:10
+MOVE    :8:R0,6,3,1
+MOVE    :8:R6,4,1,3
+MOVE    :8:R3,6,3,1
+MOVE    :8:R4,4,2,1
+MOVE    :8:R3,5,3,1
+MOVE    :8:R6,1,1,3
+MOVE    :8:R5,0,2,1
+MOVE    :8:R5,1,1,2
+MOVE    :8:R4,3,2,1
diff --git a/icons/samegame-16d24.png b/icons/samegame-16d24.png
new file mode 100644 (file)
index 0000000..1996137
Binary files /dev/null and b/icons/samegame-16d24.png differ
diff --git a/icons/samegame-16d4.png b/icons/samegame-16d4.png
new file mode 100644 (file)
index 0000000..2ee4390
Binary files /dev/null and b/icons/samegame-16d4.png differ
diff --git a/icons/samegame-16d8.png b/icons/samegame-16d8.png
new file mode 100644 (file)
index 0000000..ba83c36
Binary files /dev/null and b/icons/samegame-16d8.png differ
diff --git a/icons/samegame-32d24.png b/icons/samegame-32d24.png
new file mode 100644 (file)
index 0000000..2ce716b
Binary files /dev/null and b/icons/samegame-32d24.png differ
diff --git a/icons/samegame-32d4.png b/icons/samegame-32d4.png
new file mode 100644 (file)
index 0000000..37834ea
Binary files /dev/null and b/icons/samegame-32d4.png differ
diff --git a/icons/samegame-32d8.png b/icons/samegame-32d8.png
new file mode 100644 (file)
index 0000000..e0c5837
Binary files /dev/null and b/icons/samegame-32d8.png differ
diff --git a/icons/samegame-48d24.png b/icons/samegame-48d24.png
new file mode 100644 (file)
index 0000000..ac21a68
Binary files /dev/null and b/icons/samegame-48d24.png differ
diff --git a/icons/samegame-48d4.png b/icons/samegame-48d4.png
new file mode 100644 (file)
index 0000000..2ce6f47
Binary files /dev/null and b/icons/samegame-48d4.png differ
diff --git a/icons/samegame-48d8.png b/icons/samegame-48d8.png
new file mode 100644 (file)
index 0000000..40f3764
Binary files /dev/null and b/icons/samegame-48d8.png differ
diff --git a/icons/samegame-base.png b/icons/samegame-base.png
new file mode 100644 (file)
index 0000000..23a831b
Binary files /dev/null and b/icons/samegame-base.png differ
diff --git a/icons/samegame-ibase.png b/icons/samegame-ibase.png
new file mode 100644 (file)
index 0000000..23a831b
Binary files /dev/null and b/icons/samegame-ibase.png differ
diff --git a/icons/samegame-ibase4.png b/icons/samegame-ibase4.png
new file mode 100644 (file)
index 0000000..538ad05
Binary files /dev/null and b/icons/samegame-ibase4.png differ
diff --git a/icons/samegame-icon.c b/icons/samegame-icon.c
new file mode 100644 (file)
index 0000000..680df95
--- /dev/null
@@ -0,0 +1,720 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 248 2 ",
+"   c #E6E6E6",
+".  c #E2E2E2",
+"X  c #E2E2E2",
+"o  c #E2E2E2",
+"O  c #E2E2E2",
+"+  c #E2E2E2",
+"@  c #E2E2E2",
+"#  c #E1E1E2",
+"$  c #DDDEE1",
+"%  c #E2DDDE",
+"&  c #E0DEDD",
+"*  c #DEE1DE",
+"=  c #E6E6E6",
+"-  c #E2E2E2",
+";  c gray87",
+":  c #E2E2E2",
+">  c #E1E1E1",
+",  c #E2E2E2",
+"<  c #E2E2E2",
+"1  c #E2E2E2",
+"2  c #E2E2E2",
+"3  c #DEE0DE",
+"4  c #E9E3EA",
+"5  c #BAD0B7",
+"6  c #169B0E",
+"7  c #E2362B",
+"8  c #A14E26",
+"9  c #2CA13B",
+"0  c #E6E3E2",
+"q  c #E2E2E2",
+"w  c #E2E2E2",
+"e  c gray91",
+"r  c #E7E7E7",
+"t  c #E7E7E7",
+"y  c #E7E7E7",
+"u  c #E7E7E7",
+"i  c #E7E7E7",
+"p  c #ECE8EF",
+"a  c #F9F5ED",
+"s  c #C3CDD4",
+"d  c #054A99",
+"f  c #EB1329",
+"g  c #9B3D04",
+"h  c #0F9C25",
+"j  c #E6E3E2",
+"k  c #E2E2E2",
+"l  c #E1E1E1",
+"z  c #E7E7E7",
+"x  c #E6E6E6",
+"c  c #E6E6E6",
+"v  c #E6E6E6",
+"b  c #E6E6E6",
+"n  c #E4E5E5",
+"m  c #C2D5C3",
+"M  c #CBD3DA",
+"N  c #9C99F0",
+"B  c #0005FF",
+"V  c #BF1468",
+"C  c #9B4426",
+"Z  c #468D30",
+"A  c #E6E3E3",
+"S  c #E6E6E6",
+"D  c #E5E6E5",
+"F  c #E9E7E9",
+"G  c #DCE0E0",
+"H  c #1F9E06",
+"J  c #12449C",
+"K  c #0100FF",
+"L  c #0B05F8",
+"P  c #000DFF",
+"I  c #771597",
+"U  c #FF241E",
+"Y  c #E3E2E7",
+"T  c #E2E2E2",
+"R  c #E1E1E2",
+"E  c #E7E7E7",
+"W  c #E6E6E6",
+"Q  c #E6E6E6",
+"!  c #E5E5E6",
+"~  c #E8E8E6",
+"^  c #DCDDE5",
+"/  c #2637D9",
+"(  c #0304FB",
+")  c #1221DB",
+"_  c #157E45",
+"`  c #C72142",
+"'  c #9C3F27",
+"]  c #3D902D",
+"[  c #E6E3E3",
+"{  c #E2E2E2",
+"}  c #E4E4E1",
+"|  c #E8E8E7",
+" . c #E6E6E6",
+".. c #E6E6E6",
+"X. c #E5E5E6",
+"o. c #E9E8E8",
+"O. c #E0E0E6",
+"+. c #2649B4",
+"@. c #0A34AA",
+"#. c #19587C",
+"$. c #059200",
+"%. c #992B63",
+"&. c #923549",
+"*. c #6D7D2B",
+"=. c #E5E3E4",
+"-. c #E2E2E2",
+";. c #D9D9E3",
+":. c #E0E0E8",
+">. c #E3E3E6",
+",. c #E6E6E6",
+"<. c #E6E6E6",
+"1. c gray90",
+"2. c #D3DED2",
+"3. c #1B9215",
+"4. c #088900",
+"5. c #0F860A",
+"6. c #1F870A",
+"7. c #0131EA",
+"8. c #740C9B",
+"9. c #FF2311",
+"0. c #E3E2E7",
+"q. c #E1E1E2",
+"w. c #2020F7",
+"e. c #9A9AEF",
+"r. c #FFFFE3",
+"t. c #DFE1E3",
+"y. c #F3ECF3",
+"u. c #9EC69F",
+"i. c #08800E",
+"p. c #089300",
+"a. c #0C4AAA",
+"s. c #4B16D6",
+"d. c #FF110A",
+"f. c #FD0F06",
+"g. c #FF0100",
+"h. c #FD2124",
+"j. c #E8E3E2",
+"k. c #DFDFE2",
+"l. c #1717F8",
+"z. c #3030FB",
+"x. c #8A8AF0",
+"c. c #E5E7E2",
+"v. c #F5EBF5",
+"b. c #92C394",
+"n. c #007B00",
+"m. c #B85100",
+"M. c #4C20B0",
+"N. c #181DDD",
+"B. c #7D6B00",
+"V. c #76189A",
+"C. c #6D2D6B",
+"Z. c #747C25",
+"A. c #E4E3E5",
+"S. c #DDDDE3",
+"D. c #1F1FF5",
+"F. c blue",
+"G. c #3636F9",
+"H. c #E9EAE3",
+"J. c #F2EBF1",
+"K. c #96BCAB",
+"L. c #156845",
+"P. c #E31742",
+"I. c #6B249F",
+"U. c #0D47B9",
+"Y. c #11853A",
+"T. c #0C3CD6",
+"R. c #1D4F8E",
+"E. c #518F21",
+"W. c #E2E3E6",
+"Q. c #DDDDE3",
+"!. c #2520F7",
+"~. c #3B3BF8",
+"^. c #F2F1E5",
+"/. c #FBFBE4",
+"(. c #9E9BEE",
+"). c #0000FC",
+"_. c #0904FF",
+"`. c #126E63",
+"'. c #138234",
+"]. c #2800FF",
+"[. c #0F8A37",
+"{. c #7E5C02",
+"}. c #FF192C",
+"|. c #E4E5E2",
+" X c #DDDDE1",
+".X c #2D912E",
+"XX c #113FA3",
+"oX c #0D05FF",
+"OX c #383AF6",
+"+X c #3A3BF8",
+"@X c #201EFE",
+"#X c #0A0CF5",
+"$X c #008451",
+"%X c #9E4D1D",
+"&X c #C83B18",
+"*X c #038B3D",
+"=X c #0C42CC",
+"-X c #7F1981",
+";X c #FF2213",
+":X c #E4E3E6",
+">X c #DDDDE2",
+",X c #25696E",
+"<X c #0928C1",
+"1X c blue",
+"2X c #0202FB",
+"3X c #310ADA",
+"4X c #871685",
+"5X c #785A08",
+"6X c #932D57",
+"7X c #9A1C6E",
+"8X c #806400",
+"9X c #1817E7",
+"0X c #4222A0",
+"qX c #AB621A",
+"wX c #E3E2E7",
+"eX c #DEDEE3",
+"rX c #3B31F8",
+"tX c #1B17FF",
+"yX c #2424FC",
+"uX c #2825F9",
+"iX c #1523FF",
+"pX c #8336A8",
+"aX c #FF2306",
+"sX c #FF1A17",
+"dX c #6830BB",
+"fX c #3B2FE1",
+"gX c #FF271A",
+"hX c #5326E9",
+"jX c #1A5EA6",
+"kX c #32AF30",
+"lX c #E3E3E6",
+"zX c #E6E6E6",
+"xX c #E2E4E3",
+"cX c #E2E3E6",
+"vX c #E2E2E8",
+"bX c #E2E2E8",
+"nX c #E2E2E8",
+"mX c #E3E2E7",
+"MX c #E4E2E6",
+"NX c #E3E4E2",
+"BX c #E6E3E3",
+"VX c #E6E3E3",
+"CX c #E3E5E2",
+"ZX c #E3E3E7",
+"AX c #E4E3E6",
+"SX c #E7E3E2",
+"DX c #E6E6E6",
+"FX c white",
+/* pixels */
+"  . X o O O O O + @ # $ % & * = ",
+"- ; : > , < 1 2 3 4 5 6 7 8 9 0 ",
+"q w e r t y u i p a s d f g h j ",
+"k l z x c v b n m M N B V C Z A ",
+"O , t c S D F G H J K L P I U Y ",
+"T R E W Q ! ~ ^ / ( ) _ ` ' ] [ ",
+"{ } |  ...X.o.O.+.@.#.$.%.&.*.=.",
+"-.;.:.>.,.<.1.2.3.4.5.6.7.8.9.0.",
+"q.w.e.r.t.y.u.i.p.a.s.d.f.g.h.j.",
+"k.l.z.x.c.v.b.n.m.M.N.B.V.C.Z.A.",
+"S.D.F.G.H.J.K.L.P.I.U.Y.T.R.E.W.",
+"Q.!.F.~.^./.(.)._.`.'.].[.{.}.|.",
+" X.XXXoXOX+X@X#X$X%X&X*X=X-X;X:X",
+">X,X<X1X2XF.3X4X5X6X7X8X9X0XqXwX",
+"eXrXtXyXuXiXpXaXsXdXfXgXhXjXkXlX",
+"zXxXcXvXbXnXmXMXNXBXVXCXZXAXSXDX"
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 185 2 ",
+"   c #007600",
+".  c #007C01",
+"X  c #2E7E0E",
+"o  c #17792C",
+"O  c #217F39",
+"+  c #7B5D19",
+"@  c #4A7C15",
+"#  c #606817",
+"$  c #4B7E27",
+"%  c #1D7846",
+"&  c #247B4B",
+"*  c #257A51",
+"=  c #195C78",
+"-  c #1D6D64",
+";  c #1D7172",
+":  c #2B746A",
+">  c #326F7A",
+",  c #517A71",
+"<  c #FF0000",
+"1  c #FE090A",
+"2  c #FF1F0C",
+"3  c #FF0F17",
+"4  c #FF1013",
+"5  c #FF1B15",
+"6  c #F51D1D",
+"7  c #FB161B",
+"8  c #D32D14",
+"9  c #EB2C16",
+"0  c #E93918",
+"q  c #EB1D2E",
+"w  c #F51A22",
+"e  c #D4392A",
+"r  c #E32F24",
+"t  c #FF2C25",
+"y  c #FA292E",
+"u  c #FF3327",
+"i  c #E3223B",
+"p  c #E72933",
+"a  c #F32E31",
+"s  c #FB3132",
+"d  c #FC3A3F",
+"f  c #BD441F",
+"g  c #BB512D",
+"h  c #BF4B37",
+"j  c #B14F27",
+"k  c #A56234",
+"l  c #BF235E",
+"z  c #A51F7B",
+"x  c #B92A6D",
+"c  c #A7207D",
+"v  c #BC3576",
+"b  c #D3234C",
+"n  c #D82F41",
+"m  c #D22857",
+"M  c #E32B46",
+"N  c #B65D42",
+"B  c #86784C",
+"V  c #93724C",
+"C  c #8D7260",
+"Z  c #C94E4F",
+"A  c #DA5C4C",
+"S  c #C9475E",
+"D  c #D94C6D",
+"F  c #018200",
+"G  c #028C00",
+"H  c #0B830C",
+"J  c #008B0A",
+"K  c #058A07",
+"L  c #059307",
+"P  c #139407",
+"I  c #1D8C1E",
+"U  c #168616",
+"Y  c #099317",
+"T  c #22930E",
+"R  c #23931D",
+"E  c #1B8921",
+"W  c #1E8620",
+"Q  c #1E8332",
+"!  c #1C942C",
+"~  c #238B23",
+"^  c #3B8422",
+"/  c #3C8C2B",
+"(  c #229620",
+")  c #32922F",
+"_  c #228639",
+"`  c #249232",
+"'  c #329531",
+"]  c #319A30",
+"[  c #339F3B",
+"{  c #528939",
+"}  c #198F44",
+"|  c #298A47",
+" . c #3B885E",
+".. c #499D49",
+"X. c #529254",
+"o. c #49A348",
+"O. c #59A859",
+"+. c #988151",
+"@. c #86894F",
+"#. c #723CAD",
+"$. c #6535BF",
+"%. c #6B27B6",
+"&. c #22539A",
+"*. c #2B6983",
+"=. c #256883",
+"-. c #376C8D",
+";. c #224FA0",
+":. c #2A5BA4",
+">. c #2753A2",
+",. c #274AB6",
+"<. c #294ABA",
+"1. c #2B58BE",
+"2. c #3057A5",
+"3. c #3460AB",
+"4. c #535D9D",
+"5. c #7F4B94",
+"6. c #4259B6",
+"7. c #446AAB",
+"8. c #4973A3",
+"9. c #293BD2",
+"0. c #233AD3",
+"q. c #0101FE",
+"w. c #0B02FF",
+"e. c #010DFF",
+"r. c #0E0AFE",
+"t. c #130EFF",
+"y. c #1609FF",
+"u. c #0A14FF",
+"i. c #0315FF",
+"p. c #1719FC",
+"a. c #2D1CED",
+"s. c #1D29FF",
+"d. c #1B28E4",
+"f. c #2327EB",
+"g. c #2323F1",
+"h. c #2823FE",
+"j. c #222CFC",
+"k. c #2E2EFC",
+"l. c #2020FB",
+"z. c #2E39FF",
+"x. c #3537FC",
+"c. c #3C34ED",
+"v. c #4C18DC",
+"b. c #5333D7",
+"n. c #403AFF",
+"m. c #3D47DA",
+"M. c #4A4AEF",
+"N. c #4A4AF6",
+"B. c #6363F4",
+"V. c #6B6BF3",
+"C. c #9D368E",
+"Z. c #A6368C",
+"A. c #8128A2",
+"S. c #8639A7",
+"D. c #984C96",
+"F. c #9348A0",
+"G. c #9851B1",
+"H. c #854FC3",
+"J. c #638DA0",
+"K. c #9CBF9C",
+"L. c #97C397",
+"P. c #A3C7A6",
+"I. c #B1CCB7",
+"U. c #9C9CEE",
+"Y. c #B8B9EB",
+"T. c #A9A7F0",
+"R. c #DCDCDC",
+"E. c #D5D5D5",
+"W. c #DDE2DD",
+"Q. c #E4E4DF",
+"!. c #D8D8E8",
+"~. c #C8C8E9",
+"^. c #DFE4E4",
+"/. c #E5E5E5",
+"(. c #E8E7E7",
+"). c #ECECE6",
+"_. c #E7E7E8",
+"`. c #E9E5E9",
+"'. c #E7ECEC",
+"]. c #E9E9E9",
+"[. c #E7E8E7",
+"{. c #F1EDED",
+"}. c #F4F4E4",
+"|. c #FFFFE3",
+" X c #F3ECF3",
+/* pixels */
+"/./.(.(.(._._._._._.(.(.(.(.(.[.[.(.(.[.`.(.(.`.(.(.[./._./././.",
+"[./.Q.!.Q.Q.!.Q.Q./.Q.!./.Q.R././.R.Q./.R./././.!./.^./././.(./.",
+"[.R.E.Q.R.R.R.R.R.R.R.R.R.R.R.R.R.R.R.E.`.K.U ! h 6 s { U ..[._.",
+"[./.R.].(.(.(.(.[.[.[.[././.[.[.(.(._._. XL.  J j < 4 ^   ' ]./.",
+"/.Q.R.[.[./.[./._./.[.[./././.[._./././. XP.o } h < 4 ^ . ' '.(.",
+"[./.R.[.[./.(././.(././._.[././././././.).U.q.u.x < 3 ^   ' ].(.",
+"[./.R.(./.[./.(.(./.[./.[././.[. X{.{.).|.U.q.e.x < 4 ^ F ' '.(.",
+"(.R.R.(./././.[./.[././.[./././.I.P.I.T.Y.V.q.q.A.x v B # @.].(.",
+"(.R.R.(.[././.[./.(./._._./.[.Q.W   & r.q.q.q.q.q.q.u.i < s './.",
+"[./.R.[./.(.[./.(./.[./././.[./.( F | r.q.q.q.q.q.q.u.i < s './.",
+"(.!.R.(._./.[./.[./././._./././.7.&.,.w.q.9.> =.D.c C.V + +.]./.",
+"(./.R.[./.(./._././.(.[./././.^.p.q.q.q.q.:.G L j < 4 ^ F ' './.",
+"(.Q.R.(._._.(./.(././.(./._.[./.h.w.r.r.w.2.F J j < 5 / G [ ].(.",
+"/.Q.R._./././././._.[./././././. .% & & & _ . G 4.v.b.Z 8 A [.`.",
+"[.R.R.[././._._.(././.(._./.[./.I F F F . . . G ,.q.u.i < s '.(.",
+"[./.R././._./././.[.[././././.W.E   H E I I W T 6.y.k.i < s [./.",
+"[.Q.c.r.N.)./././.[././. XO.U ~ H F | h.u.S.2 6 6 w w 1 < s [./.",
+"[./.g.q.x.}.).)././.[./. Xo.  F F F & w.q.#.< < < < < 1 < s '.(.",
+"[.Q.f.q.k.~.Y.!.[./././. Xo.  H $ @ , w.q.#.0 e n b b e 8 A [./.",
+"_.Q.g.q.q.w.q.B.}./././. X..  ~ y < b i.q.:.L L 1.q.p.} K [ './.",
+"_.Q.g.q.q.e.q.B.}./././. Xo.F R 6 < b e.q.>.G G ,.q.t._ F ] ]./.",
+"_.Q.g.q.q.w.q.B.}./././.).J.= -.C.z D.:.>.8.*.*.8.>.3.B + +.].(.",
+"[.Q.g.q.q.q.q.B.}.!.^.^.).M.q.w.q.q.0.P G *.q.q.: G Y r < s ]._.",
+"_.Q.g.q.q.q.q.V.|.}.}.}.|.M.q.w.w.q.9.P G *.q.w.: G Y r < s ]./.",
+"/./., - -.r.q.k.V.B.B.B.V.p.q.p.> - , g f C ; : 7.,.1.p < s [.(.",
+"/./.~ F Q u.q.q.q.q.q.q.q.q.q.p.~ F $ 1 < j K L ,.q.e.i < s './.",
+"(.Q.) F | t.q.q.r.q.q.q.q.u.e.j.` K { 5 1 k Y P <.q.u.M < d '.(.",
+"(./.m.d.f.q.q.q.q.q.q.q.e.v q i r 9 S c.a.F.9 0 $.q.t.X.T X.]./.",
+"_.W.g.q.w.q.q.q.q.q.q.q.e.b < < < < b e.q.A.< < %.q.t._   / ].(.",
+"_./.M.k.x.k.k.k.k.k.k.k.z.D t s s t D x.j.G.u u H.s.n.X.~ O.`.(.",
+"/./.[.).).).).).).).).[.).'.].'.'.'.'.).].).'.'.[.).].].].].(./.",
+"(././._._././._._./._._./././.(./././._./././.(./._./.(.(././.(."
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 164 2 ",
+"   c #007600",
+".  c #007C00",
+"X  c #FE0000",
+"o  c #FF150D",
+"O  c #FF1415",
+"+  c #FF1C15",
+"@  c #FF1519",
+"#  c #FE1B1C",
+"$  c #EF251F",
+"%  c #FD201D",
+"&  c #F2221E",
+"*  c #FC1E23",
+"=  c #EC2424",
+"-  c #EF2B27",
+";  c #EF242F",
+":  c #EF2F2A",
+">  c #F62326",
+",  c #FA2425",
+"<  c #F42926",
+"1  c #F82827",
+"2  c #F2252F",
+"3  c #F02E29",
+"4  c #EF302A",
+"5  c #FF392E",
+"6  c #F22730",
+"7  c #FF3634",
+"8  c #FE3538",
+"9  c #F63F3F",
+"0  c #FD3C3C",
+"q  c #FC3E36",
+"w  c #F4453B",
+"e  c #F9413C",
+"r  c #ED3D40",
+"t  c #F63D44",
+"y  c #DF4C48",
+"u  c #EF4943",
+"i  c #EF434E",
+"p  c #EA4C4C",
+"a  c #F2434A",
+"s  c #EA4A51",
+"d  c #FE5353",
+"f  c #F85857",
+"g  c #F85759",
+"h  c #F35E5A",
+"j  c #F45960",
+"k  c #038103",
+"l  c #158F0F",
+"z  c #0C8B13",
+"x  c #178D15",
+"c  c #1B8E14",
+"v  c #158E1B",
+"b  c #1C8D1B",
+"n  c #16911D",
+"m  c #1F901C",
+"M  c #218C1D",
+"N  c #21901E",
+"B  c #1D8D22",
+"V  c #1F8C2B",
+"C  c #208620",
+"Z  c #248D25",
+"A  c #2C8B24",
+"S  c #258C2B",
+"D  c #2B8C2A",
+"F  c #308B26",
+"G  c #328C29",
+"H  c #259025",
+"J  c #299229",
+"K  c #2F9F2F",
+"L  c #359B2E",
+"P  c #278B30",
+"I  c #298E31",
+"U  c #2B9E37",
+"Y  c #359C34",
+"T  c #3A9E34",
+"R  c #3C963E",
+"E  c #319E3A",
+"W  c #3C9C3B",
+"Q  c #38A02B",
+"!  c #40973B",
+"~  c #439B39",
+"^  c #439C37",
+"/  c #4D9447",
+"(  c #419C43",
+")  c #4C9A42",
+"_  c #449B4A",
+"`  c #4D9A4B",
+"'  c #529A47",
+"]  c #52974E",
+"[  c #489450",
+"{  c #479A52",
+"}  c #519750",
+"|  c #4CA14D",
+" . c #51A94B",
+".. c #4DA952",
+"X. c #57A157",
+"o. c #53A955",
+"O. c #5CA858",
+"+. c #68B368",
+"@. c #0101FF",
+"#. c #0A0AFE",
+"$. c #0E17FF",
+"%. c #1515FE",
+"&. c #1917FF",
+"*. c #1419FF",
+"=. c #1B1BFD",
+"-. c #201EFE",
+";. c #1F22F6",
+":. c #1E21FE",
+">. c #252AEF",
+",. c #292AEC",
+"<. c #3429EF",
+"1. c #2B30EF",
+"2. c #3E3BEB",
+"3. c #2224F5",
+"4. c #2C26F4",
+"5. c #262AF5",
+"6. c #292DF2",
+"7. c #2222FC",
+"8. c #2A2AFA",
+"9. c #3229F2",
+"0. c #2B30F0",
+"q. c #3C3EF6",
+"w. c #3535FD",
+"e. c #3833FF",
+"r. c #313AFD",
+"t. c #3C3BFD",
+"y. c #4B3EF0",
+"u. c #413CFB",
+"i. c #3C40F8",
+"p. c #474AE1",
+"a. c #4245EB",
+"s. c #4F44EC",
+"d. c #464AEF",
+"f. c #484FEA",
+"g. c #504CE6",
+"h. c #5247EC",
+"j. c #5849E8",
+"k. c #5757EB",
+"l. c #4244F4",
+"z. c #4943F1",
+"x. c #4349F0",
+"c. c #4140FB",
+"v. c #4948F8",
+"b. c #4E52FF",
+"n. c #5C5CF4",
+"m. c #5355FC",
+"M. c #CECECE",
+"N. c #D7D8D7",
+"B. c #DBDBDB",
+"V. c #D4D6D4",
+"C. c #E5E5DC",
+"Z. c #DEDEE7",
+"A. c #D7D7E8",
+"S. c #DCE5E5",
+"D. c #E6E6E6",
+"F. c #E8E7E7",
+"G. c #E7E8E7",
+"H. c #E9E9E6",
+"J. c #E6E6E8",
+"K. c #E8E7E8",
+"L. c #E9E9E9",
+"P. c #F1F1E8",
+"I. c #F1EDF1",
+"U. c #E8F1F1",
+/* pixels */
+"D.D.D.D.G.D.D.G.D.D.D.D.D.D.F.J.D.D.D.J.F.D.J.D.D.D.D.D.D.D.D.D.D.D.F.D.D.D.D.D.D.D.D.D.D.D.D.D.",
+"D.D.D.D.D.D.D.D.G.F.G.D.D.D.F.L.D.D.D.D.L.L.D.D.D.D.D.D.G.D.D.D.D.D.D.D.D.D.D.D.D.D.G.D.D.D.G.D.",
+"D.D.D.D.L.J.L.L.L.L.L.L.F.F.L.L.L.J.L.L.L.L.L.L.L.J.F.G.L.J.L.L.D.D.D.L.L.D.D.G.D.F.G.D.D.D.G.D.",
+"D.D.D.G.Z.C.B.B.Z.B.B.B.B.C.B.B.B.C.B.B.B.B.B.B.B.C.Z.B.B.C.B.B.Z.D.D.Z.D.S.S.S.Z.F.Z.D.D.D.D.D.",
+"D.D.G.Z.M.B.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.N.R A C / r = = y ! A C X.L.D.D.D.",
+"D.D.G.B.N.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.G.L.L.L.D.b . . G # X X : v   . R I.D.D.D.",
+"D.D.L.C.N.G.G.D.D.D.D.D.D.D.D.D.J.D.D.D.F.D.D.D.D.D.D.D.D.D.F.Z.b   . G * X X 4 b k . W I.D.D.G.",
+"D.D.L.B.N.L.D.G.D.D.D.D.F.D.G.D.J.J.D.D.J.D.D.D.D.G.D.D.D.D.D.D.L c l ~ # X X : v . . W I.D.D.D.",
+"D.D.L.B.N.L.D.D.D.D.D.D.G.D.D.D.G.D.D.G.D.D.D.D.D.D.D.D.D.D.D.D.a.>.5.g.+ X X : n k . W I.D.D.D.",
+"G.D.L.B.N.L.D.F.J.D.D.D.D.D.D.D.D.D.D.D.D.D.G.D.D.G.D.D.G.D.G.Z.=.@.@.4.% X X 4 n . . W I.D.D.G.",
+"D.D.L.B.N.L.D.D.D.D.D.D.D.D.D.L.D.D.D.D.G.D.D.D.D.D.D.D.D.D.D.Z.:.@.@.9.+ X X 4 v . . R I.D.D.D.",
+"D.J.L.C.N.L.D.D.D.D.D.G.D.D.D.D.G.D.D.G.D.D.D.D.L.L.L.L.H.F.P.G.=.@.@.9.5 + O w U v x ..L.D.D.D.",
+"J.D.L.B.B.L.D.D.D.L.D.D.D.D.D.D.D.D.G.D.D.G.D.D.` J J | v.8.9.8.@.@.@.#.<.<.-.j.w < & h U.D.D.D.",
+"D.D.L.B.N.L.D.D.D.D.D.D.D.G.D.D.D.D.D.D.D.D.L.D.Z . . S -.@.@.@.@.@.@.@.@.@.@.9.+ X X 0 L.D.D.D.",
+"D.D.L.B.B.L.L.D.D.D.D.D.D.D.D.D.D.D.G.D.D.D.G.D.Z . . Z 7.@.@.@.@.@.@.@.@.@.@.<.+ X X 0 U.D.D.G.",
+"D.D.L.B.N.L.D.D.D.D.D.G.D.D.G.D.G.D.D.D.D.D.D.D.W c c ( 7.@.@.#.-.-.=.7.=.:.*.z.7 # O d U.D.D.D.",
+"D.D.P.B.N.L.D.D.D.D.D.D.G.D.D.G.D.D.D.D.D.D.D.D.z.3.5.5.#.@.@.5.( Z B ` t > * p W D M O.L.D.G.D.",
+"D.D.D.Z.N.L.D.D.D.G.D.G.D.D.D.D.D.D.D.G.D.D.G.D.7.@.@.@.@.@.@.5.N . . D # X X 3 v .   W I.D.D.D.",
+"D.D.L.B.N.L.D.D.D.G.D.D.D.D.D.D.G.D.D.D.D.G.G.D.7.@.@.@.@.@.@.5.N . . G # X X 3 v .   W I.D.D.D.",
+"G.D.L.B.N.L.D.D.D.D.D.D.G.D.D.G.D.D.D.D.D.D.D.D.q.=.7.-.7.7.&.l.m . k J 0 # # u E b x o.L.D.D.D.",
+"D.D.L.B.N.L.D.D.D.D.D.D.G.D.D.D.D.D.D.D.D.D.D.D.( B J H S H H Z k k . I u.-.-.h.0 > # f U.D.D.D.",
+"D.D.L.B.N.L.D.D.D.G.G.D.D.D.D.D.D.D.D.G.D.D.D.D.Z . . . . . . . k k k D =.@.@.<.+ X X 0 U.D.D.D.",
+"D.D.L.C.N.L.D.D.D.D.D.D.D.D.D.D.D.D.D.D.G.D.D.G.H . k k . . k . . .   D -.@.@.9.+ X X 0 U.D.D.D.",
+"D.D.D.Z.N.G.D.D.D.G.D.D.D.D.G.D.G.D.D.D.D.D.D.D.H . k k H Z H H Z Z b _ q.-.=.h.+ X X 0 U.D.D.D.",
+"D.D.L.C.a.=.-.w.Z.H.D.D.D.D.D.D.D.D.D.D.( b H H k k . H c.=.:.z.q # , , , , , , X X X 0 U.D.D.D.",
+"D.D.F.C.,.@.@.&.Z.D.D.D.D.D.D.D.G.D.D.G.D . . . k k . D 7.@.@.4.% X X X X X X X X X X 9 U.D.D.D.",
+"D.D.F.C.,.@.@.%.Z.H.D.D.D.D.D.D.D.D.D.G.J . k k . . . Z 7.@.@.4.% X X X X X X X X X X q U.D.D.D.",
+"D.D.F.C.,.@.@.%.A.D.D.D.D.D.D.D.D.D.G.G.J . k k H H M _ -.@.@.4.e % < 1 , > > > 1 1 @ f U.D.D.D.",
+"D.D.D.C.,.@.@.@.=.7.=.w.Z.G.D.D.D.D.D.L.J . k H 9 # # a :.@.@.5.W b b _ t.=.=.d.T m v o.I.D.D.D.",
+"D.D.G.C.,.@.@.@.@.@.@.=.Z.G.D.D.D.G.D.L.J . . H , X X > 7.@.@.5.M . . P =.@.@.>.m . . W I.D.D.D.",
+"D.D.D.C.,.@.@.@.@.@.@.=.Z.H.D.D.G.D.D.L.D . . Z , X X > :.@.@.5.b . . D =.@.@.>.c .   W I.D.D.D.",
+"G.D.D.C.,.@.@.@.@.@.@.=.Z.H.D.D.D.D.D.D._ C Z _ a > > a l.3.3.x.( V Z { i.3.7.f.~ A M O.L.D.D.D.",
+"D.D.G.C.,.@.@.@.@.@.@.=.Z.D.D.D.D.D.D.G.c.&.-.=.:.:.*.i.W c c ( e.=.&.l.L b c ~ 8 # @ d U.D.D.D.",
+"D.D.G.C.,.@.@.@.@.@.@.%.Z.D.D.D.D.D.D.D.7.@.@.@.@.@.@.7.M . . D -.@.@.6.b . . G # X X 0 U.D.D.D.",
+"D.D.G.C.,.@.@.@.@.@.@.=.Z.H.D.H.H.D.H.H.5.@.@.@.@.@.@.5.Z . . S =.@.@.6.b . . G * X X 0 L.D.D.D.",
+"D.D.G.C.p.3.6.6.#.@.@.=.A.D.Z.Z.Z.Z.Z.Z.8.@.@.#.6.6.3.d.~ A A ` l.3.3.f.( S V ] @ X X 0 U.D.D.D.",
+"D.D.D.Z.! c c L 1.@.@.@.=.=.%.=.=.%.=.=.#.@.@.7.W c x ~ 8 @ @ 9 T c c ( e.&.%.y.+ X X 0 U.D.D.D.",
+"D.D.D.D.F   . c 1.@.@.@.@.@.@.@.@.@.@.@.@.@.@.7.Z . . Z , X X < B . . P =.@.@.<.+ X X 0 U.D.D.D.",
+"G.D.G.D.D .   c 1.@.@.@.@.@.@.@.@.@.@.@.@.@.@.7.Z . . A # X X < b . . I -.@.@.<.+ X X 0 U.D.D.D.",
+"D.D.G.Z.[ P P ( >.@.@.@.@.@.@.@.@.@.@.#.<.<.4.s.) A F ' i ; 2 s ) A A } =.@.@.<.w - - h L.D.D.D.",
+"D.D.J.C.y.%.=.=.@.@.@.@.@.@.@.@.@.@.@.7.q o + + @ # O t r.$.$.q.7 @ @ t =.@.@.0.K v z ..L.D.D.D.",
+"D.D.J.C.,.@.@.@.@.@.@.@.@.#.@.@.@.@.@.7.1 X X X X X X > 7.@.@.4.% X X 6 :.@.@.1.c .   W I.D.D.G.",
+"D.D.D.C.,.@.@.@.@.@.@.@.@.@.@.@.@.@.@.7.> X X X X X X > :.@.@.4.# X X 6 =.@.@.1.c     R I.D.D.D.",
+"D.D.D.C.k.w.t.t.u.t.t.t.t.t.t.t.t.t.w.m.g 7 t 0 0 t 7 g m.w.w.n.d 0 7 j b.w.w.n. .W Y +.L.D.D.D.",
+"D.D.D.D.H.P.P.P.P.P.P.L.P.P.P.P.P.P.P.H.U.L.U.U.U.U.U.U.P.P.P.L.U.U.U.U.L.P.P.P.I.L.I.I.L.D.D.D.",
+"D.D.D.D.D.D.D.D.D.D.D.D.D.D.D.D.D.D.D.D.D.D.D.D.D.D.D.D.D.D.D.D.D.D.D.C.D.D.D.D.D.D.D.D.D.D.D.G.",
+"D.D.D.D.D.D.D.D.D.D.D.D.D.D.D.D.D.D.D.D.G.G.D.D.D.D.D.D.D.D.D.D.D.D.D.D.G.D.D.D.D.L.D.D.D.G.D.D.",
+"D.D.D.J.D.G.D.D.D.D.D.D.D.D.D.D.D.D.G.D.D.D.D.D.D.D.D.D.D.J.D.D.D.D.D.D.D.D.D.D.D.D.D.D.D.D.D.D."
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/samegame-web.png b/icons/samegame-web.png
new file mode 100644 (file)
index 0000000..75e9500
Binary files /dev/null and b/icons/samegame-web.png differ
diff --git a/icons/samegame.ico b/icons/samegame.ico
new file mode 100644 (file)
index 0000000..2be853a
Binary files /dev/null and b/icons/samegame.ico differ
diff --git a/icons/samegame.rc b/icons/samegame.rc
new file mode 100644 (file)
index 0000000..a6eb73b
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "samegame.ico"
diff --git a/icons/samegame.sav b/icons/samegame.sav
new file mode 100644 (file)
index 0000000..f92b52d
--- /dev/null
@@ -0,0 +1,34 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :9:Same Game
+PARAMS  :9:10x10c3s2
+CPARAMS :9:10x10c3s2
+SEED    :15:785412408200083
+DESC    :199:1,1,3,1,2,2,1,2,3,2,2,2,3,3,2,1,1,1,3,2,3,3,2,3,1,3,2,1,1,3,1,2,2,2,3,2,3,2,3,2,1,3,1,2,1,2,3,2,1,3,2,3,1,1,3,3,1,3,3,3,1,1,3,2,2,1,1,2,1,2,2,2,3,1,3,2,2,1,2,3,3,1,2,3,1,3,3,2,1,3,3,1,3,1,2,2,1,3,1,2
+NSTATES :2:26
+STATEPOS:2:13
+MOVE    :6:M94,95
+MOVE    :6:M83,84
+MOVE    :9:M83,93,94
+MOVE    :6:M93,94
+MOVE    :6:M20,21
+MOVE    :15:M20,21,22,31,32
+MOVE    :6:M70,71
+MOVE    :6:M80,90
+MOVE    :9:M73,82,83
+MOVE    :18:M72,73,74,82,83,92
+MOVE    :12:M51,61,62,72
+MOVE    :9:M35,36,46
+MOVE    :12:M49,57,58,59
+MOVE    :6:M59,69
+MOVE    :9:M69,79,89
+MOVE    :12:M78,79,89,99
+MOVE    :24:M45,46,47,54,55,57,64,67
+MOVE    :36:M36,46,55,56,57,66,67,68,77,78,88,98
+MOVE    :9:M76,77,87
+MOVE    :6:M97,98
+MOVE    :6:M94,95
+MOVE    :45:M50,60,61,70,71,81,82,83,84,85,90,91,92,93,94
+MOVE    :12:M73,81,82,83
+MOVE    :6:M92,93
+MOVE    :9:M81,90,91
diff --git a/icons/screenshot.sh b/icons/screenshot.sh
new file mode 100755 (executable)
index 0000000..0e2a06e
--- /dev/null
@@ -0,0 +1,25 @@
+#!/bin/sh 
+
+# Generate a screenshot from a puzzle save file. Takes the
+# following arguments, in order:
+#
+#  - the name of the puzzle binary
+#  - the name of the save file
+#  - the name of the output image file
+#  - (optionally) the proportion of the next move to redo before
+#    taking the screenshot.
+#
+# This script requires access to an X server in order to run, but
+# seems to work fine under xvfb-run if you haven't got a real one
+# available (or if you don't want to use it for some reason).
+
+binary="$1"
+save="$2"
+image="$3"
+if test "x$4" != "x"; then
+  redo="--redo $4"
+else
+  redo=
+fi
+
+"$binary" $redo --screenshot "$image" --load "$save"
diff --git a/icons/signpost-16d24.png b/icons/signpost-16d24.png
new file mode 100644 (file)
index 0000000..bf39723
Binary files /dev/null and b/icons/signpost-16d24.png differ
diff --git a/icons/signpost-16d4.png b/icons/signpost-16d4.png
new file mode 100644 (file)
index 0000000..d3772a3
Binary files /dev/null and b/icons/signpost-16d4.png differ
diff --git a/icons/signpost-16d8.png b/icons/signpost-16d8.png
new file mode 100644 (file)
index 0000000..5fa6592
Binary files /dev/null and b/icons/signpost-16d8.png differ
diff --git a/icons/signpost-32d24.png b/icons/signpost-32d24.png
new file mode 100644 (file)
index 0000000..2170410
Binary files /dev/null and b/icons/signpost-32d24.png differ
diff --git a/icons/signpost-32d4.png b/icons/signpost-32d4.png
new file mode 100644 (file)
index 0000000..5d8c1f2
Binary files /dev/null and b/icons/signpost-32d4.png differ
diff --git a/icons/signpost-32d8.png b/icons/signpost-32d8.png
new file mode 100644 (file)
index 0000000..67922bb
Binary files /dev/null and b/icons/signpost-32d8.png differ
diff --git a/icons/signpost-48d24.png b/icons/signpost-48d24.png
new file mode 100644 (file)
index 0000000..4b89454
Binary files /dev/null and b/icons/signpost-48d24.png differ
diff --git a/icons/signpost-48d4.png b/icons/signpost-48d4.png
new file mode 100644 (file)
index 0000000..b68d0c9
Binary files /dev/null and b/icons/signpost-48d4.png differ
diff --git a/icons/signpost-48d8.png b/icons/signpost-48d8.png
new file mode 100644 (file)
index 0000000..d3a7eaf
Binary files /dev/null and b/icons/signpost-48d8.png differ
diff --git a/icons/signpost-base.png b/icons/signpost-base.png
new file mode 100644 (file)
index 0000000..44cca4d
Binary files /dev/null and b/icons/signpost-base.png differ
diff --git a/icons/signpost-ibase.png b/icons/signpost-ibase.png
new file mode 100644 (file)
index 0000000..1b1e6e4
Binary files /dev/null and b/icons/signpost-ibase.png differ
diff --git a/icons/signpost-ibase4.png b/icons/signpost-ibase4.png
new file mode 100644 (file)
index 0000000..942cfab
Binary files /dev/null and b/icons/signpost-ibase4.png differ
diff --git a/icons/signpost-icon.c b/icons/signpost-icon.c
new file mode 100644 (file)
index 0000000..e4704f7
--- /dev/null
@@ -0,0 +1,643 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 227 2 ",
+"   c #A4A4A4",
+".  c gray63",
+"X  c #A1A19F",
+"o  c #9F9F9F",
+"O  c #9F9F9F",
+"+  c #9F9F9F",
+"@  c #9F9F9F",
+"#  c #A0A0A0",
+"$  c gray63",
+"%  c #A2A2A2",
+"&  c #9F9F9F",
+"*  c #9F9F9F",
+"=  c #9F9F9F",
+"-  c #9F9F9F",
+";  c gray63",
+":  c #A4A4A4",
+">  c gray63",
+",  c #CECED0",
+"<  c #DCDCE6",
+"1  c #E2E2E2",
+"2  c gray89",
+"3  c #E2E2E2",
+"4  c gray90",
+"5  c gray85",
+"6  c gray83",
+"7  c #DADADA",
+"8  c #E2E2E2",
+"9  c gray89",
+"0  c #E2E2E2",
+"q  c #E7E7E7",
+"w  c #D0D0D0",
+"e  c #A1A1A0",
+"r  c #9F9F9F",
+"t  c #E3E3E4",
+"y  c #DEDEFF",
+"u  c #F9F9FE",
+"i  c white",
+"p  c #FEFEFE",
+"a  c gray94",
+"s  c #ECECEC",
+"d  c #D2D2D2",
+"f  c #F4F4F4",
+"g  c #FDFDFD",
+"h  c gray89",
+"j  c #9F9F9F",
+"k  c #9F9F9F",
+"l  c #E1E1E2",
+"z  c #D7D7FE",
+"x  c #EFEFFB",
+"c  c #FFFFFD",
+"v  c #FDFDFD",
+"b  c #EEEEEE",
+"n  c gray90",
+"m  c #D7D7D7",
+"M  c gray95",
+"N  c #FEFEFE",
+"B  c #FBFBFB",
+"V  c #E1E1E1",
+"C  c #9F9F9F",
+"Z  c #9F9F9F",
+"A  c #E2E2E3",
+"S  c #FBFBFC",
+"D  c #EEEEEE",
+"F  c gray93",
+"G  c gray95",
+"H  c #ECECEC",
+"J  c #FEFEFE",
+"K  c #F8F8F8",
+"L  c #FDFDFD",
+"P  c gray90",
+"I  c #9F9F9F",
+"U  c #9F9F9F",
+"Y  c #E3E3E2",
+"T  c gray99",
+"R  c #D0D0D0",
+"E  c #CACACA",
+"W  c #EFEFEF",
+"Q  c #EEEEEE",
+"!  c white",
+"~  c #EAEAEA",
+"^  c #CACACA",
+"/  c #C3C3C4",
+"(  c gray86",
+")  c gray63",
+"_  c #9F9F9F",
+"`  c gray89",
+"'  c #F9F9F9",
+"]  c gray75",
+"[  c #BBBBBB",
+"{  c gray89",
+"}  c #F1F1F1",
+"|  c #E6E6E6",
+" . c #C5C5C5",
+".. c #C1C1C0",
+"X. c #DADADA",
+"o. c gray63",
+"O. c #A0A0A0",
+"+. c gray85",
+"@. c gray97",
+"#. c #EEEEEE",
+"$. c gray95",
+"%. c gray88",
+"&. c gray87",
+"*. c #E6E6E6",
+"=. c #E1E1E1",
+"-. c #F3F3F2",
+";. c #EDEEED",
+":. c gray96",
+">. c #ECECEC",
+",. c #E9E9E8",
+"<. c #DDDDDC",
+"1. c #9F9F9F",
+"2. c gray63",
+"3. c #D5D5D5",
+"4. c gray88",
+"5. c #E9E9E9",
+"6. c #EFEFEF",
+"7. c gray94",
+"8. c #F4F4F4",
+"9. c #E1E1E1",
+"0. c #9880CA",
+"q. c #9275CF",
+"w. c #967AD2",
+"e. c #9377CF",
+"r. c #9378CE",
+"t. c #9678D6",
+"y. c #9985C2",
+"u. c #A5A7A2",
+"i. c #9F9F9F",
+"p. c gray89",
+"a. c gray86",
+"s. c gray93",
+"d. c #FEFDFE",
+"f. c #F2F1F2",
+"g. c #886DC0",
+"h. c #795ABC",
+"j. c #8565CA",
+"k. c #8766CD",
+"l. c #8061C2",
+"z. c #916BE1",
+"x. c #977ECB",
+"c. c #A5A7A1",
+"v. c gray63",
+"b. c #DADADA",
+"n. c #D8D8D8",
+"m. c #F4F4F4",
+"M. c #FEFEFE",
+"N. c gray94",
+"B. c #8D73C5",
+"V. c #8766CC",
+"C. c #8D6DD1",
+"Z. c #8C6CD1",
+"A. c #896ACB",
+"S. c #936FE0",
+"D. c #9780C9",
+"F. c #A5A7A1",
+"G. c #9F9F9F",
+"H. c gray89",
+"J. c #FDFDFD",
+"K. c #FDFDFD",
+"L. c #F1F1F1",
+"P. c gray96",
+"I. c #EDEDEC",
+"U. c #967AD3",
+"Y. c #936FDF",
+"T. c #9371DB",
+"R. c #9673E0",
+"E. c #8566C5",
+"W. c #8564CD",
+"Q. c #9B82CE",
+"!. c #A4A6A0",
+"~. c #E2E2E2",
+"^. c #FBFBFB",
+"/. c #CDCDCE",
+"(. c #AAAAAA",
+"). c #E2E2E2",
+"_. c #977CD2",
+"`. c #906EDA",
+"'. c #9271D9",
+"]. c #916FD7",
+"[. c #7057A5",
+"{. c #7457B2",
+"}. c #9880C9",
+"|. c #A5A7A1",
+" X c #9F9F9F",
+".X c gray89",
+"XX c gray97",
+"oX c #C6C6C6",
+"OX c #D8D8D8",
+"+X c #E5E5E4",
+"@X c #9678D6",
+"#X c #8F6ADF",
+"$X c #926EDF",
+"%X c #8B68D4",
+"&X c #684EA0",
+"*X c #6B4DAB",
+"=X c #947BC8",
+"-X c #A6A8A2",
+";X c gray63",
+":X c #E7E7E7",
+">X c gray89",
+",X c gray88",
+"<X c gray85",
+"1X c #EAEAEA",
+"2X c #D8D8D7",
+"3X c #9986C3",
+"4X c #977ECB",
+"5X c #9780C9",
+"6X c #9A82CD",
+"7X c #927CC0",
+"8X c #927AC6",
+"9X c #9D8CC1",
+"0X c #A4A6A1",
+"qX c #9F9F9F",
+"wX c #9F9F9F",
+"eX c #A0A0A0",
+"rX c gray63",
+"tX c gray62",
+"yX c #A0A0A0",
+"uX c #A5A6A2",
+"iX c #A5A7A1",
+"pX c #A5A7A1",
+"aX c #A4A6A0",
+"sX c #A6A8A3",
+"dX c #A6A8A2",
+"fX c #A4A6A1",
+"gX c #A4A4A4",
+/* pixels */
+"  . X o O + @ # $ % & * = - ; : ",
+"> , < 1 2 3 4 5 6 7 8 9 0 q w e ",
+"r t y u i p i a s d f i g i h j ",
+"k l z x c v i b n m M N B i V C ",
+"Z A i S i D F G H i J i K L P I ",
+"U Y i T i R E W Q i ! ~ ^ / ( ) ",
+"_ ` i i ' ] [ { } i i |  ...X.o.",
+"O.+.@.#.$.%.&.*.=.-.;.:.>.,.<.1.",
+"2.3.4.5.6.7.8.9.0.q.w.e.r.t.y.u.",
+"i.p.a.s.i d.i f.g.h.j.k.l.z.x.c.",
+"v.b.n.m.i M.i N.B.V.C.Z.A.S.D.F.",
+"G.H.i J.K.L.P.I.U.Y.T.R.E.W.Q.!.",
+"= ~.i ^.i /.(.)._.`.'.].[.{.}.|.",
+" X.Xi i XXoXOX+X@X#X$X%X&X*X=X-X",
+";Xw :X>X,X<X1X2X3X4X5X6X7X8X9X0X",
+": e qXwXeXrXtXyXuXiXpXaXsXdXfXgX"
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 238 2 ",
+"   c #614A91",
+".  c #634C92",
+"X  c #634C93",
+"o  c #644C95",
+"O  c #644C96",
+"+  c #654D97",
+"@  c #654D98",
+"#  c #664E98",
+"$  c #674F9B",
+"%  c #6C52A1",
+"&  c #6D53A3",
+"*  c #6E54A4",
+"=  c #7055A7",
+"-  c #7257A9",
+";  c #755BAE",
+":  c #7457B2",
+">  c #755AB0",
+",  c #775CB1",
+"<  c #785BB3",
+"1  c #785CB2",
+"2  c #7B5FB7",
+"3  c #7859BA",
+"4  c #7E61BD",
+"5  c #7F61BE",
+"6  c #7D5EC0",
+"7  c #8062BE",
+"8  c #8163C0",
+"9  c #8263C2",
+"0  c #8160C7",
+"q  c #8365C5",
+"w  c #8567C5",
+"e  c #8566C6",
+"r  c #8567C6",
+"t  c #8566C7",
+"y  c #8568C5",
+"u  c #8667C9",
+"i  c #8563CD",
+"p  c #8A6ACD",
+"a  c #8A6ACE",
+"s  c #8B6ACF",
+"d  c #8F78C1",
+"f  c #9179C4",
+"g  c #9179C5",
+"h  c #927AC4",
+"j  c #927AC5",
+"k  c #927BC5",
+"l  c #9178C7",
+"z  c #937AC6",
+"x  c #937BC6",
+"c  c #937BC7",
+"v  c #9177C8",
+"b  c #9278C8",
+"n  c #8A68D1",
+"m  c #8C6BD1",
+"M  c #8C6CD2",
+"N  c #8D6CD3",
+"B  c #8C6AD6",
+"V  c #8E6ED4",
+"C  c #8F6BD9",
+"Z  c #8F6CD9",
+"A  c #906ED6",
+"S  c #906FD7",
+"D  c #916FD8",
+"F  c #916FD9",
+"G  c #906DDB",
+"H  c #906CDD",
+"J  c #916DDD",
+"K  c #926FDD",
+"L  c #906CDE",
+"P  c #916CDE",
+"I  c #926EDE",
+"U  c #936EDF",
+"Y  c #9170D8",
+"T  c #9170D9",
+"R  c #9271D8",
+"E  c #9270D9",
+"W  c #9271D9",
+"Q  c #9372D8",
+"!  c #9270DA",
+"~  c #9271DA",
+"^  c #9270DB",
+"/  c MediumPurple",
+"(  c #9371DB",
+")  c #9371DC",
+"_  c #9573DE",
+"`  c #9573DF",
+"'  c #9674DE",
+"]  c #8E67E1",
+"[  c #916AE3",
+"{  c #936FE0",
+"}  c #936EE1",
+"|  c #936EE2",
+" . c #956EE7",
+".. c #9570E2",
+"X. c #9774E1",
+"o. c #9D9D9D",
+"O. c #9D9D9E",
+"+. c gray62",
+"@. c #9F9F9F",
+"#. c #A09F9F",
+"$. c #A5A79F",
+"%. c #9583B9",
+"&. c #9C8EBA",
+"*. c #9C8EBB",
+"=. c #9C8CBD",
+"-. c #9C8DBD",
+";. c #9D8EBC",
+":. c #9E8FBD",
+">. c #9E8FBE",
+",. c #9D92B2",
+"<. c #9D92B3",
+"1. c #A19AAF",
+"2. c #A0A0A0",
+"3. c gray63",
+"4. c #A5A7A0",
+"5. c #A6A7A1",
+"6. c #A5A5A3",
+"7. c #A5A6A2",
+"8. c #A5A7A2",
+"9. c #A4A3A5",
+"0. c #A4A4A4",
+"q. c #A5A5A5",
+"w. c gray65",
+"e. c #A5A8A0",
+"r. c gray66",
+"t. c #A9A9A9",
+"y. c #AAAAAA",
+"u. c gray67",
+"i. c #ACACAC",
+"p. c gray68",
+"a. c #AEAEAE",
+"s. c #AFAFAF",
+"d. c gray69",
+"f. c #B1B1B1",
+"g. c gray70",
+"h. c #B4B4B4",
+"j. c #B5B5B4",
+"k. c gray71",
+"l. c #B9B9B9",
+"z. c #BCBCBC",
+"x. c gray",
+"c. c gray75",
+"v. c #C1C1BE",
+"b. c #BFBFF7",
+"n. c #BBBBF8",
+"m. c #C0C0C0",
+"M. c #C1C1C1",
+"N. c #C3C3C3",
+"B. c #C8C8C8",
+"V. c gray79",
+"C. c #CACACA",
+"Z. c #CACACB",
+"A. c #CBCBCB",
+"S. c #CCCCCA",
+"D. c #CCCCCB",
+"F. c #CDCDCB",
+"G. c #CFCFCA",
+"H. c #CECECB",
+"J. c #CBCBCC",
+"K. c gray80",
+"L. c #CDCDCD",
+"P. c #CDCECE",
+"I. c #CECECE",
+"U. c gray81",
+"Y. c #D0D1CD",
+"T. c #D0D0D0",
+"R. c #D2D2D2",
+"E. c LightGray",
+"W. c gray83",
+"Q. c #D6D5D6",
+"!. c #D7D7D7",
+"~. c #D8D8D8",
+"^. c #DADADA",
+"/. c gray86",
+"(. c gainsboro",
+"). c #DDDDDD",
+"_. c gray87",
+"`. c #DFDFDF",
+"'. c #E2E3DE",
+"]. c #C4C4F6",
+"[. c #C4C4F8",
+"{. c #C6C6F9",
+"}. c #CACAF8",
+"|. c #DBDBFB",
+" X c #DCDCF9",
+".X c gray88",
+"XX c #E1E1E1",
+"oX c #E2E2E2",
+"OX c #E2E2E3",
+"+X c gray89",
+"@X c #E4E6E0",
+"#X c #E5E7E1",
+"$X c #E6E7E2",
+"%X c #E4E4E4",
+"&X c gray90",
+"*X c #E6E6E6",
+"=X c #E7E7E7",
+"-X c #E6E8E1",
+";X c #E6E8E2",
+":X c #E9EAE5",
+">X c #E9EBE5",
+",X c #EAEBE5",
+"<X c #EAECE6",
+"1X c gray91",
+"2X c #E9E9E9",
+"3X c #EAEAEA",
+"4X c gray92",
+"5X c #EBEAEC",
+"6X c #ECEBEC",
+"7X c #ECECEC",
+"8X c #ECECED",
+"9X c #EEEDEF",
+"0X c #EEEEEE",
+"qX c #EFEFEF",
+"wX c #E8E8FB",
+"eX c #ECECFD",
+"rX c gray94",
+"tX c #F1F1F1",
+"yX c gray95",
+"uX c #F3F3F3",
+"iX c #F4F4F4",
+"pX c gray96",
+"aX c gray97",
+"sX c #F3F3FF",
+"dX c #F9F9F9",
+"fX c gray98",
+"gX c #FBFBFB",
+"hX c #F9F9FE",
+"jX c #FBFBFF",
+"kX c gray99",
+"lX c #FDFDFD",
+"zX c #FEFEFD",
+"xX c #FFFFFD",
+"cX c #FDFDFE",
+"vX c #FCFCFF",
+"bX c #FEFEFE",
+"nX c #FFFFFE",
+"mX c white",
+/* pixels */
+"0.0.0.0.0.0.0.r.0.0.0.$.e.r.0.0.0.0.r.0.0.0.0.0.r.0.3.0.0.0.0.0.",
+"0.0.3.@.2.@.@.@.@.@.1.o.@.@.@.3.3.@.@.@.@.@.@.@.@.@.@.@.@.3.0.0.",
+"0.3.k.L.H.H.A.A.A.A.A.A.A.B.H.z.z.L.R.L.A.A.A.A.A.A.A.A.L.k.3.0.",
+"0.@.L.mXeXsXmXmXmXmXmXmXmXmXmX).oXmX2XiXmXmXmXmXmXmXmXmXmXA.@.r.",
+"0.o.A.mX].b.mXkXfXkXkXfXkXfXmX).oX4XL.k.0XmXfXfXfXfXmXfXmXA.@.0.",
+"0.@.A.mXwX].mXmXmXmXmXmXmXmXmX.XoXmXQ.z.iXmXmXmXmXmXmXfXmXA.@.0.",
+"0.@.A.mX Xn.hXmXmXfXfXmXmXfXmX).).iX0Xl.oXmXmXkXmXmXmXfXmXA.@.0.",
+"0.@.L.hX}.]. XmXmXmXmXmXmXmXmXoXoXoXA.R.fXmXmXmXmXmXmXkXmXL.3.0.",
+"0.@.A.mXmXmXkXkXmXmXmXmXmXmXmX).).mXmXmXmXmXmXmXfXmXmXkXmXA.@.0.",
+"r.@.A.mXfXmXmXmXmXkXmXB.z.uXmX).oXmXfXfXmXmXfXmXmX2XoXmXmXA.@.0.",
+"0.@.A.mXhXmXmXmXmXmXmXz.i.yXmXoX).mXmXmXfXmXmXmXmX2Xi.&XmXA.@.0.",
+"0.@.A.mXfXmXkXmXmXaXgXz.i.0XmX).oXmXfXmXmXmXiXz.z.z.f.f.2XL.@.0.",
+"0.2.A.mXhXmXmXkXmX~.f.h.f.f.L.).).mXmXmXfXmXyXi.f.i.k.r.).L.@.0.",
+"0.@.A.mXkXkXkXmXkXmXR.i.i.c.mXoX).mXfXmXmXmXfXyXiXoXr.R.mXA.@.0.",
+"r.@.A.mXkXmXmXmXmXmXmX~.A.kXmX).oXmXmXmXmXmXmXmXmX2XQ.mXmXA.@.0.",
+"0.3.z.+X).).).'.).'.`..X).`.oXJ.G.>X@X'.;X@X@X@X@X'.;X@X>Xv.#.0.",
+"0.@.z.&X&X&XoX).oX).).).'.).+XY.%.l l c l l h z z z l k l ,.5.9.",
+"0.@.L.mXQ.).fXmXmXmXmXmXmXmXmX;Xb  .F B ..} ) F F I I ) [ ;.e.9.",
+"0.@.L.yX2Xm.L.mXfXmXhXmXmXfXmX'.l n ; 7 ) y ' s - B ( W I *.e.9.",
+"0.@.A.mXiXA.yXmXkXmXmXkXmXmXmX;Xf : 1 5 e = u ) > M ) W H *.e.9.",
+"r.@.L.yXi.A.0XmXmXmXmXkXmXkXmX;Xf 5 1 q A 5 W p = 9 W W H *.5.9.",
+"0.@.L.iX~./.&XmXkXmXmXkXmXhXmX'.l I Y I W W ( F F ) ) W H ;.e.9.",
+"0.@.A.mXmXmXmXmXmXmXmXmXmXmXmX@Xl I W W W ) F I F s M W I *.e.9.",
+"0.@.A.mXfXmXfXmXkXmX&X&X2X&XmX;Xk ) W W W F W ) s o & F H *.e.9.",
+"0.3.A.mXfXmXkXmXmXmX/.i.f.r.8X>Xl I W W F W W ..N o * ' } *.$.0.",
+"0.@.A.mXmXmXkXmXmXmXfXz.f.f.0X>Xl I W I ) W Y u 9 @ & y i *.e.9.",
+"0.@.A.mXfXmXmXmXmXiXN.r.z.i.0X>Xf F W W F W W <   @ @ o 0 ;.5.9.",
+"r.@.A.mXfXkXfXfXmX+Xr.m.mXQ.5X>Xl I W W W W W ' 2 . @ y ..*.e.9.",
+"0.@.A.mXmXmXmXmXmXmX2XfXmXmXmX;Xl [ I I I H C [  .3 6 ..] ;.5.9.",
+"0.3.k.L.A.A.A.A.A.A.U.L.A.A.L.v.,.;.*.*.*.*.;.*.*.>.>.*.;.1.5.9.",
+"0.0.0.@.@.@.@.@.@.@.@.@.o.@.@.2.5.e.$.e.e.e.e.e.e.e.4.4.5.5.9.0.",
+"0.0.0.0.0.0.0.0.r.0.0.0.0.0.0.0.9.9.9.9.9.9.9.9.9.9.9.9.9.0.0.0."
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 55 1 ",
+"  c #634C94",
+". c #664E99",
+"X c #6E54A5",
+"o c #7055A7",
+"O c #7157AA",
+"+ c #7459AE",
+"@ c #775AB2",
+"# c #7B5FB7",
+"$ c #7C5FB9",
+"% c #7E61BC",
+"& c #8063BF",
+"* c #8F7ABC",
+"= c #8264C3",
+"- c #8667C8",
+"; c #8A6ACD",
+": c #8F74C7",
+"> c #8F75C8",
+", c #9176CA",
+"< c #8D6CD3",
+"1 c #8F6CD8",
+"2 c #906FD7",
+"3 c #926FDC",
+"4 c #9271DA",
+"5 c #936EE2",
+"6 c #9571E1",
+"7 c #9875E2",
+"8 c #9D9D9D",
+"9 c #A29EAA",
+"0 c #A4A4A4",
+"q c #AAA9A7",
+"w c #ADADAD",
+"e c #B2B2B2",
+"r c #BCBCBC",
+"t c #AEAEF7",
+"y c #B0B0F7",
+"u c #BEBEF7",
+"i c #BABAF8",
+"p c #C4C4C3",
+"a c #C7C8C4",
+"s c #C9CAC6",
+"d c #CDCDCD",
+"f c #CFCFD0",
+"g c #D0D0D0",
+"h c gainsboro",
+"j c #CFCFF8",
+"k c #D4D4F7",
+"l c #D5D5FA",
+"z c #DFDFFB",
+"x c #E4E4E4",
+"c c #ECECEC",
+"v c #E4E4FC",
+"b c #E8E8FB",
+"n c #F3F3F3",
+"m c #F3F3FC",
+"M c #FEFEFE",
+/* pixels */
+"000000000000000000000000000000000000000000000000",
+"000000000000000000000000000000000000000000000000",
+"00000000q00000q00q00000000000q0000q00q00w0000000",
+"000008888888888888888880088888888888888888800000",
+"0000rgggggggggggggdggfgeagfdggggddggggggfffq0000",
+"0008fMMMMMMMMMMMMMMMMMMpxMMMMMMMMMMMMMMMMMMw0000",
+"0008fMkuimMMMMMMMMMMMMMpxMprpxMMMMMMMMMMMMMw0000",
+"0008gMbltmMMMMMMMMMMMMMpxMxcreMMMMMMMMMMMMMw8000",
+"0008gMMvymMMMMMMMMMMMMMpxMxpehMMMMMMMMMMMMMw0000",
+"0008gMMvymMMMMMMMMMMMMMpxMMcapMMMMMMMMMMMMMw0000",
+"00q8gMmktvMMMMMMMMMMMMMpxnxnawmMMMMMMMMMMMMw8000",
+"0008fMjiiizMMMMMMMMMMMMrccrrrxMMMMMMMMMMMMMw0000",
+"0008gMMMMMMMMMMMMMMMMMMpxMMMMMMMMMMMMMMMMMMw0000",
+"00q8gMMMMMMMMMMMMnnMMMMpxMMMMMMMMMMMMMMMMMMw0000",
+"0008gMMMMMMMMMMMneedMMMpxMMMMMMMMMMMMcgMMMMw8000",
+"0008gMMMMMMMMMMMcwqdMMMaxMMMMMMMMMMMMcqgMMMw9000",
+"0008gMMMMMMMMMMMnewdMMMpxMMMMMMMMmncnxewgMMw0000",
+"0008gMnMMMMMMMMMcewaMmMaxMMMMMMMMxqeeeeewgMw0000",
+"00q8gMMMMMMMMMdereeerecaxMMMMMMMMxqwqweeeene8000",
+"0008gMMMMMMMMMnrweeewhMpxMMMMMMMMcaadpewenMw0000",
+"0008gMMMMMMMMMMmpweehMMaxMMMMMMMMMMMMcwenMMw0000",
+"00q8gMMMMMMMMMMMmrqhMMMrxMMMMMMMMMMMMxenMMMw0000",
+"0008gMMMMMMMMMMMMmcMMMMpxMMMMMMMMMMMMnnMMMMw8000",
+"0000epppppppppappaapppaeraaaaappaaaaasspaaaq0000",
+"0000pcxxxxxxxxxxxxxxxxcr*,,>>>>>>>>,>>:>>>:90000",
+"0008fMMmmMMMMMMMMMMMMMMa,63453545446633334490000",
+"0008gMrrepMMMMMMMMMMMMMa,57-$44444<=;43444400000",
+"0008gMcMx0cMMMMMMMMMMMMa,1%++74#44<OO44444480000",
+"0008gMMMdrnMMMMMMMMMMMMa,@+@+2=O=<6-+44433290000",
+"00q8gMMapMMMMMMMMMMMMMMa,O&%O1%X#<4%X14444490000",
+"0008gMeqdfcMMMMMMMMMMMMa,<@@%44&44;+o%4444490000",
+"00q8gMggddnMMMMMMMMMMMMa>64444444444442444290000",
+"0008gMMMMMMMMMMMMMMMMMMa,34444432441464444490000",
+"0008gMMMMMMMMMMMMMMMMMMa>44423444444;%=<44490000",
+"0008dMMMMMMMMMMnnnnnnmMa>34444434444#  %73490000",
+"00q8dMMMMMMMMMMhqeeeqxMa>34444244444%  =44200000",
+"00q8fMMMMMMMMMMMheeewxMa>32444244447%  =74490000",
+"0008fMMMMMMMMMMMceeewxMp>344444441=;#  %<-190000",
+"0008fMMMMMMMMMMceeeewxMa>54443244<X .... X200000",
+"00q8fMMMMMMMMMxeewgh0xMa>4444444442o .. O1480000",
+"0008gMMMMMMMMMcewfMMgxMa>34423344242X  O44490000",
+"00q8gMMMMMMMMMMxgMMMMMMa>323444344342XO243390000",
+"0008dMMMMMMMMMMMMMMMMMMa>44424444243421444290000",
+"0000wwwwwwwwwwwwwwwwwwwq999990909999909099000000",
+"000009808000000800800080000000080000000000080000",
+"000000000000000000000000000000000000000000000000",
+"000000000000000000000000000000000000000000000000",
+"000000000000000000000000000000000000000000000000"
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/signpost-web.png b/icons/signpost-web.png
new file mode 100644 (file)
index 0000000..445ee70
Binary files /dev/null and b/icons/signpost-web.png differ
diff --git a/icons/signpost.ico b/icons/signpost.ico
new file mode 100644 (file)
index 0000000..49c9104
Binary files /dev/null and b/icons/signpost.ico differ
diff --git a/icons/signpost.rc b/icons/signpost.rc
new file mode 100644 (file)
index 0000000..818ef37
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "signpost.ico"
diff --git a/icons/signpost.sav b/icons/signpost.sav
new file mode 100644 (file)
index 0000000..9ad1958
--- /dev/null
@@ -0,0 +1,23 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :8:Signpost
+PARAMS  :4:4x4c
+CPARAMS :4:4x4c
+SEED    :15:230468784719861
+DESC    :19:1eceebecfbfhgcaa16a
+NSTATES :2:15
+STATEPOS:2:11
+MOVE    :8:L2,1-3,1
+MOVE    :8:L0,1-1,0
+MOVE    :8:L2,2-1,1
+MOVE    :8:L1,2-0,3
+MOVE    :8:L0,2-2,0
+MOVE    :8:L1,3-1,2
+MOVE    :8:L1,1-1,3
+MOVE    :8:L1,0-3,0
+MOVE    :8:L0,0-0,1
+MOVE    :8:L3,0-3,2
+MOVE    :8:L3,2-0,2
+MOVE    :8:L3,1-2,2
+MOVE    :8:L2,3-2,1
+MOVE    :8:L2,0-2,3
diff --git a/icons/singles-16d24.png b/icons/singles-16d24.png
new file mode 100644 (file)
index 0000000..becbb1f
Binary files /dev/null and b/icons/singles-16d24.png differ
diff --git a/icons/singles-16d4.png b/icons/singles-16d4.png
new file mode 100644 (file)
index 0000000..3a6c964
Binary files /dev/null and b/icons/singles-16d4.png differ
diff --git a/icons/singles-16d8.png b/icons/singles-16d8.png
new file mode 100644 (file)
index 0000000..becbb1f
Binary files /dev/null and b/icons/singles-16d8.png differ
diff --git a/icons/singles-32d24.png b/icons/singles-32d24.png
new file mode 100644 (file)
index 0000000..b7c7e98
Binary files /dev/null and b/icons/singles-32d24.png differ
diff --git a/icons/singles-32d4.png b/icons/singles-32d4.png
new file mode 100644 (file)
index 0000000..4ab6be1
Binary files /dev/null and b/icons/singles-32d4.png differ
diff --git a/icons/singles-32d8.png b/icons/singles-32d8.png
new file mode 100644 (file)
index 0000000..b7c7e98
Binary files /dev/null and b/icons/singles-32d8.png differ
diff --git a/icons/singles-48d24.png b/icons/singles-48d24.png
new file mode 100644 (file)
index 0000000..981fd0b
Binary files /dev/null and b/icons/singles-48d24.png differ
diff --git a/icons/singles-48d4.png b/icons/singles-48d4.png
new file mode 100644 (file)
index 0000000..1f933ce
Binary files /dev/null and b/icons/singles-48d4.png differ
diff --git a/icons/singles-48d8.png b/icons/singles-48d8.png
new file mode 100644 (file)
index 0000000..981fd0b
Binary files /dev/null and b/icons/singles-48d8.png differ
diff --git a/icons/singles-base.png b/icons/singles-base.png
new file mode 100644 (file)
index 0000000..bfbe38d
Binary files /dev/null and b/icons/singles-base.png differ
diff --git a/icons/singles-ibase.png b/icons/singles-ibase.png
new file mode 100644 (file)
index 0000000..a61460f
Binary files /dev/null and b/icons/singles-ibase.png differ
diff --git a/icons/singles-ibase4.png b/icons/singles-ibase4.png
new file mode 100644 (file)
index 0000000..4f1aaf2
Binary files /dev/null and b/icons/singles-ibase4.png differ
diff --git a/icons/singles-icon.c b/icons/singles-icon.c
new file mode 100644 (file)
index 0000000..696de48
--- /dev/null
@@ -0,0 +1,891 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"Q.!.(.P./.^.].].].].~.).I./.^.Q.",
+"!.XXn.N.C.I.v n n c E.D.m.x.].^.",
+"/.N.Q.d.|.1..     o 0./.n.=Xl.!.",
+"I.b.0XB %Xq.  @ O O ;.gX{ 4XG.D.",
+"(.m.E.Y.oX,.X     o 9.).v.!.c.Q.",
+"Q.%Xf.6.k.(./ M x ! ).F.K.M.[.^.",
+"(.!.F.I.N.'.!.K.).H.<XqX|.tX<XE.",
+"W.x.%X^ :X2.!.M.J.|.L.rX%.Q.8XR.",
+"T.v.W.Z oX1.`.V.r.@XI.3XQ XX5XR.",
+").!.~.<XY.Q.S.K.R.b.%X4X}.4X:XE.",
+"(.3.x a l k.XX5.>.T.1X2XyX8X#XQ.",
+"].b   +   I U.k.^.A. X8Xn.-X3XE.",
+"].N   #   j ;X5.} -XH.tX( E.7XR.",
+"].b   +   A W.E.K.P.`.<X,.>X4XE.",
+"_.] j N f 3.Q.c.A.V.=X1X8X1X#XW.",
+"Q.`.[.].[.(.(.L.G.).W.E.T.E.W.!."
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"!.!.!.~.~.Q.E.E.Q.~.~.Q.E.E.E.E.E.E.E.E.Q.~.~.Q.W.E.W.!.~.!.!.!.",
+"!.!.Q.R.R.'.}.}.'.R.T./.|.}.}.}.}.}.}.|./.R.T.(.}.}.[.Q.T.Q.!.!.",
+"!.Q.`.%X*Xj.O.+.k.*X=XC. .@.+.+.+.+.@. .Z.$X>XG.*...q.|.:X_.Q.!.",
+"~.R.%XqXO.7.^.T.1.o.iXd.  X         X   u.nXe.O.I.~.s.)  X:XT.~.",
+"~.R.*XO.D.wX^.2XMXV.=.j.  + o o o o +   v.c.>.sXXX*XxX$X/ !.^.!.",
+"Q.'.k.1.CXf.Q 3 T.VX8.!   .         X   7.+.iX+Xz c qXkXH.X.{.W.",
+"E.}.@.L.gX9Xo.9  XjXQ.c o           .   / u.aXfXK.l aXwX>X_ ^.Q.",
+"E.}.@.P.sXXX_.9 x.bXE.c o           .   / u.sXuXx.f 0XeX>X_ /.Q.",
+"Q.'.k.2.MX*.G J [.mX8.~   .         X   7.+.pXXXB t ;.nXF.X.}.W.",
+"~.R.=X@.S.nXrXlXNXV.-.h.  o         o   v.b.>.jXsXdXjX&X( ~.~.!.",
+"~.R.#X5X{ 4./.!.3.[ eXs.  X X & * O X   y.lX8.} C.I.i.! ].-XT.~.",
+"!.Q._.*X6X1.d s 1.7X*XR.A.W.f.] ( 7.T.G.E.@X<XH.3.:.y. X>X_.Q.!.",
+"~.R.@XuX<.;.C.D.;.1.qX%XzXh.#.M.V.<.:.tX=X8X0XpXcXzXfXeXeX+XE.~.",
+"~.T.:X<.p.hXhX9XgXs.&.0Xe.,.uX6XrXkXV.} %X9XqX0X@X;XuX9XrXOXE.~.",
+"Q._.M.*.lXdX` > :XnX&.u.+.zX!.0 ( iXvX9.t.aXpXA.G g l.sXwXOXE.~.",
+"W.}.%.F.bXA.) f 1XgXE.k N.aXdX7.J hXyX`.o.sX0X1XvXS ..lXqXOXE.~.",
+"E.}.O.U.sXz _ = n.zX/.p S.iXlX0.K zXrX|.X.aX9XrXO./ eXqXrXOXE.~.",
+"Q.].i.e.zXm.<.3 B.VXr.o.2.nXT.d 6 a.BXx.<.sXsXz.  A t.pXwXOXE.~.",
+"!.E.@X' ^.SXzX8XBX^.^ ,X#.A.gX;X:XeX=X~ '.wXwX3X>XoX-XwXrXOXE.~.",
+"~.T.:X4X| j.XX#Xk. .>X>X5X{ e.$X:XC.Q '.:X8XwXrXyXiXtXqXtX+XE.~.",
+"!.~.P.L.A.( Z Z ( A.L.Q.%X3Xq.v h ..$X:X_.+X@X+XOXOX+X+X#X].Q.!.",
+"E.}.+.  O % $ $ & .   A.lXn.+.t.a.=.8.qX@X6X0X8X0X8X7X8XwXOXE.~.",
+"E.}.+.  o         .   E.S. .7XsXtXyXi.-.:X8XqXyX0XtXaX0XtX+XE.~.",
+"E.}.+.  o       .     c.} jXm.s Y ^.ZX-.h.iXpXH.D N J.pXwXOXE.~.",
+"E.}.+.  o           %  .g.CX9.> I |.hX^.+.dXwX@XrXD ' lXqXOXE.~.",
+"E.}.+.  o           * ) V.hX#X7XD ] nX{.o.aX7XgXd.W 7XwXrXOXE.~.",
+"E.}.+.  o           @ <.6.mXi.` s k.BXV.-.sXsXv.  D j.uXwXOXE.~.",
+"E.}.@.  @ o o o o o   P.+.~.aXR.+XvX8X/ E.eXeX&X_.U.{.eXeXOXE.~.",
+"E.}.+.  o         .   P.5X( N.7X6XR._ I.<X0XrXiXfXhXsXeXiX@XE.~.",
+"Q.).l. .@.+.+.+.+.+.o.P.:X[.%./ ) { K.>X_.OX+XOXoXoXoXOX@X'.W.!.",
+"!.Q.).|.}.}.}.}.}.}.|.^.T.Q.}.(.^.{.(.Y.Q.E.E.E.E.E.E.E.E.W.!.!.",
+"!.!.Q.E.E.E.E.E.E.E.E.!.~.!.W.Q.!.W.Q.~.!.~.~.~.~.~.~.~.~.!.!.!."
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.",
+"!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.",
+"!.!.!.!.!.~.~.~.!.W.E.E.W.!.~.~.~.Q.E.E.E.E.E.E.E.E.E.E.E.E.!.~.~.~.!.W.E.E.W.!.~.~.~.!.!.!.!.!.",
+"!.!.!.!.Q.R.R.Y.^.}.XXXX[.Q.Y.E.R.). X|.|.|.|.|.|.|.|.|. X}.Q.R.E.Y.Q.[.XXXX}.^.Y.R.E.!.!.!.!.!.",
+"!.!.!.Q.{.#X#X4XW.-.E ! 9.|.3XOX%Xl./ { ] ] ] ] ] ] ] [ ' ..^.%XOX3X|.9.! E -.W.4X$X+X/.Q.!.!.!.",
+"!.!.~.R.#XsXyX%.c o.r.q.( z j.jXyX>.  o               .   - _.uXjXj.x ( 0.r.O.x %.uXyX(.Q.!.!.!.",
+"!.!.~.R.#XyXJ ) 7XMXVXvXgXXXv >.kX:.  @ o o o o o o o O   > /.kX:.b XXvXNXmXjX5X) K wX).W.!.!.!.",
+"!.!.~.Y.4X%.) mX5XR.G.|.rXaXrXl S.0.  o               .   * OXS.l tXsX|.P.(.8XeXbX_ @.@XR.!.!.!.",
+"!.!.!.^.W.n 2XjXl.w g X 8.uXsXY.D 5.  o               .   - }.D U.lX#Xc X d wX9XeX6Xx A.(.Q.!.!.",
+"!.!.W.}.-.O.jX8X5X].A.O E gX7XqXU Q   X               .   4 k.H wXqX5X0XR w tXqX7XjXX.#.|.W.!.!.",
+"!.!.E.XXE y.kX6XfX>.* & T.aX6XfX} N   .               .   0 1.^ jX8X9XhXT 0 wX0X8XkXy.E XXE.!.!.",
+"!.!.E.XX! e.jX7XeXlXjXs h iX8XsX[ C   .               .   9 4.! fX8X0XVX( y vXtX7XjXw.! XXE.!.!.",
+"!.!.W.[.0.) gXtX$.U )   U pX0X5XG [   X               .   1 C.A 5XrX<X-.8 = [ .XeXhX^ 1.{.W.!.!.",
+"!.!.!.Q.|.v .XjXF. .' p.5X8XxXh.E q.  o               .   * OXW h.bX-XO.| +._ ].kX.Xl R.~.!.!.!.",
+"!.!.~.Y.3Xj.v rXbXzXkXdXeXxX{.y  X6.  O X X X X X X X o   - |.|.y }.vXxXkXhXxXlXeXv f.+XR.!.!.!.",
+"!.!.~.R.@XzX1.c Y.8XpXiX6Xk.a G.zX-.  o               .   ; /.xXF.a l.5XaXfX0XT.c 3.hX/.Q.!.!.!.",
+"!.!.~.W..X1XwXj.z P ..| C C R.wX1X8.1 r 9 w f z z f w 0 9 d /.<XqXQ.Z x T E B k l.eX>X(.Q.!.!.!.",
+"!.!.!.Q.'. X[.9XXX .d l 7.1X1X].{._.$XOX2X#Xj.%.%.j.#X2X+XoX(..X{.$X-XY.g.s.D.$X=X}. X/.!.!.!.!.",
+"!.!.~.R.$XiXfXr.c ` 2.>.U M J.cX<X.XtXhXv.b I +.@.U v v.kX5X}.yXrXeXwXiXgXhXsXeXwXtXrX).Q.!.!.!.",
+"!.!.~.R.@XfX` D OXpXlXvXpXP.s f.eX].jX:.c _.fXnXcXaX).x ,.uX].wX0X0X0XwXtXwX7X0X0XqX0X).Q.!.!.!.",
+"!.!.~.Y.4X8.J kXuXqX1X|.3XlX,Xg Z.6Xv.c tXfX:X|.&X9XdXtXb h.*X0XqX0XrX2XXX:XpXwXqXqX0X).Q.!.!.!.",
+"!.!.!.~.).b ;XyX9XqXj   F.iXgXD.M &XM ).zX_.s   K aX5XdX].l {.eX8XaXI.7 6 # 3.uX0XqX0X).Q.!.!.!.",
+"!.!.W.{.2.} jX5XjX+.t   B.fX8X9XG =.~ pX0X<XOXg N dX9X8XsXJ g.dX8XuX[.F.8XJ O 3XrX0X0X).Q.!.!.!.",
+"!.!.E.XXW t.gXpXS.V `.  T.lX5XdX..h 2.jX7XwXgXl M aX9X8XlX+.;.lX8X0XrXcX7X8 K iX0XqX0X).Q.!.!.!.",
+"!.!.E.XXW r.jXtXd Y >.  ] 4X0XsX .h 2.jX7XwXnXb S BXqX7XlX+.;.lX8X9XaX{.r _ lXtX0XqX0X).Q.!.!.!.",
+"!.!.W.{.2.} jX9X| b u   r #XyX8XG =.! iXwX-Xe.5 0 u.>XqXsXJ h.dX7XpX~.o 1 :.<.>XrXqX0X).Q.!.!.!.",
+"!.!.!.~._.b ;XuXeXlX%XD {.uXhXS.N &XM ).lX XF L J K OXjX`.l {.eX8XaXU.D R N G -XrX0X0X).Q.!.!.!.",
+"!.!.~.Y.4X8.H jXuX6X9XfXwXhX,Xd A.5Xv.x tXhXhXfXfXhXgXrXc h.*X0XqX0XyXkXhXlXkXeX0XqX0X).Q.!.!.!.",
+"!.!.~.R.+XdX` S oXaXhXsXyXP.a g.eX[.zX,.b `.uXdXdXuX`.v 2.pX].rXwXeXqX9X9X9X9XwXwXeXwX).Q.!.!.!.",
+"!.!.~.R.%XaXhXp.n ) ,.;.U C I.vX2X}.6XuXf.f T 1.1.T f f.pX>X[.6X5X5X5X5X5X5X5X5X5X5X4X(.Q.!.!.!.",
+"!.!.Q./.C.i.s.M.y.~ M C | k.b.u.k.).}._.3X$X*.g h *.%X3X`.[.(.}.{.{.{.{.{.{.{.{.{.{.{.^.!.!.!.!.",
+"!.!.E.|.]   o     o # # .   X   q #XwXhXY.G P  . .P G U.lX4X}.rXwXeXeXwXwXwXeXeXwXeXwX).Q.!.!.!.",
+"!.!.E.|.[   + X .         . o   e oXjXa.s Z.0XhXhX0XC.a f.iX].wX0XqX0X0XeXqX9XqXqXqX0X).Q.!.!.!.",
+"!.!.E.|.]   o               X   9 <XK.s 1XjX4X,X,X7XlX<Xg Z.%X0XqX0XyXyX4XwXaXqXqXqX0X).Q.!.!.!.",
+"!.!.E.|.]   o               X   q *XC L.mXY., a 2 ..tXgXY.c XXwX9XpX!.a 8 6 h.uX9XqX0X).Q.!.!.!.",
+"!.!.E.|.]   o               X   s b.K yXaXI.  ] 5.XXtX8XaXS c.sX8XiX~.p. Xn # 3XrX0X0X).Q.!.!.!.",
+"!.!.E.|.]   o               X   z *.+.hXiXT.e n X  .uX7XkXo.>.lX8X0XrXcXhXf x tXqXqX0X).Q.!.!.!.",
+"!.!.E.|.]   o               X   x #.$.lX8XwXdXSX .. &XeXkX@.-.lX8X9XpX-Xx C rXuX9XqX0X).Q.!.!.!.",
+"!.!.E.|.]   o               X   g p.W aXuXb.:.B.e w 6X0XdXY p.fX7XuX}., < a.c.1XeXqX0X).Q.!.!.!.",
+"!.!.E.|.]   o               X   e .Xn XXjXK.N d S ).tXuX@Xj _.rX8XsXF.t n u h *XtX0X0X).Q.!.!.!.",
+"!.!.E.|.]   o               X   9 3Xy.S hXgXsXuXsXrXyXjXD 8.=X0XqX0XtXsXiXaXsXwXqXqX0X).Q.!.!.!.",
+"!.!.E.|.]   o               X   q +XfX' J -XgXhXgXhX;XH { tX[.eXqXqXqX0X0X0X0XqXqXqXqX).Q.!.!.!.",
+"!.!.E.|.[   @ o o o o o o o O   e +XeXiX5.h ] r.r.] h 5.sX4X{.wX0X0X0X0X0X0X0X0X0XqX0X).Q.!.!.!.",
+"!.!.!.~.L.C.A.Z.Z.Z.Z.Z.Z.Z.Z.Z.D.^.(.(.@XL.=.W W -.L.@X(.(./.).).).).).).).).).).).).!.!.!.!.!.",
+"!.!.!.!./.).).).).).).).).).).).(.!.Q.Q.R./.}.XXXX}./.R.Q.Q.!.Q.Q.Q.Q.Q.Q.Q.Q.Q.Q.Q.Q.!.!.!.!.!.",
+"!.!.!.!.Q.Q.Q.Q.Q.Q.Q.Q.Q.Q.Q.Q.Q.!.!.!.!.Q.W.E.E.W.Q.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.",
+"!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.",
+"!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!.!."
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/singles-web.png b/icons/singles-web.png
new file mode 100644 (file)
index 0000000..992f20e
Binary files /dev/null and b/icons/singles-web.png differ
diff --git a/icons/singles.ico b/icons/singles.ico
new file mode 100644 (file)
index 0000000..a7741b4
Binary files /dev/null and b/icons/singles.ico differ
diff --git a/icons/singles.rc b/icons/singles.rc
new file mode 100644 (file)
index 0000000..5121821
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "singles.ico"
diff --git a/icons/singles.sav b/icons/singles.sav
new file mode 100644 (file)
index 0000000..260fd1f
--- /dev/null
@@ -0,0 +1,45 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :7:Singles
+PARAMS  :5:6x6dk
+CPARAMS :5:6x6dk
+SEED    :15:781273601054598
+DESC    :36:361566412253452144234115163346553461
+NSTATES :2:37
+STATEPOS:2:22
+MOVE    :4:B1,0
+MOVE    :4:C0,0
+MOVE    :4:C1,1
+MOVE    :4:C2,0
+MOVE    :4:C0,1
+MOVE    :4:B0,2
+MOVE    :4:C0,3
+MOVE    :4:C1,2
+MOVE    :4:C4,3
+MOVE    :4:B3,3
+MOVE    :4:C3,2
+MOVE    :4:C2,3
+MOVE    :4:C3,4
+MOVE    :4:B2,4
+MOVE    :4:C1,4
+MOVE    :4:C2,5
+MOVE    :4:B1,5
+MOVE    :4:C0,5
+MOVE    :4:C0,4
+MOVE    :4:C1,3
+MOVE    :4:C3,5
+MOVE    :4:B5,4
+MOVE    :4:C4,4
+MOVE    :4:C5,5
+MOVE    :4:C5,3
+MOVE    :4:C4,5
+MOVE    :4:B4,0
+MOVE    :4:C3,0
+MOVE    :4:C4,1
+MOVE    :4:C5,0
+MOVE    :4:C5,1
+MOVE    :4:B4,2
+MOVE    :4:C5,2
+MOVE    :4:C3,1
+MOVE    :4:B2,1
+MOVE    :4:C2,2
diff --git a/icons/sixteen-16d24.png b/icons/sixteen-16d24.png
new file mode 100644 (file)
index 0000000..059a43e
Binary files /dev/null and b/icons/sixteen-16d24.png differ
diff --git a/icons/sixteen-16d4.png b/icons/sixteen-16d4.png
new file mode 100644 (file)
index 0000000..ba7f494
Binary files /dev/null and b/icons/sixteen-16d4.png differ
diff --git a/icons/sixteen-16d8.png b/icons/sixteen-16d8.png
new file mode 100644 (file)
index 0000000..059a43e
Binary files /dev/null and b/icons/sixteen-16d8.png differ
diff --git a/icons/sixteen-32d24.png b/icons/sixteen-32d24.png
new file mode 100644 (file)
index 0000000..70f78b8
Binary files /dev/null and b/icons/sixteen-32d24.png differ
diff --git a/icons/sixteen-32d4.png b/icons/sixteen-32d4.png
new file mode 100644 (file)
index 0000000..dd1d871
Binary files /dev/null and b/icons/sixteen-32d4.png differ
diff --git a/icons/sixteen-32d8.png b/icons/sixteen-32d8.png
new file mode 100644 (file)
index 0000000..70f78b8
Binary files /dev/null and b/icons/sixteen-32d8.png differ
diff --git a/icons/sixteen-48d24.png b/icons/sixteen-48d24.png
new file mode 100644 (file)
index 0000000..65588f2
Binary files /dev/null and b/icons/sixteen-48d24.png differ
diff --git a/icons/sixteen-48d4.png b/icons/sixteen-48d4.png
new file mode 100644 (file)
index 0000000..4840c25
Binary files /dev/null and b/icons/sixteen-48d4.png differ
diff --git a/icons/sixteen-48d8.png b/icons/sixteen-48d8.png
new file mode 100644 (file)
index 0000000..65588f2
Binary files /dev/null and b/icons/sixteen-48d8.png differ
diff --git a/icons/sixteen-base.png b/icons/sixteen-base.png
new file mode 100644 (file)
index 0000000..e2974e0
Binary files /dev/null and b/icons/sixteen-base.png differ
diff --git a/icons/sixteen-ibase.png b/icons/sixteen-ibase.png
new file mode 100644 (file)
index 0000000..1dd073d
Binary files /dev/null and b/icons/sixteen-ibase.png differ
diff --git a/icons/sixteen-ibase4.png b/icons/sixteen-ibase4.png
new file mode 100644 (file)
index 0000000..52136ef
Binary files /dev/null and b/icons/sixteen-ibase4.png differ
diff --git a/icons/sixteen-icon.c b/icons/sixteen-icon.c
new file mode 100644 (file)
index 0000000..e625e30
--- /dev/null
@@ -0,0 +1,891 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"qXyXyXtXtXyXyXyXtXtXyXwX0X0X0XqX",
+"yXsXpXsXaXrXiXpXsXaXrXrXrXtXeXqX",
+"yXiX:X&X;X9XrX>X=X;X9XeX,XQ.*XrX",
+"rXhXI.&.z.yXsXR.q.V.uXeXG.Z.K.0X",
+"yXpX>X$X3X9XtX1XoX,X0XeX:XR.%XrX",
+"yXrX0XrX0X6XwX0XtXqX6XrXrXeXeXqX",
+"wXeXwXrXyXpXeXwXrXtXaXpXqXtXeXqX",
+"wX9XwX9X:X=X8XqX9X>X2XyX1X^.;XrX",
+"0X8XrX2Xi.5.;XyX6Xt.*.3XK.C.K.0X",
+"qX9XwX0XOX&X7XwXqXOX&XtX*XY.@XrX",
+"qX7X0X0XeXeX8X0X0XwXuXiXrXeXeXqX",
+"qXeXtXrXwXwXeXtXtXwXeXeX0XqXqXqX",
+"0XrX,XF.:XrXeX2XG.=XtX0XqXqXqXqX",
+"0XtXQ.Z.E.eXtX^.Z.Y.eXqXqXqXqXqX",
+"0XeX*XK.%XeXeX;XK.@XeXqXqXqXqXqX",
+"qXqXrX0XrXqXqXrX0XrXqXqXqXqXqXqX"
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"qXqX9X9X9X9X9X9X9X9X9X9X9X9X9X9X9X9X9X9X9X9X0XqXqXqXqXqXqXqXqXqX",
+"qXwXiXpXpXpXpXpXpXpXpXpXiXpXpXpXpXpXpXpXpXaXrXqXqXqXqXqXqXqXqXqX",
+"9XiXbXdXgXgXgXgXgXgXhXyXzXgXgXgXgXgXgXgXhXuXdX0XqXqXqX9XqXqXqXqX",
+"9XpXdX6X9X6X8X7X6X8X9X,XiX8X9X6X8X7X6X8X0X>XiXqX0X9X0XiXqXqXqXqX",
+"9XpXgX9XeXgXeXiXdX0XeX2XaXqXwXfXtXpXfXqXeX2XpX0XtXsXrXT.tXqXqXqX",
+"9XpXgX9X9XC.1X}.P.yXqX2XaX0XwXD.%X/.H.0XeX1XiXtX%XR.Y.9.L.uXwXqX",
+"9XpXgX7XaX%.F.-.( `.aX<XaX8XfX2.M.,XY +XiX<XuXpXU.M.P./.A.F.rXqX",
+"9XpXgX7XsXO.q.,.( }.pX<XaX8XfX,.0.x.! 8XrX1XuXpXY.Z.R._.G.N.eXqX",
+"9XpXgX9X8XP.].$X).uXqX2XaX0XqXR.^.^.P.4XrX1XiXyXoXH.H.r.S.rXwXqX",
+"9XpXgX8XwXdXiXtXpX9XwX1XaX0XqXsXaXpXdXwXwX1XpX0XtXsXwXD.wXeX0XqX",
+"9XpXhX0XwX0X0XqX0XwXeX1XaXqXwX0X0X0X0XqXrX1XiXqX0X9X0XiXqX0XqXqX",
+"9XpXuX,X2X1X2X1X2X1X1X<XyX<X2X1X2X1X2X1X2X>XsXqXqXqXqX9XqXqXqXqX",
+"0XeXsXaXrXhXpXaXaXaXaXdXdXaXeXgXaXaXaXaXpXgXzX9XqXqXqX0XqXqXqXqX",
+"qXqX0X9X2XaX8X0X8X0X9X8X9XqX<XpX9XqX8X0X9XqXkX9XqX9X0XiX0XqXqXqX",
+"qXqXqXqX4XsX9XeXsXeXpXpXqXwX2XaX0XwXsXrXwXfXlX9XrXaXrX_.yX0XqXqX",
+"qXqXqX0X3XaX0X7XR.4X`.).wXqX2XaX0X0XE.<X3X`.hXeX;X_.^.6.E.iXqXqX",
+"qXqXqX0X3XaX9XyX] `.] %.rXqX2XaX8XsX+.I.$.| aXpXI.c.F.!.B.U.rX0X",
+"wX6X6XqX3XaX7XsXo.x.L.F ;XtX1XaX7XgX,.u.+.H -XgXY.G.~.`.L.c.eXqX",
+"qX0X0X0X3XaXqX6Xn.W.Q.W.eXqX2XaX0X0XV.U.#XY.jXtX}.V.Z.a.B.9XeX0X",
+"qXqXqX0X3XaX9XwXgXpXpXaX0XqX2XaX9XqXfXaXrXhXlX8XtXpX0Xb.9XrX0XqX",
+"qXqXeXwX3XsXqXeX0XqXqXqXwXeX2XsXwXeX0XqXqXrXkX9XqX0X0XuXwX0XqXqX",
+"qX0X1X<X1XrX,X2X1X1X1X1X1X1X,XtX,X2X1X1X<X5XjX0XqXqXqX0XqXqXqXqX",
+"qXwXpXiXdXsXyXuXiXpXpXpXpXiXdXdXyXuXiXpXiXsXsX0XqXqXqXqXqXqXqXqX",
+"qXqXqXqX0XrXaXpXyX0XqXqXqXqX0XeXaXpXuXqXqXqX0XqXqXqXqXqXqXqXqXqX",
+"qXqXqX0XtX%XP.P.oXtX0XqXqX0XrX;XI.I.}.tX0XqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqX9XsXW.M.Z.K.aX9XqXqX9XaX`.v.F.Z.pX0XqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqX0XeXT.P.R.K.wX0XqXqX0XrX/.G.^.S.0X0XqXqXqXqXqXqXqXqXqXqXqX",
+"qXqX9XiXT.9./._.r.D.iX9X0XiX_.5.!.`.a.b.uX0XqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXqXtXL.A.G.S.wXqXqXqX0XyXW.B.L.B.9XwXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXqXqXuXF.N.rXeX0XqXqXqX0XiXU.c.9XrX0XqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXqXqXqXrXeXwX0XqXqXqXqXqXqXrXeXeX0XqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX0XqX0XqXqXqXqXqXqXqXqXqXqXqXqXqX"
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXqX0X9X9X9X9X9X9X9X9X9X9X9X9X9X9X9X9X9X9X9X9X9X9X9X9X9X9X9X9X0XqXqXqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXqXuXpXpXpXpXpXpXpXpXpXpXpXpXaXiXiXpXpXpXpXpXpXpXpXpXpXpXpXaXeXqXqXqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqX0XuXAXcXbXvXvXvXvXvXvXvXvXvXnXhXzXNXcXvXvXvXvXvXvXvXvXvXvXvXfXzX9XwXqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqX9XpXcX5X9X8X8X8X8X8X8X8X8X8XqX*XwXiX6X8X8X8X8X8X8X8X8X8X8X7X&XzX9XwXqXqXqXqX9XqXqXqXqXqXqXqX",
+"qXqX9XpXbX9XwXwXwXwXwXwXwXwXwXqXtX:XtXsX9XwXwXwXwXwXwXwXwXwXwXqX;XzX9XwXqXqXqXwXpX0XqXqXqXqXqXqX",
+"qXqX9XpXvX8XwXqX9X8XqX0X8X8XqXqXrX:XrXsX9XqXqX8X9XqX9X8X0XqXqX0X-XzX9XwX0X0XqX7XQ.yXqXqXqXqXqXqX",
+"qXqX9XpXvX8XwX0XpXhXwXrXhXfXqX0XrX:XrXsX9XqXeXhXpXqXdXhXiX0XqX0X-XzX9XqXyXtXyX0X@.A.aX0XqXqXqXqX",
+"qXqX9XpXvX8X0XpXM.Q >X,X[ 8.eX0XrX:XrXsX8XrX,X` h.yX>.] '.uX0X0X-XzX7XpXL.t.l.y.c.l.N.aXqXqXqXqX",
+"qXqX9XpXvX8XqXrXXXd %X%.&.L A.fXwX:XrXsX8XqXtX&.` tX#X{ { lX8X0X-XzX7XsXb.S.Q.R.~.'.g.A.rXqXqXqX",
+"qXqX9XpXvX8XwX0XhXS :X_ m.| j.hXwX:XrXsX9X8XxXM.' VX/.R #XyX0X0X-XzX7XaXN.Y.[.`.!.~.(.>.<XrX0XqX",
+"qXqX9XpXvX8X0XpXb.3 >.R.N T :XrXeX:XrXsX8XrX<XJ a ~.z l N.uX9X0X-XzX6XsXx.d.V.n.T.^.8.XXuX0XqXqX",
+"qXqX9XpXvX8XqXeX;X=X%XiX,X7XyX0XrX:XrXsX9XwX7X&X*X,X3X$X=XeXqX0X-XzX8XyXoXG.Y.A.0.t.}.sX9XqXqXqX",
+"qXqX9XpXvX8XwXqXtXyXyX9XrXwX0XqXrX:XrXsX9XqXwXyXyXrXrXyXtXqXqX0X-XzX9XqXyXdXfXyX$..XaX8XqXqXqXqX",
+"qXqX9XpXvX8XqXqX0X0X0XqX0X0XqX0XrX;XrXaX8XqX0X0X0X0X0X0X0XqXqX0X-XzX9XwX0X9X9X7X3XuX9XqXqXqXqXqX",
+"qXqX9XpXnXqXtXrXrXrXrXrXrXrXrXrXuX<XtXfXwXrXrXrXrXrXrXrXrXrXrXrX:XzX9XwXqXqXqXqXeX0XqXqXqXqXqXqX",
+"qXqX9XaXhX*X:X:X:X:X:X:X:X:X:X;X,XXXqX8X=X:X>X;X:X:X:X:X:X:X:X-X@XzX9XwXqXqXqXqX0XqXqXqXqXqXqXqX",
+"qXqX0XuXlXeXyXqXwXtXrXrXrXrXrXrXtXeXxXiXrXrX9XtXrXrXrXrXrXrXtXwXgXvX8XwXqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXqXiXaXfX3XlXfXaXsXsXsXsXsXaXsXiXaXdXrX0XbXiXsXsXsXsXsXsXpXlXvX8XwXqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXqX9X8XwX$XfXqX8X9X9X9X9X9X9X9X9X9XqX1X2XhX5X9X9X9X9X9X9X7XiXvX8XwXqXqXqXqXqX0XqXqXqXqXqXqX",
+"qXqXqXqXqXqXrX*XhXeXqXqXqX9XqXqX9X9XqXqXeX3X4XjX8XwXqX0X0XqXqX8XpXvX8XwX0X9X0X8XwXtX9XqXqXqXqXqX",
+"qXqXqXqXqX0XrX*XhXeXqX0XrXsXeXqXaXfXeX0XeX3X4XjX8XwXqXpXiXqX0XiXkXcX8XqXtXaXsXtX<.=XpX9XqXqXqXqX",
+"qXqXqXqXqX0XrX*XhXeX0XeX>XQ.1X9X).I.<XeXwX3X4XjX8XqXwX'.}.eXuX^.oXnX7XrX=X~.`.R.4.t.&XiX9XqXqXqX",
+"qXqXqXqXqX0XrX*XhXeX9XuX_.7 B.$Xq o.;XrXwX3X4XjX7XqXrX .l hXq.M A.mX5XdXl.t.v.h.I.E.8.-XuX0XqXqX",
+"qXqXqXqXqX0XrX*XhXeXqX6XMX@.M.6XX.! W.pXqX3X4XjX8X0XaX&XH jXk E 6.vX6XaXM.Y.].`.~.^.W.3.2XeXqXqX",
+"qXqXeX3X<XeXeX*XhXeX0XeX3XN =.$XU.n r.kX0X3X4XjX8X0XuXz.r %XQ | D hX8XaXn.J.(.^.^.`.b.x.qXqXqXqX",
+"qXqXqX0X0XqXrX*XhXeX9XiX_.=.7.}.5.p.8XwXwX3X4XjX7XwXqXp.$.H.,X:._.NX5XaXA.9.g.e.B.M.j.uXeX0XqXqX",
+"qXqXqXqXqX0XrX*XhXeXqX0XuXlXjXuXkXhXwXqXeX3X4XjX8XqXqXgXzXaXrXjXlXcX8XwXeX7X8X1X%.v.yXwX0XqXqXqX",
+"qXqXqXqXqX0XrX*XhXeXqXqX0X7X8X0X8X8XqXqXeX3X4XjX8XqXqX8X7X9X0X5XiXvX8XwXqXwXeX8XA.eXwX0XqXqXqXqX",
+"qXqXqXqXqX0XrX*XhXeXqXqXqXqXqXqXqXqXqXqXeX3X4XjX8XwXqXqXqXqXqX9XaXvX8XwXqXqXqXqXaXqX0XqXqXqXqXqX",
+"qXqXqXqX0X0XeX%XgXeX0X0X0X0X0X0X0X0X0X0XwX2X2XjX7XqX0X0X0X0X0X8XpXvX8XwXqXqXqXqX9XqXqXqXqXqXqXqX",
+"qXqXqX9X-X-X-X%XdX;X-X-X-X-X-X-X-X-X-X-X;X$X9XeX%X;X-X-X-X-X-X*X6XvX8XwXqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXeXlXzXlXxXxXlXzXzXzXzXzXzXzXzXzXzXzXzXcXzXzXzXzXzXzXzXzXlXcXdX9XqXqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXqX9X9X9X8X9X7X7X7X6X8X9X9X9X9X9X9X9X9X8X8X6X7X7X7X9X9X9X9X8X9XqXqXqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXqXwXwXwXwXqXpXsXaXdXyXqXwXwXwXwXwXwXwXqXrXdXaXsXaXqXwXwXwXwXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXqXqXqXqX0XyXL.z.N.k.oXyX0XqXqXqXqXqX0XtX=Xl.M.c.A.eXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXqXqXqXqX0XtXa.A.Y.g.L.sX9XqXqXqXqXqX9XpX(.i.Y.H.r.7XwXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXqXqXqXqXqXyXl.Q.[.V.Y.fX9XqXqXqXqXqX0XsX`.v.].(.g.9XeXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXqXqXqXwX7X8Xa.R._.M.F.tX7XqXqXqXqXqX8XrXW.k._.~.u.<X8XqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXqXqX9XpXQ.} c.~.!.T.0.$.3XeX0XqXqXqXwX1.2.I.~.^.B...A.aX9XqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXqXqXqX0XuXD.l.'.~.^.y..XuX0XqXqXqX0XtX=Xy.R./.`.M.b.eXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXqXqXqXqXqXaXB.g.(.8.}.sX9XqXqXqXqXqX9XiX*X8.W.b.j.yXwX0XqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXqXqXqXqXqX0XsXA.:.oXaX8XqXqXqXqXqXqXqX9XiX;X3.x.uXwX0XqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXqXqXqXqXqXqXqXrX<XuX9XqXqXqXqXqXqXqXqXqX9XuX2XqXeX0XqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXrX0XqXqXqXqXqXqXqXqXqXqXqX0XeXqX0XqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqX0XqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX"
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/sixteen-web.png b/icons/sixteen-web.png
new file mode 100644 (file)
index 0000000..3d83e3d
Binary files /dev/null and b/icons/sixteen-web.png differ
diff --git a/icons/sixteen.ico b/icons/sixteen.ico
new file mode 100644 (file)
index 0000000..5d9263c
Binary files /dev/null and b/icons/sixteen.ico differ
diff --git a/icons/sixteen.rc b/icons/sixteen.rc
new file mode 100644 (file)
index 0000000..1d0e5fa
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "sixteen.ico"
diff --git a/icons/sixteen.sav b/icons/sixteen.sav
new file mode 100644 (file)
index 0000000..076b1fb
--- /dev/null
@@ -0,0 +1,39 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :7:Sixteen
+PARAMS  :3:4x4
+CPARAMS :3:4x4
+SEED    :15:601798566229573
+DESC    :38:2,16,3,10,13,8,7,4,9,14,12,11,15,1,5,6
+NSTATES :2:31
+STATEPOS:2:24
+MOVE    :5:C3,-1
+MOVE    :4:R0,1
+MOVE    :4:C1,1
+MOVE    :5:R0,-1
+MOVE    :5:C1,-1
+MOVE    :5:C3,-1
+MOVE    :4:R2,1
+MOVE    :4:R2,1
+MOVE    :4:C3,1
+MOVE    :5:C2,-1
+MOVE    :5:C2,-1
+MOVE    :4:R1,1
+MOVE    :4:R1,1
+MOVE    :4:C2,1
+MOVE    :4:C2,1
+MOVE    :4:R3,1
+MOVE    :4:R3,1
+MOVE    :4:C1,1
+MOVE    :5:R2,-1
+MOVE    :5:R2,-1
+MOVE    :5:C1,-1
+MOVE    :4:R2,1
+MOVE    :4:C0,1
+MOVE    :4:R3,1
+MOVE    :5:R2,-1
+MOVE    :5:C1,-1
+MOVE    :4:R2,1
+MOVE    :4:C1,1
+MOVE    :5:R3,-1
+MOVE    :5:C0,-1
diff --git a/icons/slant-16d24.png b/icons/slant-16d24.png
new file mode 100644 (file)
index 0000000..6070f75
Binary files /dev/null and b/icons/slant-16d24.png differ
diff --git a/icons/slant-16d4.png b/icons/slant-16d4.png
new file mode 100644 (file)
index 0000000..6bbebf1
Binary files /dev/null and b/icons/slant-16d4.png differ
diff --git a/icons/slant-16d8.png b/icons/slant-16d8.png
new file mode 100644 (file)
index 0000000..6070f75
Binary files /dev/null and b/icons/slant-16d8.png differ
diff --git a/icons/slant-32d24.png b/icons/slant-32d24.png
new file mode 100644 (file)
index 0000000..d7c7a9c
Binary files /dev/null and b/icons/slant-32d24.png differ
diff --git a/icons/slant-32d4.png b/icons/slant-32d4.png
new file mode 100644 (file)
index 0000000..2c54ed6
Binary files /dev/null and b/icons/slant-32d4.png differ
diff --git a/icons/slant-32d8.png b/icons/slant-32d8.png
new file mode 100644 (file)
index 0000000..d7c7a9c
Binary files /dev/null and b/icons/slant-32d8.png differ
diff --git a/icons/slant-48d24.png b/icons/slant-48d24.png
new file mode 100644 (file)
index 0000000..f4f3daf
Binary files /dev/null and b/icons/slant-48d24.png differ
diff --git a/icons/slant-48d4.png b/icons/slant-48d4.png
new file mode 100644 (file)
index 0000000..c15d5fc
Binary files /dev/null and b/icons/slant-48d4.png differ
diff --git a/icons/slant-48d8.png b/icons/slant-48d8.png
new file mode 100644 (file)
index 0000000..f4f3daf
Binary files /dev/null and b/icons/slant-48d8.png differ
diff --git a/icons/slant-base.png b/icons/slant-base.png
new file mode 100644 (file)
index 0000000..2765c2f
Binary files /dev/null and b/icons/slant-base.png differ
diff --git a/icons/slant-ibase.png b/icons/slant-ibase.png
new file mode 100644 (file)
index 0000000..423bdfb
Binary files /dev/null and b/icons/slant-ibase.png differ
diff --git a/icons/slant-ibase4.png b/icons/slant-ibase4.png
new file mode 100644 (file)
index 0000000..2073c4c
Binary files /dev/null and b/icons/slant-ibase4.png differ
diff --git a/icons/slant-icon.c b/icons/slant-icon.c
new file mode 100644 (file)
index 0000000..6afa90d
--- /dev/null
@@ -0,0 +1,891 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"9X6X7X6X6X5XqX;X8X6X4X5X9X3X2X0X",
+"6X4X9X5X3XeXY.x.R.yX:XeX%Xh.R.qX",
+"7X9XrX9X0XW.(.9X!.^.sX*XG.<X8XeX",
+"6X5XqX{.f._.<Xm.2X~.Z.K.4XS.K.eX",
+"6X3X8XD.i.7X!.q./.hXB.I.rXy.c.qX",
+"7XtXE./.6X[.P.=XJ.OXhX~./.4X1XeX",
+"3XD.'.-XI.T.yX9XeXK.H.:X/.G.+XwX",
+"@Xm.0XU.d.,X6X;X7X=Xe.I.qXv.Z.9X",
+"7XY.~.,X(.8XqX4XwX5X'.G.OX+X XeX",
+"8XaX^.!.pXwXqX5XwXeXqXeXH.#XuXqX",
+":X}.dXI.L.rX3X,X5X5X>X4XeXj.,XrX",
+"8XF.|.kXR.W.iX2X0X0X2XuX`.Y.wXqX",
+"6X7XI.`.9X/.Q.qXwXwXwX`.R.7X3XeX",
+"*XN.5XZ.n.8XE.~.qX0X[.K.qXi.c.wX",
+">XP.7X].N.<XeX3X8X8X3XwX5XV.K.qX",
+"0XqXrX8X7XeXqXeXwXwXeXqXeX0XqXwX"
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"qXwXrXeXeXeXeXeXeXeXeXeXrXqXeXiXwXrXeXeXrXtXeXeXeXrX0XrXeXwXwXqX",
+"wX6X,X3X2X2X2X2X1X2X2X3X-XwX,X X7X:X3X2X<X=X3X2X3X-XrX>X1X9X6XwX",
+"rX,X:X6X4X4X5X3X*X5X4X7XXXXXr.$..X*X6X5X1X+X6X3X8X.XoXE v.'.:XrX",
+"eX3X6XrXwXeXeXqX,XrX8XlXW.<.P.~.=.=XdX0XwX1XrX8XzXU.1.U.C.'.tX0X",
+"eX2X4XwX0XqXqX9X,X8XhX!.$.4X#X-XoXo.;XaX6X,X8XhXT.*.6X#X-XtXqXqX",
+"eX2X4XeXqX0XeXtX*XhX!.#.9XgX4X6XlX+XX.:XiX&XkXT.%.eXdX1X9XtX0XqX",
+"eX2X5XeXqXeX5XK.U.A.=.0XpX[.V.R.@XjX@Xo.*XsXY.&.eXuX|.L.Y.=XtX0X",
+"eX2X3XqX9XwX~.!.| N.{.dX@XQ.j.,.^.:XdXOX#.c.<.8XiX#XL.&.8.W.5XwX",
+"eX1X*X,X<X$X/.qXF Y./.2XE.9Xt.u.;X`.<X7X Xn ].0X>X_.I.| ;.(.*XyX",
+"eX2X5XrX8XhXF.c.s.K. XfX;Xk.,.b.c.rXeX9XaX.X+.-XgX&XW.g.N.!.8XwX",
+"eX2X4X8XhX!.=.'./.|.rXuX<.M..X{.t.f.jX9X5X6X@X..-XfX+X(._.1XrX0X",
+"eX2X6XlX!.#.0XdX,XfXuX:.F.cX,X7XlXy.s.jXeX>XhX+XX.=XzX3XeXtX0XqX",
+"rX,X*XR.%.0XiX%X[.=X1.F.jX8X<X4X0XhXu.f.<X[.>XhXOX+..XoX XqXwXqX",
+"yX$Xk.5.3XfXXXv.f.b.m.cX8XtX3X7XrXqXhXi.p.e.D.*XxX].0.i.D.).eX0X",
+"pX[.=.*X|.1XW.1XX.eX/.2X<X3X*X;X2X2X-X`.9X} Q.~.7X/.eXw.Y.+X&XtX",
+"pX'.x. X*X5XQ.+XW ).~.8X4X7X;X<X6X5X1X(.V.~ +X].rX(.&X3.r.{.:XrX",
+"eX1XJ.&.oXlX#XF.H.F.3XtXqXrX2X6XrXqXyX$XZ.Y.y.s.zX9XT.H.A.].tX0X",
+"rX>X>X-Xo.+XjX1XXXqXeX0X0XeX1X5XwXqX0XtX4X.XyXi.a.kXwX+X:XyXqXqX",
+"eX3X5XdX;XX.+XdX,XeX0XqXqXeX1X5XwXqXqXqXqX2XeXhXu.a.lX2X8XqXqXqX",
+"eX2X3X0XaX:XX.+X8X9XwXqXqXeX2X6XeXqXqXwX9X>XwX9XjXu.f.eX5XwXqXqX",
+"eX2XwX0X6XiX;X| ].sX6X0X9XqX<X4XqX9X9X0X8X:XqX9X8XdXi.s.eXqXqXqX",
+"iX|.7.0X>X-X6XOX] `.9X-X>X<X%X=X<X>X>X,X:X@X,X>X;X2X2XE 2XeXqXqX",
+"wX9Xp.t.hXqX9XpXXXO.#XfX8XrX2X6XrXwXwXeX0X,XrX0XyXeX=.I.eXqXqXqX",
+"eX1XpXl.q.gX0X5X5X:X..@XsX9X1X5XwXqXqXqX9X>X0XrXeX$.W.eX2XwXqXqX",
+"eX2X6XzXj.w.gXrX>XgX;XX.+XgX>X5XwXqXqXqX0X:XyXeX%.R.xX<X0XeX0XqX",
+"rX<X.X7XlXj.r.:X'.&XfX;X..#XwX2XwX0XqXqX8X3XrX$.R.lX<X}.OXqXwXqX",
+"uX#Xg.^.9XkXh.d.u.W.+XjX>X@.`.eXeXeXeXeXeX-X-.W.xX<XR.t.j.).wXqX",
+"wX7X..W.XX,X`.5XX.qXY.1X9XXX#.-X2X<X1X,X8X3.D.tX2X[.J...;.!.-XtX",
+"tX&X5.~.%X8XE.XXQ /.(.9X3XqX8X3X6X6X6X6X5X4XeX2XqX|.Q.&.e.^.,XrX",
+"rX<XR.[.tXtX=XG.P.G.4XrXwXqXwXeXwXwXwXwXwXeXqXwXeXqX^.W.R.|.tX0X",
+"wX8X,XtXqX0XtX7X:XqXeX0XqXqXqXqXqXqXqXqXqXqXqXqX0XwXwX;X1XtXqXqX",
+"qXwXrX0XqXqX0XwXtXqX0XqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXtXrX0XqXqX"
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXqX0XqX0X0X0X0X0X0X0X0X0X0X0X0X0X0XqXqX9X0XqX0XqX0X0X0X0X0X0X0X0X0XqX0XqXqX0X9XqX0XqXqXqXqX",
+"qXqXqXeXtXeXrXrXrXrXrXrXrXrXrXrXrXrXrXtX0XqXsXuX0XyXrXrXrXrXrXuXrXrXrXrXeXtXeX9XrXiX9XrXwXqXqXqX",
+"qXqXeX<X&X,X:X:X:X:X:X>X:X:X>X:X:X>X:X$XrX0XW.OXwX@X>X:X:X:X:X{.:X>X:X:X,X$X,XdX<X{.pX>X5XwXqXqX",
+"qX0XtX&X%X5X2X2X2X2X2X4X;X#X5X2X2X2X4Xm.0XR.I ` wXN.0X1X2X2X4X`.,X3X2X1X9X~.(.1X0 &.sXD.=XtXqXqX",
+"qXqXeX,X5XtXeXeXeXeXeXtX6X>XyXwXeX0XhX(.c.N.q.@Xd.*XpXqXeXeXtX>X0XrXeXeXqXaXg.G.<.p.Y.G.yX0XqXqX",
+"qX0XrX:X2XeX0XqXqXqX0XwX3X;XeX0X9XrXrX~ <.~.~.Y.| $.hX0X0X0XeX*X8XqXqX6XlXS.H F.).W.D.6XeXqXqXqX",
+"qX0XrX:X2XeXqXqXqXqXqXeX3X;XrX8XrXyXY u.cX;X~.6XlX@.O.jX0X0XeX*X8XwX7XlXA.Z 2XaX`..XiXeX0XqXqXqX",
+"qX0XrX:X2XeXqXqXqXqXqXqX6X<XwXeXyXY i.xX6XeX2XeX8XkX#.O.jX0XwX*X8X7XlXA.Z 3XuX0X5X8XqX0XqXqXqXqX",
+"qX0XrX:X2XeXqXqXqXqXqXaX*X{.pXyXY i.xX6XpX3X_.0XyX8XkX#.O.jXwX*X4XzXA.Z 3XuXqXiX X@XpXwX0XqXqXqX",
+"qX0XrX:X2XeX0XqXqXqXqXK.D.U.P.E p.zX6XyX^.D.T.F.|.yX8XkX#.o.lX$XhXS.Z 2XuXqX1XS.Y.I.H.9XwXqXqXqX",
+"qX0XrX>X4XtXwXeXqXpXL.Y.t.X.Y.e.uXqXpX].G.y.` K.A.%XuXqXzX$.+.iXN.D 4XaX0XeXB.[.+.,.).D.uX0XqXqX",
+"qX0XrX:X;X6X3X3X6X=XC.fXY.y ~.+XW.0X6XB.2XrX&...9XZ.wX1X2XiX#.C ! &X0X<X0X(.|.r.%.` V.Q.=XtXqXqX",
+"qX0XrX:X#X>X;X;X<X|.S.pXY.d m.$XJ.4X-Xb.aXS.V $X4XM.3X-X-X=XyX' / yX*X=X4XU.@X9.=.' M.).#XyX0XqX",
+"qX0XrX>X5XyXeXrXwXpXL.`.5.+. XA.,XtXaX^.'.@.L D.R.oXpXeXrXeXrX0X#.@.zXwXtX4XB.,X..:.:XN.wXqXqXqX",
+"qX0XrX:X2XwX0X8XeXtXE 8.]..XA.`.uX7XlX,.' }.|.R.U l.zX6X0X0XwX&XfX#.o.jX8XyX}.B.oX|.m.*XyX0XqXqX",
+"qX0XrX:X2XeX9XrXyXY p.iX!.K.,XuX7XzX>.[ yX}.F.&X7XK l.zX7XqXeX&X7XlX#.O.jX9XyX2XP.E.8XyX0XqXqXqX",
+"qX0XrX>X2X0XrXyXY i.zXqX0X4XtX7XzX>.] fXeXtX1XyXyXqXJ z.xX6XqX*X6X9XkX#.O.jX8XrX5X9XeX0XqXqXqXqX",
+"qX0XrX;X2XhXrXY i.xX6XaX3X*XsXlX>.] fXqX0X9X%XwX8XyXqXJ l.cXuX$XwXuX8XkX#.O.hXsX=X2XsX0XqXqXqXqX",
+"qX0XrX-XJ.(.~ u.xX7XyX].I.G.^.,.[ fXqX0XwX9X*XwXqX9XyX0XI a._.P.J.OXyX8XkX#.@._.I.D.(.yX0XqXqXqX",
+"qX0XuX|.m.n.,.cX6XiX^.D.r.c.].' yXeX0XwXwX0X*XeXqXwX9XaX=X .m.-.P.C.XXyX0XdXo.S.3.Y.T.T.iX9XqXqX",
+"qX0XtX*Xt =XR.>XeX4XN.dX0.D nX^.|.tX9X9X0X8X&XqX9X9X8XyXQ.#X#Xt.M 7XV.eXeXOXQ.hXE -.MXM.2XeXqXqX",
+"qX9XaX^.=.fXF.(.2X'.D.cX].Y cX`.H.1X%X*X*X&X}.=X*X*X%X<XN.=XsX*.h.gXb.+X1XI.Q.CXd.-.VXK.$XyX0XqX",
+"qX9XaX~.| 1XI.6XeXqXM.eX=.e '.I.@XuXqXwXeXqX=XeXwXwXqXaX^.}.i., d.,XU.tXtX=XD.qXG B -XZ.5XwXqXqX",
+"qXqXwX4X^.e. .kX8XyX Xb.(.[.M.I.yX0XqXqXqX9X*XwX0XqXqXqXwXC.L.|.~.O.u.vX5XuXR.V.'.).z.[.uX0XqXqX",
+"qX0XtX$XH.-X%.+.kX8XyX*XL.S. XtXqXqXqXqXqX9X*XwXqXqXqXqXeXwX).C.W.$XU l.xX6XyX.XF.J.&XuX0XqXqXqX",
+"qX0XrX>X9XpXhX+.@.kX8XuXqX2XsX0XqXqXqXqXqX9X*XwXqXqXqXqXqXqXsX,XrXdX0XJ z.xX5XaX4X8XiX9XqXqXqXqX",
+"qX0XrX:X1XqX0XjX+.@.jXqX1X-XwX0XqXqXqXqXqX9X*XwXqXqXqXqXqX0XqX&X7X9XyXqXJ z.zX9X;X<XwXqXqXqXqXqX",
+"qX0XrX:X2XeX0X0XjX+.+.lX3X-XrX0XqXqXqXqXqX9X*XwXqXqXqXqXqXqXeX*X8XwX9XyXqXJ l.vX=X2XeXqXqXqXqXqX",
+"qX0XrX:X<XeX0X0X9XjXO.#.pX=XwX0XqXqXqXqXqX9X&XwX0XqXqXqXqX0XwX*X8XqXqX8XtX0XH c.tX,XeXqXqXqXqXqX",
+"qXqXeX2XrXeXeXeXwXwXlX%.| rXrXwXeXeXeXeXeXqX=XrXeXeXeXeXeXwXtX-X0XrXeXeX0XiXwXI f.wXqXqXqXqXqXqX",
+"qX9XpX/.h.9X$X*X*X&X&X7X{ X.8X&X&X*X*X*X=X&X}.=X*X*X*X*X*X*X-X|.%X=X*X*X=X$X>X;Xs #XuX0XqXqXqXqX",
+"qX0XyX%XB z.jX4X8X8X7X8XsX_ +.fX7X7X8X8X8X6X%X9X8X8X8X8X8X8X0X%X5X9X8X8X7X6XgX1.[ 8XwX0XqXqXqXqX",
+"qXqXwX3X1XL c.xX8XwXwXeX2XqX%.@.kXqXqXwXwX0X*XeXqXwXwXwXwXqXrX=X9XeXwXqX9XzX1.[ 7X3XwXqXqXqXqXqX",
+"qX0XrX-X7XwXG c.zX7XqXeX3X-XzXO.@.kX0X0XqX9X*XwXqXqXqXqXqX0XeX*X8XwX0X8XzX<._ hX>X1XeXqXqXqXqXqX",
+"qX0XrX:X,XiX0XH c.zX7XeX3X=XwXjX+.@.kX0XqX0X*XwXqXqXqXqXqXqXeX*X8XqX8XzX<.` dXrX-X1XeXqXqXqXqXqX",
+"qXqXeX,X9XqXtX0XH c.zXqX0X3XtX8XkX+.@.kX0X9X*XwXqXqXqXqXqXqXeX*X8X9XzX<.` fX0XrX5X8XrX0XqXqXqXqX",
+"qX0XtX&X).wXwXtX0XG c.fX].W.5XyX9XkX+.@.kX8X&XwXqXqXqXqXqXqXeX*X5XxX<.` fX0XtX6X~._.qXtX0XqXqXqX",
+"qX0XrX-XI.V.>XwXtX9XU 0.L.Y.m.|.yX8XjXO.@.hX&XqX0X0X0X0X0X0XwX&XhX1._ dX0XtXoXC.Y.U.V.:XtX0XqXqX",
+"qX0XiX|.~ }.Z.yX0XdXS.#X@._ dXv.3XyXwXzX$.@.3XrXeXrXrXrXrXeXrX5X,.{ hXrXrX6XC.'.T _ {.C.eXqXqXqX",
+"qXqX7XvX( M./.].5X.XA.cXQ.U bX_.U.6X-X:XeX` 6.0X-X>X>X>X>X-X0Xt.E 9X>X-X5XT.@X3.t.*.z.).#XyX0XqX",
+"qX0XwX8Xv W.R.oX8X%XC.lXh.j 8X_.W.9X1X1X2X9X%X3X2X2X2X2X2X2X3X%X8X3X1X1X8X^.|.z.E K U.W.*XtXqXqX",
+"qX0XuX|.m.OXZ.pXqXaXF.`.h.=.G.C.6XtXeXeXeXwXyXeXeXeXeXeXeXeXeXyXwXwXeXeXrXwXn.;Xs.n.+XC.yX0XqXqX",
+"qXqXeX4XR.Z.7XeX0XwX9XS.W.'.A.&XyX0XqXqXqX0X0XqXqXqXqXqXqXqXqX0X0XqXqXqX0XtX:XN.(.Q.C.6XeXqXqXqX",
+"qXqXwX4X&XyXeX0XqX0XwXuX*X+XqXyX0XqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX0XtXeX#X&XyXeX0XqXqXqX",
+"qXqXqXwXtX0XqXqXqXqXqX0XtXyXqX0XqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX0XqXyXtX0XqXqXqXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqX0XqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX0XqXqXqXqXqXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX"
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/slant-web.png b/icons/slant-web.png
new file mode 100644 (file)
index 0000000..424519b
Binary files /dev/null and b/icons/slant-web.png differ
diff --git a/icons/slant.ico b/icons/slant.ico
new file mode 100644 (file)
index 0000000..4afa946
Binary files /dev/null and b/icons/slant.ico differ
diff --git a/icons/slant.rc b/icons/slant.rc
new file mode 100644 (file)
index 0000000..2046b30
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "slant.ico"
diff --git a/icons/slant.sav b/icons/slant.sav
new file mode 100644 (file)
index 0000000..02017e5
--- /dev/null
@@ -0,0 +1,51 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :5:Slant
+PARAMS  :5:8x8de
+CPARAMS :5:8x8de
+DESC    :47:a10h23a32a02b22e3a2c1g3a20d32a0c221a210i0a101b0
+NSTATES :2:44
+STATEPOS:2:44
+MOVE    :4:/7,0
+MOVE    :4:\7,1
+MOVE    :4:\1,0
+MOVE    :4:/2,0
+MOVE    :4:\0,4
+MOVE    :4:/0,5
+MOVE    :4:\0,6
+MOVE    :4:/0,7
+MOVE    :4:\1,7
+MOVE    :4:/7,7
+MOVE    :4:/3,7
+MOVE    :4:\4,7
+MOVE    :4:\5,7
+MOVE    :4:/2,7
+MOVE    :4:/7,4
+MOVE    :4:\7,5
+MOVE    :4:\7,3
+MOVE    :4:\7,2
+MOVE    :4:/6,2
+MOVE    :4:\6,3
+MOVE    :4:\7,6
+MOVE    :4:/3,0
+MOVE    :4:/2,1
+MOVE    :4:\3,1
+MOVE    :4:/2,2
+MOVE    :4:\3,2
+MOVE    :4:/2,3
+MOVE    :4:\3,3
+MOVE    :4:\1,1
+MOVE    :4:/0,1
+MOVE    :4:\0,2
+MOVE    :4:\1,2
+MOVE    :4:\1,3
+MOVE    :4:/0,3
+MOVE    :4:\1,4
+MOVE    :4:\0,0
+MOVE    :4:\5,3
+MOVE    :4:\6,4
+MOVE    :4:/5,4
+MOVE    :4:/5,5
+MOVE    :4:\6,5
+MOVE    :4:/4,5
+MOVE    :4:\4,6
diff --git a/icons/solo-16d24.png b/icons/solo-16d24.png
new file mode 100644 (file)
index 0000000..cb26162
Binary files /dev/null and b/icons/solo-16d24.png differ
diff --git a/icons/solo-16d4.png b/icons/solo-16d4.png
new file mode 100644 (file)
index 0000000..8386cb1
Binary files /dev/null and b/icons/solo-16d4.png differ
diff --git a/icons/solo-16d8.png b/icons/solo-16d8.png
new file mode 100644 (file)
index 0000000..63d2522
Binary files /dev/null and b/icons/solo-16d8.png differ
diff --git a/icons/solo-32d24.png b/icons/solo-32d24.png
new file mode 100644 (file)
index 0000000..a4be0cb
Binary files /dev/null and b/icons/solo-32d24.png differ
diff --git a/icons/solo-32d4.png b/icons/solo-32d4.png
new file mode 100644 (file)
index 0000000..6baa6e0
Binary files /dev/null and b/icons/solo-32d4.png differ
diff --git a/icons/solo-32d8.png b/icons/solo-32d8.png
new file mode 100644 (file)
index 0000000..8ecb195
Binary files /dev/null and b/icons/solo-32d8.png differ
diff --git a/icons/solo-48d24.png b/icons/solo-48d24.png
new file mode 100644 (file)
index 0000000..6c57173
Binary files /dev/null and b/icons/solo-48d24.png differ
diff --git a/icons/solo-48d4.png b/icons/solo-48d4.png
new file mode 100644 (file)
index 0000000..a21f7b0
Binary files /dev/null and b/icons/solo-48d4.png differ
diff --git a/icons/solo-48d8.png b/icons/solo-48d8.png
new file mode 100644 (file)
index 0000000..f62e55b
Binary files /dev/null and b/icons/solo-48d8.png differ
diff --git a/icons/solo-base.png b/icons/solo-base.png
new file mode 100644 (file)
index 0000000..128b169
Binary files /dev/null and b/icons/solo-base.png differ
diff --git a/icons/solo-ibase.png b/icons/solo-ibase.png
new file mode 100644 (file)
index 0000000..d72e632
Binary files /dev/null and b/icons/solo-ibase.png differ
diff --git a/icons/solo-ibase4.png b/icons/solo-ibase4.png
new file mode 100644 (file)
index 0000000..1043ce8
Binary files /dev/null and b/icons/solo-ibase4.png differ
diff --git a/icons/solo-icon.c b/icons/solo-icon.c
new file mode 100644 (file)
index 0000000..65b7871
--- /dev/null
@@ -0,0 +1,771 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 246 2 ",
+"   c #010101",
+".  c #101010",
+"X  c gray7",
+"o  c #0F110F",
+"O  c gray7",
+"+  c #101010",
+"@  c gray7",
+"#  c #111111",
+"$  c #101010",
+"%  c #111111",
+"&  c #101010",
+"*  c gray7",
+"=  c #0F110F",
+"-  c #111211",
+";  c #101010",
+":  c #010101",
+">  c #101010",
+",  c #C2C3C2",
+"<  c gray85",
+"1  c #DCD6DC",
+"2  c #D6D5D6",
+"3  c gray75",
+"4  c gray82",
+"5  c #D5D5D5",
+"6  c gainsboro",
+"7  c #D2D2D2",
+"8  c gray75",
+"9  c #D5D5D5",
+"0  c #DCD6DC",
+"q  c #DCDADC",
+"w  c gray76",
+"e  c #101010",
+"r  c gray7",
+"t  c gray85",
+"y  c #EBEEEB",
+"u  c #94C794",
+"i  c #E6EAE6",
+"p  c #D5D4D5",
+"a  c #EDEEED",
+"s  c #CBCBCB",
+"d  c #9D9D9D",
+"f  c #EBECEB",
+"g  c #D2D3D2",
+"h  c #F4F0F4",
+"j  c #A2CDA2",
+"k  c #BBDBBB",
+"l  c #E0DBE0",
+"z  c #101110",
+"x  c #101110",
+"c  c #DAD5DA",
+"v  c #C2DAC2",
+"b  c #3F9E3F",
+"n  c #D4DED4",
+"m  c #D1CFD1",
+"M  c #EBECEB",
+"N  c gray68",
+"B  c #6C6C6C",
+"V  c #E9E9E9",
+"C  c #CCCDCC",
+"Z  c #F0EAF0",
+"A  c #5BAC5B",
+"S  c #7BBD7B",
+"D  c #DED7DE",
+"F  c #0F110F",
+"G  c gray7",
+"H  c #D5D5D5",
+"J  c gray93",
+"K  c #D2DED2",
+"L  c #E5E8E5",
+"P  c #D2D1D2",
+"I  c #EAEAEA",
+"U  c #CDCDCD",
+"Y  c #C6C6C6",
+"T  c gray91",
+"R  c #D0D1D0",
+"E  c #EDEBED",
+"W  c #D6E0D6",
+"Q  c #D8E5D8",
+"!  c #D8D6D8",
+"~  c #111211",
+"^  c #101010",
+"/  c gray75",
+"(  c gray83",
+")  c #D4D1D4",
+"_  c #D2D2D2",
+"`  c #BBBCBB",
+"'  c gray80",
+"]  c gray84",
+"[  c #D8D8D8",
+"{  c #CDCDCD",
+"}  c #BCBCBC",
+"|  c gray82",
+" . c #D2D0D2",
+".. c #D9D6D9",
+"X. c #BEBFBE",
+"o. c #101010",
+"O. c #111111",
+"+. c #D2D2D2",
+"@. c #E7E7E7",
+"#. c #DFDFDF",
+"$. c #E6E6E6",
+"%. c #CDCDCD",
+"&. c #E1E1E1",
+"*. c gray89",
+"=. c #E2E2E2",
+"-. c #E1E1E1",
+";. c #CDCDCD",
+":. c gray90",
+">. c #E1E2E1",
+",. c #E7E8E7",
+"<. c gray82",
+"1. c #111111",
+"2. c #101010",
+"3. c gray86",
+"4. c #CACACA",
+"5. c gray38",
+"6. c #E1E1E1",
+"7. c #D2D2D2",
+"8. c #E4E4E4",
+"9. c #E7E7E7",
+"0. c #E4E4E4",
+"q. c #D0D0D0",
+"w. c gray91",
+"e. c gray90",
+"r. c gray92",
+"t. c gray83",
+"y. c gray7",
+"u. c #111111",
+"i. c #D8D8D8",
+"p. c gray85",
+"a. c #686868",
+"s. c LightGray",
+"d. c gray84",
+"f. c gray89",
+"g. c #111111",
+"h. c #D2D2D2",
+"j. c gray89",
+"k. c #D8D8D8",
+"l. c gray91",
+"z. c #CDCDCD",
+"x. c #E1E1E1",
+"c. c #E4E4E4",
+"v. c #E3E4E3",
+"b. c #E1E1E1",
+"n. c #CDCDCD",
+"m. c gray90",
+"M. c #E1E1E1",
+"N. c gray91",
+"B. c gray82",
+"V. c #111111",
+"C. c #101010",
+"Z. c gray",
+"A. c #D8D8D8",
+"S. c #D2D2D2",
+"D. c #BBBBBB",
+"F. c #CDCDCD",
+"G. c #D3D1D3",
+"H. c #D4D2D4",
+"J. c #CDCDCD",
+"K. c #BCBCBC",
+"L. c gray82",
+"P. c LightGray",
+"I. c gray84",
+"U. c gray75",
+"Y. c #101010",
+"T. c #111111",
+"R. c #D7D7D7",
+"E. c gray89",
+"W. c #C6C6C6",
+"Q. c #E6E6E6",
+"!. c #D2D2D2",
+"~. c #E7E6E7",
+"^. c #DBE3DB",
+"/. c #D8E2D8",
+"(. c #E7E6E7",
+"). c #D0D1D0",
+"_. c #ECECEC",
+"`. c gray83",
+"'. c gray90",
+"]. c #D7D7D7",
+"[. c #111111",
+"{. c #111111",
+"}. c #D5D5D5",
+"|. c #DDDDDD",
+" X c #606060",
+".X c #D7D7D7",
+"XX c #D1D2D1",
+"oX c #E9E5E9",
+"OX c #BBD4BB",
+"+X c #5FAF5F",
+"@X c #E7E4E7",
+"#X c #CBCCCB",
+"$X c #F3F3F3",
+"%X c #959595",
+"&X c #ACACAC",
+"*X c #DFDFDF",
+"=X c gray6",
+"-X c gray7",
+";X c gray86",
+":X c gray90",
+">X c #AEAEAE",
+",X c #F3F3F3",
+"<X c #D1D2D1",
+"1X c #EFEBEF",
+"2X c #C3DBC3",
+"3X c #8DC68D",
+"4X c #EBE9EB",
+"5X c #D2D2D2",
+"6X c #F7F8F7",
+"7X c gray65",
+"8X c #A5A5A5",
+"9X c gray89",
+"0X c #101010",
+"qX c #101010",
+"wX c #C3C3C3",
+"eX c #D7D7D7",
+"rX c gray84",
+"tX c #D5D5D5",
+"yX c gray75",
+"uX c gray82",
+"iX c #D3D4D3",
+"pX c #DAD7DA",
+"aX c #D2D2D2",
+"sX c gray75",
+"dX c #D5D5D5",
+"fX c LightGray",
+"gX c #DADADA",
+"hX c gray76",
+"jX c #101010",
+"kX c #010101",
+"lX c #101010",
+"zX c gray7",
+"xX c #101010",
+"cX c gray7",
+"vX c #101010",
+"bX c #111111",
+"nX c gray7",
+"mX c #101110",
+"MX c #111111",
+"NX c #101010",
+"BX c gray7",
+"VX c #111111",
+"CX c gray7",
+"ZX c #101010",
+"AX c #010101",
+"SX c white",
+/* pixels */
+"  . X o O + @ # $ % & * = - ; : ",
+"> , < 1 2 3 4 5 6 7 8 9 0 q w e ",
+"r t y u i p a s d f g h j k l z ",
+"x c v b n m M N B V C Z A S D F ",
+"G H J K L P I U Y T R E W Q ! ~ ",
+"^ / ( ) _ ` ' ] [ { } |  ...X.o.",
+"O.+.@.#.$.%.&.*.=.-.;.:.>.,.<.1.",
+"2.3.4.5.6.7.8.9.9.0.q.w.e.r.t.y.",
+"u.i.p.a.s.d.f.9.9.0.q.w.e.r.t.y.",
+"g.h.j.k.l.z.x.c.v.b.n.m.M.N.B.V.",
+"C.Z.A.p.S.D.F.G.H.J.K.L.P.I.U.Y.",
+"T.R.E.W.Q.!.~.^./.(.)._.`.'.].[.",
+"{.}.|. X.XXXoXOX+X@X#X$X%X&X*X=X",
+"-X;X:X>X,X<X1X2X3X4X5X6X7X8X9X0X",
+"qXwXeXrXtXyXuXiXpXaXsXdXfXgXhXjX",
+"kXlXzXxXcXvXbXnXmXMXNXBXVXCXZXAX"
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 180 2 ",
+"   c black",
+".  c #010101",
+"X  c #0E0E0E",
+"o  c #101010",
+"O  c #111111",
+"+  c gray8",
+"@  c #1D1D1D",
+"#  c #282828",
+"$  c #2A2A2A",
+"%  c #2C2C2C",
+"&  c gray23",
+"*  c #3F3F3F",
+"=  c gray25",
+"-  c #494949",
+";  c #4B4B4B",
+":  c gray30",
+">  c #515151",
+",  c #555555",
+"<  c gray35",
+"1  c gray37",
+"2  c #656565",
+"3  c DimGray",
+"4  c #777777",
+"5  c #797979",
+"6  c #7B7B7B",
+"7  c gray49",
+"8  c #7E7E7E",
+"9  c #158E15",
+"0  c #2B9B2B",
+"q  c #2E9C2E",
+"w  c #359D35",
+"e  c #399F39",
+"r  c #38A038",
+"t  c #44A344",
+"y  c #44A544",
+"u  c #4AA74A",
+"i  c #4BA74B",
+"p  c #4EA94E",
+"a  c #53AB53",
+"s  c #5AAD5A",
+"d  c #5CAF5C",
+"f  c #77B677",
+"g  c #74B874",
+"h  c #7CBB7C",
+"j  c gray53",
+"k  c gray54",
+"l  c gray55",
+"z  c #989898",
+"x  c gray60",
+"c  c #9B9B9B",
+"v  c #9D9D9D",
+"b  c #82BD82",
+"n  c #84BF84",
+"m  c #86BF86",
+"M  c gray65",
+"N  c #A7A7A7",
+"B  c gray67",
+"V  c gray69",
+"C  c #B1B1B1",
+"Z  c gray70",
+"A  c #B4B4B4",
+"S  c #B7B7B7",
+"D  c #BBBBBB",
+"F  c gray74",
+"G  c gray",
+"H  c #8CC28C",
+"J  c #8DC28D",
+"K  c #8FC38F",
+"L  c #99C799",
+"P  c #A0C9A0",
+"I  c #A2CAA2",
+"U  c #A2CBA2",
+"Y  c #A6CCA6",
+"T  c #A8CCA8",
+"R  c #AECFAE",
+"E  c #B6D2B6",
+"W  c #B6D3B6",
+"Q  c #B7D2B7",
+"!  c #B7D3B7",
+"~  c #C1C1C1",
+"^  c #C3C3C3",
+"/  c gray79",
+"(  c gray80",
+")  c #CDCDCD",
+"_  c #CECECE",
+"`  c #C0D7C0",
+"'  c #C3D8C3",
+"]  c #C5D8C5",
+"[  c #D2D2D2",
+"{  c LightGray",
+"}  c gray83",
+"|  c #D5D5D5",
+" . c gray84",
+".. c #D6D7D6",
+"X. c #D7D7D7",
+"o. c #D2DDD2",
+"O. c #D6D8D6",
+"+. c #D7D8D7",
+"@. c #D7D9D7",
+"#. c #D8D8D8",
+"$. c #D8D9D8",
+"%. c #D9D8D9",
+"&. c gray85",
+"*. c #DADADA",
+"=. c gainsboro",
+"-. c #DDDDDD",
+";. c gray87",
+":. c #DFDFDF",
+">. c #D7E0D7",
+",. c #DDE2DD",
+"<. c #DEE2DE",
+"1. c #E1E1E1",
+"2. c #E2E2E2",
+"3. c gray89",
+"4. c #E1E4E1",
+"5. c #E2E4E2",
+"6. c #E3E4E3",
+"7. c #E4E4E4",
+"8. c #E5E4E5",
+"9. c gray90",
+"0. c #E5E6E5",
+"q. c #E6E5E6",
+"w. c #E7E5E7",
+"e. c #E6E6E6",
+"r. c #E7E6E7",
+"t. c #E7E7E7",
+"y. c #E6E8E6",
+"u. c #E7E8E7",
+"i. c #E7E9E7",
+"p. c #E8E6E8",
+"a. c #EBE7EB",
+"s. c gray91",
+"d. c #E8E9E8",
+"f. c #E9E8E9",
+"g. c #E9E9E9",
+"h. c #E9EBE9",
+"j. c #EAE9EA",
+"k. c #EAEAEA",
+"l. c #EAEBEA",
+"z. c #EBEAEB",
+"x. c gray92",
+"c. c #ECE8EC",
+"v. c #EDE8ED",
+"b. c #ECEAEC",
+"n. c #EEE9EE",
+"m. c #EEEBEE",
+"M. c #EFEAEF",
+"N. c #ECECEC",
+"B. c gray93",
+"V. c #EEEEEE",
+"C. c #EFEFEF",
+"Z. c #F1EAF1",
+"A. c #F1EBF1",
+"S. c #F3EBF3",
+"D. c #F0ECF0",
+"F. c #F1EDF1",
+"G. c #F2EDF2",
+"H. c #F3EDF3",
+"J. c #F4EBF4",
+"K. c #F5EBF5",
+"L. c #F4EEF4",
+"P. c #F6ECF6",
+"I. c #F6EEF6",
+"U. c #F7EFF7",
+"Y. c #F8EDF8",
+"T. c gray94",
+"R. c #F1F1F1",
+"E. c gray95",
+"W. c #F3F3F3",
+"Q. c #F4F4F4",
+"!. c gray96",
+"~. c #F6F6F6",
+"^. c gray97",
+"/. c #F9F0F9",
+"(. c #FBF1FB",
+"). c #F8F8F8",
+"_. c #FBFBFB",
+"`. c #FDFDFD",
+"'. c #FEFEFE",
+"]. c white",
+/* pixels */
+"                                                                ",
+"    O O O O O O O O O X O O O O O O O O X X O O O O O O O O     ",
+"  O _ -.$.$.$.@.$.:._ A :.#.$.#. .$.$.:.A _ -.$.$.$.$.$.-._ O   ",
+"  O -.B.d.d.m.L.d.B.$.~ T.d.d.!.).m.d.F.~ -.B.d.d.F./.d.B.-.O   ",
+"  O $.d.9.a.o.E 9.9.$.G m.9.d.C M 9.8.m.G  .9.9.m.' T <.d.#.O   ",
+"  O $.d.9.S.p w S.u.$.G d.9.-.j * 3 ^.d.G @.a.m.E 0 f <.m.$.O   ",
+"  O $.a.S.n a y <.m.$.G B.9.d.`.3 x T.d.G #.9.Y.f e d Q L.$.O   ",
+"  O $.u.m.J i 9 ' F.$.G d.9.B.= $ G d.B.G #.d.L.L i t I L.$.O   ",
+"  O $.d.5.S.5.R m.d.$.G l.9.9.x l B m.d.G #.d.9.a.I Y a.u.$.O   ",
+"  O -.B.d.a.d.L.9.B.-.~ C.a.d.).).Q.u.F.G -.B.d.d.L.L.a.B.-.O   ",
+"  o _ -. .$.$. .#.-.( C -.$.#. . . .#.-.A ( -.#.$. . .@.$._ O   ",
+"  X A ~ G G G G G ~ A v ~ G G G G G G ^ v A ~ G G G G G ~ A X   ",
+"  O :.F.B.l.d.B.m.C.-.^ W.m.B.B.B.B.d.!.~ -.T.B.l.B.l.m.T.:.O   ",
+"  O $.d.9.d.T.d.9.u. .G l.2.9.9.9.9.9.d.G  .d.9.9.9.9.5.d.$.O   ",
+"  O $.9.B.C - 7 5.m.@.G B.9.9.9.9.9.9.B.G $.d.a.9.9.9.9.m.$.O   ",
+"  O $.a.!.x + 2 9.d.#.G l.9.9.d.u.9.9.m.G $.d.9.9.d.a.9.d.$.O   ",
+"  O $.d.9.{ _ $ F W. .G l.9.9.9.9.9.9.B.G #.d.a.9.9.9.9.B.$.O   ",
+"  O $.9.T.M - 1 -.d.$.G B.9.a.9.9.u.9.B.G $.d.9.9.9.9.9.d.$.O   ",
+"  O $.a.9.9.$.B.d.a. .G d.9.9.9.9.9.2.B.G @.d.9.9.9.9.9.a.#.O   ",
+"  O 2.T.d.B.F.d.d.C.2.~ !.d.B.B.d.B.B.T.^ -.F.d.B.B.B.d.B.2.O   ",
+"  X A ~ G G G G ~ ~ C v ^ G G G G G G ~ v A ~ G G G G G ~ A X   ",
+"  O _ -.$.#. . .$.-._ C -.#.$. .$.$.$.-.C _ -.$.#. . .@.-._ O   ",
+"  O -.B.d.T.).T.m.B.-.~ B.d.m.W.L.d.d.F.~ -.B.u.d.!.B.a.B.-.O   ",
+"  O @.a.d./ B G -.d.#.G m.2.a.! Q 9.9.B.G $.d.a.-.S } d.d.$.O   ",
+"  O $.a.B.N < @ _ T.#.G m.9.9.H w h L.l.G $.9.d.} % 4 !.9.-.O   ",
+"  O $.m.2.`.B , !.9.$.G l.5.m.>.w n P.d.G $.d.2.`.k 6 `.9.$.O   ",
+"  O $.d.9.d.& C T.d. .G B.a.<.U i s P.d.G $.d.a.9.> : 2.m.$.O   ",
+"  O $.d.d. .x u.5.a.#.G d.9.2.J b o.9.m.G $.d.d._ 7 6 / B. .O   ",
+"  O -.B.d.m.^.d.d.B.-.~ F.d.d././.m.u.T.~ -.m.9.B.).).B.B.-.O   ",
+"  o _ -.$.$.#.$.$.-._ A 2.$.$.$.@.$.$.-.A _ -.$.$. .#.$.-._ O   ",
+"    O O O O O O O O O X O O O O O O O O X O O O + O O O O O     ",
+"                                                                "
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 222 2 ",
+"   c black",
+".  c #010101",
+"X  c #060606",
+"o  c #0E0E0E",
+"O  c #101010",
+"+  c #111111",
+"@  c gray7",
+"#  c #131313",
+"$  c gray8",
+"%  c #151515",
+"&  c #1B1B1B",
+"*  c gray13",
+"=  c gray14",
+"-  c gray15",
+";  c gray16",
+":  c gray17",
+">  c #2C2C2C",
+",  c #2D2D2D",
+"<  c gray18",
+"1  c gray19",
+"2  c #353535",
+"3  c #373737",
+"4  c gray22",
+"5  c #3A3A3A",
+"6  c #3C3C3C",
+"7  c gray25",
+"8  c gray27",
+"9  c gray28",
+"0  c gray29",
+"q  c #4B4B4B",
+"w  c #505050",
+"e  c #515151",
+"r  c #535353",
+"t  c gray35",
+"y  c #606060",
+"u  c gray39",
+"i  c DimGray",
+"p  c gray43",
+"a  c #727272",
+"s  c #7C7C7C",
+"d  c gray49",
+"f  c #008300",
+"g  c #008400",
+"h  c #008800",
+"j  c #048C04",
+"k  c #088D08",
+"l  c #109010",
+"z  c #119011",
+"x  c #129012",
+"c  c #159215",
+"v  c #169316",
+"b  c #209720",
+"n  c #229722",
+"m  c #269726",
+"M  c #229822",
+"N  c #289A28",
+"B  c #2C9C2C",
+"V  c #309D30",
+"C  c #319D31",
+"Z  c #329C32",
+"A  c #359F35",
+"S  c #36A036",
+"D  c #38A038",
+"F  c #39A139",
+"G  c #3DA03D",
+"H  c #42A542",
+"J  c #45A545",
+"K  c #47A647",
+"L  c #4AA84A",
+"P  c #53A953",
+"I  c #5BAE5B",
+"U  c #5EAF5E",
+"Y  c #68B468",
+"T  c #69B469",
+"R  c #868686",
+"E  c #898989",
+"W  c #8D8D8D",
+"Q  c gray60",
+"!  c gray62",
+"~  c #A0A0A0",
+"^  c #A2A2A2",
+"/  c #A4A4A4",
+"(  c gray65",
+")  c #A7A7A7",
+"_  c #A9A9A9",
+"`  c #AAAAAA",
+"'  c #ACACAC",
+"]  c gray68",
+"[  c #AEAEAE",
+"{  c #AFAFAF",
+"}  c gray69",
+"|  c #B2B2B2",
+" . c #B4B4B4",
+".. c #B7B7B7",
+"X. c #B9B9B9",
+"o. c #BCBCBC",
+"O. c gray74",
+"+. c gray",
+"@. c gray75",
+"#. c #8CC28C",
+"$. c #91C391",
+"%. c #90C490",
+"&. c #94C594",
+"*. c #96C696",
+"=. c #99C799",
+"-. c #9BC89B",
+";. c #A0CAA0",
+":. c #A1CAA1",
+">. c #A3CBA3",
+",. c #A8CDA8",
+"<. c #ACCFAC",
+"1. c #B1D1B1",
+"2. c #B5D2B5",
+"3. c #B5D3B5",
+"4. c #B6D3B6",
+"5. c #B7D3B7",
+"6. c #B8D3B8",
+"7. c #BED6BE",
+"8. c #BFD6BF",
+"9. c gray77",
+"0. c #C8C8C8",
+"q. c gray79",
+"w. c #CBCBCB",
+"e. c gray80",
+"r. c #CDCDCD",
+"t. c #CECECE",
+"y. c gray81",
+"u. c #C1D6C1",
+"i. c #C4D9C4",
+"p. c #C8DAC8",
+"a. c #C9DAC9",
+"s. c #C9DBC9",
+"d. c #CADBCA",
+"f. c #CFDDCF",
+"g. c #D0D0D0",
+"h. c gray82",
+"j. c #D2D2D2",
+"k. c LightGray",
+"l. c gray83",
+"z. c gray84",
+"x. c #D7D7D7",
+"c. c #D5DFD5",
+"v. c #D8D8D8",
+"b. c gray85",
+"n. c #DADADA",
+"m. c gray86",
+"M. c gainsboro",
+"N. c #DDDDDD",
+"B. c gray87",
+"V. c #DFDFDF",
+"C. c #D8E0D8",
+"Z. c #D9E1D9",
+"A. c #DCE2DC",
+"S. c #DFE3DF",
+"D. c gray88",
+"F. c #E1E1E1",
+"G. c #E1E3E1",
+"H. c #E2E2E2",
+"J. c #E2E3E2",
+"K. c gray89",
+"L. c #E0E4E0",
+"P. c #E1E4E1",
+"I. c #E2E4E2",
+"U. c #E3E4E3",
+"Y. c #E3E5E3",
+"T. c #E4E4E4",
+"R. c #E4E5E4",
+"E. c #E5E4E5",
+"W. c gray90",
+"Q. c #E5E6E5",
+"!. c #E6E5E6",
+"~. c #E6E6E6",
+"^. c #E7E6E7",
+"/. c #E7E7E7",
+"(. c #E6E8E6",
+"). c #E7E8E7",
+"_. c #E8E6E8",
+"`. c #E8E7E8",
+"'. c #E9E7E9",
+"]. c #EBE7EB",
+"[. c gray91",
+"{. c #E8E9E8",
+"}. c #E9E9E9",
+"|. c #EAE8EA",
+" X c #EBE8EB",
+".X c #EAEAEA",
+"XX c #EAEBEA",
+"oX c gray92",
+"OX c #EBECEB",
+"+X c #ECE8EC",
+"@X c #EDE8ED",
+"#X c #EDE9ED",
+"$X c #EEE9EE",
+"%X c #EFE9EF",
+"&X c #EFEAEF",
+"*X c #ECECEC",
+"=X c gray93",
+"-X c #EEEEEE",
+";X c #EFEFEF",
+":X c #F0EAF0",
+">X c #F1EAF1",
+",X c #F1EBF1",
+"<X c #F2EBF2",
+"1X c #F3EBF3",
+"2X c #F4EBF4",
+"3X c #F5ECF5",
+"4X c #F5EDF5",
+"5X c #F6ECF6",
+"6X c #F6EDF6",
+"7X c #F7ECF7",
+"8X c gray94",
+"9X c #F1F1F1",
+"0X c gray95",
+"qX c #F3F3F3",
+"wX c #F4F4F4",
+"eX c gray96",
+"rX c #F6F6F6",
+"tX c gray97",
+"yX c #F8F8F8",
+"uX c #F9F9F9",
+"iX c #FBFBFB",
+"pX c #FEFEFE",
+/* pixels */
+"                                                                                                ",
+"                                    X                                                           ",
+"                                                                                                ",
+"        # # # % # # # # % # # # # o # # # # # # % # # # # % o # # # # # % # # # # # # %         ",
+"      # y.N.m.m.m.m.m.m.m.m.m.K.9.( W.m.m.m.m.m.m.m.m.m.m.W.( 9.A.m.N.m.m.m.M.M.m.m.N.h.#       ",
+"      # N.*X}.}.}.}.}.}.}.}.W.8Xh.} 0XW.}.}.}.W.}.}.}.}.W.0X} h.<X`.}.}.}.}.`.}.}.W.}.N.#       ",
+"      # m.}.K.W.W.W.W.}.W.W.W.}.y.{ *XK.W.W.W.}.*XW.W.W.K.0X} y.*XW.W.W.K.W.#X`.W.W.}.m.#       ",
+"      # m.}.W.W.`.W.}.f.U.W.W.*Xy.{ 0XW.W.W.}.m.w.}.}.W.W.*X} y.*XW.W.W.#XW.i.Z.`.W.}.m.#       ",
+"      % m.}.W.W.U.6XY g 1.<XU.*Xy.} *XK.W.0X{ * & 5 m.W.K.*X} h.*XW.W.@Xd.A F I W.W.}.m.%       ",
+"      # m.}.W.U.6X>.F x ,.<XU.*Xy.} 0XW.W.}.w.y.y.  { 0XK.*X} h.*XW.U.qXP m %.7.}.W.}.m.#       ",
+"      # m.}.W.`.C.S 5.v ;.<XU.*Xy.} 0XW.W.W.*XpXt 2 K.W.K.0X} y.*XW.W.#XZ M K b Z.`.}.m.#       ",
+"      # m.}.U.:X4.x B h V U.W.OXy.} *XW.W.W.W.w : m.}.W.W.*X} y.OXW.U.6XG P U.g u.#XW.m.#       ",
+"      # m.}.W.`.U.f.7.x *.#XW.*Xy.} *XW.W.rX^   : * ( 0XK.*X} h.*XW.W.#X4.N M I }.W.}.M.#       ",
+"      # m.}.W.W.W.#X`.c.W.`.W.*Xy.} *XW.W.W.N.h.w.9.N.}.K.*X} h.*XW.W.W.#XA.d.#X`.W.`.M.#       ",
+"      % m.}.W.W.W.W.W.`.W.W.W.}.y.{ 0XK.W.W.}.*X}.*X}.W.W.*X{ y.OXU.W.W.T.`.#XW.W.W.`.m.#       ",
+"      % N.*X*X*X*X*XOXOXOX*XOX0Xl. .rX}.*X*X*X}.}.}.*X*X}.rX .h.0X#X*X*XOX#X}.*X*X*X*XD.%       ",
+"      # 9.h.y.y.y.y.y.y.y.y.y.l.X.! l.y.y.y.y.y.y.y.h.y.y.l.^ X.l.y.y.y.y.y.h.y.y.y.h.9.#       ",
+"      o _ } { } } } } { } } {  .! R  .{ } } } } } } } } {  .R !  .} } } } } } } } { } ( o       ",
+"      # W.0X*X0X*X0X*X0X*X0X*XrXm. .iX*X*X0X0X*X*X*X0X*X}.iX .m.rX*X*X0X*X0X*X*X0X*XrXW.%       ",
+"      # m.W.K.W.K.K.K.N.K.W.W.}.y.{ *XK.K.W.K.W.W.W.W.W.K.}.{ y.}.W.W.W.W.W.W.W.K.K.W.m.%       ",
+"      % m.}.W.W.W.0XiXiX0XW.W.*Xy.{ 0XK.W.W.W.}.W.W.W.W.W.0X} y.*XW.}.W.W.W.W.W.W.W.}.m.#       ",
+"      # m.}.W.W.}.^ s a } 0XK.*Xy.{ *XW.}.W.W.W.}.W.}.W.K.*X} y.*XW.W.W.W.}.W.}.W.W.}.m.#       ",
+"      # m.}.W.W.*X2 * q { *XK.*Xy.} *XK.W.}.W.}.W.W.W.}.W.0X} y.*XW.W.W.}.W.W.W.W.W.}.N.#       ",
+"      # N.}.W.W.*X7 - 1 } *XK.*Xy.} 0XW.W.W.W.W.}.W.W.W.K.*X} y.*XW.W.}.W.W.W.}.W.W.}.N.#       ",
+"      # m.}.W.W.W.N.0Xq , 0XK.*Xy.} *XK.W.W.W.W.W.W.W.}.W.0X} y.*XW.}.W.}.W.W.W.W.W.}.m.%       ",
+"      # m.}.W.}.m.s _ - 9 rXW.*Xy.} 0XW.}.W.}.W.}.W.W.W.K.*X} y.*XW.W.W.W.W.W.}.W.W.}.m.#       ",
+"      # N.}.W.W.W.p 5 t h.}.W.*Xy.} 0XK.W.W.W.}.W.}.W.}.W.0X} y.*XW.W.W.}.W.}.W.W.W.}.m.#       ",
+"      % N.}.W.W.W.rXrXrX}.W.W.*Xy.} *XW.}.W.W.W.W.W.W.W.K.*X} y.*XW.W.W.W.W.W.W.W.W.}.m.#       ",
+"      # m.W.W.W.W.K.K.K.K.W.K.}.y.} *XW.W.K.W.K.W.W.W.W.K.*X{ y.}.W.W.W.W.W.W.W.W.W.W.m.#       ",
+"      # K.0X0X*X0X*X*X*X*X0X*XrXl. .iX*X0X*X*X0X*X0X*X*X*XiX .l.rX*X*X0X*X0X*X*X0X*X0XW.%       ",
+"      o ( } { { } } } } } } {  .! R  .{ } } } } } } { { {  .R !  .} } } } } } } } { } _ o       ",
+"      # 9.h.y.y.y.y.y.y.y.h.y.l.X.! l.y.y.y.y.y.y.h.y.y.y.m.! X.h.y.y.y.y.h.y.y.y.y.h.9.#       ",
+"      % N.*X*X*X*X*X*X*X*X*X*X0Xk. .rX}.*X*X*X*X*XOX*X*X}.rX .k.0XOXOX*X}.*X*X*X*X}.*XK.#       ",
+"      % m.}.W.W.W.W.W.W.W.W.W.}.y.{ *XW.W.W.W.W.W.U.W.W.K.8X{ y.}.W.W.W.W.K.W.W.W.W.}.m.#       ",
+"      # m.}.W.}.W.0X0X*X*XW.W.*Xy.} *XU.W.W.}.:X:X:X`.W.U.8X} y.*XW.W.W.*XrX*XW.W.W.}.m.%       ",
+"      # m.}.W.}.l.6 , , u *XW.*Xy.} 0XW.W.#Xd.H V Y U.(.U.8X} y.*XW.U.0X( , q K.}.W.}.m.#       ",
+"      # m.}.W.W.N._ { # i rXK.*Xy.} 8XW.W.}.c.,.$.f 4.:XK.*X} y.*XW.W.}.w.i % K.}.K.}.m.#       ",
+"      # m.}.W.W.W.pXO.o l.*XW.*Xy.} *XW.W.U.<X-.v S W.`.K.0X} y.*XW.W.K.iX( # N.W.W.}.m.#       ",
+"      # m.}.W.W.W.rX9 w iXK.W.*Xy.} 8XU.(.W.W.W.4.j ;.2XK.8X{ y.*XW.W.K.iX_ % rX}.W.}.m.%       ",
+"      % m.}.W.W.*XO.X O.*XW.W.*Xh.} *XW.W.<X=.J L k 6.#XK.*X{ y.*XW.W.*X{ : X w m.}.}.m.#       ",
+"      # m.}.W.W.*X9.^ }.W.W.W.*Xy.} *XW.W.`.c.*.#.d.#XW.W.0X{ y.*XW.W.}.O.W Q W m.W.}.m.#       ",
+"      # m.}.W.W.W.*X0XW.W.W.W.}.y.} 0XK.W.W.`.6X6X`.W.W.K.*X{ y.*XW.W.W.*XrX0XiXW.W.}.m.#       ",
+"      # N.}.W.}.}.}.W.}.}.}.}.*Xh.} 0XW.}.}.}.(.W.}.}.}.W.0X} h.*X}.}.}.}.W.W.W.}.}.}.N.%       ",
+"      # h.N.m.m.N.m.m.m.m.m.m.K.9.( W.m.m.m.m.m.m.M.m.m.m.W.( 9.K.m.m.m.m.m.m.m.N.m.N.h.#       ",
+"        % # # # # # # % # # # # # o % # # # # % # # # # # % o # # # # # % # # # # # # #         ",
+"                                                                                                ",
+"                                                            X                                   ",
+"                                                                                                "
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/solo-web.png b/icons/solo-web.png
new file mode 100644 (file)
index 0000000..daacab5
Binary files /dev/null and b/icons/solo-web.png differ
diff --git a/icons/solo.ico b/icons/solo.ico
new file mode 100644 (file)
index 0000000..521eca4
Binary files /dev/null and b/icons/solo.ico differ
diff --git a/icons/solo.rc b/icons/solo.rc
new file mode 100644 (file)
index 0000000..631070c
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "solo.ico"
diff --git a/icons/solo.sav b/icons/solo.sav
new file mode 100644 (file)
index 0000000..385cc68
--- /dev/null
@@ -0,0 +1,36 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :4:Solo
+PARAMS  :3:3x3
+CPARAMS :3:3x3
+DESC    :73:a2e9a5b6a2b3_7a1_4a9_6a2b4_1a7_2b1_7e6_9b2_5a8_1b6a5_9a3_8a7_2b8a6b1a1e4a
+NSTATES :2:29
+STATEPOS:2:29
+MOVE    :6:R7,1,1
+MOVE    :6:R4,6,1
+MOVE    :6:R5,0,1
+MOVE    :6:R0,0,4
+MOVE    :6:R2,0,6
+MOVE    :6:R1,2,3
+MOVE    :6:R0,3,9
+MOVE    :6:R1,3,5
+MOVE    :6:R2,4,8
+MOVE    :6:R0,5,3
+MOVE    :6:R1,5,6
+MOVE    :6:R1,6,4
+MOVE    :6:R4,7,4
+MOVE    :6:R7,6,2
+MOVE    :6:R7,7,5
+MOVE    :6:R8,8,6
+MOVE    :6:R7,3,3
+MOVE    :6:R8,3,7
+MOVE    :6:R8,3,8
+MOVE    :6:R6,4,5
+MOVE    :6:R7,5,7
+MOVE    :6:R8,5,4
+MOVE    :6:R7,2,8
+MOVE    :6:R8,0,5
+MOVE    :6:R4,2,5
+MOVE    :6:R4,3,6
+MOVE    :6:R5,4,4
+MOVE    :6:R4,5,9
diff --git a/icons/square.pl b/icons/square.pl
new file mode 100755 (executable)
index 0000000..815b94b
--- /dev/null
@@ -0,0 +1,95 @@
+#!/usr/bin/perl 
+
+# Read an input image, crop its border to a standard width, and
+# convert it into a square output image. Parameters are:
+#
+#  - the required total image size
+#  - the output border thickness
+#  - the input image file name
+#  - the output image file name.
+
+($osize, $oborder, $infile, $outfile) = @ARGV;
+
+# Determine the input image's size.
+$ident = `identify -format "%w %h" $infile`;
+$ident =~ /(\d+) (\d+)/ or die "unable to get size for $infile\n";
+($w, $h) = ($1, $2);
+
+# Read the input image data.
+$data = [];
+open IDATA, "convert -depth 8 $infile rgb:- |";
+push @$data, $rgb while (read IDATA,$rgb,3,0) == 3;
+close IDATA;
+# Check we have the right amount of data.
+$xl = $w * $h;
+$al = scalar @$data;
+die "wrong amount of image data ($al, expected $xl) from $infile\n"
+  unless $al == $xl;
+
+# Find the background colour, by looking around the entire border
+# and finding the most popular pixel colour.
+for ($i = 0; $i < $w; $i++) {
+    $pcount{$data->[$i]}++; # top row
+    $pcount{$data->[($h-1)*$w+$i]}++; # bottom row
+}
+for ($i = 1; $i < $h-1; $i++) {
+    $pcount{$data->[$i*$w]}++; # left column
+    $pcount{$data->[$i*$w+$w-1]}++; # right column
+}
+@plist = sort { $pcount{$b} <=> $pcount{$a} } keys %pcount;
+$back = $plist[0];
+
+# Crop rows and columns off the image to find the central rectangle
+# of non-background stuff.
+$ystart = 0;
+$ystart++ while $ystart < $h and scalar(grep { $_ ne $back } map { $data->[$ystart*$w+$_] } 0 .. ($w-1)) == 0;
+$yend = $h-1;
+$yend-- while $yend >= $ystart and scalar(grep { $_ ne $back } map { $data->[$yend*$w+$_] } 0 .. ($w-1)) == 0;
+$xstart = 0;
+$xstart++ while $xstart < $w and scalar(grep { $_ ne $back } map { $data->[$_*$w+$xstart] } 0 .. ($h-1)) == 0;
+$xend = $w-1;
+$xend-- while $xend >= $xstart and scalar(grep { $_ ne $back } map { $data->[$_*$w+$xend] } 0 .. ($h-1)) == 0;
+
+# Decide how much border we're going to put back on to make the
+# image perfectly square.
+$hexpand = ($yend-$ystart) - ($xend-$xstart);
+if ($hexpand > 0) {
+    $left = int($hexpand / 2);
+    $xstart -= $left;
+    $xend += $hexpand - $left;
+} elsif ($hexpand < 0) {
+    $vexpand = -$hexpand;
+    $top = int($vexpand / 2);
+    $ystart -= $top;
+    $yend += $vexpand - $top;
+}
+$ow = $xend - $xstart + 1;
+$oh = $yend - $ystart + 1;
+die "internal computation problem" if $ow != $oh; # should be square
+
+# And decide how much _more_ border goes on to add the bit around
+# the edge.
+$realow = int($ow * ($osize / ($osize - 2*$oborder)));
+$extra = $realow - $ow;
+$left = int($extra / 2);
+$xstart -= $left;
+$xend += $extra - $left;
+$top = int($extra / 2);
+$ystart -= $top;
+$yend += $extra - $top;
+$ow = $xend - $xstart + 1;
+$oh = $yend - $ystart + 1;
+die "internal computation problem" if $ow != $oh; # should be square
+
+# Now write out the resulting image, and resize it appropriately.
+open IDATA, "| convert -size ${ow}x${oh} -depth 8 -resize ${osize}x${osize}! rgb:- $outfile";
+for ($y = $ystart; $y <= $yend; $y++) {
+    for ($x = $xstart; $x <= $xend; $x++) {
+       if ($x >= 0 && $x < $w && $y >= 0 && $y < $h) {
+           print IDATA $data->[$y*$w+$x];
+       } else {
+           print IDATA $back;
+       }
+    }
+}
+close IDATA;
diff --git a/icons/tents-16d24.png b/icons/tents-16d24.png
new file mode 100644 (file)
index 0000000..e3fe538
Binary files /dev/null and b/icons/tents-16d24.png differ
diff --git a/icons/tents-16d4.png b/icons/tents-16d4.png
new file mode 100644 (file)
index 0000000..8e5c03a
Binary files /dev/null and b/icons/tents-16d4.png differ
diff --git a/icons/tents-16d8.png b/icons/tents-16d8.png
new file mode 100644 (file)
index 0000000..e48986e
Binary files /dev/null and b/icons/tents-16d8.png differ
diff --git a/icons/tents-32d24.png b/icons/tents-32d24.png
new file mode 100644 (file)
index 0000000..b8e248c
Binary files /dev/null and b/icons/tents-32d24.png differ
diff --git a/icons/tents-32d4.png b/icons/tents-32d4.png
new file mode 100644 (file)
index 0000000..b5a74ec
Binary files /dev/null and b/icons/tents-32d4.png differ
diff --git a/icons/tents-32d8.png b/icons/tents-32d8.png
new file mode 100644 (file)
index 0000000..dbe0f09
Binary files /dev/null and b/icons/tents-32d8.png differ
diff --git a/icons/tents-48d24.png b/icons/tents-48d24.png
new file mode 100644 (file)
index 0000000..f3bafb4
Binary files /dev/null and b/icons/tents-48d24.png differ
diff --git a/icons/tents-48d4.png b/icons/tents-48d4.png
new file mode 100644 (file)
index 0000000..7b05e55
Binary files /dev/null and b/icons/tents-48d4.png differ
diff --git a/icons/tents-48d8.png b/icons/tents-48d8.png
new file mode 100644 (file)
index 0000000..06eb375
Binary files /dev/null and b/icons/tents-48d8.png differ
diff --git a/icons/tents-base.png b/icons/tents-base.png
new file mode 100644 (file)
index 0000000..36a6d1b
Binary files /dev/null and b/icons/tents-base.png differ
diff --git a/icons/tents-ibase.png b/icons/tents-ibase.png
new file mode 100644 (file)
index 0000000..d033027
Binary files /dev/null and b/icons/tents-ibase.png differ
diff --git a/icons/tents-ibase4.png b/icons/tents-ibase4.png
new file mode 100644 (file)
index 0000000..21d514d
Binary files /dev/null and b/icons/tents-ibase4.png differ
diff --git a/icons/tents-icon.c b/icons/tents-icon.c
new file mode 100644 (file)
index 0000000..fecbc37
--- /dev/null
@@ -0,0 +1,719 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 256 2 ",
+"   c #E7E7E7",
+".  c #EBE6EE",
+"X  c #EAE3ED",
+"o  c #EAE5EE",
+"O  c #EFE7F1",
+"+  c #F3E8F4",
+"@  c #ECE6F0",
+"#  c #EAE4EE",
+"$  c #EAE4ED",
+"%  c #EBE6EF",
+"&  c #F2E7F4",
+"*  c #F0E7F3",
+"=  c #E8E7E9",
+"-  c #E6E6E6",
+";  c gray90",
+":  c #E6E6E6",
+">  c #DFE1DD",
+",  c #B9D6A6",
+"<  c #BCE2AB",
+"1  c #BAD8A5",
+"2  c #A9D198",
+"3  c #9CD18F",
+"4  c #B2D29E",
+"5  c #BBDDA8",
+"6  c #BCE0AA",
+"7  c #B5D3A1",
+"8  c #9ED191",
+"9  c #A3D192",
+"0  c #D7DCD1",
+"q  c #E9E7EA",
+"w  c gray92",
+"e  c #E7E7E7",
+"r  c #DAE0D7",
+"t  c #A6EB75",
+"y  c #BFDD31",
+"u  c #B1EF71",
+"i  c #37C527",
+"p  c #00AF00",
+"a  c #6EDA53",
+"s  c #BDE956",
+"d  c #BEE346",
+"f  c #86E465",
+"g  c #00B300",
+"h  c #1BBE0E",
+"j  c #CCDBBF",
+"k  c #EBE7ED",
+"l  c #A4A5A3",
+"z  c gray88",
+"x  c #D9E2DB",
+"c  c #ABDB69",
+"v  c #C9BE12",
+"b  c #ADDB5D",
+"n  c #98E46F",
+"m  c #92CE4D",
+"M  c #9CEB7B",
+"N  c #BBCB36",
+"B  c #C3C426",
+"V  c #9EEA79",
+"C  c #86CD4C",
+"Z  c #8AD855",
+"A  c #CBDDC3",
+"S  c #EAE6EC",
+"D  c #BDBEBC",
+"F  c gray89",
+"G  c #D9DED6",
+"H  c #9DD973",
+"J  c #A6F279",
+"K  c #9FDD72",
+"L  c #78CF56",
+"P  c #5FCE43",
+"I  c #8BD262",
+"U  c #A4E877",
+"Y  c #A4EC77",
+"T  c #97D66C",
+"R  c #AFED7B",
+"E  c #A3E670",
+"W  c #C7D6BD",
+"Q  c #EBE8ED",
+"!  c #EBEBEA",
+"~  c #E7E7E7",
+"^  c #DBE0D7",
+"/  c #ACEB7F",
+"(  c #B9FF83",
+")  c #B5F382",
+"_  c #40C72E",
+"`  c #00B100",
+"'  c #78D956",
+"]  c #BDFE87",
+"[  c #B5FF81",
+"{  c #A7E977",
+"}  c #B7FF84",
+"|  c #AFFA7A",
+" . c #CBDAC0",
+".. c #E9E6EB",
+"X. c #A2A3A2",
+"o. c gray90",
+"O. c #DADFD6",
+"+. c #A1E077",
+"@. c #ADF87B",
+"#. c #A0E574",
+"$. c #9FDF6D",
+"%. c #A0D45D",
+"&. c #9EE273",
+"*. c #A7ED77",
+"=. c #ABF37A",
+"-. c #9BDD6F",
+";. c #ACF47C",
+":. c #A3ED71",
+">. c #C8D7BE",
+",. c #ECE8EE",
+"<. c #D3D4D2",
+"1. c #E4E4E4",
+"2. c #DADFD6",
+"3. c #A0DE76",
+"4. c #ABF679",
+"5. c #9DE171",
+"6. c #A1E473",
+"7. c #B1F072",
+"8. c #9ADD71",
+"9. c #A5EB75",
+"0. c #A8F079",
+"q. c #99DA6E",
+"w. c #AAF17A",
+"e. c #A1EA70",
+"r. c #C8D7BE",
+"t. c #EAE7ED",
+"y. c #DCDCDB",
+"u. c #E7E7E7",
+"i. c #DAE0D6",
+"p. c #ACEB7F",
+"a. c #BCFF84",
+"s. c #AAF382",
+"d. c #B6DF57",
+"f. c #D1C718",
+"g. c #A9EA76",
+"h. c #B4FB82",
+"j. c #B8FF83",
+"k. c #A7E978",
+"l. c #B9FF85",
+"z. c #B0FA7B",
+"x. c #CBDAC0",
+"c. c #E9E6EC",
+"v. c #A0A19F",
+"b. c #E4E4E4",
+"n. c #DADED7",
+"m. c #A2DC79",
+"M. c #ADF379",
+"N. c #9FE176",
+"B. c #A2D259",
+"V. c #B3D347",
+"C. c #9AD465",
+"Z. c #A2E976",
+"A. c #A6EC76",
+"S. c #97D76C",
+"D. c #A8EE79",
+"F. c #9EE66E",
+"G. c #C7D6BD",
+"H. c #ECE9EE",
+"J. c #E4E5E4",
+"K. c gray90",
+"L. c #EAE1E9",
+"P. c #6FC85B",
+"I. c #00B400",
+"U. c #57C63F",
+"Y. c #AFF282",
+"T. c #ACFF86",
+"R. c #9FE373",
+"E. c #AAF279",
+"W. c #AEF77C",
+"Q. c #9EE172",
+"!. c #B0F880",
+"~. c #A7F175",
+"^. c #C9D8BF",
+"/. c #EAE6EC",
+"(. c #C8C9C8",
+"). c #E7E7E7",
+"_. c #E6DFE7",
+"`. c #8BDD6C",
+"'. c #41C017",
+"]. c #79DC52",
+"[. c #B3F67F",
+"{. c #B8FF81",
+"}. c #A6EA77",
+"|. c #B2FA7F",
+" X c #B6FF82",
+".X c #A4E973",
+"XX c #B4FF7C",
+"oX c #ABFA72",
+"OX c #CADABE",
+"+X c #EAE7ED",
+"@X c #A8A9A7",
+"#X c gray89",
+"$X c #DADDDA",
+"%X c #B4D39B",
+"&X c #C8DA9A",
+"*X c #B2D594",
+"=X c #9CDD71",
+"-X c #A8ED79",
+";X c #97D66D",
+":X c #A2E475",
+">X c #A3EA74",
+",X c #9FD07E",
+"<X c #BAE1A0",
+"1X c #AFDA93",
+"2X c #CBD3C5",
+"3X c #EBE9EC",
+"4X c #ECECEB",
+"5X c #E6E6E6",
+"6X c #D8E1D2",
+"7X c #CFD5CB",
+"8X c #EEEAF8",
+"9X c #D3D6D2",
+"0X c #A7F374",
+"qX c #B2FF7A",
+"wX c #A0EA6F",
+"eX c #ACFA78",
+"rX c #AAFF70",
+"tX c #BBDBA6",
+"yX c #EEE3F6",
+"uX c #E1DDE4",
+"iX c #D5D4D6",
+"pX c #E7E7E7",
+"aX c #B6B6B6",
+"sX c #E6E6E6",
+"dX c #E1E4E0",
+"fX c #DEE2DB",
+"gX c gray91",
+"hX c #DFE2DD",
+"jX c #D2EBC1",
+"kX c #D6F1C3",
+"lX c #CFE8BF",
+"zX c #D4EDC2",
+"xX c #D3F0C0",
+"cX c #D7E3CF",
+"vX c #E7E7E8",
+"bX c #E3E5E2",
+"nX c gray88",
+"mX c #E7E7E7",
+"MX c #C6C6C6",
+"NX c #E4E4E4",
+"BX c #E7E6E7",
+"VX c #E8E7E8",
+"CX c #E5E6E5",
+"ZX c #E7E7E8",
+"AX c #EAE5EE",
+"SX c #E9E4ED",
+"DX c #EBE6EE",
+"FX c #EAE5ED",
+"GX c #EAE4EE",
+"HX c #E9E7EA",
+"JX c #E6E6E5",
+"KX c #E6E6E7",
+"LX c #E7E7E7",
+"PX c #E6E6E6",
+"IX c #ECECEC",
+"UX c #E6E6E6",
+/* pixels */
+"  . X o O + @ # $ % & * = - ; : ",
+"> , < 1 2 3 4 5 6 7 8 9 0 q w e ",
+"r t y u i p a s d f g h j k l z ",
+"x c v b n m M N B V C Z A S D F ",
+"G H J K L P I U Y T R E W Q ! ~ ",
+"^ / ( ) _ ` ' ] [ { } |  ...X.o.",
+"O.+.@.#.$.%.&.*.=.-.;.:.>.,.<.1.",
+"2.3.4.5.6.7.8.9.0.q.w.e.r.t.y.u.",
+"i.p.a.s.d.f.g.h.j.k.l.z.x.c.v.b.",
+"n.m.M.N.B.V.C.Z.A.S.D.F.G.H.J.K.",
+"L.P.I.U.Y.T.R.E.W.Q.!.~.^./.(.).",
+"_.`.'.].[.{.}.|. X.XXXoXOX+X@X#X",
+"$X%X&X*X=X-X;X:X>X,X<X1X2X3X4X5X",
+"6X7X8X9X0XqXwXeXrXtXyXuXiXpXaXsX",
+"dXfXgXhXjXkXlXzXxXcXvXbXnXmXMXNX",
+"BXVXCXZXAXSXDXFXGXHXJXKXLXPXIXUX"
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 169 2 ",
+"   c gray37",
+".  c #00AD00",
+"X  c #03B302",
+"o  c #04BA04",
+"O  c #0BB709",
+"+  c #1ABE13",
+"@  c #28BF1D",
+"#  c #479B0F",
+"$  c #499E12",
+"%  c #1EC112",
+"&  c #25C21A",
+"*  c #28C41B",
+"=  c #2FC222",
+"-  c #3EC72C",
+";  c #33CA24",
+":  c #38D024",
+">  c #54C83B",
+",  c #53DA39",
+"<  c #7AAE57",
+"1  c #7CB059",
+"2  c #5DC942",
+"3  c #5DD942",
+"4  c #63C749",
+"5  c #65C94B",
+"6  c #63CE47",
+"7  c #6ED145",
+"8  c #73D94A",
+"9  c #6FCF52",
+"0  c #72D352",
+"q  c #7FD15B",
+"w  c #6AE248",
+"e  c #8E8F1C",
+"r  c #8EA630",
+"t  c #B1A43A",
+"y  c #CEA700",
+"u  c #CDAC00",
+"i  c #CAB408",
+"p  c #CAB810",
+"a  c #C5CE2D",
+"s  c #C7C926",
+"d  c #C3D131",
+"f  c #CAD530",
+"g  c #C5DA3F",
+"h  c #80B75B",
+"j  c #82BA5D",
+"k  c #80AF61",
+"l  c #89BE65",
+"z  c #9BC24B",
+"x  c #8DD05F",
+"c  c #83D95E",
+"v  c #BDDF4D",
+"b  c #A3C656",
+"n  c #84E65F",
+"m  c #BEEC5C",
+"M  c #87C161",
+"N  c #8AC463",
+"B  c #8ECC65",
+"V  c #8FC868",
+"C  c #91CE67",
+"Z  c #92CF6A",
+"A  c #8FD261",
+"S  c #8ADC62",
+"D  c #93D266",
+"F  c #96DD67",
+"G  c #95D46B",
+"H  c #99D56E",
+"J  c #95D96B",
+"K  c #9BDB6E",
+"L  c #96C079",
+"P  c #99CA7E",
+"I  c #94D771",
+"U  c #9DD670",
+"Y  c #95DA72",
+"T  c #9DDD71",
+"R  c #A5D561",
+"E  c #A3DC73",
+"W  c #9CE56E",
+"Q  c #9FE96D",
+"!  c #87F162",
+"~  c #8CF567",
+"^  c #9CFB6E",
+"/  c #9EE171",
+"(  c #9CE779",
+")  c #AAE76F",
+"_  c #A0EE6C",
+"`  c #A4E26F",
+"'  c #BBED64",
+"]  c #B2E86F",
+"[  c #BAF065",
+"{  c #A2E475",
+"}  c #A5EA76",
+"|  c #A9ED77",
+" . c #A6E17A",
+".. c #A5EE7A",
+"X. c #AAEE79",
+"o. c #A9E479",
+"O. c #B8E978",
+"+. c #A0F673",
+"@. c #ACFC77",
+"#. c #AAF27A",
+"$. c #AEFC7A",
+"%. c #A6F775",
+"&. c #B9F670",
+"*. c #B2FD7E",
+"=. c #B6F877",
+"-. c #C4DF44",
+";. c #C5E146",
+":. c #C0E24C",
+">. c #C1E852",
+",. c #868686",
+"<. c #8B8B8B",
+"1. c #959595",
+"2. c #9A9A9A",
+"3. c #98AC8C",
+"4. c #9FB78E",
+"5. c #A8BF96",
+"6. c #A2B695",
+"7. c gray68",
+"8. c #AFB5AB",
+"9. c gray70",
+"0. c #BDBDBC",
+"q. c #9FCE82",
+"w. c #A6CD87",
+"e. c #AACE8B",
+"r. c #ACD98E",
+"t. c #A5C192",
+"y. c #AAC796",
+"u. c #ABC897",
+"i. c #ADC898",
+"p. c #ADFF88",
+"a. c #B5FE82",
+"s. c #BAFF85",
+"d. c #B3FF89",
+"f. c #BAFF8B",
+"g. c #B5E197",
+"h. c #B7EB94",
+"j. c #B8CDA0",
+"k. c #B6C7A9",
+"l. c #BECBBB",
+"z. c #BCC6B5",
+"x. c #C1FF8D",
+"c. c #C5FF95",
+"v. c #C1F79D",
+"b. c #C3FA9F",
+"n. c #C3CBBC",
+"m. c #C4FBA0",
+"M. c #C3C3C4",
+"N. c #CFCFCE",
+"B. c #C9CEC5",
+"V. c #D1CFCF",
+"C. c #CDD2C9",
+"Z. c #D2D1D1",
+"A. c #DAD4D7",
+"S. c #D7DFD2",
+"D. c #D9DFD4",
+"F. c #D7D7D8",
+"G. c #D8D6DA",
+"H. c #D9DBD8",
+"J. c #D7E4CE",
+"K. c #DAE2D5",
+"L. c #E3DDE7",
+"P. c #E5DFE8",
+"I. c #E5E5E5",
+"U. c #E7E8E6",
+"Y. c #EAE6EC",
+"T. c #EBEAEB",
+"R. c #E7E4EC",
+"E. c #F4EFF9",
+"W. c #F2F2F3",
+/* pixels */
+"I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.I.U.U.I.U.I.I.I.R.U.I.U.I.",
+"I.U.T.Y.T.T.T.T.T.U.Y.T.T.T.T.U.T.T.T.T.U.T.U.T.Y.I.I.U.I.I.I.I.",
+"U.I.N.N.N.N.V.N.N.A.A.A.Z.N.N.N.N.N.V.V.A.A.A.V.H.U.U.I.I.P.I.U.",
+"Y.K.l { { / } V G 9 4 4 J l ( { { { V G 0 5 5 c 5.Y.I.I.T.W.I.I.",
+"U.K.H f.' v f.E c o o X 3 E d.' -.f.o.S o X . , j.R.I.T.0.0.T.I.",
+"Y.K.G d.s i a. .> . o . = U d.a i a.o.2 . o . & i.I.I.W.2.  W.I.",
+"Y.K.I ' u y ;.( W ! # 8 +.I [ u y g ( W ~ $ 7 ^ i.Y.I.Y.2.<.T.I.",
+"Y.K.I >.a d ;.T | x.t O.a.I m a d -.T } x.t O.s.y.Y.I.U.U.T.U.I.",
+"T.S.< K I I Y 1 N U J Y G < H I I Y j j G J G x 4.Y.I.I.U.I.I.I.",
+"Y.K.G *.*.$.a.T W ; & & n U $.*.*.s.W { a.$.a.@.y.Y.I.I.I.U.I.I.",
+"U.K.H a.a.a.a.o.6 . X . - U *.a.*.s.T } a.a.a.@.i.Y.I.W.2.7.W.I.",
+"Y.K.G a.*.*.a.{ 0 * X + 3 H a.*.*.a.W ( a.a.a.$.y.Y.I.W.1.,.W.P.",
+"U.K.H s.a.a.s.T O.f.e ] x.G s.a.a.s.E } s.a.a.*.i.Y.I.T.0.9.U.I.",
+"Y.K.N | } } ..B K ..b { } j } } } #.B H | } } Q 5.Y.I.I.T.W.I.U.",
+"T.D.j { K K { M Z / ( / K h { K K { N B ` K { F 4.T.I.I.T.U.I.I.",
+"Y.K.H s.a.s.s.E X.f.-.=.a.H s.a.s.f.{ } s.s.s.*.i.Y.U.T.0.V.Y.I.",
+"U.K.G a.a.a.a.K ..&.u v p.G a.$.*.a.T { a.*.a.@.u.Y.I.W.1.2.W.I.",
+"Y.K.G a.$.a.a.U ..a u p #.G a.a.*.a.W } s.*.*.$.u.Y.I.W.2.,.T.I.",
+"Y.K.H a.a.a.s./ | f a s ' H a.a.a.s.E } s.a.s.$.i.Y.I.I.I.I.I.I.",
+"U.H.k K U I / 1 N Y I I G < H G G K j M J G G D 4.T.I.I.I.I.U.I.",
+"U.L.e.w @ @ , U { *.*.=.X.B a.$.#.a.K / *.#.*.%.y.Y.U.I.I.U.I.I.",
+"U.P.w.% . . O A X.s.a.*.a.G a.a.*.s.{ } s.a.a.*.y.Y.I.T.2.9.W.I.",
+"I.R.P : O o = q X.a.*.a.$.G a.*.a.a./ { a.*.a.$.y.Y.I.W.2.<.W.I.",
+"U.L.w.x.z r c.E X.a.a.a.a.H a.a.a.s./ } s.s.a.*.i.Y.I.Y.9.7.U.I.",
+"U.L.L +.R b @.A K #.| | | N #.| | #.G F %.} } _ t.Y.I.I.T.W.I.U.",
+"Y.S.3.B.l.l.B.6.x E K K J 1 K K K K l 8.n.z.n.n.9.Y.I.I.T.U.I.I.",
+"U.J.k.E.T.U.E.B.| s.s.s.a.H s.s.s.s. .G.W.T.Y.T.M.I.I.U.M.F.U.U.",
+"Y.J.k.Y.I.I.Y.l._ *.$.*.$.D $.$.*.*.K V.T.I.I.I.0.I.I.W.1.2.W.I.",
+"T.K.n.Y.I.I.Y.C.h.m.v.v.v.r.m.v.v.m.g.G.Y.U.I.I.N.I.I.W.7.2.W.I.",
+"U.U.U.U.I.I.U.U.Y.R.R.Y.R.Y.Y.U.U.U.Y.U.I.I.I.U.I.I.I.I.I.U.I.I.",
+"U.I.I.I.U.I.U.R.U.I.I.U.U.I.I.I.I.I.I.U.I.U.I.U.I.I.I.I.U.I.I.I.",
+"I.I.R.I.I.U.I.I.I.I.I.I.I.I.I.I.I.R.I.U.I.I.I.I.R.I.I.U.I.I.R.U."
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 171 2 ",
+"   c #3C3C3C",
+".  c #484848",
+"X  c #5B5B5B",
+"o  c #535353",
+"O  c #6B6B6B",
+"+  c #626262",
+"@  c gray48",
+"#  c #A45A02",
+"$  c #A66407",
+"%  c #369801",
+"&  c #00AD00",
+"*  c #04B402",
+"=  c #00B800",
+"-  c #0CB609",
+";  c #0BB808",
+":  c #0AB807",
+">  c #1ABE13",
+",  c #418E00",
+"<  c #5B8000",
+"1  c #26C21C",
+"2  c #2FCC21",
+"3  c #31CD23",
+"4  c #5FC835",
+"5  c #46D332",
+"6  c #4DD334",
+"7  c #51D636",
+"8  c #54D83B",
+"9  c #669249",
+"0  c #69944C",
+"q  c #77B051",
+"w  c #7BB454",
+"e  c #7EB35A",
+"r  c #778F67",
+"t  c #789167",
+"y  c #7D9070",
+"u  c #5BDA41",
+"i  c #62DC44",
+"p  c #6ADC4C",
+"a  c #69D947",
+"s  c #74DD4E",
+"d  c #79DA4D",
+"f  c #76DC50",
+"g  c #68E149",
+"h  c #76E552",
+"j  c #7CEB55",
+"k  c #CEAD00",
+"l  c #D0A800",
+"z  c #CCB301",
+"x  c #CBBB0D",
+"c  c #C8BE15",
+"v  c #C9C31A",
+"b  c #C7C21C",
+"n  c #C6C926",
+"m  c #C7C720",
+"M  c #C4D133",
+"N  c #84BC5D",
+"B  c #88BF5F",
+"V  c #80B75C",
+"C  c #86BF60",
+"Z  c #89BF65",
+"A  c #85C15C",
+"S  c #88C05F",
+"D  c #9CD95D",
+"F  c #A4C64E",
+"G  c #BFD943",
+"H  c #BFDF4C",
+"J  c #A6CC55",
+"K  c #B4C257",
+"L  c #81E659",
+"P  c #91E05B",
+"I  c #BDE04E",
+"U  c #BDE454",
+"Y  c #BBE658",
+"T  c #87C162",
+"R  c #8AC363",
+"E  c #8DC964",
+"W  c #89CB69",
+"Q  c #90C667",
+"!  c #92C569",
+"~  c #94C86B",
+"^  c #93DF60",
+"/  c #94CF71",
+"(  c #B8DF6D",
+")  c #9BE868",
+"_  c #8BF065",
+"`  c #9AF36C",
+"'  c #95F06A",
+"]  c #9FE372",
+"[  c #99F770",
+"{  c #9EF873",
+"}  c #A2ED6D",
+"|  c #ADE96D",
+" . c #BAED63",
+".. c #A4F66D",
+"X. c #B6F36C",
+"o. c #B8F56E",
+"O. c #A4EE73",
+"+. c #A9EF75",
+"@. c #A7EF78",
+"#. c #A8EF78",
+"$. c #BAED78",
+"%. c #B8E370",
+"&. c #A5F373",
+"*. c #ACF275",
+"=. c #ACFD76",
+"-. c #ABF47A",
+";. c #AFFA7C",
+":. c #A5F977",
+">. c #B6F673",
+",. c #B2FD7E",
+"<. c #B8FD78",
+"1. c #B2F67B",
+"2. c #C0D740",
+"3. c #848484",
+"4. c #888888",
+"5. c #989898",
+"6. c #91A285",
+"7. c #94AB85",
+"8. c #97B783",
+"9. c #99B685",
+"0. c #97B881",
+"q. c #9CB986",
+"w. c #9DB58D",
+"e. c #9FB98C",
+"r. c #9DA896",
+"t. c #A5AE9F",
+"y. c #A2B792",
+"u. c #A4B993",
+"i. c #AABB96",
+"p. c #A4B599",
+"a. c #ABBB98",
+"s. c #A2A2A2",
+"d. c #AAABAA",
+"f. c #ACB7A4",
+"g. c #ACB8A5",
+"h. c #AEBAAA",
+"j. c #B3BBAB",
+"k. c #B5B5B5",
+"l. c #BABABA",
+"z. c #ADFC84",
+"x. c #B4FF81",
+"c. c #B9FF84",
+"v. c #B2FF89",
+"b. c #BAFF89",
+"n. c #BFC2BD",
+"m. c #C8DABD",
+"M. c #C0C0C0",
+"N. c #CFCFCF",
+"B. c #CED8C7",
+"V. c #CED7C8",
+"C. c #D2D7CE",
+"Z. c #D5D5D5",
+"A. c #DBDBDB",
+"S. c #D6EEC6",
+"D. c #DFE5DB",
+"F. c #E0EDD7",
+"G. c #E3ECDE",
+"H. c #E0E3DF",
+"J. c #E2DEE4",
+"K. c #E6E6E6",
+"L. c #E8E8E7",
+"P. c #E7E5E8",
+"I. c #EAE6ED",
+"U. c #EBEAEB",
+"Y. c #E4E8E2",
+"T. c #EFECF1",
+"R. c #F0EEF1",
+"E. c #F3EBF8",
+"W. c #F3F2F3",
+"Q. c #F9F9F9",
+"!. c #F8F2FD",
+/* pixels */
+"K.K.K.K.L.L.P.K.K.K.K.K.L.K.K.K.K.K.K.L.K.K.K.K.K.K.K.K.K.K.K.K.L.K.K.K.K.L.L.K.K.K.L.K.K.K.K.K.",
+"K.K.L.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.L.K.K.K.K.K.L.K.K.K.K.K.K.K.K.K.K.L.K.L.K.K.K.",
+"K.L.K.K.K.K.K.K.K.K.K.L.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.L.K.K.K.L.K.K.K.P.L.K.K.K.K.K.P.K.",
+"K.K.K.L.P.Y.K.K.K.K.K.L.K.K.K.K.Y.K.K.K.Y.K.K.K.K.K.K.K.P.Y.K.K.K.K.Y.K.K.K.K.K.K.K.L.K.K.K.K.K.",
+"K.K.L.K.T.I.I.I.I.I.I.I.R.I.I.I.I.I.I.I.R.I.I.T.I.I.I.I.I.I.I.I.P.I.I.I.L.K.K.K.K.L.K.K.K.K.K.L.",
+"K.K.L.H.6.w.w.w.w.w.y.w.6.w.i.a.a.a.u.w.6.w.w.w.w.w.w.w.6.y.a.a.a.a.a.7.m.I.K.K.K.K.P.L.P.K.K.K.",
+"K.K.P.G.N :.=.=.;.=.=.=.N =.i 7 6 6 g =.A ,.=.=.=.=.=.&.A =.8 7 7 6 j } d.I.Y.K.L.L.T.L.K.K.K.K.",
+"K.K.L.Y.Z x.x.c.M <.x.,.Q [ & & * & - -.R c.x.>.M x.c.-.~ _ & * & & 1 #.f.T.K.K.L.A.M.A.L.K.P.K.",
+"K.K.P.G.Z ;.v.Y k I v.,.! u & * * * & a ! ,.v.G l  .v.*.~ 5 & * * * * s j.R.J.K.U.N.o O W.K.K.K.",
+"L.K.P.G.Z ,.x.b k c ,.z.! 8 * * = * * i Q x.1.x z n v.+.Q 5 & * = * * s j.T.Y.K.K.U.@ X W.K.K.K.",
+"K.K.P.Y.Z z.Y k z l H v.N c.&.a % s :.;.S v.G k z z  .z.R c.[ 4 % L :.&.g.T.K.Y.W.l.X @ U.K.K.P.",
+"K.L.L.G.T ;.b k z z c ,.T ,.b.( # $.b.,.T <.x z z z n z.S c.b.K $ b.b.} g.T.K.K.K.H.A.U.K.K.K.L.",
+"K.K.L.G.Z 1.X.X.o.o.o.,.S ,.;.*.F -.,.-.S ,.X.o.o.o.o.-.T c.;.| J ;.x.O.g.T.K.K.K.L.L.P.P.K.K.K.",
+"K.K.P.H.0 N W T T T T T 9 T ! ~ / ! ! C 9 T T T T T T e 9 E T T W T R q s.T.Y.K.K.K.K.K.K.K.K.K.",
+"K.K.L.G.Z ,.c.,.x.,.,.,.C c.u 5 5 5 g ,.T c.,.,.x.x.c.-.R c.x.,.x.,.c.&.g.W.H.P.P.L.W.K.K.K.K.K.",
+"K.L.K.G.Z ;.,.,.x.z.c.;.T [ & & & & - &.R c.,.,.z.x.c.-.T x.;.x.,.x.x.} g.T.K.K.L.A.k.H.L.K.K.K.",
+"K.K.L.G.T ;.x.x.x.,.x.;.! 8 & : & - & i ! x.x.,.c.,.x.-.T c.;.,.x.x.c.} g.T.Y.J.P.Z.  k.W.P.K.K.",
+"K.P.L.G.Z ;.x.x.x.x.x.;.! u * * = * * p Q ,.z.,.x.,.x.-.E c.,.x.,.x.x.O.g.T.Y.K.Y.Q.O d.W.K.K.K.",
+"K.K.L.Y.Z ;.x.,.,.x.x.x.Z c.:.f , L ;.x.S c.z.,.,.x.x.-.T c.,.x.,.x.x.O.g.T.Y.P.L.N.. @ U.K.K.P.",
+"K.K.L.Y.Z ,.,.c.x.x.,.c.N ,.b.( # $.b.-.S c.,.x.x.x.c.-.T c.x.x.x.c.c.O.g.T.Y.K.P.P.P.K.K.K.K.K.",
+"K.K.L.G.C -.z.;.;.;.;.-.N ;.;.*.J -.;.-.N ,.;.;.;.;.;.@.S ,.;.;.;.;.x.) f.T.J.K.L.L.P.K.P.K.L.K.",
+"K.K.P.D.0 N Z T T T T T 9 T C T W R R N 9 R S T C T R N 9 E C T T N T w t.R.Y.Y.K.K.Y.P.K.K.K.K.",
+"K.K.P.Y.Z ,.x.c.c.c.c.c.T c.x.x.<.c.c.z.R c.x.c.c.c.c.-.T c.c.c.c.c.c.O.f.T.Y.P.K.L.W.L.K.K.K.K.",
+"K.K.L.G.Z ;.x.,.;.,.,.;.C c.x.>.m 1.x.;.N c.,.,.,.;.c.-.R c.;.;.,.,.c.O.f.T.P.K.L.A.d.A.L.K.K.K.",
+"K.K.P.Y.Z ;.x.x.,.x.x.x.T ,.v.G l U v.;.T x.x.x.x.x.x.-.T c.;.,.x.x.c.} g.T.Y.J.U.A.  k.R.K.K.K.",
+"K.K.L.G.Z ;.x.,.x.x.;.x.C x.>.c k c ,.,.S c.,.,.,.x.x.-.E c.,.x.,.x.x.O.g.T.Y.K.H.Q.O d.R.K.P.L.",
+"K.K.L.G.T ;.x.x.,.,.,.,.N v.M z z k I z.N c.,.x.x.x.x.-.T c.,.x.,.x.c.O.g.T.Y.P.U.N.o @ U.Y.K.K.",
+"K.K.L.Y.Z ;.c.x.c.x.x.c.R ,.v x x x b ,.T c.,.c.x.x.c.-.R c.x.x.,.c.c.O.g.T.Y.K.K.K.U.U.K.L.K.K.",
+"K.K.K.G.C @.-.@.-.-.-.-.e +.+.*.*.*.*.+.N -.-.-.-.-.-.O.N ;.-.-.-.-.-.) f.T.K.K.K.K.K.K.K.K.K.K.",
+"K.K.K.K.r N ~ E E Q Q R 9 E R T R R R C 0 T R R T T R N 0 E T R T R E w t.R.H.K.K.K.H.K.K.K.K.P.",
+"K.K.K.I.y.,.8 2 3 3 5 c.T c.c.c.c.x.c.x.Z c.c.x.c.c.c.-.R c.c.c.c.c.b.@.f.T.K.P.K.L.W.U.K.K.K.K.",
+"K.K.K.I.y.` * & & & & ' R x.x.,.x.,.c.-.C x.x.,.,.c.c.-.R c.,.;.,.,.v.{ f.T.H.K.U.Z.5.A.U.K.K.K.",
+"L.K.K.I.a.7 & * : * & 6 ! ,.x.x.x.,.c.;.C c.,.,.c.x.x.-.T c.,.x.,.,.x.&.g.T.K.K.L.A.. k.W.K.K.K.",
+"K.K.K.I.u.h > ; = ; > h Z x.x.,.x.,.x.;.T x.z.x.,.,.x.-.T c.,.,.x.x.c.O.g.T.H.K.J.Q.+ s.W.H.K.K.",
+"K.K.K.I.w.1.c.) < P c.c.C x.x.,.,.x.x.-.S c.;.,.x.;.c.-.R c.;.x.x.,.c.` g.T.H.K.U.N.X 4.U.K.K.K.",
+"K.K.K.T.w.;.b.$.$ %.b.c.T x.x.c.c.x.c.x.T c.c.x.x.c.c.-.Z b.c.c.x.c.b.+.g.T.K.K.K.L.W.U.K.P.K.K.",
+"K.K.K.I.7.) &.} D } O.} w &.@.@.@.@.@.O.e -.@.@.@.@.+.} e &.O.} O.O.&.D f.T.Y.K.K.K.H.K.K.K.K.K.",
+"K.K.L.H.t f.f.f.h.g.f.h.r S E R E R R Z 0 E T E E E E N y j.f.f.f.f.j.r.d.R.K.K.K.K.J.K.K.K.K.K.",
+"K.K.P.F.e.T.R.R.T.T.T.E.e.,.b.c.c.c.c.x.R b.x.c.x.c.b.*.f.!.T.T.T.I.E.J.k.U.K.K.K.U.Q.L.K.K.K.L.",
+"K.K.P.F.8.K.K.J.Y.K.K.P.9.=.x.,.,.,.x.,.N c.,.,.,.x.x.&.p.I.Y.Y.Y.Y.L.Z.k.U.K.K.L.Z.4.Z.L.K.K.P.",
+"K.K.L.F.9.J.K.K.K.K.K.I.9.,.c.,.x.x.v.;.R c.c.,.x.x.x.=.p.T.K.K.K.K.U.Z.k.R.K.K.K.K.. k.W.K.K.K.",
+"K.P.L.D.8.P.K.P.K.K.K.I.9.=.,.=.=.;.,.=.A ,.=.,.,.=.,.` p.T.K.K.K.K.U.Z.k.U.K.K.K.W.X s.W.K.K.K.",
+"K.K.K.Y.V.L.K.K.K.K.K.P.V.S.S.S.S.S.S.S.m.S.S.S.S.S.S.S.C.L.K.K.K.K.K.K.Z.K.K.P.K.A.s.l.U.K.K.K.",
+"K.L.K.P.I.L.L.K.K.K.K.K.I.I.I.P.I.I.L.I.P.I.I.I.I.I.I.I.U.K.K.K.K.K.K.K.L.K.K.K.P.K.W.R.K.K.K.K.",
+"P.K.K.K.K.K.K.K.L.K.L.K.K.Y.K.K.K.K.K.K.K.K.K.K.K.K.K.K.P.K.K.K.K.K.K.L.P.K.K.K.K.K.K.K.K.K.K.K.",
+"K.K.K.K.P.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.L.K.K.K.L.K.K.L.K.K.K.K.K.K.K.K.K.L.K.",
+"L.K.L.K.L.K.K.K.K.L.K.L.K.L.K.K.K.P.K.L.K.K.K.L.K.K.K.K.K.K.K.K.P.K.L.K.K.K.L.K.K.K.K.K.L.K.K.K.",
+"P.K.K.K.K.K.K.K.K.P.K.L.K.K.K.K.K.L.K.K.K.K.K.L.K.K.K.K.K.K.K.K.K.K.L.K.L.K.K.K.K.K.K.K.K.K.K.K."
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/tents-web.png b/icons/tents-web.png
new file mode 100644 (file)
index 0000000..f2ed992
Binary files /dev/null and b/icons/tents-web.png differ
diff --git a/icons/tents.ico b/icons/tents.ico
new file mode 100644 (file)
index 0000000..9056209
Binary files /dev/null and b/icons/tents.ico differ
diff --git a/icons/tents.rc b/icons/tents.rc
new file mode 100644 (file)
index 0000000..ec89f8e
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "tents.ico"
diff --git a/icons/tents.sav b/icons/tents.sav
new file mode 100644 (file)
index 0000000..292c2d2
--- /dev/null
@@ -0,0 +1,32 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :5:Tents
+PARAMS  :5:8x8de
+CPARAMS :5:8x8de
+DESC    :45:ea_ddidfaabkd,3,0,2,1,2,2,1,1,3,1,1,1,1,1,3,1
+NSTATES :2:25
+STATEPOS:2:25
+MOVE    :9:N5,6;N6,6
+MOVE    :14:N5,7;N6,7;N7,7
+MOVE    :9:N0,7;N1,7
+MOVE    :4:N1,6
+MOVE    :14:N6,2;N6,3;N6,4
+MOVE    :9:N7,2;N7,3
+MOVE    :14:N1,0;N2,0;N3,0
+MOVE    :4:N3,1
+MOVE    :4:N0,3
+MOVE    :4:N3,4
+MOVE    :4:N5,4
+MOVE    :4:T6,0
+MOVE    :4:T4,0
+MOVE    :4:T0,0
+MOVE    :4:N1,1
+MOVE    :4:N4,1
+MOVE    :9:N6,1;N7,1
+MOVE    :4:T2,1
+MOVE    :9:N1,2;N3,2
+MOVE    :4:T5,2
+MOVE    :4:N4,2
+MOVE    :4:N5,3
+MOVE    :4:N0,2
+MOVE    :9:N1,3;N1,5
diff --git a/icons/towers-16d24.png b/icons/towers-16d24.png
new file mode 100644 (file)
index 0000000..37e41b4
Binary files /dev/null and b/icons/towers-16d24.png differ
diff --git a/icons/towers-16d4.png b/icons/towers-16d4.png
new file mode 100644 (file)
index 0000000..fdb779c
Binary files /dev/null and b/icons/towers-16d4.png differ
diff --git a/icons/towers-16d8.png b/icons/towers-16d8.png
new file mode 100644 (file)
index 0000000..632de37
Binary files /dev/null and b/icons/towers-16d8.png differ
diff --git a/icons/towers-32d24.png b/icons/towers-32d24.png
new file mode 100644 (file)
index 0000000..41047cc
Binary files /dev/null and b/icons/towers-32d24.png differ
diff --git a/icons/towers-32d4.png b/icons/towers-32d4.png
new file mode 100644 (file)
index 0000000..c802adb
Binary files /dev/null and b/icons/towers-32d4.png differ
diff --git a/icons/towers-32d8.png b/icons/towers-32d8.png
new file mode 100644 (file)
index 0000000..8cae517
Binary files /dev/null and b/icons/towers-32d8.png differ
diff --git a/icons/towers-48d24.png b/icons/towers-48d24.png
new file mode 100644 (file)
index 0000000..3c3e368
Binary files /dev/null and b/icons/towers-48d24.png differ
diff --git a/icons/towers-48d4.png b/icons/towers-48d4.png
new file mode 100644 (file)
index 0000000..4f3fc47
Binary files /dev/null and b/icons/towers-48d4.png differ
diff --git a/icons/towers-48d8.png b/icons/towers-48d8.png
new file mode 100644 (file)
index 0000000..50e103a
Binary files /dev/null and b/icons/towers-48d8.png differ
diff --git a/icons/towers-base.png b/icons/towers-base.png
new file mode 100644 (file)
index 0000000..ce2c801
Binary files /dev/null and b/icons/towers-base.png differ
diff --git a/icons/towers-ibase.png b/icons/towers-ibase.png
new file mode 100644 (file)
index 0000000..4980e90
Binary files /dev/null and b/icons/towers-ibase.png differ
diff --git a/icons/towers-ibase4.png b/icons/towers-ibase4.png
new file mode 100644 (file)
index 0000000..c562492
Binary files /dev/null and b/icons/towers-ibase4.png differ
diff --git a/icons/towers-icon.c b/icons/towers-icon.c
new file mode 100644 (file)
index 0000000..aae6c24
--- /dev/null
@@ -0,0 +1,801 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 245 2 ",
+"   c #E6E6E6",
+".  c #E6E6E6",
+"X  c #E6E6E6",
+"o  c gray91",
+"O  c #EAEAEA",
+"+  c #E6E6E6",
+"@  c #E6E6E6",
+"#  c #E6E6E6",
+"$  c #EAEAEA",
+"%  c #E9E9E9",
+"&  c #E6E6E6",
+"*  c #E6E6E6",
+"=  c #E6E6E6",
+"-  c gray91",
+";  c gainsboro",
+":  c #D7D7D7",
+">  c #E7E7E7",
+",  c #E6E6E6",
+"<  c #E6E6E6",
+"1  c #E6E6E6",
+"2  c gray91",
+"3  c #D7D7D7",
+"4  c #DADADA",
+"5  c gray91",
+"6  c #E6E6E6",
+"7  c #E6E6E6",
+"8  c #E4E4E4",
+"9  c #ECECEC",
+"0  c #C6C6C6",
+"q  c #767676",
+"w  c #EFEFEF",
+"e  c gray89",
+"r  c gray90",
+"t  c gray94",
+"y  c #AEAEAE",
+"u  c #5D5D5D",
+"i  c gray92",
+"p  c gray90",
+"a  c #E6E6E6",
+"s  c #E6E6E6",
+"d  c gray92",
+"f  c #EFEFEF",
+"g  c #CECECE",
+"h  c gray38",
+"j  c gray89",
+"k  c #EAEAEA",
+"l  c gray91",
+"z  c gray91",
+"x  c gray93",
+"c  c #AEAEAE",
+"v  c DimGray",
+"b  c gray91",
+"n  c #E7E7E7",
+"m  c #E6E6E6",
+"M  c #E6E6E6",
+"N  c #E7E7E7",
+"B  c gray76",
+"V  c gray76",
+"C  c #C0C0C0",
+"Z  c #B1B2B1",
+"A  c #C0C2C0",
+"S  c gray78",
+"D  c gray77",
+"F  c #C6C6C6",
+"G  c gray91",
+"H  c gray84",
+"J  c #DDDDDD",
+"K  c #E6E6E6",
+"L  c #E4E4E4",
+"P  c #E6E6E6",
+"I  c #E7E7E7",
+"U  c gray91",
+"Y  c #9F9F9F",
+"T  c #DADADA",
+"R  c #E6E7E6",
+"E  c #E9E8E9",
+"W  c #F0E8F0",
+"Q  c #E2E2E2",
+"!  c #E7E6E7",
+"~  c gray72",
+"^  c #C1C1C1",
+"/  c gray79",
+"(  c #C4C6C4",
+")  c gray77",
+"_  c gray78",
+"`  c gray",
+"'  c gray86",
+"]  c gray92",
+"[  c gray68",
+"{  c #DDDDDD",
+"}  c #EDEAED",
+"|  c #DFE3DF",
+" . c #A9CEA9",
+".. c #E8E7E8",
+"X. c gray91",
+"o. c gray78",
+"O. c gray90",
+"+. c #ECE9EC",
+"@. c #F5EDF5",
+"#. c #ECE9EC",
+"$. c #EBECEB",
+"%. c gray82",
+"&. c gray83",
+"*. c #EAEAEA",
+"=. c #A9AAA9",
+"-. c #DBDCDB",
+";. c #F0EBF0",
+":. c #87C087",
+">. c #3FA03F",
+",. c gray90",
+"<. c gray91",
+"1. c #C4C5C4",
+"2. c #EAE6EA",
+"3. c #C5D9C5",
+"4. c #80BD80",
+"5. c #C9DAC9",
+"6. c #F0EDF0",
+"7. c #CFD0CF",
+"8. c gray83",
+"9. c #EAEAEA",
+"0. c #AAAAAA",
+"q. c #DEDDDE",
+"w. c #E5E7E5",
+"e. c #71B771",
+"r. c #349E34",
+"t. c #CDDCCD",
+"y. c #EDEAED",
+"u. c #C4C5C4",
+"i. c #E4E4E4",
+"p. c #E5E6E5",
+"a. c #69B469",
+"s. c #9DC89D",
+"d. c #F9F0F9",
+"f. c #CDCFCD",
+"g. c gray83",
+"h. c #EAEAEA",
+"j. c #A9A9A9",
+"k. c gainsboro",
+"l. c #E9E8E9",
+"z. c #EBE8EB",
+"x. c #C9DAC9",
+"c. c #E4E5E4",
+"v. c #E8E7E8",
+"b. c #C3C5C3",
+"n. c #EEE8EE",
+"m. c #B0D1B0",
+"M. c #38A038",
+"N. c #C6D8C6",
+"B. c #F0EDF0",
+"V. c #CECFCE",
+"C. c gray92",
+"Z. c #AEAEAE",
+"A. c gray87",
+"S. c #EAEAEA",
+"D. c #E6E7E6",
+"F. c #EEEAEE",
+"G. c #E7E7E7",
+"H. c #E9E9E9",
+"J. c #C8C8C8",
+"K. c #E8E7E8",
+"L. c #DAE2DA",
+"P. c #C5DAC5",
+"I. c #D8E1D8",
+"U. c #EFEDEF",
+"Y. c gray82",
+"T. c gray83",
+"R. c gray56",
+"E. c #A4A4A4",
+"W. c gray68",
+"Q. c #AAABAA",
+"!. c #A9AAA9",
+"~. c #ACACAC",
+"^. c #A7A7A7",
+"/. c #AAAAAA",
+"(. c #E4E4E4",
+"). c #E3E1E3",
+"_. c #E9E4E9",
+"`. c #E3E1E3",
+"'. c gray90",
+"]. c #CDCDCD",
+"[. c gray83",
+"{. c #D8D8D8",
+"}. c gray64",
+"|. c #E1E1E1",
+" X c gray86",
+".X c #E0DEE0",
+"XX c #DEDDDE",
+"oX c #DDDEDD",
+"OX c #D7D7D7",
+"+X c #AAAAAA",
+"@X c #BBBBBB",
+"#X c #B9B9B9",
+"$X c #B8B9B8",
+"%X c #B9B9B9",
+"&X c #BBBBBB",
+"*X c #AAAAAA",
+"=X c gray86",
+"-X c #E4E4E4",
+";X c gray79",
+":X c #E9E9E9",
+">X c #E9E8E9",
+",X c #D8E2D8",
+"<X c #E1E5E1",
+"1X c #EBEAEB",
+"2X c gray89",
+"3X c gray",
+"4X c #D5D5D5",
+"5X c #D2D2D2",
+"6X c #D2D2D2",
+"7X c LightGray",
+"8X c gray82",
+"9X c gray75",
+"0X c gray91",
+"qX c #E6E6E6",
+"wX c gray89",
+"eX c #E6E6E6",
+"rX c #E6E6E6",
+"tX c #D9E0D9",
+"yX c #E0E3E0",
+"uX c #E7E6E7",
+"iX c gray90",
+"pX c #E6E6E6",
+"aX c #EAEAEA",
+"sX c #EAEAEA",
+"dX c #EAEAEA",
+"fX c #EAEAEA",
+"gX c #EAEAEA",
+"hX c gray90",
+"jX c #E6E6E6",
+"kX c #E7E7E7",
+"lX c #E6E6E6",
+"zX c #E6E6E6",
+"xX c #E9E7E9",
+"cX c #E7E7E7",
+"vX c #E6E6E6",
+"bX c #E6E6E6",
+"nX c #E6E6E6",
+"mX c gray90",
+"MX c gray90",
+"NX c gray90",
+"BX c gray90",
+"VX c gray90",
+"CX c #E6E6E6",
+"ZX c #E6E6E6",
+"AX c white",
+/* pixels */
+"  . X o O + .   @ # $ % & *     ",
+"  = - ; : > , < 1 2 3 4 5 6     ",
+"7 8 9 0 q w e r e t y u i p a   ",
+"s d f g h j k l z x c v b n m M ",
+"N B V C Z A S D F G H J K L P I ",
+"U Y T R E W Q ! ~ ^ / ( ) _ ` ' ",
+"] [ { } |  ...X.o.O.+.@.#.$.%.&.",
+"*.=.-.;.:.>.,.<.1.2.3.4.5.6.7.8.",
+"9.0.q.w.e.r.t.y.u.i.p.a.s.d.f.g.",
+"h.j.k.l.z.x.c.v.b.n.m.M.N.B.V.g.",
+"C.Z.A.S.D.F.G.H.J.K.L.P.I.U.Y.T.",
+"b R.E.W.Q.!.~.^./.(.)._.`.'.].[.",
+"{.}.|. X.XXXoXOX+X@X#X$X%X&X*X=X",
+"-X;X:X>X,X<X1X2X3X4X5X6X7X8X9X0X",
+"qXwXeXrXtXyXuXiXpXaXsXdXfXgXhX# ",
+"jXkXlXzXxXcXvXbXnXmXMXNXBXVXCXZX"
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 194 2 ",
+"   c gray6",
+".  c #1B1B1B",
+"X  c #232323",
+"o  c gray18",
+"O  c #3C3C3C",
+"+  c #3E3E3E",
+"@  c #414141",
+"#  c #434343",
+"$  c #515151",
+"%  c #535353",
+"&  c gray35",
+"*  c gray36",
+"=  c #676767",
+"-  c #686868",
+";  c gray42",
+":  c gray46",
+">  c #777777",
+",  c gray47",
+"<  c gray49",
+"1  c #7E7E7E",
+"2  c gray50",
+"3  c #008400",
+"4  c #038903",
+"5  c #129112",
+"6  c #169316",
+"7  c #199419",
+"8  c #1B941B",
+"9  c #1E911E",
+"0  c #219721",
+"q  c #2A9B2A",
+"w  c #2E982E",
+"e  c #2E9D2E",
+"r  c #309730",
+"t  c #379E37",
+"y  c #389C38",
+"u  c #41A241",
+"i  c #43A543",
+"p  c #4AA84A",
+"a  c #4EA94E",
+"s  c #51A751",
+"d  c #5BAE5B",
+"f  c #63B263",
+"g  c #6BB56B",
+"h  c #71B771",
+"j  c #7FBD7F",
+"k  c gray51",
+"l  c #838383",
+"z  c #848484",
+"x  c #868686",
+"c  c #8B8B8B",
+"v  c #909090",
+"b  c gray57",
+"n  c #929292",
+"m  c #939393",
+"M  c gray58",
+"N  c #959595",
+"B  c gray59",
+"V  c #979797",
+"C  c #989898",
+"Z  c gray60",
+"A  c #9A9A9A",
+"S  c #9B9B9B",
+"D  c #9D9D9D",
+"F  c #81BE81",
+"G  c #84BF84",
+"H  c #A0A0A0",
+"J  c gray64",
+"K  c #A4A4A4",
+"L  c gray65",
+"P  c gray66",
+"I  c #A9A9A9",
+"U  c #AAAAAA",
+"Y  c gray67",
+"T  c #AEAEAE",
+"R  c #AFAFAF",
+"E  c gray69",
+"W  c #B1B1B1",
+"Q  c #B2B2B2",
+"!  c gray70",
+"~  c #B4B4B4",
+"^  c gray71",
+"/  c #B6B6B6",
+"(  c #B7B7B7",
+")  c #B9B9B9",
+"_  c gray73",
+"`  c #BBBBBB",
+"'  c #BCBCBC",
+"]  c gray74",
+"[  c gray",
+"{  c #8EC38E",
+"}  c #97C697",
+"|  c #97C797",
+" . c #A0CAA0",
+".. c #AACEAA",
+"X. c #AFD0AF",
+"o. c #B2D1B2",
+"O. c #B5D2B5",
+"+. c #B8D3B8",
+"@. c #B9D4B9",
+"#. c #BAD5BA",
+"$. c #C1C1C1",
+"%. c gray76",
+"&. c gray77",
+"*. c #C5C5C5",
+"=. c #C6C6C6",
+"-. c gray78",
+";. c #C8C8C8",
+":. c gray79",
+">. c #CACACA",
+",. c #CBCBCB",
+"<. c #CDCDCD",
+"1. c #CECECE",
+"2. c gray81",
+"3. c #C5D9C5",
+"4. c #C7D9C7",
+"5. c #C7DAC7",
+"6. c #CADBCA",
+"7. c #CCDCCC",
+"8. c #CEDCCE",
+"9. c #CEDDCE",
+"0. c #D0D0D0",
+"q. c gray82",
+"w. c #D2D2D2",
+"e. c #D5D5D5",
+"r. c #D7D7D7",
+"t. c #D3DED3",
+"y. c #D5DFD5",
+"u. c gray85",
+"i. c gray86",
+"p. c #DDDDDD",
+"a. c gray87",
+"s. c #D6E0D6",
+"d. c #D7E0D7",
+"f. c #DEE3DE",
+"g. c gray88",
+"h. c #E1E1E1",
+"j. c #E1E3E1",
+"k. c #E2E2E2",
+"l. c #E2E3E2",
+"z. c gray89",
+"x. c #E0E4E0",
+"c. c #E1E4E1",
+"v. c #E3E4E3",
+"b. c #E3E5E3",
+"n. c #E4E4E4",
+"m. c #E4E5E4",
+"M. c gray90",
+"N. c #E5E6E5",
+"B. c #E6E6E6",
+"V. c #E6E7E6",
+"C. c #E7E6E7",
+"Z. c #E7E7E7",
+"A. c #E8E7E8",
+"S. c #E9E7E9",
+"D. c #EAE7EA",
+"F. c gray91",
+"G. c #E9E9E9",
+"H. c #EAE8EA",
+"J. c #EBE8EB",
+"K. c #EAEAEA",
+"L. c gray92",
+"P. c #ECE8EC",
+"I. c #EDE9ED",
+"U. c #EEE9EE",
+"Y. c #EFE9EF",
+"T. c #EEEAEE",
+"R. c #EFEAEF",
+"E. c #ECECEC",
+"W. c #ECEDEC",
+"Q. c gray93",
+"!. c #EEEEEE",
+"~. c #EFEFEF",
+"^. c #EFF0EF",
+"/. c #F0EAF0",
+"(. c #F0EBF0",
+"). c #F1EAF1",
+"_. c #F3EBF3",
+"`. c #F2ECF2",
+"'. c #F3ECF3",
+"]. c #F4EBF4",
+"[. c #F5ECF5",
+"{. c #F6EDF6",
+"}. c gray94",
+"|. c #F1F1F1",
+" X c gray95",
+".X c #F3F3F3",
+"XX c #F4F4F4",
+"oX c gray96",
+"OX c #F6F6F6",
+"+X c gray97",
+"@X c #FEF0FE",
+"#X c #FFF1FF",
+"$X c #F8F8F8",
+"%X c #F9F9F9",
+/* pixels */
+"B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.",
+"B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.z.B.B.B.B.B.B.B.B.B.B.",
+"B.B.B.B.B.B.B.G.G.B.B.B.B.B.B.B.B.B.B.B.B.~.G.z.B.B.B.B.B.B.B.B.",
+"B.B.B.B.B.B.G.i.2.z.B.B.B.B.B.B.B.B.B.B.B.:.2.~.B.B.B.B.B.B.B.B.",
+"B.B.B.B.B.z.~.M   L  Xz.B.B.B.B.B.B.B.G.q.= o 2  Xz.B.B.B.B.B.B.",
+"B.B.B.B.B.B.z.XX# M XXB.B.B.B.B.B.B.B.B.~.P . z XXz.B.B.B.B.B.B.",
+"B.B.B.B.B.B.z. X+ m XXz.B.B.B.z.B.B.B.B.a.q.& $ XXB.B.B.B.B.B.B.",
+"B.B.B.B.~.G.XXM X @ q. XG.G.~.G.G.G.z.~._ $ + c  Xz.B.B.B.B.B.B.",
+"B.B. Xz.2.q.q.-.-.$.2.q.2.2.q.q.q.z.v.z.N.r.a.~.z.z.z.z.B.B.B.B.",
+"B.z.$.: T T T Q Q Q Q T Q T T Q C ] %X~.^. X`.~.~. X X~.~.~.B.B.",
+"B.G.C : XX X~.^.~.~.~.~.~.~.^.XXe.m ' ^ ^ ( ^ ^ ^ ^ ^ ^ ^ ^ z.B.",
+"B.~.-.l G.z.B.v.n.z.f.z.v.z.B.G.:.D 2.-.-.-.-.-.-.-.:.-.q.D :.~.",
+"z.~.] 2 ~.z.B.B.B.B.[.Y.B.B.B.L.:._ XX~.~.L.L.~.~.L.~.G.XX_ :.~.",
+"z.~.] 2 G.B.B.B.D.f.i F _.v.v.~.:.^ ~.z.B.v.G.B.z.B.B.z. XQ :.~.",
+"z.~.] 2 ~.B.B.v.[.f 3 s `.v.v.L.:.! ~.z.B.G.s.f.Y.B.B.z. X^ :.~.",
+"z.~.] 2 G.B.v._.} h g p #Xv.B.~.,.! ^.z.Y.d 0 7 | Y.B.z.~.^ :.G.",
+"h.].[ 1 L.B.v.Y.y u q 5 O.Y.z.~.:.!  Xv.N.X.N.s r `.B.v.^.^ :.~.",
+"h.].[ 1 L.B.B.D. .F w 8 6.D.z.~.,.^  Xv.v.#Xs.7 { [.v.v. XQ :.~.",
+"D.).' 1 L.B.B.v._.#Xo...[.B.B.G.:.! ~.z.Y.O.6 j _.v.B.v.~.^ :.~.",
+"k.Y.) h L.B.B.B.v.z.Y._.v.B.B.~.,.! ~.z.Y.t 4 e a D.B.B. X^ :.G.",
+"k.Y.] 1 G.z.B.z.v.z.z.f.z.z.z.G.-.! ^.B.B.7.3.@.6.B.B.z.~.Q :.~.",
+"C.).' z  X~.~.~.~.~.~.~.~.~.~. Xw.^ ~.v.B.P.Y._.Y.B.B.z.~.^ :.~.",
+"n._.*.; _ ^ ^ Q ^ Q ^ ^ Q ^ ^ ] A R  XB.B.B.z.v.v.B.B.z. XQ :.~.",
+"n.P.< * D c m m m m m m m m m M : q.G.v.B.B.B.v.v.B.B.z.~.^ :.~.",
+"n.Y.- ] G.z.z.z.z.z.z.z.z.z.z.B.M L T P T P P P P P P P Q 2 q.L.",
+"G.i.x :.`.N.N.B.B.B.B.N.N.B.B.G.J :.q.0.2.q.q.q.q.q.q.q.:.J G.B.",
+"B.~._ $.~.z.N.N.Y.Y.G.B.N.B.B.~.M P Q T T R T T Q T T ( c _ ^.B.",
+"n.~.] $.~.B.B.G.7.O.7.D.G.B.B.B.Y L.^.^.^.`.~.~.~.~.~.XX2.] ~.z.",
+"B.B.z.z.B.B.B.B.y.5.s.B.B.B.B.B.f.z.v.B.B.z.z.B.z.z.z.B.z.z.B.B.",
+"B.B.B.B.B.B.B.B.G.Y.D.B.B.B.B.B.D.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.",
+"B.B.B.B.B.B.B.B.v.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.",
+"B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B.B."
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 239 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c #040404",
+"O  c #0C0C0C",
+"+  c #0E0E0E",
+"@  c #111111",
+"#  c #131313",
+"$  c #151515",
+"%  c gray13",
+"&  c #2C2C2C",
+"*  c #3F3F3F",
+"=  c #007900",
+"-  c #007C00",
+";  c #007D00",
+":  c #007F00",
+">  c gray26",
+",  c gray29",
+"<  c #4C4C4C",
+"1  c gray31",
+"2  c #505050",
+"3  c gray32",
+"4  c #555555",
+"5  c #5D5D5D",
+"6  c gray37",
+"7  c #626262",
+"8  c gray39",
+"9  c #646464",
+"0  c gray40",
+"q  c DimGray",
+"w  c #6A6A6A",
+"e  c gray44",
+"r  c #717171",
+"t  c #727272",
+"y  c gray45",
+"u  c gray46",
+"i  c gray47",
+"p  c #797979",
+"a  c gray48",
+"s  c #7B7B7B",
+"d  c #7C7C7C",
+"f  c #7E7E7E",
+"g  c gray50",
+"h  c #008200",
+"j  c #008300",
+"k  c #008500",
+"l  c #018501",
+"z  c #008600",
+"x  c #008800",
+"c  c #028B02",
+"v  c #078C07",
+"b  c #078D07",
+"n  c #098C09",
+"m  c #0E8D0E",
+"M  c #148D14",
+"N  c #109010",
+"B  c #1D951D",
+"V  c #1F951F",
+"C  c #1E961E",
+"Z  c #299A29",
+"A  c #2A9B2A",
+"S  c #2E9B2E",
+"D  c #2F9D2F",
+"F  c #37A037",
+"G  c #39A139",
+"H  c #3EA23E",
+"J  c #3FA33F",
+"K  c #43A143",
+"L  c #42A442",
+"P  c #46A646",
+"I  c #49A649",
+"U  c #4CA64C",
+"Y  c #4BA84B",
+"T  c #4EA84E",
+"R  c #5AAE5A",
+"E  c #63B163",
+"W  c #66B366",
+"Q  c #69B469",
+"!  c #6AB46A",
+"~  c #6FB46F",
+"^  c #70B770",
+"/  c #72B772",
+"(  c #77BA77",
+")  c #7FBD7F",
+"_  c #818181",
+"`  c gray51",
+"'  c #848484",
+"]  c #868686",
+"[  c gray53",
+"{  c #888888",
+"}  c #898989",
+"|  c gray54",
+" . c #8B8B8B",
+".. c gray55",
+"X. c gray56",
+"o. c #909090",
+"O. c gray57",
+"+. c #929292",
+"@. c gray58",
+"#. c #959595",
+"$. c gray59",
+"%. c #989898",
+"&. c gray60",
+"*. c #9B9B9B",
+"=. c #A0A0A0",
+"-. c #A2A2A2",
+";. c gray64",
+":. c #A4A4A4",
+">. c gray65",
+",. c gray66",
+"<. c #A9A9A9",
+"1. c gray67",
+"2. c #ACACAC",
+"3. c gray68",
+"4. c #AEAEAE",
+"5. c gray69",
+"6. c #B1B1B1",
+"7. c #B2B2B2",
+"8. c gray71",
+"9. c #B6B6B6",
+"0. c #B7B7B7",
+"q. c gray72",
+"w. c #B9B9B9",
+"e. c gray73",
+"r. c #BBBBBB",
+"t. c #BCBCBC",
+"y. c gray",
+"u. c gray75",
+"i. c #90C390",
+"p. c #91C291",
+"a. c #95C695",
+"s. c #97C697",
+"d. c #97C797",
+"f. c #9BC89B",
+"g. c #A0CAA0",
+"h. c #A4CBA4",
+"j. c #A5CCA5",
+"k. c #A6CDA6",
+"l. c #ADCFAD",
+"z. c #AFD0AF",
+"x. c #B2D2B2",
+"c. c #B7D3B7",
+"v. c #BAD4BA",
+"b. c #BDD5BD",
+"n. c #BDD6BD",
+"m. c #BFD7BF",
+"M. c #C3C3C3",
+"N. c gray77",
+"B. c #C5C5C5",
+"V. c #C6C6C6",
+"C. c gray78",
+"Z. c #C8C8C8",
+"A. c gray79",
+"S. c #CACACA",
+"D. c #CECECE",
+"F. c gray81",
+"G. c #C2D8C2",
+"H. c #C5D9C5",
+"J. c #C6D9C6",
+"K. c #C7D9C7",
+"L. c #CADBCA",
+"P. c #D0D0D0",
+"I. c gray82",
+"U. c #D2D2D2",
+"Y. c LightGray",
+"T. c gray83",
+"R. c #D4DFD4",
+"E. c gray85",
+"W. c #DADADA",
+"Q. c gray86",
+"!. c #DDDDDD",
+"~. c gray87",
+"^. c #DFDFDF",
+"/. c #D8E2D8",
+"(. c #DAE1DA",
+"). c #DBE2DB",
+"_. c #DCE2DC",
+"`. c #DEE3DE",
+"'. c gray88",
+"]. c #E1E1E1",
+"[. c #E2E2E2",
+"{. c gray89",
+"}. c #E0E4E0",
+"|. c #E1E4E1",
+" X c #E2E4E2",
+".X c #E2E5E2",
+"XX c #E3E5E3",
+"oX c #E4E4E4",
+"OX c #E4E5E4",
+"+X c gray90",
+"@X c #E5E6E5",
+"#X c #E6E6E6",
+"$X c #E7E6E7",
+"%X c #E7E7E7",
+"&X c #E8E7E8",
+"*X c #E9E7E9",
+"=X c gray91",
+"-X c #E9E9E9",
+";X c #EAE8EA",
+":X c #EBE8EB",
+">X c #EAEAEA",
+",X c gray92",
+"<X c #ECE9EC",
+"1X c #EDE9ED",
+"2X c #EEE9EE",
+"3X c #EFEAEF",
+"4X c #ECECEC",
+"5X c gray93",
+"6X c #EEEEEE",
+"7X c #EFEFEF",
+"8X c #F0EAF0",
+"9X c #F1EAF1",
+"0X c #F1EBF1",
+"qX c #F2EBF2",
+"wX c #F4EBF4",
+"eX c #F4ECF4",
+"rX c #F5ECF5",
+"tX c #F6ECF6",
+"yX c #F6EDF6",
+"uX c #F7EDF7",
+"iX c #F8EDF8",
+"pX c #F9EEF9",
+"aX c #FAEEFA",
+"sX c gray94",
+"dX c #F1F1F1",
+"fX c gray95",
+"gX c #F3F3F3",
+"hX c #F4F4F4",
+"jX c gray96",
+"kX c #F6F6F6",
+"lX c gray97",
+"zX c #FFF0FF",
+"xX c #FFF1FF",
+"cX c #F8F8F8",
+"vX c #F9F9F9",
+"bX c gray98",
+"nX c #FBFBFB",
+"mX c gray99",
+"MX c white",
+/* pixels */
+"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X",
+"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X",
+"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X",
+"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X",
+"#X#X#X#X#X#X#X#X#X#X#X#X{.{.#X=X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X{.{.{.#X#X#X#X#X#X#X#X#X#X#X#X#X#X",
+"#X#X#X#X#X#X#X#X#X#X#X4XnXnX=X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X=XhXmXhX=X#X#X#X#X#X#X#X#X#X#X#X#X#X",
+"#X#X#X#X#X#X#X#X#X#X4Xq.e u U.=X#X#X#X#X#X#X#X#X#X#X#X#X#X=XQ._ 0 ] Q.=X#X#X#X#X#X#X#X#X#X#X#X#X",
+"#X#X#X#X#X#X#X#X#X{.fX} & X q.fX{.#X#X#X#X#X#X#X#X#X#X#X#X4XV.4 q X < fX{.#X#X#X#X#X#X#X#X#X#X#X",
+"#X#X#X#X#X#X#X#X#X#X{.nX9.X t.4X{.#X#X#X#X#X#X#X#X#X#X#X#X#X4XV.e # e fX{.#X#X#X#X#X#X#X#X#X#X#X",
+"#X#X#X#X#X#X#X#X#X#X#X4X>.X t.fX#X#X#X#X#X#X#X#X#X#X#X#X#X{.fX4.< # +.fX#X#X#X#X#X#X#X#X#X#X#X#X",
+"#X#X#X#X#X#X#X#X#X#X{.fX<.X t.fX{.#X#X#X#X#X#X#X#X#X#X#X#X=XU.^.mX> % =X#X#X#X#X#X#X#X#X#X#X#X#X",
+"#X#X#X#X#X#X{.#X{.{.fX_ + X + @.4X{.{.#X{.{.#X#X{.#X#X#X{.hX+.# % o _ 4X{.#X#X#X#X#X#X#X#X#X#X#X",
+"#X#X#X#X#X#XfXfXfX4XhXT.q.M.q.Q.hXfXfXfXfXfXfXfXhX4X#X#X#X#X{.q.4.S.4X#X#X#X#X#X#X#X#X#X#X#X#X#X",
+"#X#X#X=X4X^.6.6.6.6.6.q.t.t.t.9.6.6.6.6.6.6.6.6.4.S.=X#X#X#X#XfXfX=X#X#X#X#X#X#X#X#X#X#X#X#X#X#X",
+"#X#X#XQ.^.8 ] 6.<.<.<.<.<.>.>.<.<.<.<.<.<.<.<.4.=.d 4X#X#X#X#X#X#X#X#X#X&X#X#X#X#X#X#X#X#X#X#X#X",
+"#X#X4XD.a > U.nXfXfXhXfXfXhXfXhXfXhXfXhXhXfXhXhX4X} #X{.#X{.#X{.{.{.#X{.#X#XoX{.#XXX#X=X#X#X#X#X",
+"#X#XXXhXU.7 V.,X{.oXoX{.oXoXXXXX#X{.XX{.#X{.{.#X{.0 ] } } } [ } } [ } } [ } } [ } [ } a ^.%X#X#X",
+"#X#X#X4XM.6 Z.6X{.#X#X#X#X#X#X#X#X#X#X#X#X#X#X%X^.} 4X#X%X=X=X%X=X%X%X=X=X%X=X%X%X#XhX+.D.4X#X#X",
+"#X#X#X4XV.6 Z.6XoX#X#X#X#X#X#X&X1X&X#X#X#X#X#X=X{.} =X#X#X#X#X#X#X#X#X#X%X#X#X#X#X{.fX+.U.,X#X#X",
+"#X#XXX4XV.6 S.0XoX#X#X#X#X#X=X`.L.(.&X#X#X#X#X=X^.[ 4X#X#X#X#X#X#X#X#X%X#X#X#X#X#X{.fX+.F.=X#X#X",
+"#X#X{.4XV.6 Z.4XoX#X#X#X#X%XXXS h ^ rXXX#X#X#X=X`.} =X#X#X#X#X#XXXXXXXXX#X#X#X#X#X{.fX+.D.,XXX#X",
+"#X#X#X4XV.6 Z.4XoX#X#X#XXXrXE m z ! rXXX#X#X#X=X^.} ,X#X#X#X#X1XrXzXrX1XoX#X#X#X#X{.fXo.F.,X#X#X",
+"#X#X#X4XV.6 S.4XoX#X#XXXrXk.M a.z Q wXXX#X#X#X=X^.} ,X#X#X#X=Xc.E Y E c.1X#X#X#X#X{.fX+.F.4X#X#X",
+"#X#X#X4XV.6 S.4XoX#X#X&X(.B g.(.: ( xXXX#X#X#X%X^.} =X#X#X#XwXK M G z n b.1X#X#X#X{.fXo.F.4X#X#X",
+"#X#XXX4XZ.6 S.4XoX#XXXrXi.h R L x B f.1X#X#X#X=X^.} 4X#X#X#X#XG.XXaX( : i.wXXX#X#X{.fX+.D.4X#X#X",
+"#X#X#X0XV.6 S.4XoX#XXXqXk.Z Z B x v ) qXXX#X#X=X^.} =X#X#X#X&X=X&XqXI z G.qXoX#X#X{.fX+.U.=X#X#X",
+"#X#X#X4XV.6 S.4XoX#X#X#X#X#XrXb.: ^ pXXX#X#X#X=X^.} =X#X#X#X#X=X&XU z a.qXoX#X#X#X{.fX+.D.4X#X#X",
+"#X#XXX4XV.6 S.4XoX#X#X#X#X#X=X(.h.K.&X#X#X#X#X=X^.} 1X#X#X#X&X(.J n x.xX&X#X#X#X#X{.fX+.D.4X#X#X",
+"#X#X#X6XV.6 S.4XoX#X#X#X#X#X#X#XwX&X#X#X#X#X#X&X`.} =X#X#XXXqXU = B I A a.qXXX#X#X{.fXX.U.=X#X#X",
+"#X#X#X4XV.6 S.4X{.#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X^.} ,X#X#X#X1X^ J L G S a.qXXX#X#X{.fX+.D.4X#X#X",
+"#X#XXX4XV.6 S.4X{.#X#X#X#X#X#X#X#X#X#X#X#X#X#X&X^.} 1X#X#X#X#XqXwXwXrXpX1X#X#X#X#X{.fX+.D.4X#X#X",
+"#X#X#X4XV.6 Z.4X{.#XoX#XoX#X#XoX#XoX#X#X#X#X#X#X^.} =XoX#X#X#X#XXXXX#X{.#X#X#X#X#X{.fXX.U.=X{.#X",
+"#X#XXX4XM.8 V.4X#X=X=X=X=X=X=X=X=X=X&X=X=X=X=X=X{.X.=X#X#X#X#X#X#X#X#X#X#X#X#X#X#X{.hX+.D.4X#X#X",
+"#X#XoXfXU.3 } &.@.@.@.@.@.@.&.@.@.@.@.@.&.@.@.&._ e 4X{.{.#X{.#X{.{.{.#X#X{.{.{.{.{.4X+.U.=X#X#X",
+"#X#X=XU.e & @.X.+.+.+.X.X.+.+.X.o.o.X.+.+.X.X.@.e M.mXfXfXfXfXfXfXfXfXfXfXfXfXfXfXfXmX@.U.4X#X#X",
+"#X#X&X4X, } Q.V.S.S.S.S.S.S.S.S.S.S.S.S.S.S.S.U.e =.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.<.9.a U.=X#X#X",
+"#X#X=XU.3 4.nX=X4X4X4X1X4X4X4X4X4X4X4X4X4X4X=XnX] =.6.4.4.4.4.4.4.4.4.4.4.4.4.4.4.4.>.q #X#X#X#X",
+"#X{.fX4.} 4.fX{.#X#X#X#X#X#X#X#X#X#X#X#X#X#XoX=XX.#XfX4XfX4XfX4X4X4XfX4X4X4XfX4X=XnX6.6.fX#X#X#X",
+"#X#X#X#X9.>.hX{.#X#X#X#X#XoXXXXX#X#X#X#X#X#X{.fXu a } } ] } ] ] } } ] ] } } ] ] } ] 0 #X#X#X#X#X",
+"#X#X#XfX9.=.hX#X#X#X#X#X&XqXqXqX&X#X#X#X#X#X#X4X} {.4X=X=X=X=X,X=X=X=X4X=X=X=X4X=X=X} #X#X#X#X#X",
+"#X#X#XfX9.>.fX{.#X#X#X&XR.l.f.x.`.#X#X#X#X#X#X4X} Q.#X{.#X#X{.#X#X{.#X#X{.#X#X#X#X=X} #X#X#X#X#X",
+"#X#X#X#X{.^.=X#X#X#X#X#X`.H.b.H.XX&X#X#X#X#X#X#XQ.#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#XQ.#X#X#X#X#X",
+"#X#X#X#X#X#X#X#X#X#X#X#X&X1XqX1X#X#X#X#X#X#X#X#X=X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X=X#X#X#X#X#X",
+"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X",
+"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X",
+"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X",
+"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X",
+"#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X"
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/towers-web.png b/icons/towers-web.png
new file mode 100644 (file)
index 0000000..a513844
Binary files /dev/null and b/icons/towers-web.png differ
diff --git a/icons/towers.ico b/icons/towers.ico
new file mode 100644 (file)
index 0000000..49f40f4
Binary files /dev/null and b/icons/towers.ico differ
diff --git a/icons/towers.rc b/icons/towers.rc
new file mode 100644 (file)
index 0000000..53ee43d
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "towers.ico"
diff --git a/icons/towers.sav b/icons/towers.sav
new file mode 100644 (file)
index 0000000..351d473
--- /dev/null
@@ -0,0 +1,26 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :6:Towers
+PARAMS  :3:4de
+CPARAMS :3:4de
+SEED    :15:888431554483015
+DESC    :31:2/2/1/3/2/2/3/1/3/1/2/2/2/3/2/1
+AUXINFO :34:297d7a2fcf9e14403a74c976fe0fefd306
+NSTATES :2:17
+STATEPOS:2:10
+MOVE    :6:R2,0,4
+MOVE    :6:R0,1,4
+MOVE    :6:R3,3,4
+MOVE    :6:R1,2,4
+MOVE    :6:R0,3,3
+MOVE    :6:R1,0,3
+MOVE    :6:R3,2,3
+MOVE    :6:R2,1,3
+MOVE    :6:R3,0,2
+MOVE    :6:R3,1,1
+MOVE    :6:R1,1,2
+MOVE    :6:R1,3,1
+MOVE    :6:R2,3,2
+MOVE    :6:R2,2,1
+MOVE    :6:R0,2,2
+MOVE    :6:R0,0,1
diff --git a/icons/tracks-16d24.png b/icons/tracks-16d24.png
new file mode 100644 (file)
index 0000000..53540b1
Binary files /dev/null and b/icons/tracks-16d24.png differ
diff --git a/icons/tracks-16d4.png b/icons/tracks-16d4.png
new file mode 100644 (file)
index 0000000..c083827
Binary files /dev/null and b/icons/tracks-16d4.png differ
diff --git a/icons/tracks-16d8.png b/icons/tracks-16d8.png
new file mode 100644 (file)
index 0000000..4db47f1
Binary files /dev/null and b/icons/tracks-16d8.png differ
diff --git a/icons/tracks-32d24.png b/icons/tracks-32d24.png
new file mode 100644 (file)
index 0000000..f70b752
Binary files /dev/null and b/icons/tracks-32d24.png differ
diff --git a/icons/tracks-32d4.png b/icons/tracks-32d4.png
new file mode 100644 (file)
index 0000000..2ec6b26
Binary files /dev/null and b/icons/tracks-32d4.png differ
diff --git a/icons/tracks-32d8.png b/icons/tracks-32d8.png
new file mode 100644 (file)
index 0000000..5ddcdc7
Binary files /dev/null and b/icons/tracks-32d8.png differ
diff --git a/icons/tracks-48d24.png b/icons/tracks-48d24.png
new file mode 100644 (file)
index 0000000..d8fab1f
Binary files /dev/null and b/icons/tracks-48d24.png differ
diff --git a/icons/tracks-48d4.png b/icons/tracks-48d4.png
new file mode 100644 (file)
index 0000000..a32d694
Binary files /dev/null and b/icons/tracks-48d4.png differ
diff --git a/icons/tracks-48d8.png b/icons/tracks-48d8.png
new file mode 100644 (file)
index 0000000..b9b07c4
Binary files /dev/null and b/icons/tracks-48d8.png differ
diff --git a/icons/tracks-base.png b/icons/tracks-base.png
new file mode 100644 (file)
index 0000000..7332528
Binary files /dev/null and b/icons/tracks-base.png differ
diff --git a/icons/tracks-ibase.png b/icons/tracks-ibase.png
new file mode 100644 (file)
index 0000000..b344a80
Binary files /dev/null and b/icons/tracks-ibase.png differ
diff --git a/icons/tracks-ibase4.png b/icons/tracks-ibase4.png
new file mode 100644 (file)
index 0000000..ab3d693
Binary files /dev/null and b/icons/tracks-ibase4.png differ
diff --git a/icons/tracks-icon.c b/icons/tracks-icon.c
new file mode 100644 (file)
index 0000000..3689243
--- /dev/null
@@ -0,0 +1,642 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 247 2 ",
+"   c #D5D5D5",
+".  c #D5D5D5",
+"X  c #D5D5D5",
+"o  c LightGray",
+"O  c #CBCBCB",
+"+  c #D5D5D5",
+"@  c gray84",
+"#  c gray81",
+"$  c gray80",
+"%  c gray84",
+"&  c gray84",
+"*  c gray80",
+"=  c #D2D2D2",
+"-  c gray84",
+";  c #D5D5D5",
+":  c #D5D5D5",
+">  c #D5D5D5",
+",  c #D5D5D5",
+"<  c LightGray",
+"1  c gray43",
+"2  c #C5C5C5",
+"3  c #DFDFDF",
+"4  c gray71",
+"5  c #7E7E7E",
+"6  c #DADADA",
+"7  c gray87",
+"8  c #979797",
+"9  c gray66",
+"0  c gray87",
+"q  c LightGray",
+"w  c #D5D5D5",
+"e  c #D5D5D5",
+"r  c #D7D7D7",
+"t  c LightGray",
+"y  c gray62",
+"u  c #D0D0D0",
+"i  c gray87",
+"p  c #BBBBBB",
+"a  c #A2A2A2",
+"s  c gainsboro",
+"d  c gray86",
+"f  c #AEAEAE",
+"g  c gray69",
+"h  c gainsboro",
+"j  c gray83",
+"k  c #D5D5D5",
+"l  c gray84",
+"z  c gray82",
+"x  c #CBCBCB",
+"c  c #D3D3D4",
+"v  c #CECECE",
+"b  c #CDCDCD",
+"n  c #D0D0CF",
+"m  c #D2D2D2",
+"M  c #CACACB",
+"N  c gray82",
+"B  c #D8D8D8",
+"V  c #D8D8D8",
+"C  c #D2D2D2",
+"Z  c gray83",
+"A  c #D8D8D8",
+"S  c #C6C6C6",
+"D  c #A5A5A3",
+"F  c #A8A7A6",
+"G  c #9A978E",
+"H  c #9B9993",
+"J  c #9E9C95",
+"K  c #A8A8A7",
+"L  c #A9A9A8",
+"P  c #CDCDCD",
+"I  c gray82",
+"U  c #D0D0D0",
+"Y  c gray82",
+"T  c gray83",
+"R  c gray83",
+"E  c #D8D8D7",
+"W  c #C7C7C8",
+"Q  c #A7A6A4",
+"!  c #918D82",
+"~  c #918D81",
+"^  c #938E7F",
+"/  c #8F8B80",
+"(  c #99958B",
+")  c #ADADAE",
+"_  c gray81",
+"`  c gray77",
+"'  c gray77",
+"]  c gray83",
+"[  c gray83",
+"{  c gray83",
+"}  c #D7D8D8",
+"|  c #C8C8C6",
+" . c #97948C",
+".. c #918D7F",
+"X. c #8D856D",
+"o. c #877D5F",
+"O. c #918A76",
+"+. c #8F8C82",
+"@. c #A6A49E",
+"#. c #D2D3D5",
+"$. c #D2D2D3",
+"%. c #D1D2D3",
+"&. c #D4D5D6",
+"*. c gray83",
+"=. c gray83",
+"-. c #D7D7D8",
+";. c #C9C8C7",
+":. c #969287",
+">. c #928C7B",
+",. c #938C77",
+"<. c #A7A18D",
+"1. c #8F8A79",
+"2. c #918B7A",
+"3. c #98917D",
+"4. c #BEBDB7",
+"5. c #C0BFBB",
+"6. c #BFBDB9",
+"7. c #C2C1BE",
+"8. c #D5D5D6",
+"9. c gray83",
+"0. c #D8D8D8",
+"q. c #C7C6C4",
+"w. c #928D7D",
+"e. c #908A79",
+"r. c #989488",
+"t. c #B0AFAD",
+"y. c #95928B",
+"u. c #918B7A",
+"i. c #8C846C",
+"p. c #908977",
+"a. c #908B7C",
+"s. c #8C8676",
+"d. c #9D998D",
+"f. c #D6D7D8",
+"g. c #D5D5D5",
+"h. c gray83",
+"j. c #D8D8D8",
+"k. c #C7C6C3",
+"l. c #928D7F",
+"z. c #908A79",
+"x. c #969182",
+"c. c gray69",
+"v. c #A4A29C",
+"b. c #908C82",
+"n. c #928D80",
+"m. c #928D7D",
+"M. c #918B7A",
+"N. c #8E8876",
+"B. c #9D998C",
+"V. c #D6D6D8",
+"C. c #D5D5D5",
+"Z. c gray83",
+"A. c #D7D7D7",
+"S. c #CACAC9",
+"D. c #969181",
+"F. c #908B7A",
+"G. c #999588",
+"H. c #ADADAB",
+"J. c #A9A9A8",
+"K. c #A7A6A4",
+"L. c #9C9991",
+"P. c #9C988E",
+"I. c #999588",
+"U. c #969285",
+"Y. c #A5A298",
+"T. c #D6D6D8",
+"R. c #D5D5D5",
+"E. c gray85",
+"W. c #DCDDE0",
+"Q. c #BAB5A8",
+"!. c #574F35",
+"~. c #6B6657",
+"^. c #777369",
+"/. c #C8C8C8",
+"(. c #CBCBCB",
+"). c gray80",
+"_. c gray80",
+"`. c #CBCBCB",
+"'. c #CECECE",
+"]. c #CDCDCD",
+"[. c #CDCDCD",
+"{. c #D5D5D5",
+"}. c gainsboro",
+"|. c #6C6C6C",
+" X c #BDBEC0",
+".X c #A6A299",
+"XX c #5A533E",
+"oX c #4B483C",
+"OX c #908E89",
+"+X c #CBCBCB",
+"@X c gray82",
+"#X c #C8C8C8",
+"$X c LightGray",
+"%X c #D5D5D5",
+"&X c #CBCACA",
+"*X c #CCCCCB",
+"=X c gray83",
+"-X c gray83",
+";X c gray82",
+":X c gray48",
+">X c #B6B7B8",
+",X c #ADACA7",
+"<X c #464237",
+"1X c #848179",
+"2X c #ADACAA",
+"3X c #C4C4C5",
+"4X c #CECECE",
+"5X c gray76",
+"6X c #D2D2D2",
+"7X c gray83",
+"8X c #C6C6C6",
+"9X c gray78",
+"0X c LightGray",
+"qX c gray83",
+"wX c #D5D5D5",
+"eX c #DFDFDF",
+"rX c #DDDDDD",
+"tX c #C0C0C0",
+"yX c #A8A59F",
+"uX c #B8B9BB",
+"iX c gray67",
+"pX c #C5C5C5",
+"aX c gray83",
+"sX c LightGray",
+"dX c gray82",
+"fX c gray82",
+"gX c LightGray",
+"hX c LightGray",
+"jX c gray82",
+"kX c gray83",
+"lX c #D5D5D5",
+"zX c LightGray",
+"xX c gray83",
+"cX c gray84",
+"vX c #D4D5D6",
+"bX c gray82",
+"nX c LightGray",
+"mX c LightGray",
+"MX c gray83",
+"NX c LightGray",
+"BX c gray83",
+"VX c gray83",
+"CX c gray83",
+"ZX c gray83",
+"AX c gray83",
+"SX c #D5D5D5",
+"DX c white",
+/* pixels */
+"    . X o O + @ # $ % & * = - ; ",
+"  : > , < 1 2 3 4 5 6 7 8 9 0 q ",
+"  w e r t y u i p a s d f g h j ",
+"  k l z x c v b n m M N B V C . ",
+"  Z A S D F G H J K L P I U Y T ",
+"  R E W Q ! ~ ^ / ( ) _ ` ' ] [ ",
+"  { } |  ...X.o.O.+.@.#.$.%.&.*.",
+"  =.-.;.:.>.,.<.1.2.3.4.5.6.7.8.",
+"  9.0.q.w.e.r.t.y.u.i.p.a.s.d.f.",
+"g.h.j.k.l.z.x.c.v.b.n.m.M.N.B.V.",
+"C.Z.A.S.D.F.G.H.J.K.L.P.I.U.Y.T.",
+"R.E.W.Q.!.~.^./.(.)._.`.'.].[.{.",
+"}.|. X.XXXoXOX+X@X#X$X%X&X*X=X-X",
+";X:X>X,X<X1X2X3X4X5X6X7X8X9X0XqX",
+"wXeXrXtXyXuXiXpXaXsXdXfXgXhXjXkX",
+"lXzXxXcXvXbXnXmXMXNXBXVXCXZXAXSX"
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 122 2 ",
+"   c #040506",
+".  c #1A1300",
+"X  c #121315",
+"o  c #15171C",
+"O  c gray11",
+"+  c #211C0E",
+"@  c #38321F",
+"#  c #161922",
+"$  c gray18",
+"%  c #3E3823",
+"&  c #33312C",
+"*  c #3A372F",
+"=  c #3A3A3A",
+"-  c #403C34",
+";  c #4F4116",
+":  c #4A422A",
+">  c #3E3F42",
+",  c #444444",
+"<  c #4B4B4C",
+"1  c #525252",
+"2  c #545559",
+"3  c #5C5C5C",
+"4  c #796C47",
+"5  c gray39",
+"6  c #6C6C6C",
+"7  c #7B7460",
+"8  c #74726C",
+"9  c #7D796D",
+"0  c #717171",
+"q  c #7E7C75",
+"w  c #7D7C79",
+"e  c #8B773C",
+"r  c #877748",
+"t  c #86784D",
+"y  c #827551",
+"u  c #8A7E5B",
+"i  c #887F65",
+"p  c #847E6E",
+"a  c #817E74",
+"s  c #8E825E",
+"d  c #958557",
+"f  c #95865B",
+"g  c #8E8260",
+"h  c #87816F",
+"j  c #8C846C",
+"k  c #928665",
+"l  c #968A64",
+"z  c #9B8D64",
+"x  c #90866B",
+"c  c #968B6C",
+"v  c #998E6C",
+"b  c #9E916C",
+"n  c #858175",
+"m  c #8B8574",
+"M  c #85827A",
+"N  c #89857B",
+"B  c #8C897E",
+"V  c #928A72",
+"C  c #938D7A",
+"Z  c #9D9273",
+"A  c #9E957C",
+"S  c #A0936A",
+"D  c #A89768",
+"F  c #A0977A",
+"G  c #A49A7E",
+"H  c #858481",
+"J  c #898782",
+"K  c #8A8984",
+"L  c #858689",
+"P  c #87888A",
+"I  c #8C8C8A",
+"U  c #949085",
+"Y  c #9A9484",
+"T  c #9F9885",
+"R  c #96938B",
+"E  c #98958D",
+"W  c #9E998C",
+"Q  c #8B8C90",
+"!  c #8A8D98",
+"~  c #8E9096",
+"^  c #8E9199",
+"/  c #939393",
+"(  c #999792",
+")  c #9D9A92",
+"_  c #95979E",
+"`  c #9C9C9B",
+"'  c #A29A84",
+"]  c #A29C8A",
+"[  c #A29E92",
+"{  c #A5A194",
+"}  c #AAA495",
+"|  c #A6A39A",
+" . c #A9A69C",
+".. c #9295A0",
+"X. c #9D9EA0",
+"o. c #A6A6A5",
+"O. c #A8A7A2",
+"+. c #ACAAA4",
+"@. c #A7A8AB",
+"#. c #ABABAB",
+"$. c #B9B6AF",
+"%. c #ADAEB1",
+"&. c #AFB0B3",
+"*. c #B4B4B4",
+"=. c #B8B7B5",
+"-. c #B8B8B7",
+";. c #B3B4B9",
+":. c #BBBBBB",
+">. c #BBBCC0",
+",. c #BFC0C3",
+"<. c #C2C3C3",
+"1. c #C3C5C8",
+"2. c #CDCDCD",
+"3. c #D5D5D5",
+"4. c #D8D7D7",
+"5. c #D8D8D7",
+"6. c #D7D7D8",
+"7. c #D7D8DB",
+"8. c #DADADA",
+"9. c #E0E0DF",
+"0. c #E3E3E3",
+"q. c gray91",
+/* pixels */
+"3.3.6.3.3.3.3.5.3.3.8.6.3.3.3.3.3.6.8.4.3.3.3.3.3.6.6.3.3.3.3.6.",
+"3.3.3.3.3.3.3.3.3.3.>.2.6.3.3.3.6.1.<.8.3.3.3.3.3.<.2.3.3.3.3.3.",
+"3.3.3.3.3.3.3.3.6.3.6 < 6.3.3.3.6.o., ( 0.3.3.3.3., / 0.3.3.3.3.",
+"3.3.3.3.6.3.3.3.3.9.[ = 3.3.3.3.3.6.5 #.8.3.3.3.q.M I q.3.3.3.3.",
+"3.3.3.3.3.3.3.3.5.2.6 1 2.3.3.3.0.0 $ :.6.3.3.3.5.< 3 8.3.3.3.3.",
+"3.5.3.3.3.3.3.3.4.3.:.3.4.3.3.3.3.2.<.2.3.3.3.3.3.1.1.3.3.3.3.3.",
+"3.3.3.3.3.3.3.6.5.5.8.5.3.6.5.5.3.6.8.8.5.5.3.3.3.8.6.6.3.6.3.3.",
+"3.3.3.3.3.3.3.2.<.<.<.<.1.1.<.1.1.<.<.<.<.<.2.3.3.2.2.2.3.2.3.3.",
+"3.3.3.3.3.3.6.-.o.o.o.#.[ ] ;. .T #.o.o.o.o.2.6.3.3.3.5.3.2.3.3.",
+"3.5.3.3.3.3.6.:.o.#.#.%.Y h _ B m *.#.#.#.#.2.3.<.5.3.<.3.3.3.3.",
+"3.3.3.3.3.3.3.-.#. .l / L h ` m M ^ c ' %.o.2.3.1.<.:.2.3.2.3.3.",
+"3.3.6.3.3.3.6.-.@.#.J 7 O.f { f  .n 9 o.%.#.2.3.8.$.:.5.3.3.3.3.",
+"3.3.3.3.3.3.3.:.| ` ! ' f L a H m b ../  .#.2.3.:.2.2.:.3.2.3.3.",
+"5.3.3.6.5.3.3.:.v p j A H y e r a Y k a v O.2.3.3.6.3.3.3.3.3.3.",
+"3.3.3.3.3.3.6.:.;.I ) C n d +.z i m { K | #.2.3.3.3.3.3.3.2.3.3.",
+"3.3.3.3.3.3.5.:.v M V C n A ;.*.( I b a t z #.O.+.O. .#.} :.3.3.",
+"3.3.3.3.3.3.6.-.[ J W R K { *.{ g a ] U 9 u c Y V Y k ) u #.6.4.",
+"3.3.3.3.3.3.6.-.c M l j n Z &.#.#.^ / d R a H M M J M K a ` 4.3.",
+"3.6.3.3.3.3.6.-.[ J ) E K | #.#.#.W 7 / A F ' A Z ] z } f -.6.3.",
+"3.3.3.3.3.3.6.-.l n f j n Z *.#.%.A ] ! a K B J N K n I a [ 4.3.",
+"3.3.3.5.3.3.5.-.| J ) ( K  .&.#.#.%.%.#.f X.C C V U j E i +.6.3.",
+"3.3.3.3.3.3.5.-.l J k j n v *.o.o.o.o.o.[ %.} [ ] | ] O.Y =.5.3.",
+"3.3.3.3.3.3.6.=.z * Y w 2 :.*.1.<.<.<.<.1.<.<.1.1.1.<.<.1.<.4.3.",
+"3.3.3.0.3.3.7.D ; X S % @ { -.6.6.6.3.8.4.3.3.3.4.4.5.5.3.3.3.3.",
+"3.6.:.1 2.3.8.-   4 O.o 9 } -.3.2.2.8.1.1.3.2.3.<.3.3.<.3.3.3.3.",
+"3.0.P O o.9.7.j Z } . , ;.@.*.6.3.<.:.<.3.3.3.3.2.:.,.2.3.2.3.3.",
+"3.6.< = 3 9.7.U : # & l #.o.-.3.3.2.#.2.5.3.3.3.3.-.-.6.3.2.3.3.",
+"3.2.#.3.#.3.8.< + 8 -. .#.@.-.3.2.:.8.:.2.3.2.3.:.3.2.:.5.2.3.3.",
+"3.3.8.6.8.3.6.,.G +.#.+.#.o.$.8.3.3.3.3.3.3.2.3.3.3.3.2.4.2.3.3.",
+"3.5.3.3.3.3.6.:.#.%.&.&.&.#.-.2.2.3.2.3.3.2.2.3.2.3.3.3.3.2.3.3.",
+"3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.",
+"3.5.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.3.4.3.3.3.3.3."
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 150 2 ",
+"   c #020201",
+".  c #0C0A05",
+"X  c #0A0A0C",
+"o  c #110B00",
+"O  c #1D1C17",
+"+  c #1D1C1B",
+"@  c #221E14",
+"#  c #26231C",
+"$  c #373019",
+"%  c #252525",
+"&  c #2B2B2C",
+"*  c #2D2E32",
+"=  c #343434",
+"-  c #3A3A3A",
+";  c #4F4423",
+":  c #5F532F",
+">  c #454135",
+",  c #715E26",
+"<  c #695B33",
+"1  c #796938",
+"2  c #414141",
+"3  c gray29",
+"4  c #545453",
+"5  c #5B5B5B",
+"6  c #636364",
+"7  c #6D6D6D",
+"8  c #7D7766",
+"9  c #78746A",
+"0  c #7E796B",
+"q  c #6F7177",
+"w  c #757575",
+"e  c #7D7C77",
+"r  c #7E7E7D",
+"t  c #866D24",
+"y  c #866F29",
+"u  c #87702C",
+"i  c #89722D",
+"p  c #91782F",
+"a  c #8A7535",
+"s  c #86733D",
+"d  c #8B7639",
+"f  c #8E793C",
+"g  c #867645",
+"h  c #887745",
+"j  c #8D7A44",
+"k  c #83764F",
+"l  c #87784E",
+"z  c #8A7A4C",
+"x  c #947F42",
+"c  c #917F49",
+"v  c #857956",
+"b  c #837A5F",
+"n  c #8A7E59",
+"m  c #8A7C53",
+"M  c #837C66",
+"N  c #847E6B",
+"B  c #827E73",
+"V  c #807F7C",
+"C  c #99823D",
+"Z  c #94814B",
+"A  c #8F8051",
+"S  c #8D815B",
+"D  c #928253",
+"F  c #9B8954",
+"G  c #94865B",
+"H  c #8D8363",
+"J  c #8B836C",
+"K  c #928660",
+"L  c #978963",
+"P  c #9A8C65",
+"I  c #93896B",
+"U  c #998E6D",
+"Y  c #9C916E",
+"T  c #858174",
+"R  c #8D8774",
+"E  c #84827C",
+"W  c #888479",
+"Q  c #918A75",
+"!  c #948E79",
+"~  c #9D9377",
+"^  c #9C947B",
+"/  c #A39569",
+"(  c #A0967A",
+")  c #7E7F81",
+"_  c #7F8082",
+"`  c #828282",
+"'  c #888783",
+"]  c #8A8885",
+"[  c #84858A",
+"{  c #86888D",
+"}  c #8C8C8C",
+"|  c #908D85",
+" . c #9C9683",
+".. c #9E9885",
+"X. c #9A968B",
+"o. c #9E998A",
+"O. c #868991",
+"+. c #8C8D91",
+"@. c #8F9093",
+"#. c #939494",
+"$. c #989796",
+"%. c #9E9B94",
+"&. c #9C9B9A",
+"*. c #A09984",
+"=. c #A49E8C",
+"-. c #A29E93",
+";. c #A09E99",
+":. c #AFA587",
+">. c #A8A28F",
+",. c #A6A194",
+"<. c #A9A390",
+"1. c #A5A39C",
+"2. c #A9A69C",
+"3. c #ACA89A",
+"4. c #9C9EA3",
+"5. c #A5A5A4",
+"6. c #A9A7A5",
+"7. c #AAA8A4",
+"8. c #A5A6AA",
+"9. c #A6A8AC",
+"0. c #AAAAAA",
+"q. c #B0AFAE",
+"w. c #B0B0AF",
+"e. c #A7AAB3",
+"r. c #ACAEB2",
+"t. c #AFB1B4",
+"y. c #B2B2B2",
+"u. c #B8B7B3",
+"i. c #B3B5BC",
+"p. c #B7B8BB",
+"a. c #BBBBBB",
+"s. c #C0BEB7",
+"d. c #B2B5C0",
+"f. c #B5B9C5",
+"g. c #B9BBC1",
+"h. c #B6BBC8",
+"j. c #BEC2CC",
+"k. c #C3C3C4",
+"l. c #C8C8C7",
+"z. c #C3C5C9",
+"x. c #C6C8CF",
+"c. c #CDCDCD",
+"v. c #D5D5D5",
+"b. c #D8D7D7",
+"n. c #D8D8D7",
+"m. c #D5D7DC",
+"M. c #DADADA",
+"N. c #DCDEE3",
+"B. c #E2E2E2",
+"V. c #EAEAEA",
+/* pixels */
+"v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.",
+"v.v.v.v.v.v.b.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.",
+"v.v.v.v.v.v.v.v.v.v.v.n.v.v.v.M.N.v.v.v.v.v.v.v.v.v.M.M.v.v.v.v.v.v.v.v.v.M.M.v.v.v.v.v.v.v.v.v.",
+"v.v.v.v.v.v.v.v.v.v.v.v.v.v.n.l.0.c.n.v.v.v.v.v.v.v.k.y.M.M.v.v.v.v.v.v.v.a.k.v.v.n.v.v.v.v.v.v.",
+"v.v.v.b.v.v.v.v.v.v.v.v.v.v.M.} 3 & k.M.v.v.v.v.v.c.4 * 5 M.n.v.v.v.v.v.k.& - B.v.v.v.v.v.v.v.v.",
+"v.v.v.v.b.v.v.v.v.v.v.v.v.n.n.c.r % l.M.v.v.v.v.v.c.V.} & N.v.v.v.v.v.v.B.#.= B.v.v.v.v.v.v.v.v.",
+"v.v.v.v.v.v.v.v.v.v.v.v.v.v.n.v.V + u.M.v.v.v.v.v.M.0.- a.M.v.v.v.v.v.v.V.1.- V.v.v.v.v.v.v.v.v.",
+"v.v.v.v.v.v.b.v.v.v.v.v.v.v.N.w 5 % p.M.v.v.v.v.v.v.+ # w v.v.v.v.v.v.v.x.2 X } M.v.v.v.v.v.v.v.",
+"b.v.v.v.v.v.v.v.v.v.v.v.v.v.M.a.$.z.M.v.v.v.v.v.v.v.w.5.6.n.v.v.v.v.v.v.c.5.5.y.M.v.v.v.v.v.v.b.",
+"v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.M.B.n.v.v.v.v.v.v.v.v.M.N.N.v.v.v.v.v.v.v.v.B.B.M.v.v.v.v.v.v.v.v.",
+"v.v.v.v.v.v.v.v.v.v.v.n.v.v.v.v.v.v.v.n.n.v.n.n.v.v.v.v.n.n.n.v.v.v.v.v.v.v.v.v.v.v.b.v.v.v.v.v.",
+"v.v.v.b.v.v.v.m.v.v.v.v.c.c.v.c.c.c.v.c.c.c.c.c.c.c.v.c.c.c.c.c.v.c.c.c.c.v.c.c.v.c.c.v.v.v.v.v.",
+"v.v.v.v.v.v.v.v.v.v.n.k.7.0.0.0.0.0.0.t.0.0.y.0.t.0.0.0.0.0.0.0.y.v.v.v.v.c.v.c.c.v.c.c.v.n.v.v.",
+"v.v.v.v.b.v.v.v.v.v.n.j.6.0.0.6.0.0.0.P ,.y.u.1.L 0.0.0.0.0.0.0.0.v.v.v.n.v.b.v.M.b.v.c.v.v.v.v.",
+"b.v.v.v.v.v.v.v.v.v.v.k.6.0.0.t.0.0.t.S J 4.4.Q z t.0.0.t.0.0.7.t.v.n.l.c.n.v.b.k.v.b.c.v.v.v.v.",
+"v.v.v.v.v.v.v.v.v.v.n.k.8.0.0...1.0.#.` _ E ` _ ` } 6.7.^ 0.0.0.t.v.n.c.y.c.M.a.a.n.v.c.v.v.v.v.",
+"v.v.v.v.v.v.v.v.v.v.v.k.7.0.r.G z O._ | z 6.0.z | ` O.v D r.0.6.y.v.v.v.c.u.u.g.M.v.v.c.v.v.b.v.",
+"v.b.v.v.v.b.v.v.v.v.n.k.6.0.0.5.r 8 1.i.d *.3.a t.5.0 e 5.0.6.0.w.v.v.v.M.a.5.v.b.v.v.c.v.v.v.v.",
+"v.v.v.v.v.v.v.v.v.v.n.k.0.r.r.} { K d 5.T E E J 5.f D { ] 0.t.8.w.v.v.v.a.k.v.y.c.b.v.c.v.v.v.v.",
+"v.v.v.v.v.v.v.v.v.v.b.k.5.~ X.) 4.d.S 8 _ T T _ 0 S i.8._ X.~ 5.r.v.M.k.a.M.v.c.u.v.v.c.v.v.v.v.",
+"v.v.v.v.v.b.v.v.v.v.v.k.5.D b V D ~ } _ z i u j E { ^ A V M A 1.y.v.v.v.v.v.v.v.v.v.v.c.v.v.v.v.",
+"v.v.v.v.v.v.v.v.v.v.v.j.5.t.} ' U j r v t ..,.y v _ h P ' ] d.0.q.v.v.M.n.n.M.v.b.b.v.v.v.v.v.v.",
+"v.v.v.n.v.v.v.v.v.v.v.k.w.p.{ #.j.r._ =.<.i.p.>.*._ e.j.#.E L =.g.k.z.k.k.k.k.z.k.k.k.l.v.v.v.v.",
+"v.v.v.v.v.v.v.v.v.v.n.k.*.f T N d z _ z P t.w.t.0._ R j g _ g i ,.1.=.0.o.5.5...9.;.o.g.n.v.v.v.",
+"v.v.v.v.v.v.v.v.v.v.v.k.1.=.' ] =.X.)  .,.r.y...a B T ~ ;.{ E s P Y D f.d =.2.i i.L D x.v.v.v.v.",
+"v.v.v.v.v.v.v.v.v.v.M.k.5.6.{ } 7.&.) ;.7.0.y.1.,.#._ e.,.s _ _ ' E T } T ' ] B } W e 4.M.v.v.v.",
+"v.v.v.v.v.v.v.n.v.v.v.z. .i B M i h ` s D r.0.0.t.6.[ ] d ~ 8.k @.R T @.M ' ] N @.W B 5.n.v.v.v.",
+"v.v.n.v.v.v.v.v.v.v.M.k.7.t.] +.i.5.) 8.y.0.y.0.6.t.X.9 E e.=.a g.P D f.d >.3.i i.P Z x.b.v.v.v.",
+"v.v.b.v.v.v.v.v.v.v.v.k.-.U E W P I _ I ^ r.w.7.0.5.d ! ] _ T J r.I H e.z o.%.h 9.I S g.n.v.v.v.",
+"v.v.v.v.v.v.v.v.v.v.v.k.o.Z E T D G ) S U t.y.0.0.0.,.0.8.#.E _ r _ _ _ _ _ _ _ _ ` r #.M.v.v.v.",
+"v.v.v.v.v.v.v.n.v.v.v.k.0.f.{ #.h.e.r e.t.0.t.7.0.0.r.0.0.7.a %.r.I S e.g X.%.s 9.I n g.v.v.v.v.",
+"v.v.v.v.v.v.v.v.v.v.b.z.o.d T N d l ` h G r.w.0.0.0.0.0.0.1.~ 0.p.( I i.G ,.7.D t.~ I j.v.v.v.v.",
+"v.v.v.v.v.v.v.v.v.v.n.k.1.*.E ' >. .e | >.0.w.7.0.0.0.0.0.0.r.0.0.r.0.0.0.0.0.0.0.0.0.a.M.v.v.v.",
+"n.v.v.v.v.v.v.v.v.v.v.x.~ c O > s.}   4.g.y.k.c.l.l.x.l.l.x.l.l.l.l.l.l.l.l.l.x.x.l.l.l.v.v.v.v.",
+"v.v.v.v.v.M.M.v.v.v.M.:.p <   : C ;   J 7.7.x.M.n.M.n.n.b.n.n.n.c.v.M.M.v.n.n.v.M.n.n.c.v.v.v.v.",
+"v.v.v.v.n.k.l.n.v.v.N.Y ;   O 5.y.# + j ^ r.l.n.v.c.b.v.b.v.c.b.c.v.v.c.v.v.v.b.c.v.b.c.v.v.v.v.",
+"b.v.v.v.M.% * M.v.v.B.4   * s / `   6 a.6.0.l.M.c.y.c.M.v.u.c.m.v.c.n.x.u.v.n.l.p.v.v.c.v.v.v.v.",
+"v.v.v.B.5.= = 0.M.v.M.$., 2.u.1   & 0.0.0.0.c.v.n.c.y.c.u.x.b.v.c.v.v.n.k.a.c.y.v.b.v.c.v.v.b.v.",
+"v.v.v.B.6 = * 7 B.v.b.m.F Q w X . v t.0.0.8.c.v.v.M.c.&.k.M.v.b.c.v.v.v.M.p.&.b.b.v.v.c.v.v.v.v.",
+"v.v.v.v.& 5 4 = v.v.M.` # .   = X.Z ,.0.7.0.x.n.v.v.u.k.u.c.b.b.c.v.v.n.l.a.k.p.v.v.v.c.v.v.v.v.",
+"v.v.v.v.0.B.B.y.v.v.B.5 . $ V w.t.0.0.0.0.8.l.n.v.y.x.M.c.y.c.b.c.c.M.l.y.v.M.k.p.b.v.c.b.v.v.v.",
+"v.v.v.v.M.v.v.M.v.v.v.k.0.Z 3.0.6.0.0.0.0.0.c.n.c.c.M.v.M.c.c.b.v.v.M.x.v.v.v.n.l.v.v.c.v.v.v.v.",
+"v.v.v.b.v.v.v.v.v.v.v.k.5.1.5.0.7.0.0.8.7.6.c.M.n.n.b.v.n.n.n.b.c.v.n.n.b.v.v.n.n.n.n.c.v.v.v.v.",
+"v.v.v.v.v.v.v.v.v.v.v.k.0.y.y.y.t.w.w.t.t.w.l.c.c.c.c.c.v.c.c.v.c.l.c.v.c.c.c.c.c.c.c.c.v.v.v.v.",
+"v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.c.v.v.v.v.v.c.v.v.c.v.v.c.v.c.v.v.c.v.v.c.v.v.c.v.v.v.v.v.v.v.",
+"v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.b.b.n.v.v.v.v.b.v.v.v.v.v.v.v.b.v.v.v.v.v.b.n.v.v.v.",
+"v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.b.v.v.v.v.v.b.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.",
+"v.b.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.v.n.v.v.v.v.v."
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/tracks-web.png b/icons/tracks-web.png
new file mode 100644 (file)
index 0000000..6227527
Binary files /dev/null and b/icons/tracks-web.png differ
diff --git a/icons/tracks.ico b/icons/tracks.ico
new file mode 100644 (file)
index 0000000..549e2b6
Binary files /dev/null and b/icons/tracks.ico differ
diff --git a/icons/tracks.rc b/icons/tracks.rc
new file mode 100644 (file)
index 0000000..89943e1
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "tracks.ico"
diff --git a/icons/tracks.sav b/icons/tracks.sav
new file mode 100644 (file)
index 0000000..ca30644
--- /dev/null
@@ -0,0 +1,31 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :12:Train Tracks
+PARAMS  :5:6x6dt
+CPARAMS :5:6x6dt
+SEED    :15:145870397370785
+DESC    :31:l6t9b,3,2,1,S4,5,4,2,6,S3,2,3,3
+NSTATES :2:23
+STATEPOS:2:20
+MOVE    :5:TD0,0
+MOVE    :5:TR0,0
+MOVE    :5:TL5,5
+MOVE    :5:TU5,5
+MOVE    :29:TS1,1;TS2,1;TS3,1;TS4,1;TS5,1
+MOVE    :29:NS2,0;NS2,2;NS2,3;NS2,4;NS2,5
+MOVE    :5:TU1,1
+MOVE    :17:NS0,3;NS0,4;NS0,5
+MOVE    :23:NS1,2;NS1,3;NS1,4;NS1,5
+MOVE    :5:TL2,1
+MOVE    :5:TL3,1
+MOVE    :5:TS4,4
+MOVE    :5:TS3,4
+MOVE    :17:NS3,0;NS4,0;NS5,0
+MOVE    :5:TS4,2
+MOVE    :5:TS4,3
+MOVE    :5:TU3,4
+MOVE    :5:TL4,4
+MOVE    :5:TL5,1
+MOVE    :5:TU5,2
+MOVE    :5:NS5,3
+MOVE    :5:NS3,2
diff --git a/icons/twiddle-16d24.png b/icons/twiddle-16d24.png
new file mode 100644 (file)
index 0000000..32ecce5
Binary files /dev/null and b/icons/twiddle-16d24.png differ
diff --git a/icons/twiddle-16d4.png b/icons/twiddle-16d4.png
new file mode 100644 (file)
index 0000000..8587ce6
Binary files /dev/null and b/icons/twiddle-16d4.png differ
diff --git a/icons/twiddle-16d8.png b/icons/twiddle-16d8.png
new file mode 100644 (file)
index 0000000..32ecce5
Binary files /dev/null and b/icons/twiddle-16d8.png differ
diff --git a/icons/twiddle-32d24.png b/icons/twiddle-32d24.png
new file mode 100644 (file)
index 0000000..1a47225
Binary files /dev/null and b/icons/twiddle-32d24.png differ
diff --git a/icons/twiddle-32d4.png b/icons/twiddle-32d4.png
new file mode 100644 (file)
index 0000000..7b70389
Binary files /dev/null and b/icons/twiddle-32d4.png differ
diff --git a/icons/twiddle-32d8.png b/icons/twiddle-32d8.png
new file mode 100644 (file)
index 0000000..1a47225
Binary files /dev/null and b/icons/twiddle-32d8.png differ
diff --git a/icons/twiddle-48d24.png b/icons/twiddle-48d24.png
new file mode 100644 (file)
index 0000000..38146fa
Binary files /dev/null and b/icons/twiddle-48d24.png differ
diff --git a/icons/twiddle-48d4.png b/icons/twiddle-48d4.png
new file mode 100644 (file)
index 0000000..0e32a37
Binary files /dev/null and b/icons/twiddle-48d4.png differ
diff --git a/icons/twiddle-48d8.png b/icons/twiddle-48d8.png
new file mode 100644 (file)
index 0000000..38146fa
Binary files /dev/null and b/icons/twiddle-48d8.png differ
diff --git a/icons/twiddle-base.png b/icons/twiddle-base.png
new file mode 100644 (file)
index 0000000..7eed9af
Binary files /dev/null and b/icons/twiddle-base.png differ
diff --git a/icons/twiddle-ibase.png b/icons/twiddle-ibase.png
new file mode 100644 (file)
index 0000000..3018643
Binary files /dev/null and b/icons/twiddle-ibase.png differ
diff --git a/icons/twiddle-ibase4.png b/icons/twiddle-ibase4.png
new file mode 100644 (file)
index 0000000..61eda42
Binary files /dev/null and b/icons/twiddle-ibase4.png differ
diff --git a/icons/twiddle-icon.c b/icons/twiddle-icon.c
new file mode 100644 (file)
index 0000000..3814b7d
--- /dev/null
@@ -0,0 +1,891 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"qX7X5X5X5X5X3X4X6X5X5X5X5X5X5XqX",
+"0X1X1X8X7X1X9X6X2X8X5X2X2X1X0XtX",
+"7X1XrXhXeXrX).*XuXqXyXfXuXwXaXuX",
+"8X,XsXiX7XpX1.J.dX6XuXwXeXtXfXyX",
+"7X1XgX0XwXwXP.-XeX0XiX0XpXqXsXuX",
+"6X0XdX6X8X0XdXtX6XuXeX9X(.1XsXyX",
+"7X0XrX0X0X8X6X9X9XiX9XyXX.W.zXtX",
+"8X2XeXsXyXwXqX3XrXwXwX8XU.>XdXyX",
+"9X5X9Xv.@XtXqX5XiX8X8XqXsXwXaXuX",
+"7X1XwXW A.gX3X0XeXwXeX0X7X9XwXyX",
+"8X1XrX&X8XqX5XyXqXaXwXrXrX4XqXiX",
+"8X,XwXyXeX5XeXrX5XH.4XwXwX1XpXuX",
+"8X>X7X8X9X9XyXyX+XA .XpX5X2XsXyX",
+"7X,XqX6X3XrX0X0X9X}.3XqX>X7XpXuX",
+"8XwXfXsXdXiXuXsXsXlXgXiXtXdXgXyX",
+"qXtXrXrXrXtXtXrXrXeXrXtXtXrXeXqX"
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"qXqXeXeXeXeXeXeXeXeXeXeXeXeXeXeXeXeXeXeXeXeXeXeXeXeXeXeXeXeXqXqX",
+"qX0X3X4X4X4X4X3X2X4X4X4X4X4X4X4X4X4X3X3X4X4X4X4X4X4X4X4X4X3X0XqX",
+"qXwX2X#X%X%X$X-X,X#X%X%X%X%X%X%X$X&X,X;X#X$X%X%X%X%X%X%X@X4XtX0X",
+"qXwX>XeXrXrXqXbXfXwXrXrXeXwXwXrXrXrXuXgXhXyXwXeXrXrXrXrXqXvXiX9X",
+"qX0XOX0XqX9XuXcX8XqX0X0XyXpXpX9XqX0X0X9XeX9XzXtX0X9XqXqX8XxXiX9X",
+"qXqX@X0XwX9XvXiX9XqX0XuXk.~ S.pX9XqXqXeX2XdXhXaXgXyX0X0X8XcXiX9X",
+"qXqX@XqX9XpXbX9XwXqXqX8XrX9.( kX8XqXqX0X5XvX8X0XrXdXfXuX7XxXiX9X",
+"qXqX@XqX9XnXiX9XqXqX9XpXH.v [.yX0XqXeX1XdXsX9XwX0X0XrXdXaXnXiX9X",
+"qXqX@X8XdXmX8XqXqXqX9XaX6.Q v.yX0XqX0X3XvX9XqXqXqXqX0X0XqXmXiX9X",
+"qXqX@X9XxXeXqXwXqXqXqX0XdXgXyXqXqXrX,XsXdX9XqXqX9X8X0XwX8XxXiX9X",
+"qXqXOXuXnX4X<X7XeXwXqXqX9X8X0XqXqX9X<XvX9XqXqXwXaXhXtXqX9XcXiX9X",
+"qXqXoXbXhXuXuX4X<X7XeXwXqXqXqXqXrX:XaXdX9XqXwX8Xp.-.#XyX7XcXiX9X",
+"qX9X;XMX9X0XeXpXtX3X1X8XeXqXqXqX9X>XvX9XqXqX0XeXU.a L.fX6XcXiX9X",
+"qX9X:XiX9XwXqX9XeXiXrX2X1X9XwXtX-XaXdX9XqXqX0XwX(.k b.hX6XcXiX9X",
+"qX0XoX0XqXqXqX0X0X0XeXiXwX2X3X7X:XnX8XqXqX0XrX>X<.} '.iX7XcXiX9X",
+"qX9X=XqXqX0XwXuX0XqX0X0XrXyXwX&XiXiX0XwXqXqXqXeXwXiXiX0X8XcXiX9X",
+"0XrX8X9XqXrX9XOXuX0XqXqX0XqXqX=XcX3X1X8XeXqXqXqXqX9X0XwX8XcXiX9X",
+"qX0XoX0XqXqX| Y c.dX9XqX0XtX*X9XgXdXuX4X2X9XeXqXqXqXqXwX8XvXiX9X",
+"qXqX@X0XwX6X~ ) R jX8XqXwX8X&XjX8X0XyXdXuX5X3X9XwXqXqXwX7XsXpX9X",
+"qXqX@X0X0XuXA.h 0.gX8X0XtX*XrXpX9XqX0X0XtXsXyX5X4X0XwXrX-XyXsX9X",
+"qXqX@X0XqXrXE.F.wXwXqXwX7X=XlX9XqXqXqXqX0X0XtXsXyX6X5X0XOXxXpX9X",
+"qXqX@X0XqX0XaXfXqXqX0XrX&XuXiX9XqX0X0XrXqXqX0X0XtXaXuX>X;XbXiX9X",
+"qX0X@XqXqXqX9X8XqXqXwX6X:XzX8XwX0XeXyX;X9XwXqXqX0X0XwX;X9XcXiX9X",
+"qXqXOX8XeXqXqXqXqX0XrX&XsXiX9XqXwX0X#.+.-XtX0XqXqXrX:X1X0XxXiX9X",
+"qXqXOX,X2X9XwXqXqXwX5X,XcX8XwX9XsXE.y @._.iX0XqXwX9X&XwX8XcXiX9X",
+"qX0X@XqX6X<X3X0XwXtX&XgXiX0XqX0XpX_.F L p.jX8X0XrX;X1XrX8XcXiX9X",
+"qXqX@X0XwXwX6X1X6X2X3XvX7XwXqXqX0XuXH.f.8XwXqXwX8X$XwXqX8XcXiX9X",
+"qXqX@X0XqXqXwXqX7X:XjXrXqXqXqXqXqX0XsXhXwXqX0XrX*X<XeXqX8XcXiX9X",
+"wX8XoXqXwXwXqXwXwXrX0X:X3X0XwXwXqXwX9X9XqXqXeX7X#XeXqXwX9XvXiX9X",
+"qXqXpXNXbXnXnXnXnXbXnXbXlXcXmXlXbXmXnXnXnXnXnXxXxXmXbXnXcXbXuX9X",
+"qXqXwXeXqXwXqXqXqXqXqXwXeXwXwX0XeXwXqXqXqXqXqXwXwXqXqXwXqXqXwXqX",
+"qXqXqX0XqXqXqXqXqXqXqXqXqXqXqXqX0XqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX"
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXqXeXwXwXwXwXwXwXwXeXeXwXwXwXwXwXwXwXwXwXwXwXwXeXeXeXwXwXwXwXwXwXwXwXwXwXwXwXwXwXeXqXqXqXqX",
+"qXqXqX0X3X5X5X5X5X5X5X6X4X4X5X5X5X5X5X5X5X5X5X5X5X5X5X4X5X6X5X5X5X5X5X5X5X5X5X5X5X5X5X4X0XqXqXqX",
+"qXqXqX0X-X X}.}.}.}.}.{.oXXX{.}.}.}.}.}.}.}.}.}.}.}. X+X X[.}.}.}.}.}.}.}.}.}.}.}.|.[.;XrX0XqXqX",
+"qXqX0XyXsX7XrXwXeXeXeXwXZXaXqXeXeXeXeXeXeXeXeXeXeXwXrXfXzXfXeXqXeXeXeXeXeXeXeXeXeXeXqXZXuX0XqXqX",
+"qXqX0XrX[.,XeX0XqXqX8XjXbX7XqXqXqXqX0X9X8X0XqXqXqXqX0X9XwXdXjXaX9X9X0XqXqXqXqXqXqXqX9XZXyX0XqXqX",
+"qXqX0XrX.X1XeXqXqXqXqXCXyX0XqXqXqXqXwXfXjXrX0XqXqXqXqXqXqX9XyX3XaXxXwX9X0XqXqXqXqXqX9XZXuX0XqXqX",
+"qXqX0XrX.X1XeXqXwX8XlXnX8XwXqXqXqXwX5XG.m.,XyX0XqXqXqXqXqXqX0X;XAXjXlXfXeX9X0XqXqXqX9XZXuX0XqXqX",
+"qXqX0XrX.X1XeXqXqXwXAXyX0XqXqXqX9XaXT.L m s 1XrX0XqXqXqXqXrX:XaXbX6XrXdXlXfXeX9X0XwX0XZXuX0XqXqX",
+"qXqX0XrX.X1XeXqX8XxXnX8XwXqXqXqXqXwX3XNXW.* *XyX0XqXqXqXqX0X;XZXrXqXqX9XwXsXlXfXeX0X9XZXuX0XqXqX",
+"qXqX0XrX.X1XrX0XeXDXyX0XqXqXqXqXqX9XdX Xg F.gX8XqXqXqX0XrX-XsXmX7XwXqXqXqX9XwXsXkXfXwXZXuX0XqXqX",
+"qXqX0XrX.X1XrX7XvXnX8XwXqXqXqXqX9XiX[.# n p.*XtX0XqXqXqX9X*XZXrX0XqXqXqXqXqXqX9XwXsXjXFXyX0XqXqX",
+"qXqX0XrX.X1XeXrXDXtX0XqXqXqXqXqX0XuX{.*.:.o.*XtX0XqX0XrX*XiXbX7XwXqXqXqXqXqXqXqXqX0XqXAXyX0XqXqX",
+"qXqX0XrX.X1X0XbXmX0XwXqXqXqXqXqXqX0XyXzXlXxXrX0XqXqXwX9X&XAXrX0XqXqXqXqXqXqXqXqXqXwX9XZXuX0XqXqX",
+"qXqX0XrX.X<XtXnX,X>XqXeXqXqXqXqXqXqX0X7X7X7X0XqXqX0XtX%XpXmX7XwXqXqXqX0X7X7X0XqXqXqX0XZXuX0XqXqX",
+"qXqX0XrX.X>XzXDXaX5X*X<XqXeXqXqXqXqXqXqXqXqXqXqXqXwX8X#XAXrX0XqXqXqX0XpXxXlXyX0XqXqX9XZXuX0XqXqX",
+"qXqX0XrX X1XHXsXeXhXdX3X*X2XwXeXqXqXqXqXqXqXqXqX0XtX#XiXnX7XwXqXqX0XuXW.( #.XXtX0XqX9XZXuX0XqXqX",
+"qXqX0XtX}.pXAX8XqX9XtXgXaX2X=X3XwXwXqXqXqXqXqXqXwX7X@XSXrX0XqXqXqX0XuXP.V.l K fX9XqX9XZXuX0XqXqX",
+"qXqX0XrX XbXdX9XwXqX0X9XtXgXiX<X=X4XeXwXqXqXqX0XtX+XiXmX7XwXqXqXqXqX0XpXe.6 p.fX9XqX9XZXuX0XqXqX",
+"qXqX0XrXXXiXeXqXqXqXqXqX0X9XtXfXyX,X-X5XeXwX0XwX6XOXAXrX0XqXqXqXqXqXqX8X[.J m aX0XqX9XZXuX0XqXqX",
+"qXqX0XrXXX2XeXqXqXqXqXqXqXqX0X9XyXdXrX>X;X6XwXyXoXuXnX7XwXqXqXqXqX9XaX,./ s ] aX9XqX9XZXuX0XqXqX",
+"qXqXqXrX].<XrXqXqXqXqXqXqXqXqXqX0X0XyXdXwX:X>X1X+XFXeX0XqXqXqXqXqX0XrX+XB.W.tXwXqXqX9XZXuX0XqXqX",
+"qXqXqX0X$X7XwXqXqXqX9X7X8XqXqXqXqXqX0X0XyXaXwXoXpXzX8XeXqXqXqXqXqXqX0XuXhXsX0XqXqXqX9XZXuX0XqXqX",
+"qXqX0XyXfX6XwXqXqX0XaXzXfXqXqXqXqXqXqXqX0XwX9X.XmX3X*X5XwXwXqXqXqXqXqX0X9X9XqXqXqXqX9XZXuX0XqXqX",
+"qXqX0XrX{.<XrXqX0XiXG.P p.tX0XqXqXqXqXqX0XtX X6XVXlXpX,X-X6XeXwXqXqXqXqXqXqXqXqXqXqX9XZXuX0XqXqX",
+"qXqX0XrX.X1XeX9XuX+X: m.t z.gX8XqXqXqXqXeX4X}.nX0XqXdXzXpX<X;X6XeXwXqXqXqXqXqXqXqXqX0XZXuX0XqXqX",
+"qXqX0XrX.X1XeX9XuXOX& a.7 +.lX7XqXqXqX0XtX XwXkX8XqX9XqXsXzXiX<X:X7XeXwXqXqXqXqXqXeX3XbXiX9XqXqX",
+"qXqX0XrX.X1XeXqX0XyXJ.=.: 8.kX8XqXqXqXeX3X XMXqXqXqXqXqX9XqXsXlXiX1X>X8XeXwXqXqX0XrXoXxXpX9XqXqX",
+"qXqX0XrX.X1XeX0XwX8X>.U Z -XtX0XqXqX0XtX|.yXkX8XqXqXqXqXqXqX9XqXaXkXuX1X,X8XeXqXeX4X XAXyX0XqXqX",
+"qXqX0XrX.X1XeXqXqXeX{.P.7XyX0XqXqXqXeX1XoXVX0XqXqXqXqXqXqXqXqXqX9XqXaXkXuX2X<X8XrXOX:XDXyX0XqXqX",
+"qXqX0XrX.X1XeXqXqXqXiXdXwX0XqXqXqX0XrX|.pXkX8XqXqXqXqXqXqXqXqXqXqXqX9XqXpXjXyX4X<XOX0XZXuX0XqXqX",
+"qXqX0XrX.X1XeXqXqXqX0X9X0XqXqXqXqXeX<X+XCX0XqXqXqXqXqX9XwXwXqXqXqXqXqXqX9X0XaXdX&X6XqXZXuX0XqXqX",
+"qXqX0XrX.X1XeXqXqXqXqXqXqXqXqXqXqXrX}.fXkX8XqXqXqXqXqXsX7X9XwXqXqXqXqXqXqXqXwX<X#XrX9XZXuX0XqXqX",
+"qXqX0XrX X<XtXqXqXqXqXqXqXqXqXqXrX,X$XZX0XqXqXqXqX0XyX2.B [ 1XeX0XqXqXqXqXqXeX@X5XeX9XZXuX0XqXqX",
+"qXqX0XrX.X+X4XqXwXqXqXqXqXqXqXqXrX}.jXjX8XqXqXqX9XdXF.; 9.T.wXqXqXqXqXqXqXeX<X#XrXqX0XZXuX0XqXqX",
+"qXqX0XrX.X>X2X;X3XqXwXqXqXqXqXrX:X*XSX9XqXqXqXqX7XlX0.# } r XXuX0XqXqXqXqXwXOX5XwXqX9XZXuX0XqXqX",
+"qXqX0XrX.X1XtX8X<X:X4XwXwXqXqXeX}.zXjX8XqXqXqXqX8XgXb.5 %X, J.dX9XqXqXqXrX>X#XrX0XqX9XZXuX0XqXqX",
+"qXqX0XrX.X<XeXqXeX8X<X>X5XqXtX;X;XSX9XqXqXqXqXqXqXqXqXo.d +.0XqXqXqXqXqXwX.X5XwXqXqX9XZXuX0XqXqX",
+"qXqX0XrX.X1XeXqXqXwXeX8X<X,X8X XnXhX8XqXqXqXqXqXqXqXeXaX8XaXeXqXqXqX0XrX;X@XrX0XqXqX9XZXuX0XqXqX",
+"qXqX0XrXXX1XrXqXqXqXqXwXeX9X;XqXCX0XeXqXqXqXqXqXqXqXqX0XwX9XqXqXqXqXwXwX X6XeXqXqXwX0XZXuX0XqXqX",
+"qXqX0XrX[.>XqX8X9X9X9X9X8X0X9XtX,X%X1X8X9X9X9X9X9X9X9X9X8X9X9X9X9X8XwX$XoXwX8X9X9X9X7XCXuX0XqXqX",
+"qXqXqX0X+XeXfXsXsXsXsXsXsXsXsXaXsXyX5X8XiXdXsXsXpXsXsXsXsXsXsXsXsXsXsX;XyXdXsXsXsXsXaXSXyX0XqXqX",
+"qXqX0XtXhXCXAXBXVXVXVXVXVXVXVXVXVXCXCXBXBXBXZXjXVXAXBXVXVXVXVXVXVXVXBXVXVXBXVXVXBXZXkXMXuX0XqXqX",
+"qXqXqXqX6XqX9X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X6XqX9X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X8X6XqXwXqXqXqX",
+"qXqXqXqXwXqXqXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXqXqXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXwXqXqXqXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX",
+"qXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqXqX"
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/twiddle-web.png b/icons/twiddle-web.png
new file mode 100644 (file)
index 0000000..1b3924a
Binary files /dev/null and b/icons/twiddle-web.png differ
diff --git a/icons/twiddle.ico b/icons/twiddle.ico
new file mode 100644 (file)
index 0000000..332817f
Binary files /dev/null and b/icons/twiddle.ico differ
diff --git a/icons/twiddle.rc b/icons/twiddle.rc
new file mode 100644 (file)
index 0000000..5d60596
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "twiddle.ico"
diff --git a/icons/twiddle.sav b/icons/twiddle.sav
new file mode 100644 (file)
index 0000000..2863033
--- /dev/null
@@ -0,0 +1,35 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :7:Twiddle
+PARAMS  :5:3x3n2
+CPARAMS :5:3x3n2
+SEED    :15:635499951462226
+DESC    :17:3,7,2,6,5,1,8,4,9
+NSTATES :2:27
+STATEPOS:2:22
+MOVE    :7:M0,0,-1
+MOVE    :7:M1,0,-1
+MOVE    :6:M1,1,1
+MOVE    :6:M0,1,1
+MOVE    :6:M0,0,1
+MOVE    :6:M0,0,1
+MOVE    :7:M1,1,-1
+MOVE    :7:M0,1,-1
+MOVE    :7:M0,1,-1
+MOVE    :7:M1,1,-1
+MOVE    :6:M0,1,1
+MOVE    :7:M0,1,-1
+MOVE    :6:M1,1,1
+MOVE    :6:M1,1,1
+MOVE    :6:M0,1,1
+MOVE    :6:M0,1,1
+MOVE    :7:M0,1,-1
+MOVE    :7:M1,1,-1
+MOVE    :7:M0,1,-1
+MOVE    :7:M1,1,-1
+MOVE    :6:M0,1,1
+MOVE    :7:M1,0,-1
+MOVE    :7:M0,1,-1
+MOVE    :6:M1,0,1
+MOVE    :6:M1,1,1
+MOVE    :6:M1,1,1
diff --git a/icons/undead-16d24.png b/icons/undead-16d24.png
new file mode 100644 (file)
index 0000000..dd44656
Binary files /dev/null and b/icons/undead-16d24.png differ
diff --git a/icons/undead-16d4.png b/icons/undead-16d4.png
new file mode 100644 (file)
index 0000000..ecc24cd
Binary files /dev/null and b/icons/undead-16d4.png differ
diff --git a/icons/undead-16d8.png b/icons/undead-16d8.png
new file mode 100644 (file)
index 0000000..7ff57a3
Binary files /dev/null and b/icons/undead-16d8.png differ
diff --git a/icons/undead-32d24.png b/icons/undead-32d24.png
new file mode 100644 (file)
index 0000000..a2b3323
Binary files /dev/null and b/icons/undead-32d24.png differ
diff --git a/icons/undead-32d4.png b/icons/undead-32d4.png
new file mode 100644 (file)
index 0000000..ae41cbf
Binary files /dev/null and b/icons/undead-32d4.png differ
diff --git a/icons/undead-32d8.png b/icons/undead-32d8.png
new file mode 100644 (file)
index 0000000..a6dab64
Binary files /dev/null and b/icons/undead-32d8.png differ
diff --git a/icons/undead-48d24.png b/icons/undead-48d24.png
new file mode 100644 (file)
index 0000000..5463687
Binary files /dev/null and b/icons/undead-48d24.png differ
diff --git a/icons/undead-48d4.png b/icons/undead-48d4.png
new file mode 100644 (file)
index 0000000..29b5db4
Binary files /dev/null and b/icons/undead-48d4.png differ
diff --git a/icons/undead-48d8.png b/icons/undead-48d8.png
new file mode 100644 (file)
index 0000000..2e1565b
Binary files /dev/null and b/icons/undead-48d8.png differ
diff --git a/icons/undead-base.png b/icons/undead-base.png
new file mode 100644 (file)
index 0000000..4708fcf
Binary files /dev/null and b/icons/undead-base.png differ
diff --git a/icons/undead-ibase.png b/icons/undead-ibase.png
new file mode 100644 (file)
index 0000000..d3d53f2
Binary files /dev/null and b/icons/undead-ibase.png differ
diff --git a/icons/undead-ibase4.png b/icons/undead-ibase4.png
new file mode 100644 (file)
index 0000000..521ede8
Binary files /dev/null and b/icons/undead-ibase4.png differ
diff --git a/icons/undead-icon.c b/icons/undead-icon.c
new file mode 100644 (file)
index 0000000..a5a2ece
--- /dev/null
@@ -0,0 +1,616 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 249 2 ",
+"   c #E6E6E6",
+".  c #E6E6E6",
+"X  c gray91",
+"o  c #D5D5D5",
+"O  c gainsboro",
+"+  c gray91",
+"@  c #E6E6E6",
+"#  c #E7E7E7",
+"$  c #E4E4E4",
+"%  c #DDDDDD",
+"&  c gray91",
+"*  c gray90",
+"=  c #E6E6E6",
+"-  c #E6E6E6",
+";  c gray90",
+":  c #EEEEEE",
+">  c gray69",
+",  c #989898",
+"<  c #F1F1F1",
+"1  c #E1E1E1",
+"2  c gray95",
+"3  c #A2A2A2",
+"4  c #656565",
+"5  c gray91",
+"6  c #E6E6E6",
+"7  c #E6E6E6",
+"8  c #E6E6E6",
+"9  c #E6E6E6",
+"0  c gray89",
+"q  c gray94",
+"w  c #939393",
+"e  c #A5A5A5",
+"r  c gray94",
+"t  c #E1E1E1",
+"y  c gray94",
+"u  c #ACACAC",
+"i  c #767676",
+"p  c gray91",
+"a  c #E6E6E6",
+"s  c #E6E6E6",
+"d  c #E6E6E6",
+"f  c #E6E6E6",
+"g  c #E6E6E6",
+"h  c #E6E6E6",
+"j  c #EAEAEA",
+"k  c gray91",
+"l  c #DDDDDD",
+"z  c #DFDFDF",
+"x  c gray91",
+"c  c #E9E9E9",
+"v  c gray91",
+"b  c #EAEAEA",
+"n  c #E9E9E9",
+"m  c gray91",
+"M  c #E7E7E7",
+"N  c #E6E6E6",
+"B  c #E6E6E6",
+"V  c #E6E6E6",
+"C  c #E6E6E6",
+"Z  c gray90",
+"A  c gray",
+"S  c #C1C1C1",
+"D  c #D1D0D0",
+"F  c #D0CFCF",
+"G  c #C2C1C1",
+"H  c gray73",
+"J  c #C0C0C0",
+"K  c #C0C0C0",
+"L  c #C1C1C1",
+"P  c #C5C5C5",
+"I  c #CACACA",
+"U  c #E7E7E7",
+"Y  c #E6E6E6",
+"T  c #E4E4E4",
+"R  c gray91",
+"E  c #DFDFDF",
+"W  c gray76",
+"Q  c #E5E6E6",
+"!  c #9B9C9C",
+"~  c #9F9F9F",
+"^  c #E4E5E5",
+"/  c #D2D2D2",
+"(  c #E1E1E1",
+")  c gray90",
+"_  c gray90",
+"`  c #C5C5C5",
+"'  c #E6E6E6",
+"]  c #E6E6E6",
+"[  c #EEEEEE",
+"{  c #9B9B9B",
+"}  c LightGray",
+"|  c #E4E3E3",
+" . c #C7C8C8",
+".. c #C5C0C0",
+"X. c #887A7A",
+"o. c #8A7C7C",
+"O. c #C6C1C1",
+"+. c #D8D9D9",
+"@. c gray90",
+"#. c #F1F1F1",
+"$. c gray60",
+"%. c gray72",
+"&. c gray93",
+"*. c gray90",
+"=. c gray95",
+"-. c gray53",
+";. c gray71",
+":. c #EAE9E9",
+">. c #C1C2C2",
+",. c #D1C6C6",
+"<. c #CBB6B6",
+"1. c #CCB6B6",
+"2. c #CFC4C4",
+"3. c #D4D5D5",
+"4. c gray90",
+"5. c #989898",
+"6. c gray71",
+"7. c gray95",
+"8. c #E4E4E4",
+"9. c #E6E6E6",
+"0. c #E7E7E7",
+"q. c #CACACA",
+"w. c #D7D7D7",
+"e. c #E1E1E1",
+"r. c #C6C6C6",
+"t. c #E2E1E1",
+"y. c #BFB3B3",
+"u. c #C0B4B4",
+"i. c #DFDEDE",
+"p. c #DEDFDF",
+"a. c #C6C6C6",
+"s. c #B9B9B9",
+"d. c gray96",
+"f. c #E6E6E6",
+"g. c gray91",
+"h. c #E6E6E6",
+"j. c #E6E6E6",
+"k. c gray92",
+"l. c gray92",
+"z. c gray88",
+"x. c #B9B9B9",
+"c. c #DBDCDC",
+"v. c #DAD9D9",
+"b. c #DBDADA",
+"n. c #D9DADA",
+"m. c #C5C5C5",
+"M. c #D6D7D6",
+"N. c #E1E0E1",
+"B. c #DAD8DA",
+"V. c gray84",
+"C. c #DADADA",
+"Z. c #E7E7E7",
+"A. c gray90",
+"S. c gray92",
+"D. c #E9E9E9",
+"F. c #E0DFDF",
+"G. c gray",
+"H. c #E6E3E3",
+"J. c #CCD6D6",
+"K. c #CED7D7",
+"L. c #E5E1E1",
+"P. c #CBCDCC",
+"I. c #E1DEE1",
+"U. c #D4D7D4",
+"Y. c #CAD4CA",
+"T. c #E2DEE2",
+"R. c #E1E1E1",
+"E. c #E6E6E6",
+"W. c gray92",
+"Q. c #A9A9A9",
+"!. c gray85",
+"~. c #E1E2E2",
+"^. c #C9C6C6",
+"/. c #C5D7D7",
+"(. c #70C6C6",
+"). c #72C6C6",
+"_. c #C4D3D3",
+"`. c #DFDADC",
+"'. c #D2DBD3",
+"]. c #76CE76",
+"[. c #65D165",
+"{. c #ADD2AD",
+"}. c #EDE8ED",
+"|. c #E4E6E4",
+" X c gray95",
+".X c #8B8B8B",
+"XX c #BCBCBC",
+"oX c #E7E9E9",
+"OX c #C9C1C1",
+"+X c #97D1D1",
+"@X c #69CECE",
+"#X c #68CDCD",
+"$X c #94CCCA",
+"%X c #E6D7DE",
+"&X c #ACD2AE",
+"*X c #5CCE5C",
+"=X c #61CE61",
+"-X c #7BCF7B",
+";X c #E9E2E9",
+":X c #E5E7E5",
+">X c #EAEAEA",
+",X c gray70",
+"<X c #CACACA",
+"1X c #E3E4E4",
+"2X c #C7C2C2",
+"3X c #A3CDCD",
+"4X c #7BD2D2",
+"5X c #7BD3D3",
+"6X c #A4CCCB",
+"7X c #DDD6D7",
+"8X c #D5DDD6",
+"9X c #80C280",
+"0X c #6BC56B",
+"qX c #B4D5B4",
+"wX c #ECE7EC",
+"eX c #E5E6E5",
+"rX c gray90",
+"tX c #EFEFEF",
+"yX c gray93",
+"uX c #DFDFDF",
+"iX c #C5C6C6",
+"pX c #E7E4E4",
+"aX c #E3DDDD",
+"sX c #E3DDDD",
+"dX c #E5E2E2",
+"fX c #D4D5D4",
+"gX c #E8E6E8",
+"hX c #E2E2E2",
+"jX c #DADEDA",
+"kX c #EBE6EB",
+"lX c #E6E6E6",
+"zX c #E6E6E6",
+"xX c #E6E6E6",
+"cX c #E4E4E4",
+"vX c gray90",
+"bX c #E6E6E6",
+"nX c gray89",
+"mX c #E7E8E8",
+"MX c #E7E8E8",
+"NX c #E7E8E8",
+"BX c #E7E7E7",
+"VX c #E5E4E5",
+"CX c #E5E6E5",
+"ZX c #E7E7E7",
+"AX c #E9E8E9",
+"SX c #E5E6E5",
+"DX c #E6E6E6",
+"FX c #E6E6E6",
+"GX c white",
+/* pixels */
+"        . X o O + @ # $ % & * = ",
+"      - ; : > , < 1 2 3 4 5 6 7 ",
+"  - 8 9 0 q w e r t y u i p a s ",
+"d f g h j k l z x c v b n m M N ",
+"B V C Z A S D F G H J K L P I U ",
+"Y T R E W Q ! ~ ^ / ( ) _ ` ' ] ",
+"[ { } |  ...X.o.O.+.@.#.$.%.&.*.",
+"=.-.;.:.>.,.<.1.2.3.4.5.6.7.8.9.",
+"0.q.w.e.r.t.y.u.i.p.a.s.d.f.g.h.",
+"j.k.l.z.x.c.v.b.n.m.M.N.B.V.C.Z.",
+"A.S.D.F.G.H.J.K.L.P.I.U.Y.T.R.E.",
+"W.Q.!.~.^./.(.)._.`.'.].[.{.}.|.",
+" X.XXXoXOX+X@X#X$X%X&X*X=X-X;X:X",
+">X,X<X1X2X3X4X5X6X7X8X9X0XqXwXeX",
+"rXtXyXuXiXpXaXsXdXfXgXhXjXkXlXzX",
+"xXcXvXbXnXmXMXNXBXVXCXZXAXSXDXFX"
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 104 2 ",
+"   c #181818",
+".  c #222222",
+"X  c #323333",
+"o  c #393939",
+"O  c #434343",
+"+  c gray30",
+"@  c #515151",
+"#  c #5C5C5C",
+"$  c #6A5D5D",
+"%  c #656565",
+"&  c #6C6060",
+"*  c #6C6C6C",
+"=  c #706363",
+"-  c #777777",
+";  c #7D7D7D",
+":  c #55A555",
+">  c #6EAC6E",
+",  c #72AA72",
+"<  c #59CE59",
+"1  c #5FD65F",
+"2  c #63C763",
+"3  c #6DC36D",
+"4  c #6BCF6B",
+"5  c #60D260",
+"6  c #69D369",
+"7  c #6BDC6B",
+"8  c #74C874",
+"9  c #7FC27F",
+"0  c #72DE72",
+"q  c #6BE56B",
+"w  c #6EE86E",
+"e  c #7F8181",
+"r  c #5FB5B5",
+"t  c #6EBCBC",
+"y  c #72B8B8",
+"u  c #5CC3C3",
+"i  c #5ECCCC",
+"p  c #6BC6C6",
+"a  c #64CCCC",
+"s  c #73C3C3",
+"d  c #6ED6D6",
+"f  c #65DADA",
+"g  c #69DBDB",
+"h  c #7BDBDB",
+"j  c #6EE6E6",
+"k  c #71E3E3",
+"l  c #74EAEA",
+"z  c #808C8C",
+"x  c gray54",
+"c  c #929393",
+"v  c #9E9E9E",
+"b  c #A59494",
+"n  c #A89797",
+"m  c #8BBA8B",
+"M  c #9CBC9C",
+"N  c #87AAAA",
+"B  c #8CAAAA",
+"V  c #82BBBB",
+"C  c #93BBBB",
+"Z  c #A3A3A3",
+"A  c #ABABAB",
+"S  c #B3A1A1",
+"D  c #B8A6A6",
+"F  c #BBABAB",
+"G  c #A0BEA0",
+"H  c #B5B5B5",
+"J  c #BBBBBB",
+"K  c #C2B1B1",
+"L  c #CAB6B6",
+"P  c #C5BBBB",
+"I  c #CBB9B9",
+"U  c #D2BDBD",
+"Y  c #93C193",
+"T  c #A3C0A3",
+"R  c #AFC2AF",
+"E  c #B5C5B5",
+"W  c #B8C6B8",
+"Q  c #81CCCC",
+"!  c #BBC7C7",
+"~  c #BAC8C8",
+"^  c #C5C5C5",
+"/  c #C8C6C6",
+"(  c #C6CCC6",
+")  c #C2CDCD",
+"_  c #CACBCB",
+"`  c #DBC5C5",
+"'  c #CDD3D3",
+"]  c #D4D4D4",
+"[  c #DAD7DA",
+"{  c #DCDCDC",
+"}  c #E0C8C8",
+"|  c #E2DDDD",
+" . c #DFE0DF",
+".. c #E2DDE2",
+"X. c #E5E5E5",
+"o. c #E9E5E5",
+"O. c #EBE5EB",
+"+. c #E7E8E8",
+"@. c #ECECEC",
+"#. c #F0EDED",
+"$. c #F5EFF5",
+"%. c #EFF0F0",
+"&. c #F2F2F2",
+"*. c gray99",
+/* pixels */
+"X.X.+.X.+.+.X.X.X.X.+.X.+.#.O.X.+.X.+.X.+.o.+.+.@.o.X.X.X.X.X.X.",
+"X.X.X.X.X.X.o.+.+.X.X.+.X.^ { @.X.X.X.X.+.O.+.o._ @.X.X.X.X.X.X.",
+"X.X.+.+.X.X.X.X.X.X.X.@.^ % + _ @.X.X.X.O...O., X c &.X.X.X.X.X.",
+"+.X.X.X.+.X.X.X.X.X.X.X.@.+.O ] @.X.X.X...O...+ - # $.X.+.X.X.+.",
+"X.X.X.+.X.X.+.X.X.X.X.+.{ O v $.X.X.X.X.X.X.@.@ * # %.X.X.+.X.X.",
+"+.+.X.X.+.X.+.X.X.+.X.&.A O % _ @.X.X.X.X.X.#.A + J @.X.X.X.+.X.",
+"o.X.+.X.X.X.X.X.X.X.X.X.+.&.+.+.X.X.X.+.X.+.X.@.&.@.o.X.X.+.X.X.",
+"X.X.X.X.X.X.o.+.+.@.@.%.@.@.@.@.@.@.@.@.#.@.@.@.@.@.%.@.#.+.X.X.",
+"X.+.X.X.X.X.+.+.{ ^ ^ / ^ / ( ^ / _ ( _ / / ( ^ / ( / / ^ ] +.X.",
+"X.X.X.+.+.X.o.@.c Z H A A A A A A A Z c A A A A A A A A A ^ +.X.",
+"X.X.o.X.X.X.X.@.v X.&.%.*.*.*.*.$.&.X._ *.%.@.&.&.%.*.&.%.@.X.+.",
+"X.X.@.&.+.X.X.@.v [ +.X.e X o x @.+.] ^ +.X.X.o...&.c J @.X.X.X.",
+"X.$.A % { +.X.@.v { &.; & & & $ x &.{ / @.X.X. .*.c + ..+.X.X.X.",
+"X.+.( o ( @.X.@.v X.( D L I L I S _ { / +.X.X.*.x @ @.@.X.X.X.X.",
+"X.X.*.# ] @.X.@.v  ./ } F K F F ` _  .^ @.X.*.c @ @.+.X.X.+.+.X.",
+"X.@.Z   - O.X.@.v { ] K L U U L H { { / X.*.x @ @.+.X.X.X.X.X.X.",
+"X.+.^ A J X.+.@.v [ @.P b D S n / @.[ ^ &.c @ @.@.X.X.X.+.X.X.X.",
+"+.+.@.&.@.X.X.@.v [ @.o./ P P / o.+.] ^ @.H  .+.o.X.X.o.o.X.X.+.",
+"+.X.X.X.X.X.X.@.v  .@.+.#.+.+.@.@.@.{ _ $.&.@.+.+.O.+.+.+.+.X.X.",
+"+.X.X.X.+.X.X.@.v H / W ^ ^ ^ ^ ^ ^ H A ( J ^ ^ W ^ ^ ^ ^ ] +.X.",
+"X.X.X.o.+.X.o.@.v ]  .{  .o.o. .{ X.] ^ X.{  .X.+.+. ...{ ..X.X.",
+"X.X.@.@.X.X.X.@.v { @.@.| ! ! | @.+.{ ^ +.+.O./ W _ @.+.+.+.X.X.",
+"X.+.] J X.+.X.@.v { @.) p g f p _ @.[ ^ @...m 7 7 4 M O.X.X.+.X.",
+"o.&.Z . _ @.X.@.v { | t s d h t y o.{ ^ &.G 5 6 0 6 5 W @.X.X.X.",
+"X.X.*.# _ %.X.@.v  .) a c r Q z u '  .^ O.9 7 : 6 : q Y O.X.X.X.",
+"X.X.] o A @.X.@.v X.) g k l k l d '  .^ O.9 w 0 7 0 q Y $.X.X.X.",
+"X.@.v @ - X.X.@.v | ) i l g j j i ' { ^ $.E < , > 5 2 ^ @.X.X.X.",
+"X.X.@.&.&.X.X.@.v  .! B V C V C N ) ..^ +.O.G 3 4 8 E @.X.X.X.X.",
+"X.X.X.X.X.X.X.@.v {  .o.| o.| o.X.X.{ ^ @.X.O.{ _ | @.X.+.X.X.+.",
+"X.X.X.X.X.X.X.@.Z { @.X.X.X.+.+.X.+.{ / @.X.X.+.@.X.X.X.X.+.X.X.",
+"X.X.X.X.X.X.X.+. .+.X.X.X.X.X.X.X.X.X.X.X.X.X.o.X.+.X.+.X.X.+.X.",
+"+.X.X.+.X.+.X.X.O.+.X.X.X.X.+.X.X.X.X.X.+.X.X.X.o.+.+.X.X.X.X.o."
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 140 2 ",
+"   c #060505",
+".  c #0B0A0A",
+"X  c #110F0F",
+"o  c #151515",
+"O  c #1D1D1D",
+"+  c #222222",
+"@  c gray17",
+"#  c gray21",
+"$  c #3C3C3C",
+"%  c #3E7D3E",
+"&  c #414141",
+"*  c #4D4D4D",
+"=  c #534C4C",
+"-  c #575353",
+";  c #5C5B5B",
+":  c #636262",
+">  c #6C6D6D",
+",  c #727272",
+"<  c #7C7575",
+"1  c #7D7D7D",
+"2  c #8A7C7C",
+"3  c #539E53",
+"4  c #5E955E",
+"5  c #57AD57",
+"6  c #58AF58",
+"7  c #5CB45C",
+"8  c #63B263",
+"9  c #65BC65",
+"0  c #7DA37D",
+"q  c #71B471",
+"w  c #5DD35D",
+"e  c #61C661",
+"r  c #66CD66",
+"t  c #65D165",
+"y  c #67DD67",
+"u  c #6BD36B",
+"i  c #6FDA6F",
+"p  c #72C772",
+"a  c #71DE71",
+"s  c #6BE66B",
+"d  c #6EED6E",
+"f  c #73E573",
+"g  c #74EC74",
+"h  c #77F377",
+"j  c #78F078",
+"k  c #6D9999",
+"l  c #769797",
+"z  c #799595",
+"x  c #54A7A7",
+"c  c #54AAAA",
+"v  c #57B4B4",
+"b  c #5CBEBE",
+"n  c #61B5B5",
+"m  c #6AB1B1",
+"M  c #60BABA",
+"N  c #71B5B5",
+"B  c #58C5C5",
+"V  c #56C9C9",
+"C  c #5BCDCD",
+"Z  c #62C2C2",
+"A  c #6CC6C6",
+"S  c #65CCCC",
+"D  c #65DCDC",
+"F  c #69D8D8",
+"G  c #72DEDE",
+"H  c #65E0E0",
+"J  c #6CE3E3",
+"K  c #6CEAEA",
+"L  c #73E6E6",
+"P  c #74EAEA",
+"I  c #7AEEEE",
+"U  c #77F6F6",
+"Y  c #848484",
+"T  c #8C8C8C",
+"R  c #959494",
+"E  c #A59696",
+"W  c #AE9D9D",
+"Q  c #84AB84",
+"!  c #89A989",
+"~  c #8DB18D",
+"^  c #94AC94",
+"/  c #95B295",
+"(  c #8CA3A3",
+")  c #84ADAD",
+"_  c #89ABAB",
+"`  c #97AFAF",
+"'  c #84B1B1",
+"]  c #82BABA",
+"[  c #9AB4B4",
+"{  c #9ABABA",
+"}  c #A4A4A4",
+"|  c #AAA2A2",
+" . c #ADACAC",
+".. c #B3A2A2",
+"X. c #B2AAAA",
+"o. c #BCA9A9",
+"O. c #A1B3A1",
+"+. c #ABB5B5",
+"@. c #AFBABA",
+"#. c #B4B4B4",
+"$. c #B9B6B6",
+"%. c #B5BBB5",
+"&. c #BABBBA",
+"*. c #C2AFAF",
+"=. c #C5B2B2",
+"-. c #CBB7B7",
+";. c #CDB8B8",
+":. c #D5BEBE",
+">. c #BEC0BE",
+",. c #BEC0C0",
+"<. c #C3C3C3",
+"1. c #CCC7C7",
+"2. c #CBCBCB",
+"3. c #D6C1C1",
+"4. c #DAC3C3",
+"5. c #D1CECE",
+"6. c #DFCACA",
+"7. c #D4CFD4",
+"8. c #D6D6D6",
+"9. c #DDD6D6",
+"0. c #D7D8D8",
+"q. c #DBDBDB",
+"w. c #E2CBCB",
+"e. c #EED5D5",
+"r. c #E1D9D9",
+"t. c #F3DBDB",
+"y. c #E1D9E1",
+"u. c #DFE1E1",
+"i. c #E6E6E6",
+"p. c #ECE5E5",
+"a. c #E7E9E7",
+"s. c #EDE7ED",
+"d. c #E6E8E8",
+"f. c #ECEBEB",
+"g. c #F1ECEC",
+"h. c #EEF0EE",
+"j. c #F1ECF1",
+"k. c #EFF0F0",
+"l. c #F3F3F3",
+"z. c gray99",
+/* pixels */
+"i.i.i.d.i.i.i.i.i.i.d.i.i.i.d.d.i.i.i.i.d.d.i.i.i.i.i.d.i.d.i.i.i.i.d.i.i.i.i.i.i.i.i.i.i.i.i.d.",
+"i.i.i.a.d.d.d.i.d.i.p.i.i.i.i.i.d.i.i.d.i.d.d.p.i.i.i.i.d.i.d.i.i.i.i.i.i.i.i.i.i.i.i.d.i.i.i.i.",
+"i.i.a.i.i.d.i.i.i.d.i.i.i.i.i.i.i.i.i.i.f.a.i.i.i.i.i.i.i.i.p.i.i.i.i.d.f.i.i.i.i.i.a.i.i.i.i.i.",
+"i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.d.2.0.f.i.i.i.i.i.i.i.i.i.i.i.f.q.q.f.i.i.i.a.i.i.i.i.i.i.",
+"i.i.i.i.i.i.d.i.p.i.i.i.i.i.i.i.d.i.l.1 @ + R l.i.i.i.i.i.i.i.i.i.j.#.+ O  .k.i.d.d.i.i.i.i.i.i.",
+"i.i.d.i.i.i.i.i.d.i.i.i.i.i.d.i.i.p.a.2.l.1 $ l.i.i.i.i.i.i.d.i.i.l.& < Y # l.i.d.i.i.i.i.i.p.d.",
+"i.d.i.i.p.i.i.i.i.i.i.i.i.d.i.i.i.i.u.l.q.+ R l.i.i.i.i.i.i.i.d.p.f.$ - : # d.i.d.i.i.i.i.d.i.i.",
+"i.i.i.d.i.i.i.d.i.p.d.i.i.i.i.d.i.i.d.r.$ R z.i.i.i.i.i.i.p.i.d.p.l.# R ( @ f.i.i.p.i.i.i.i.d.d.",
+"i.i.d.i.i.i.i.i.i.d.i.i.d.d.i.i.i.i.l.* . * , s.i.i.i.i.i.i.i.i.i.l.T O O 1 l.i.i.i.d.i.i.i.d.i.",
+"i.i.i.p.i.i.d.i.i.i.i.i.i.d.i.i.i.i.f. .R Y } d.i.i.i.i.i.i.i.i.i.i.a. .} f.i.i.i.i.i.i.i.i.i.i.",
+"i.i.i.i.d.i.i.i.i.i.i.i.i.i.i.i.i.i.i.h.l.z.l.i.i.i.i.i.i.i.i.i.i.i.i.k.l.i.i.i.d.i.i.i.i.d.d.i.",
+"i.i.i.d.i.p.i.d.i.i.d.p.i.i.i.i.i.i.i.i.u.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.i.",
+"d.i.i.i.i.i.i.i.d.i.i.i.d.p.f.k.f.f.h.f.f.h.f.h.l.f.f.l.f.f.f.k.k.h.f.k.k.f.h.f.g.g.g.a.i.i.i.i.",
+"i.i.d.p.d.i.i.i.i.i.i.i.a.8.<.<.<.<.<.<.<.<.<.<.<.<.<.<.1.<.<.<.<.1.<.<.<.<.<.<.<.<.<.q.a.i.i.i.",
+"i.d.i.i.i.i.i.i.i.i.i.i.s.> Y T T T T T T T T T T T T T > T T T T T T T T T T T T T Y 1.s.i.d.d.",
+"i.i.i.i.i.i.i.d.i.d.i.i.i.Y g.l.l.l.l.l.k.l.j.l.l.l.l.l.&.l.l.l.l.l.l.l.l.l.l.l.l.l.l.f.i.d.i.i.",
+"i.i.i.i.i.i.i.i.i.i.i.i.i.1 q.i.i.i.i.l.h.f.l.f.u.i.i.i. .i.i.u.i.i.y.i.i.u.i.k.i.i.u.d.i.i.i.i.",
+"i.i.i.i.i.f.f.i.i.i.i.i.i.1 i.i.i.f.q., $ $ : 5.l.i.i.a. .i.i.i.i.i.i.i.i.i.f.< 5.f.i.i.i.i.i.i.",
+"i.i.i.i.i.2.<.i.i.i.i.i.i.1 i.i.k.-.O .     X X ( l.p.a.X.i.d.i.i.i.i.i.i.l.* # i.i.i.i.i.i.i.i.",
+"i.i.i.k.#.o + i.d.i.i.i.i.1 i.a.u.- ..t.3.;.t.=.= 5.s.i. .i.i.d.i.i.i.i.l.* $ h.a.i.i.i.i.i.i.i.",
+"d.i.i.a.i. .+ i.a.i.i.d.i.1 q.l.} o.t.< =.4.< 6.:.R l.i.#.i.i.i.i.i.i.z.* # k.a.i.i.i.i.i.d.d.p.",
+"i.i.i.i.l.$.O u.d.i.d.i.i.1 i.f. .6.4.: ..;.; =.e.| i.a. .i.i.i.i.i.l.* # l.a.i.i.i.i.i.i.i.i.d.",
+"i.i.i.i.l.<.+ f.a.i.p.i.i.1 i.f.#.3.w.e.e.e.t.w.w.X.d.d. .i.a.i.i.l.* # f.a.i.i.i.i.i.i.i.i.i.d.",
+"i.i.i.l. .+ . $ 7.a.i.i.i.1 i.s.<.=.*.E ;.;.| ..-.$.f.i.#.i.i.a.z.* # l.a.i.i.i.i.i.i.d.a.i.d.i.",
+"i.i.i.a.2.}  .} r.a.d.i.i.1 i.i.f.| w.Y ..=.< 3.W u.d.d. .y.i.i.* # j.a.i.d.i.i.i.i.d.i.i.i.i.i.",
+"i.i.i.i.f.l.l.l.a.i.a.i.i.1 i.i.a.r.| ..3.4.o.| 0.a.i.a.#.i.l.} & f.a.i.i.i.i.i.i.i.i.i.a.i.i.i.",
+"i.i.i.i.i.i.i.i.i.i.i.i.i.1 i.a.i.a.f.2.$.%.<.d.f.i.i.d. .i.a.i.j.a.i.i.i.i.i.i.a.i.d.d.i.i.i.a.",
+"i.i.i.i.i.i.i.i.i.i.d.i.i.1 i.s.s.a.a.f.l.j.j.s.a.f.d.f.%.a.s.a.a.a.a.s.a.s.f.f.s.a.s.p.i.i.i.i.",
+"i.i.i.i.i.i.i.i.i.i.d.i.i.1 0.8.0.8.8.8.8.8.8.0.0.0.0.0.} 8.8.8.0.0.0.0.8.8.0.q.8.0.0.i.a.a.i.i.",
+"i.i.i.i.i.i.i.i.a.i.i.d.i.,  .&.%.%.$.$.%.%.#.$.#.#.%.&.T %.$.$.#.%.#.$.$.#.%.$.$.$.#.8.s.i.i.i.",
+"i.i.i.i.i.i.i.i.i.i.i.i.i.1 a.f.f.f.s.f.k.l.f.f.f.f.s.j.%.a.j.f.f.f.f.k.j.f.a.f.f.f.f.i.i.i.i.a.",
+"a.i.i.i.i.i.i.i.a.i.i.i.d.1 i.i.i.i.a.s.r.r.p.s.i.i.i.d. .i.i.i.i.f.f.y.y.s.d.i.i.i.i.i.i.i.i.i.",
+"i.i.d.d.i.f.l.i.i.i.i.i.i.< y.s.i.f.2._ N N ) ,.s.i.i.a. .i.d.i.f.<.Q q q Q 1.g.i.i.i.i.d.d.i.i.",
+"i.d.i.d.i.&.$.i.i.i.i.i.i.0 i.y.g.&.n K P P K b +.f.i.a. .i.i.j.$.7 s h g s 7 %.g.i.i.i.d.i.i.i.",
+"i.i.i.g.#.o + y.d.i.i.i.i.1 i.s.r.m F A L P A G b 2.f.d.#.i.a.8.8 g f f a f g 8 8.a.i.i.i.i.i.d.",
+"i.i.i.i.s.%.+ u.d.i.i.i.i.1 i.f.@.V { 2 v F  .> V [ g.i. .i.j.O.y a % r r % a y O.g.i.i.i.i.i.i.",
+"i.i.p.i.g.%.+ i.i.i.i.i.i.1 u.g.[ D N z S J ) k D ' p.d.X.i.j.~ s i 7 u u 6 i s ~ s.i.i.d.i.i.i.",
+"i.d.d.i.l.$.+ s.a.i.i.i.i.1 i.g.[ H P L P L J L K ' p.d. .i.j./ y f h d j h f y / g.i.i.i.i.i.d.",
+"d.i.i.j.} o . @ 2.f.i.i.i.1 u.s.` H I L I P L I P ) p.d.+.i.f.>.e f 4 q 3 5 f e -.k.i.i.d.p.i.i.",
+"i.i.i.d.0.#.,.%.i.a.i.i.i.1 i.g.{ B A U Z S U Z C ] p.d.X.i.i.s.! w p z i h w ! f.d.i.i.i.i.i.i.",
+"i.i.i.i.a.l.g.j.i.i.d.d.i.1 u.j.( ( +.x +.+.c +.` l g.i. .i.i.i.y.^ e w t 8 ^ y.d.i.i.i.i.i.i.i.",
+"i.d.i.i.i.i.i.i.i.i.i.d.i.2 i.d.8.d.p.1.f.d.1.f.p.5.i.a.#.i.i.p.a.g.7.&.&.8.j.i.i.i.i.i.d.d.i.i.",
+"i.i.i.i.i.i.i.p.i.i.d.i.y.< i.i.a.a.i.a.i.i.f.i.i.f.i.a. .i.d.i.a.d.a.f.k.f.i.p.i.i.i.i.d.i.d.i.",
+"d.d.i.i.i.i.i.d.i.i.i.i.y.0 i.i.i.i.i.i.i.i.i.d.i.i.i.i.#.i.i.i.i.i.i.i.i.i.d.i.i.i.i.i.i.i.i.i.",
+"i.i.i.i.d.i.i.i.i.i.i.i.s.q.d.d.d.i.d.i.i.i.i.i.i.i.i.i.y.a.d.d.i.i.i.i.i.p.d.i.i.i.i.i.i.i.i.d.",
+"d.i.i.d.i.i.i.i.i.i.d.i.d.s.i.i.d.i.p.i.i.p.d.d.i.i.i.i.p.i.d.i.a.i.i.i.i.i.i.i.i.i.d.i.i.i.i.i.",
+"i.d.i.i.i.i.i.i.i.d.i.d.i.i.i.i.i.i.i.i.i.i.i.d.i.i.d.i.i.i.i.i.i.i.i.a.i.i.i.i.i.i.i.i.d.i.i.i.",
+"i.i.i.i.i.i.d.i.i.i.i.i.i.i.i.d.i.i.i.i.i.i.d.d.i.a.i.i.i.i.d.i.i.i.i.i.i.d.i.i.i.d.d.i.d.i.d.i."
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/undead-web.png b/icons/undead-web.png
new file mode 100644 (file)
index 0000000..6fbdc59
Binary files /dev/null and b/icons/undead-web.png differ
diff --git a/icons/undead.ico b/icons/undead.ico
new file mode 100644 (file)
index 0000000..f99957b
Binary files /dev/null and b/icons/undead.ico differ
diff --git a/icons/undead.rc b/icons/undead.rc
new file mode 100644 (file)
index 0000000..2a9c9eb
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "undead.ico"
diff --git a/icons/undead.sav b/icons/undead.sav
new file mode 100644 (file)
index 0000000..3c314dd
--- /dev/null
@@ -0,0 +1,14 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :6:Undead
+PARAMS  :6:4x4de2
+CPARAMS :6:4x4de2
+DESC    :48:5,2,2,aRLgLLaLRL,2,0,1,2,1,1,2,5,0,0,0,2,1,3,1,1
+NSTATES :1:7
+STATEPOS:1:7
+MOVE    :2:G0
+MOVE    :2:V0
+MOVE    :2:G2
+MOVE    :2:G3
+MOVE    :2:V3
+MOVE    :2:Z3
diff --git a/icons/unequal-16d24.png b/icons/unequal-16d24.png
new file mode 100644 (file)
index 0000000..dc3ee89
Binary files /dev/null and b/icons/unequal-16d24.png differ
diff --git a/icons/unequal-16d4.png b/icons/unequal-16d4.png
new file mode 100644 (file)
index 0000000..be6eb44
Binary files /dev/null and b/icons/unequal-16d4.png differ
diff --git a/icons/unequal-16d8.png b/icons/unequal-16d8.png
new file mode 100644 (file)
index 0000000..8b63b11
Binary files /dev/null and b/icons/unequal-16d8.png differ
diff --git a/icons/unequal-32d24.png b/icons/unequal-32d24.png
new file mode 100644 (file)
index 0000000..a27b73d
Binary files /dev/null and b/icons/unequal-32d24.png differ
diff --git a/icons/unequal-32d4.png b/icons/unequal-32d4.png
new file mode 100644 (file)
index 0000000..ec1ee50
Binary files /dev/null and b/icons/unequal-32d4.png differ
diff --git a/icons/unequal-32d8.png b/icons/unequal-32d8.png
new file mode 100644 (file)
index 0000000..867b23a
Binary files /dev/null and b/icons/unequal-32d8.png differ
diff --git a/icons/unequal-48d24.png b/icons/unequal-48d24.png
new file mode 100644 (file)
index 0000000..0cfb26c
Binary files /dev/null and b/icons/unequal-48d24.png differ
diff --git a/icons/unequal-48d4.png b/icons/unequal-48d4.png
new file mode 100644 (file)
index 0000000..4004ea8
Binary files /dev/null and b/icons/unequal-48d4.png differ
diff --git a/icons/unequal-48d8.png b/icons/unequal-48d8.png
new file mode 100644 (file)
index 0000000..5377f9c
Binary files /dev/null and b/icons/unequal-48d8.png differ
diff --git a/icons/unequal-base.png b/icons/unequal-base.png
new file mode 100644 (file)
index 0000000..9dd11a4
Binary files /dev/null and b/icons/unequal-base.png differ
diff --git a/icons/unequal-ibase.png b/icons/unequal-ibase.png
new file mode 100644 (file)
index 0000000..161d9a4
Binary files /dev/null and b/icons/unequal-ibase.png differ
diff --git a/icons/unequal-ibase4.png b/icons/unequal-ibase4.png
new file mode 100644 (file)
index 0000000..79f823e
Binary files /dev/null and b/icons/unequal-ibase4.png differ
diff --git a/icons/unequal-icon.c b/icons/unequal-icon.c
new file mode 100644 (file)
index 0000000..29846a6
--- /dev/null
@@ -0,0 +1,733 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 253 2 ",
+"   c LightGray",
+".  c #CACACA",
+"X  c #CBCBCB",
+"o  c #CBCBCB",
+"O  c #CBCBCB",
+"+  c #CACACA",
+"@  c #D0D0D0",
+"#  c #D5D5D5",
+"$  c #D5D5D5",
+"%  c #CECECE",
+"&  c #CACACA",
+"*  c #D1CDD1",
+"=  c #CFCCCF",
+"-  c #CACACA",
+";  c #CCCBCC",
+":  c gray83",
+">  c #C6C6C6",
+",  c #CECECE",
+"<  c gray80",
+"1  c #CDCDCD",
+"2  c #CBCBCB",
+"3  c gray76",
+"4  c gainsboro",
+"5  c #D7D7D7",
+"6  c #C1C1C1",
+"7  c #CFCECF",
+"8  c #B6C3B6",
+"9  c #BBC5BB",
+"0  c #D0CFD0",
+"q  c #C3C3C3",
+"w  c gray81",
+"e  c #CBCBCB",
+"r  c #CECECE",
+"t  c gray85",
+"y  c #D7D7D7",
+"u  c #D7D7D7",
+"i  c gray84",
+"p  c #C6C6C6",
+"a  c gray52",
+"s  c #C8C8C8",
+"d  c #CBCCCB",
+"f  c #DFDADF",
+"g  c #6AAB6A",
+"h  c #499D49",
+"j  c #E5DEE5",
+"k  c #C9CAC9",
+"l  c #CECECE",
+"z  c #CBCBCB",
+"x  c gray80",
+"c  c gray83",
+"v  c #D5D5D5",
+"b  c #D2D2D2",
+"n  c #CDCDCD",
+"m  c #BBBBBB",
+"M  c #626262",
+"N  c #C6C7C6",
+"B  c #DFD9DF",
+"V  c #BCCABC",
+"C  c #539F53",
+"Z  c #E8DEE8",
+"A  c #C6C8C6",
+"S  c #CFCECF",
+"D  c #CBCBCB",
+"F  c #CDCDCD",
+"G  c #D7D7D7",
+"H  c #D5D5D5",
+"J  c #D5D5D5",
+"K  c LightGray",
+"L  c gray80",
+"P  c #909090",
+"I  c #8D8D8D",
+"U  c #CBCBCB",
+"Y  c #DDD9DD",
+"T  c #75AE75",
+"R  c #208C20",
+"E  c #BACCBA",
+"W  c #CFCCCF",
+"Q  c #CDCECD",
+"!  c #CACACA",
+"~  c #CBCBCB",
+"^  c gray84",
+"/  c gray83",
+"(  c gray83",
+")  c LightGray",
+"_  c #C1C1C1",
+"`  c gray69",
+"'  c #DADADA",
+"]  c #C5C5C5",
+"[  c #D5D5D5",
+"{  c #B6C8B6",
+"}  c #ADC4AD",
+"|  c #C6CFC6",
+" . c #CBC9CB",
+".. c #CDCECD",
+"X. c gray81",
+"o. c gray77",
+"O. c gray78",
+"+. c #C6C6C6",
+"@. c gray78",
+"#. c #C5C5C5",
+"$. c gray79",
+"%. c gray87",
+"&. c gray83",
+"*. c #C6C6C6",
+"=. c #C5C6C5",
+"-. c #CDC9CD",
+";. c #CFCACF",
+":. c #CAC8CA",
+">. c gray77",
+",. c #D2D2D2",
+"<. c gray84",
+"1. c #D7D7D7",
+"2. c gray84",
+"3. c gray84",
+"4. c gray84",
+"5. c gray84",
+"6. c gray84",
+"7. c LightGray",
+"8. c #D7D7D7",
+"9. c gray84",
+"0. c #D5D6D5",
+"q. c #D5D6D5",
+"w. c gray84",
+"e. c #D7D7D7",
+"r. c #D5D5D5",
+"t. c #D5D5D5",
+"y. c gray83",
+"u. c LightGray",
+"i. c #D1D2D1",
+"p. c LightGray",
+"a. c gray83",
+"s. c #D5D5D5",
+"d. c #D5D5D5",
+"f. c #D5D5D5",
+"g. c #D5D5D5",
+"h. c LightGray",
+"j. c LightGray",
+"k. c #D0D0D0",
+"l. c LightGray",
+"z. c gray83",
+"x. c #D5D5D5",
+"c. c #CECECE",
+"v. c gray76",
+"b. c #CAC8CA",
+"n. c #D4CCD4",
+"m. c #CAC8CA",
+"M. c gray77",
+"N. c #C6C6C6",
+"B. c #D7D7D7",
+"V. c #D5D5D5",
+"C. c gray77",
+"Z. c #C5C5C5",
+"A. c gray79",
+"S. c #D5D5D5",
+"D. c #C8C8C8",
+"F. c gray76",
+"G. c gray82",
+"H. c gray79",
+"J. c #D2CFD2",
+"K. c #C2CFC2",
+"L. c #55A255",
+"P. c #B4C8B4",
+"I. c #DCD8DC",
+"U. c #C4C5C4",
+"Y. c gray84",
+"T. c LightGray",
+"R. c gray77",
+"E. c gray87",
+"W. c gray77",
+"Q. c #585858",
+"!. c gray80",
+"~. c #CDCDCD",
+"^. c #CDCDCD",
+"/. c #CACBCA",
+"(. c #CBCCCB",
+"). c #DFDADF",
+"_. c #539E53",
+"`. c #9ABC9A",
+"'. c #E2DAE2",
+"]. c #C4C5C4",
+"[. c gray84",
+"{. c LightGray",
+"}. c #C6C6C6",
+"|. c #747474",
+" X c gray17",
+".X c #C0C0C0",
+"XX c gray81",
+"oX c #CDCDCD",
+"OX c #CACACA",
+"+X c #CBCCCB",
+"@X c #DFDADF",
+"#X c #509C50",
+"$X c #7EB07E",
+"%X c #DED8DE",
+"&X c #C4C5C4",
+"*X c gray84",
+"=X c #D2D2D2",
+"-X c gray80",
+";X c #BBBBBB",
+":X c #444444",
+">X c #1E1E1E",
+",X c #909090",
+"<X c #D5D5D5",
+"1X c gray80",
+"2X c #C9CAC9",
+"3X c #D3D0D3",
+"4X c #C2D0C2",
+"5X c #62A862",
+"6X c #78B178",
+"7X c #D4D5D4",
+"8X c gray78",
+"9X c gray84",
+"0X c LightGray",
+"qX c gray78",
+"wX c #D8D8D8",
+"eX c #D0D0D0",
+"rX c gray51",
+"tX c gray77",
+"yX c gray81",
+"uX c #CDCDCD",
+"iX c #CCCBCC",
+"pX c #C3C3C3",
+"aX c #CDCCCD",
+"sX c #D8CFD8",
+"dX c #D6CFD6",
+"fX c #C8C8C8",
+"gX c gray77",
+"hX c #D7D7D7",
+"jX c gray83",
+"kX c gray76",
+"lX c #CACACA",
+"zX c gray80",
+"xX c #D5D5D5",
+"cX c #CECECE",
+"vX c gray76",
+"bX c #D0D0D0",
+"nX c gray83",
+"mX c gray81",
+"MX c #CECECE",
+"NX c #CBCDCB",
+"BX c #CCCDCC",
+"VX c #CECECE",
+"CX c #D2D2D2",
+"ZX c #D5D5D5",
+"AX c #D5D5D5",
+"SX c gray82",
+"DX c #CECECE",
+"FX c #CECECE",
+"GX c gray80",
+"HX c #CDCDCD",
+"JX c #D0D0D0",
+"KX c #D5D5D5",
+"LX c white",
+/* pixels */
+"  . X o O + @ # $ % & * = - ; : ",
+". > , < 1 2 3 4 5 6 7 8 9 0 q w ",
+"e r t y u i p a s d f g h j k l ",
+"z x y c v b n m M N B V C Z A S ",
+"D F G H J K L P I U Y T R E W Q ",
+"! ~ ^ / ( ) _ ` ' ] [ { } |  ...",
+"X.o.O.+.@.#.$.%.&.*.=.-.;.:.>.,.",
+"<.1.2.3.4.5.6.7.v 8.9.0.q.w.e.r.",
+"t.y.u.i.p.a.s.d.f.g.h.j.k.l.z.x.",
+"c.v.b.n.m.M.N.B.V.C.Z.A.S.D.F.G.",
+"H.J.K.L.P.I.U.Y.T.R.E.W.Q.!.~.^.",
+"/.(.)._.`.'.].[.{.}.1.|. X.XXXoX",
+"OX+X@X#X$X%X&X*X=X-X;X:X>X,X<X1X",
+"2X3X4X5X6X7X8X9X0XqXwXeXrXtXyXuX",
+"iXpXaXsXdXfXgXhXjXkXlXzXxXcXvXbX",
+"nXmXMXNXBXVXCXZXAXSXDXFXGXHXJXKX"
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 191 2 ",
+"   c black",
+".  c #070707",
+"X  c gray3",
+"o  c #111111",
+"O  c #191919",
+"+  c #1E1E1E",
+"@  c #232323",
+"#  c #252525",
+"$  c #2A2A2A",
+"%  c #2C2C2C",
+"&  c #323232",
+"*  c #343434",
+"=  c #007600",
+"-  c #007900",
+";  c #007A00",
+":  c #007D00",
+">  c #007E00",
+",  c #007F00",
+"<  c #017F01",
+"1  c #047E04",
+"2  c #484848",
+"3  c gray32",
+"4  c #646464",
+"5  c #6F6F6F",
+"6  c #717171",
+"7  c #797979",
+"8  c gray48",
+"9  c #078107",
+"0  c #088208",
+"q  c #0A830A",
+"w  c #2D912D",
+"e  c #469B46",
+"r  c #61A661",
+"t  c #67A867",
+"y  c #69A969",
+"u  c #6FAC6F",
+"i  c #72AB72",
+"p  c #71AD71",
+"a  c #74AE74",
+"s  c #79B079",
+"d  c #7DB27D",
+"f  c #838383",
+"g  c gray52",
+"h  c gray54",
+"j  c gray57",
+"k  c gray60",
+"l  c #87B687",
+"z  c #8CB78C",
+"x  c #8CB88C",
+"c  c #8EB98E",
+"v  c #94BB94",
+"b  c #9BBF9B",
+"n  c #A0A0A0",
+"m  c gray64",
+"M  c gray66",
+"N  c #AAAAAA",
+"B  c #AEAEAE",
+"V  c #AFAFAF",
+"C  c #A0BFA0",
+"Z  c gray69",
+"A  c #B1B1B1",
+"S  c #B2B2B2",
+"D  c gray70",
+"F  c #B4B4B4",
+"G  c #B7B7B7",
+"H  c gray72",
+"J  c #B8B9B8",
+"K  c #B9B9B9",
+"L  c #B9BBB9",
+"P  c gray73",
+"I  c #BABBBA",
+"U  c #BBBBBB",
+"Y  c #BABCBA",
+"T  c #BBBCBB",
+"R  c #BCBCBC",
+"E  c #BCBDBC",
+"W  c gray74",
+"Q  c gray",
+"!  c gray75",
+"~  c #A1C0A1",
+"^  c #AAC3AA",
+"/  c #A9C4A9",
+"(  c #AAC4AA",
+")  c #ABC6AB",
+"_  c #AFC5AF",
+"`  c #B2C7B2",
+"'  c #B5C8B5",
+"]  c #B5CAB5",
+"[  c #B6C9B6",
+"{  c #BFC0BF",
+"}  c #B8C9B8",
+"|  c #BACABA",
+" . c #BDCBBD",
+".. c #BECBBE",
+"X. c #BFCEBF",
+"o. c #C0C0C0",
+"O. c #C1C1C1",
+"+. c gray76",
+"@. c gray77",
+"#. c #C5C5C5",
+"$. c #C6C6C6",
+"%. c gray78",
+"&. c #C6CFC6",
+"*. c #C8C6C8",
+"=. c #C8C8C8",
+"-. c gray79",
+";. c #CACACA",
+":. c #CBCBCB",
+">. c #C9CFC9",
+",. c #CCC8CC",
+"<. c #CCCBCC",
+"1. c #CEC9CE",
+"2. c gray80",
+"3. c #CDCDCD",
+"4. c #CECECE",
+"5. c gray81",
+"6. c #C8D0C8",
+"7. c #C9D0C9",
+"8. c #CDD2CD",
+"9. c #CED2CE",
+"0. c #CED4CE",
+"q. c #D0CDD0",
+"w. c #D4CED4",
+"e. c #D0D0D0",
+"r. c gray82",
+"t. c #D0D2D0",
+"y. c #D0D3D0",
+"u. c #D1D2D1",
+"i. c #D1D3D1",
+"p. c #D2D2D2",
+"a. c #D2D3D2",
+"s. c LightGray",
+"d. c #D0D5D0",
+"f. c #D2D4D2",
+"g. c #D3D4D3",
+"h. c #D7D0D7",
+"j. c gray83",
+"k. c #D5D4D5",
+"l. c #D5D5D5",
+"z. c #D5D6D5",
+"x. c #D5D7D5",
+"c. c #D6D4D6",
+"v. c #D6D5D6",
+"b. c #D7D5D7",
+"n. c gray84",
+"m. c #D6D7D6",
+"M. c #D7D7D7",
+"N. c #D7D8D7",
+"B. c #D8D6D8",
+"V. c #D8D7D8",
+"C. c #D8D8D8",
+"Z. c #D8D9D8",
+"A. c #D9D8D9",
+"S. c gray85",
+"D. c #DBD9DB",
+"F. c #DADADA",
+"G. c gray86",
+"H. c #DCD8DC",
+"J. c #DCD9DC",
+"K. c #DDD8DD",
+"L. c #DCDADC",
+"P. c #DFD8DF",
+"I. c #DEDBDE",
+"U. c #DFDBDF",
+"Y. c gainsboro",
+"T. c #DDDDDD",
+"R. c gray87",
+"E. c #DFDFDF",
+"W. c #E1D9E1",
+"Q. c #E1DAE1",
+"!. c #E2DAE2",
+"~. c #E3DBE3",
+"^. c #E0DCE0",
+"/. c #E2DDE2",
+"(. c #E3DDE3",
+"). c #E4DBE4",
+"_. c #E5DBE5",
+"`. c #E5DCE5",
+"'. c #E5DEE5",
+"]. c #EBDEEB",
+"[. c gray88",
+"{. c #E2E2E2",
+"}. c gray89",
+"|. c #E4E4E4",
+" X c #E6E6E6",
+".X c #E7E7E7",
+"XX c gray91",
+"oX c #E9E9E9",
+"OX c gray92",
+"+X c gray93",
+"@X c #F4F4F4",
+/* pixels */
+"l.l.G.G.G.G.G.G.G.G.G.G.G.r.l.l.l.l.G.G.G.G.G.G.G.G.G.G.G.G.f.l.",
+"l.r.o.o.o.o.o.! o.o.! ! o.l.l.l.l.l.2.! o.o.! ! ! ! o.o.{ @.l.l.",
+"G.o.G -.#.#.#.#.#.#.#.-.D -.l.r.r.G.U U -.#.*.*.,.#.#.#.=.Z 2.G.",
+"G.o.-.Y.G.l.G.G.G.G.l.G.! -.XXG.r.G.! 2.Y.l.G.X.] 0.P.N.Y.! 2.l.",
+"G.o.#.l.r.r.l.l.l.l.r.G.! -.f U Y.G.! -.l.P.t 9 - z ~.r.G.G 2.G.",
+"G.o.#.G.l.l.l.l.l.l.l.G.U r.f + ! }.U 2.l.l.| v - x ~.f.N.U 2.l.",
+"G.o.#.G.l.l.l.l.l.l.l.l.! #.@Xh @ o.o.-.G.f.~.] - x ~.r.G.U 2.G.",
+"G.o.#.G.r.l.l.l.l.l.r.G.! -.r.@X4 & #.-.l.l.P.` - x _.f.l.U 2.l.",
+"G.o.#.G.r.l.l.l.l.l.l.G.U #.+Xk @ D o.-.N.r.B./ - x H.r.G.U 2.l.",
+"G.! o.G.l.l.l.l.l.l.r.G.! r.k O M XXY -.l.~.y , , , e H.l.U 2.N.",
+"G.o.#.G.r.l.l.r.r.r.r.G.U 2.6 M }.l.U -.N.l.| ~ ^ ~ _ k.l.U 9.B.",
+"G.! -.Y.G.l.G.G.G.G.G.Y.! -. XY.r.G.! 2.G.G.Y.`.`.`./.G.Y.! 2.B.",
+"G.o.D o.! U ! ! ! ! U ! B 2.l.l.k.G.U D o.! ! U Y Y U ! ! B r.l.",
+"l.r.-.-.-.-.-.-.-.-.-.-.2.l.l.l.l.l.r.-.-.-.-.-.-.-.-.-.-.2.l.l.",
+"l.l.G.l.G.l.G.l.l.G.l.G.l.l.l.l.l.l.l.G.G.l.l.G.G.G.G.l.l.G.l.l.",
+"l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.l.r.l.l.l.l.l.l.",
+"l.l.l.r.l.l.k.k.l.f.l.f.l.l.l.l.l.l.l.l.r.l.l.r.r.l.l.l.r.r.l.l.",
+"l.l.G.G.G.l.G.B.G.B.G.B.G.l.l.l.l.l.l.G.G.l.G.G.G.G.l.G.G.G.l.l.",
+"l.2.U R U U U U U U ! ! Y r.l.l.l.l.-.U ! ! U ! U U U ! U ! l.l.",
+"G.o.! 2.-.-.w.k.q.-.-.2.G -.l.l.l.G.U o.2.-.-.-.r.l.-.-.2.G 2.N.",
+"G.o.-.G.B.f./ b X.G.l.P.! -.l.l.l.G.! 2.G.l.l.Y.D k r.G.G.U 2.G.",
+"G.o.#.l.~.s q - i _.9.G.! -.G.l.l.l.! -.l.r.}.m   . ! l.l.U 2.l.",
+"G.o.#.G.l.7.' , p `.f.N.U -.B.l.l.G.U -.G.l.2.% & . ! G.l.U 2.G.",
+"G.o.o.G.r.H.&., u _.9.G.! -.G.l.r.G.U -.l.}.3 5 j   2.G.G.U 2.l.",
+"G.o.#.G.r.G.>., a ].r.G.Y -.G.f.l.l.! -.Y.D . 8 2   8 l.G.U 2.l.",
+"G.o.@.G.l.9./ - r 9.r.G.! -.B.l.l.G.U -.Y.D $ # o   & 2.G.H 2.l.",
+"G.o.#.N.~.d , 0 , w l.G.Y -.B.l.r.G.U -.G.l.l.XX8   #.G.l.U r.G.",
+"G.o.#.N.k.>.|  .| X.r.l.! -.G.f.r.G.! -.l.k.l.l.-.U r.l.B.U 2.l.",
+"G.{ -.Y.l.G.P.P.P.Y.N.Y.! -.G.l.l.l.Y 2.G.l.G.l.Y.Y.G.G.Y.U 2.N.",
+"G.@.Z ! U U J J U U U U B 2.l.l.l.G.! D U U U G U G H U U Z r.l.",
+"r.l.2.2.2.2.2.9.2.2.2.2.r.l.l.l.l.l.l.q.2.r.2.2.2.2.2.2.9.r.l.l.",
+"l.l.G.l.G.l.G.l.G.l.G.N.l.l.l.l.l.l.l.N.l.l.G.G.l.G.N.B.B.l.l.l."
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 166 2 ",
+"   c black",
+".  c #020202",
+"X  c gray1",
+"o  c gray4",
+"O  c #101010",
+"+  c gray9",
+"@  c gray11",
+"#  c #1D1D1D",
+"$  c #1E1E1E",
+"%  c gray14",
+"&  c #252525",
+"*  c gray15",
+"=  c #2D2D2D",
+"-  c gray18",
+";  c #323232",
+":  c #007200",
+">  c #007300",
+",  c #007500",
+"<  c #007700",
+"1  c #007800",
+"2  c #007900",
+"3  c #007E00",
+"4  c #494949",
+"5  c #4B4B4B",
+"6  c #4C4C4C",
+"7  c gray30",
+"8  c #5D5D5D",
+"9  c gray37",
+"0  c #5F5F5F",
+"q  c #606060",
+"w  c #626262",
+"e  c gray39",
+"r  c #656565",
+"t  c #676767",
+"y  c #6A6A6A",
+"u  c gray42",
+"i  c #6D6D6D",
+"p  c gray43",
+"a  c #717171",
+"s  c #747474",
+"d  c gray46",
+"f  c gray50",
+"g  c #098209",
+"h  c #098309",
+"j  c #0D840D",
+"k  c #1C8B1C",
+"l  c #248D24",
+"z  c #278F27",
+"x  c #288E28",
+"c  c #298F29",
+"v  c #4B9D4B",
+"b  c #4B9E4B",
+"n  c #52A052",
+"m  c #59A259",
+"M  c #5AA45A",
+"N  c #5BA45B",
+"B  c #60A660",
+"V  c #61A661",
+"C  c #66A866",
+"Z  c #67A967",
+"A  c #68A968",
+"S  c #69A969",
+"D  c #6BAA6B",
+"F  c #6EAB6E",
+"G  c #7FB27F",
+"H  c #808080",
+"J  c gray51",
+"K  c #838383",
+"L  c #868686",
+"P  c gray53",
+"I  c gray54",
+"U  c #909090",
+"Y  c gray59",
+"T  c gray61",
+"R  c #9F9F9F",
+"E  c #86B586",
+"W  c #8EB88E",
+"Q  c #A0A0A0",
+"!  c #A4A4A4",
+"~  c #A7A7A7",
+"^  c gray68",
+"/  c #AEAEAE",
+"(  c #AEAFAE",
+")  c #AFAFAF",
+"_  c #B1B1B1",
+"`  c gray74",
+"'  c gray",
+"]  c #C1C1C1",
+"[  c gray76",
+"{  c #C6C6C6",
+"}  c gray78",
+"|  c #C7CFC7",
+" . c #C8C8C8",
+".. c gray79",
+"X. c #CACACA",
+"o. c #CBCBCB",
+"O. c gray80",
+"+. c #CDCDCD",
+"@. c #CECECE",
+"#. c gray81",
+"$. c #C9D0C9",
+"%. c #CAD0CA",
+"&. c #CBD1CB",
+"*. c #CCD2CC",
+"=. c #CED1CE",
+"-. c #CFD3CF",
+";. c #D0D0D0",
+":. c gray82",
+">. c #D0D2D0",
+",. c #D0D3D0",
+"<. c #D1D3D1",
+"1. c #D2D2D2",
+"2. c #D3D2D3",
+"3. c LightGray",
+"4. c #D1D4D1",
+"5. c #D2D4D2",
+"6. c #D3D4D3",
+"7. c gray83",
+"8. c #D4D5D4",
+"9. c #D5D4D5",
+"0. c #D5D5D5",
+"q. c #D6D4D6",
+"w. c #D6D5D6",
+"e. c #D7D5D7",
+"r. c gray84",
+"t. c #D7D6D7",
+"y. c #D7D7D7",
+"u. c #D9D7D9",
+"i. c #DBD6DB",
+"p. c #D8D8D8",
+"a. c gray85",
+"s. c #D9DAD9",
+"d. c #DBD8DB",
+"f. c #DADADA",
+"g. c #DADBDA",
+"h. c gray86",
+"j. c #DCD8DC",
+"k. c #DDD8DD",
+"l. c #DCDBDC",
+"z. c #DED9DE",
+"x. c #DFD9DF",
+"c. c gainsboro",
+"v. c #DDDDDD",
+"b. c #DEDCDE",
+"n. c gray87",
+"m. c #DFDFDF",
+"M. c #E0D9E0",
+"N. c #E1DAE1",
+"B. c #E2DAE2",
+"V. c #E4DBE4",
+"C. c #E5DBE5",
+"Z. c #E5DCE5",
+"A. c #EDDFED",
+"S. c gray88",
+"D. c #E1E1E1",
+"F. c #E2E2E2",
+"G. c gray89",
+"H. c #E4E4E4",
+"J. c gray90",
+"K. c gray91",
+"L. c #E9E9E9",
+"P. c #EAEAEA",
+"I. c gray92",
+"U. c #F1E1F1",
+"Y. c gray95",
+"T. c white",
+/* pixels */
+"0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.",
+"0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.r.r.0.0.0.0.r.r.0.0.0.0.0.0.0.0.",
+"0.0.0.0.p.p.p.p.p.p.p.p.p.p.p.p.p.p.p.0.0.0.0.0.0.0.0.0.p.p.p.p.r.p.p.r.r.r.p.r.p.p.r.r.0.0.0.0.",
+"0.0.0.0.@.............................@.0.0.0.0.0.0.0.0.@.........X.X.X.X.X.X.X...X...=.0.0.0.0.",
+"0.0.p.;.R _ ) ) _ _ ) ) ) ) ) _ _ _ _ R @.0.0.0.0.0.p.;.Q _ _ ) ) ) ) ) ) _ ) _ ) ) _ R @.r.0.0.",
+"0.0.p..._ D.p.p.p.v.p.v.p.v.p.v.p.p.D._ ..p.0.0.0.0.p..._ D.p.p.v.p.p.N.v.v.p.p.v.p.D._ ..p.0.0.",
+"0.0.p..._ p.0.0.;.0.0.0.0.0.0.;.0.;.p.) ..v.p.;.0.0.p..._ p.0.0.5.u.0.X.| =.0.0.0.;.p.) ..p.0.0.",
+"0.0.0..._ p.0.0.0.0.0.0.0.0.0.0.0.0.v._ @.....v.;.0.0...) v.0.;.p.E k j 3 D N.5.0.5.v._ ..p.0.0.",
+"0.0.p...) p.0.0.0.0.0.0.0.0.0.0.0.0.p.) 0.Q   J J.;.p..._ v.0.5.l.n z x , Z V.5.0.;.g.) ..p.0.0.",
+"0.0.p..._ v.0.0.0.0.0.0.0.0.0.0.0.;.v.) ..D.L   L J.0...) p.0.0.0.-.V.V : D Z.5.0.0.v.) ..p.0.0.",
+"0.0.p...) p.0.0.0.0.0.0.0.0.0.0.0.0.v.) ..0.J.L   J L..._ v.;.0.0.0.N.M : C V.5.0.0.v.) ..p.0.0.",
+"0.0.p..._ p.0.0.0.0.0.0.0.0.0.0.0.0.p._ ..p.;.J.L   f @.) p.0.0.0.5.N.M : D V.;.0.0.p._ ..p.0.0.",
+"0.0.p...) p.0.0.0.0.0.0.0.0.0.0.0.;.v._ ..r.5.p.;.+ ; @.) p.0.0.0.;.N.M , D N.;.0.5.p._ ..p.0.0.",
+"0.0.p..._ v.0.0.0.0.0.0.0.0.0.0.0.0.p.) X.r.g.=.* * @.@.) p.0.0.0.u.U.N : F U.0.0.0.l.) ..p.0.0.",
+"0.0.p...) v.0.0.0.0.0.0.0.0.0.0.0.0.v.) } N.;.* * @.v...) v.0.5.p.E b z 2 x b W N.5.p._ ..p.0.0.",
+"0.0.p...) p.;.0.0.0.0.0.0.0.0.0.0.0.p.) @.] @ * @.p.0...) p.0.5.N.m , h j h 2 Z Z.;.p._ ..p.0.0.",
+"0.0.p..._ v.0.0.0.0.0.0.0.0.0.0.0.0.p.) ;.~ q ..v.;.p..._ p.0.0.0.;.=.=.&.&.=.;.0.0.v.) ..p.0.0.",
+"0.0.p...) p.;.0.0.;.0.0.;.0.0.0.0.;.p._ ..D.L.p.0.0.p..._ p.;.0.0.0.0.0.0.0.u.0.5.;.p.) ..p.0.0.",
+"0.0.p..._ D.p.p.v.p.p.v.v.p.p.v.v.p.D._ ..0.;.0.0.0.p..._ D.p.v.p.v.p.p.p.p.p.p.v.p.D._ ..p.0.0.",
+"0.0.0.@.R _ _ _ ) _ ) ) _ _ ) ) ) ) _ R @.p.0.0.0.0.0.@.R _ ) _ ) _ _ _ _ _ _ _ ) ) _ Q @.p.0.0.",
+"0.0.0.0.@.............................@.0.0.0.0.0.0.0.0.@.............................@.0.0.0.0.",
+"0.0.0.0.0.p.p.p.p.p.p.p.p.p.p.p.p.p.p.0.0.0.0.0.0.0.0.0.0.p.p.p.p.p.p.p.p.p.p.p.p.p.p.r.0.0.0.0.",
+"0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.",
+"0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.",
+"0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.",
+"0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.",
+"0.0.0.0.0.p.p.p.p.p.p.p.p.p.p.p.p.p.p.0.0.0.0.0.0.0.0.0.0.p.p.p.p.p.p.p.p.p.p.p.p.p.p.p.0.0.0.0.",
+"0.0.0.0.@...@.........................@.0.0.0.0.0.0.0.0.@...@.........................@.0.0.0.0.",
+"0.0.p.@.Q _ ) ) ) ) ) _ ) _ ) _ _ ) _ R ;.p.0.0.0.0.0.@.Q _ ) ) _ ) ) _ ) ) ) ) _ _ _ Q @.0.0.0.",
+"0.0.p..._ D.p.p.v.p.p.v.v.v.p.p.v.p.D._ ..p.0.0.0.0.p..._ D.p.p.v.p.p.p.v.v.v.p.p.p.D._ ..p.0.0.",
+"0.0.p..._ p.0.0.5.u.0.X.| =.0.0.0.;.p._ ..p.0.0.0.0.p..._ p.0.0.0.0.0.0...} @.0.0.;.p._ ..p.0.0.",
+"0.0.p...) p.0.0.p.E k j 3 D Z.5.0.0.p.) ..p.0.0.0.0.p..._ p.0.0.0.;.D.Y o   y D.0.0.p.) ..p.0.0.",
+"0.0.p..._ p.0.5.l.n z x , C V.;.0.0.v.) ..p.0.0.0.0.p...) p.0.0.0.p.} $     q D.;.0.v._ ..p.0.0.",
+"0.0.p..._ p.0.0.0.;.Z.V : D Z.5.0.;.p._ ..p.0.0.0.0.p..._ v.0.0.;.D.4 $ 6   t D.;.0.p.) ..p.0.0.",
+"0.0.p...) l.0.0.0.0.N.M : Z Z.5.0.0.p.) ..p.0.0.0.0.p...) p.0.0.J.U   R y   q v.0.0.v.) ..p.0.0.",
+"0.0.p..._ p.5.0.0.5.N.M : D N.5.0.0.v.) ..p.0.0.0.0.p...) v.;.p.] o 8 T.t   p Y.;.0.p._ ..p.0.0.",
+"0.0.p...) p.0.0.u.;.l.M : Z N.5.0.0.p.) ..p.0.0.0.0.p...) l.;.J.d   0 d -   - I 0.0.p.) ..p.0.0.",
+"0.0.p..._ v.0.0.0.u.A.N : F U.0.0.0.v.) ..p.0.0.0.0.p..._ p.;.J.a   X     X   - 0.0.p.) ..p.0.0.",
+"0.0.p...) v.0.5.j.E b z 2 c b W l.5.p.) ..p.0.0.0.0.p..._ v.;.p.' R ! _ 6   6 ' ;.0.v.) ..p.0.0.",
+"0.0.p...) p.0.;.N.m 2 h j h , Z Z.;.v._ ..p.0.0.0.0.p...) p.0.0.p.D.p.I.y   p L.;.0.p.) ..p.0.0.",
+"0.0.p..._ v.0.0.0.;.=.&.&.=.&.;.0.0.p._ ..p.0.0.0.0.p..._ v.0.0.0.;.0.;.@...@.0.0.0.v.) ..p.0.0.",
+"0.0.p...) p.;.0.0.0.0.u.0.0.0.0.0.0.p._ ..p.0.0.0.0.p...) p.;.0.0.0.0.0.0.0.0.0.0.;.p.) ..p.0.0.",
+"0.0.p..._ D.p.p.p.p.l.p.p.p.p.v.p.p.D._ ..r.0.0.0.0.p..._ D.p.p.p.v.p.v.p.p.p.v.v.p.D._ ..p.0.0.",
+"0.0.0.@.R _ ) _ _ ) _ ) _ ) ) _ ) ) _ R @.u.0.0.0.0.0.@.R _ _ _ ) _ ) ) _ _ ) _ _ ) _ R @.0.0.0.",
+"0.0.0.0.@.........................@...@.0.0.0.0.0.0.0.0.@.............................@.0.0.0.0.",
+"0.0.0.0.p.p.p.p.p.p.p.p.p.p.p.p.p.p.r.p.0.0.0.0.0.0.0.0.p.p.p.p.p.p.p.p.p.p.p.p.p.p.p.p.0.0.0.0.",
+"0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.",
+"0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0."
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/unequal-web.png b/icons/unequal-web.png
new file mode 100644 (file)
index 0000000..3d7c814
Binary files /dev/null and b/icons/unequal-web.png differ
diff --git a/icons/unequal.ico b/icons/unequal.ico
new file mode 100644 (file)
index 0000000..8af7618
Binary files /dev/null and b/icons/unequal.ico differ
diff --git a/icons/unequal.rc b/icons/unequal.rc
new file mode 100644 (file)
index 0000000..eec3d45
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "unequal.ico"
diff --git a/icons/unequal.sav b/icons/unequal.sav
new file mode 100644 (file)
index 0000000..c414513
--- /dev/null
@@ -0,0 +1,25 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :7:Unequal
+PARAMS  :3:4de
+CPARAMS :3:4de
+SEED    :15:143029490219212
+DESC    :37:0D,0,0L,0,0,0,0,0,0D,0U,0R,0,0,0,0,4,
+AUXINFO :34:f51274dc41e0a39caa38942fc525ed0108
+NSTATES :2:16
+STATEPOS:1:6
+MOVE    :6:R2,1,4
+MOVE    :6:R1,2,4
+MOVE    :6:R0,0,4
+MOVE    :6:R2,3,1
+MOVE    :6:R3,2,1
+MOVE    :6:R0,2,3
+MOVE    :6:R2,2,2
+MOVE    :6:R0,3,2
+MOVE    :6:R1,3,3
+MOVE    :6:R0,1,1
+MOVE    :6:R1,1,2
+MOVE    :6:R3,1,3
+MOVE    :6:R3,0,2
+MOVE    :6:R2,0,3
+MOVE    :6:R1,0,1
diff --git a/icons/unruly-16d24.png b/icons/unruly-16d24.png
new file mode 100644 (file)
index 0000000..287ff81
Binary files /dev/null and b/icons/unruly-16d24.png differ
diff --git a/icons/unruly-16d4.png b/icons/unruly-16d4.png
new file mode 100644 (file)
index 0000000..2a527bc
Binary files /dev/null and b/icons/unruly-16d4.png differ
diff --git a/icons/unruly-16d8.png b/icons/unruly-16d8.png
new file mode 100644 (file)
index 0000000..287ff81
Binary files /dev/null and b/icons/unruly-16d8.png differ
diff --git a/icons/unruly-32d24.png b/icons/unruly-32d24.png
new file mode 100644 (file)
index 0000000..e18bda0
Binary files /dev/null and b/icons/unruly-32d24.png differ
diff --git a/icons/unruly-32d4.png b/icons/unruly-32d4.png
new file mode 100644 (file)
index 0000000..bce6536
Binary files /dev/null and b/icons/unruly-32d4.png differ
diff --git a/icons/unruly-32d8.png b/icons/unruly-32d8.png
new file mode 100644 (file)
index 0000000..e18bda0
Binary files /dev/null and b/icons/unruly-32d8.png differ
diff --git a/icons/unruly-48d24.png b/icons/unruly-48d24.png
new file mode 100644 (file)
index 0000000..3fc174a
Binary files /dev/null and b/icons/unruly-48d24.png differ
diff --git a/icons/unruly-48d4.png b/icons/unruly-48d4.png
new file mode 100644 (file)
index 0000000..61e8bf2
Binary files /dev/null and b/icons/unruly-48d4.png differ
diff --git a/icons/unruly-48d8.png b/icons/unruly-48d8.png
new file mode 100644 (file)
index 0000000..3fc174a
Binary files /dev/null and b/icons/unruly-48d8.png differ
diff --git a/icons/unruly-base.png b/icons/unruly-base.png
new file mode 100644 (file)
index 0000000..05bb433
Binary files /dev/null and b/icons/unruly-base.png differ
diff --git a/icons/unruly-ibase.png b/icons/unruly-ibase.png
new file mode 100644 (file)
index 0000000..05bb433
Binary files /dev/null and b/icons/unruly-ibase.png differ
diff --git a/icons/unruly-ibase4.png b/icons/unruly-ibase4.png
new file mode 100644 (file)
index 0000000..a20d796
Binary files /dev/null and b/icons/unruly-ibase4.png differ
diff --git a/icons/unruly-icon.c b/icons/unruly-icon.c
new file mode 100644 (file)
index 0000000..c4e6492
--- /dev/null
@@ -0,0 +1,891 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"kXiXuXyXrXtXtXtXwXwXyXiXtXwXrXkX",
+"iX[ G &.s.r.p.s.}.;X&.M w.,X'.rX",
+"uXC 5 { c.t.9.r.eXNXO.7 8.cX=XqX",
+"tXf.l.>.~ ;.!.K.8.q.E f 7.rX}.wX",
+"wX-XAXq.- { VX0Xc a z q 9.BX;XwX",
+"wX}.eXl.) 1.E.S.D m _ } 0.!.F.rX",
+"wX&XrX3XvXE.w l b d E.SXj.5 H uX",
+"wX|.1X@X9XH.v S D m G.pXd.h Q uX",
+"uXQ v +.h.e.w.t.@X1Xb.u.r.q.r.tX",
+"uXP a X.l.r.w.y.6XdXM.w.t.i.p.tX",
+"uXT v L L #.XX/.n.m.u.r.q.e.t.tX",
+"uXS u l e | VXtXu.w.r.p.t.i.p.tX",
+"tX0.8.$./ @.l.d.r.t.q.t.q.e.y.tX",
+"wX:XNXE.d.;.9 g q.i.e.i.e.u.u.rX",
+"rX`.,XC.y.2.P Q r.p.t.p.y.u.a.yX",
+"kXrXqXrXtXtXuXuXtXtXtXtXtXrXyXkX"
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"lXlXcXcXcXcXcXvXvXvXvXvXvXvXvXvXbXbXbXnXvXxXcXcXcXbXbXbXnXvXlXlX",
+"lXkXpXpXpXpXiXrXeXeXeXrXeXeXeXrX8X7X8X7XeXaXpXpXpX9X7X8X7XwXlXlX",
+"cXpX` K U P W 4.6.5.5.1.5.5.5.4.H.E.U.^.8.F Y I I B.E.P.E.x.wXvX",
+"cXpXK g x j V u.a.p.p.q.a.p.a.u.3XdXtXcXg.y v z l XXiX0XjX!.6XnX",
+"cXpXY c N b D t.i.u.u.0.i.i.i.y.=XqX5XaXs.f N n n ].wX6XsXR.7XbX",
+"cXpXL g x j V p.s.s.s.q.u.u.u.t.2XsXrXxXh.a m v v |.aXrXlXW.7XnX",
+"cXiX^ J I K T 4.6.6.5.7.s.a.a.u.(.oX}.&X0.j B M n /.6X>X6XH.8XbX",
+"bX8XP.6X;X7X[.v b M d p.tX<X7X@XB l c c N n b b b `.7X>X8XL.8XbX",
+"bX8XY.rX8XsX@Xc b M s f.hX9XuX:XB x b n N c v b b XXaX0XsXR.7XbX",
+"bX8XU.eX8XaX+Xn M V g d.pX5XwX&XB c n n B n M M M }.rX5XtXU.8XbX",
+"bX8XU.pXeXgXXXa s h 8 f.mXiXjX5XC v m M v s f f s $XzXpXxX~.7XnX",
+"bX9XD.>X$X;X_.u.f.d.f.6.5.5.7.1.C M B c ] l.s.f.p.3.6.6.6.>.rXvX",
+"bX7XT.pX0XrX>XeXhXaXmX5.7 f a j n c n s 0.mXpXhXyXA u g u G aXxX",
+"bX8XY.uX8XeX-X,X0X5XiX6.g B M V n b M g 5.iX5X9X1XI v B v Y pXcX",
+"bX7XE.dXeXiX<X5XuXqXjX6.p m v M v c n s 6.jXqXyX8XK l n l I pXcX",
+"bX9XF.1X*X:X X#X,X=X7X2.l Z B C Z C S c 4.6X=X>X%XI n V b T iXcX",
+"cXiXR M Z B H r.y.y.y.7.q.q.q.9. X:X*X<XS.0.u.u.e.7.w.q.w.1.rXvX",
+"cXpXP k b z Z y.p.i.i.w.a.a.a.u.<XiXeXdXU.q.a.p.t.r.s.p.s.6.eXvX",
+"cXpXU z m c S t.u.u.y.q.p.p.p.y.-XwX6XtXL.w.p.p.t.w.p.u.p.5.eXvX",
+"cXpXI z n c S s.g.f.g.0.w.e.e.q.2XsXtXhXT.q.a.p.t.e.p.i.a.5.eXvX",
+"cXpXY b B m A |  . .] 9.H.Z.D.M.D.U.L.T.g.7.q.q.8.6.w.0.q.1.rXvX",
+"cXpXI z n b m l h z t f.zXeXaX1X0.q.w.q.7.i.i.i.r.w.p.u.p.5.eXvX",
+"cXpXI z n b M m n N g d.sX6XeX-Xu.a.p.a.q.i.i.p.r.w.p.i.p.5.eXvX",
+"cXpXI z n v M b v M f f.hX9XyX>Xu.p.p.p.q.i.p.a.t.e.a.i.a.6.eXvX",
+"cXpXI l n v m m n N f p.yX2X8X%Xe.t.t.t.8.r.r.t.q.9.y.r.t.3.rXvX",
+"bX9XB.XX'..XE.2.6.5.9.] F L J I 7.r.w.e.6.w.w.e.9.8.r.w.e.2.rXvX",
+"bX7XE.iXqXdX>Xt.d.p.k.) s x k m w.s.p.p.w.p.p.a.y.r.a.p.a.6.eXvX",
+"bX8XP.0X6XyX%Xq.i.y.d._ l N m C q.p.u.i.0.u.i.i.r.w.p.u.p.5.rXvX",
+"nX7XE.jXsXxX>Xe.a.i.h./ f v z n w.s.p.a.q.p.p.a.t.e.a.p.s.5.eXvX",
+"vXwXx.!.R.!.A.2.6.5.8.{ K Y I T 1.6.5.5.1.5.5.6.3.2.6.5.5.1.yXcX",
+"lXlXwX6X7X7X9XrXeXeXeXyXpXpXpXiXrXeXeXeXrXeXeXeXrXrXeXrXeXyXkXlX",
+"lXlXvXnXbXnXbXvXvXvXvXcXcXcXcXcXvXvXvXvXvXvXvXvXvXvXvXvXvXcXlXlX"
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 256 2 ",
+"   c black",
+".  c #010101",
+"X  c #020202",
+"o  c gray1",
+"O  c #040404",
+"+  c gray2",
+"@  c #060606",
+"#  c #070707",
+"$  c gray3",
+"%  c #090909",
+"&  c gray4",
+"*  c #0B0B0B",
+"=  c #0C0C0C",
+"-  c gray5",
+";  c #0E0E0E",
+":  c gray6",
+">  c #101010",
+",  c #111111",
+"<  c gray7",
+"1  c #131313",
+"2  c gray8",
+"3  c #151515",
+"4  c #161616",
+"5  c gray9",
+"6  c #181818",
+"7  c #191919",
+"8  c gray10",
+"9  c #1B1B1B",
+"0  c gray11",
+"q  c #1D1D1D",
+"w  c #1E1E1E",
+"e  c gray12",
+"r  c #202020",
+"t  c gray13",
+"y  c #222222",
+"u  c #232323",
+"i  c gray14",
+"p  c #252525",
+"a  c gray15",
+"s  c #272727",
+"d  c #282828",
+"f  c gray16",
+"g  c #2A2A2A",
+"h  c gray17",
+"j  c #2C2C2C",
+"k  c #2D2D2D",
+"l  c gray18",
+"z  c #2F2F2F",
+"x  c gray19",
+"c  c #313131",
+"v  c #323232",
+"b  c gray20",
+"n  c #343434",
+"m  c #353535",
+"M  c gray21",
+"N  c #373737",
+"B  c gray22",
+"V  c #393939",
+"C  c #3A3A3A",
+"Z  c gray23",
+"A  c #3C3C3C",
+"S  c gray24",
+"D  c #3E3E3E",
+"F  c #3F3F3F",
+"G  c gray25",
+"H  c #414141",
+"J  c gray26",
+"K  c #434343",
+"L  c #444444",
+"P  c gray27",
+"I  c #464646",
+"U  c gray28",
+"Y  c #484848",
+"T  c #494949",
+"R  c gray29",
+"E  c #4B4B4B",
+"W  c #4C4C4C",
+"Q  c gray30",
+"!  c #4E4E4E",
+"~  c gray31",
+"^  c #505050",
+"/  c #515151",
+"(  c gray32",
+")  c #535353",
+"_  c gray33",
+"`  c #555555",
+"'  c #565656",
+"]  c gray34",
+"[  c #585858",
+"{  c gray35",
+"}  c #5A5A5A",
+"|  c #5B5B5B",
+" . c gray36",
+".. c #5D5D5D",
+"X. c gray37",
+"o. c #5F5F5F",
+"O. c #606060",
+"+. c gray38",
+"@. c #626262",
+"#. c gray39",
+"$. c #646464",
+"%. c #656565",
+"&. c gray40",
+"*. c #676767",
+"=. c #686868",
+"-. c DimGray",
+";. c #6A6A6A",
+":. c gray42",
+">. c #6C6C6C",
+",. c #6D6D6D",
+"<. c gray43",
+"1. c #6F6F6F",
+"2. c gray44",
+"3. c #717171",
+"4. c #727272",
+"5. c gray45",
+"6. c #747474",
+"7. c gray46",
+"8. c #767676",
+"9. c #777777",
+"0. c gray47",
+"q. c #797979",
+"w. c gray48",
+"e. c #7B7B7B",
+"r. c #7C7C7C",
+"t. c gray49",
+"y. c #7E7E7E",
+"u. c gray50",
+"i. c #808080",
+"p. c #818181",
+"a. c gray51",
+"s. c #838383",
+"d. c #848484",
+"f. c gray52",
+"g. c #868686",
+"h. c gray53",
+"j. c #888888",
+"k. c #898989",
+"l. c gray54",
+"z. c #8B8B8B",
+"x. c gray55",
+"c. c #8D8D8D",
+"v. c #8E8E8E",
+"b. c gray56",
+"n. c #909090",
+"m. c gray57",
+"M. c #929292",
+"N. c #939393",
+"B. c gray58",
+"V. c #959595",
+"C. c gray59",
+"Z. c #979797",
+"A. c #989898",
+"S. c gray60",
+"D. c #9A9A9A",
+"F. c #9B9B9B",
+"G. c gray61",
+"H. c #9D9D9D",
+"J. c gray62",
+"K. c #9F9F9F",
+"L. c #A0A0A0",
+"P. c gray63",
+"I. c #A2A2A2",
+"U. c gray64",
+"Y. c #A4A4A4",
+"T. c #A5A5A5",
+"R. c gray65",
+"E. c #A7A7A7",
+"W. c gray66",
+"Q. c #A9A9A9",
+"!. c #AAAAAA",
+"~. c gray67",
+"^. c #ACACAC",
+"/. c gray68",
+"(. c #AEAEAE",
+"). c #AFAFAF",
+"_. c gray69",
+"`. c #B1B1B1",
+"'. c #B2B2B2",
+"]. c gray70",
+"[. c #B4B4B4",
+"{. c gray71",
+"}. c #B6B6B6",
+"|. c #B7B7B7",
+" X c gray72",
+".X c #B9B9B9",
+"XX c gray73",
+"oX c #BBBBBB",
+"OX c #BCBCBC",
+"+X c gray74",
+"@X c gray",
+"#X c gray75",
+"$X c #C0C0C0",
+"%X c #C1C1C1",
+"&X c gray76",
+"*X c #C3C3C3",
+"=X c gray77",
+"-X c #C5C5C5",
+";X c #C6C6C6",
+":X c gray78",
+">X c #C8C8C8",
+",X c gray79",
+"<X c #CACACA",
+"1X c #CBCBCB",
+"2X c gray80",
+"3X c #CDCDCD",
+"4X c #CECECE",
+"5X c gray81",
+"6X c #D0D0D0",
+"7X c gray82",
+"8X c #D2D2D2",
+"9X c LightGray",
+"0X c gray83",
+"qX c #D5D5D5",
+"wX c gray84",
+"eX c #D7D7D7",
+"rX c #D8D8D8",
+"tX c gray85",
+"yX c #DADADA",
+"uX c gray86",
+"iX c gainsboro",
+"pX c #DDDDDD",
+"aX c gray87",
+"sX c #DFDFDF",
+"dX c gray88",
+"fX c #E1E1E1",
+"gX c #E2E2E2",
+"hX c gray89",
+"jX c #E4E4E4",
+"kX c gray90",
+"lX c #E6E6E6",
+"zX c #E7E7E7",
+"xX c gray91",
+"cX c #E9E9E9",
+"vX c #EAEAEA",
+"bX c gray92",
+"nX c #ECECEC",
+"mX c gray93",
+"MX c #EEEEEE",
+"NX c #EFEFEF",
+"BX c gray94",
+"VX c #F1F1F1",
+"CX c gray95",
+"ZX c #F3F3F3",
+"AX c #F4F4F4",
+"SX c gray96",
+"DX c #F6F6F6",
+"FX c gray97",
+"GX c #F8F8F8",
+"HX c #F9F9F9",
+"JX c gray98",
+"KX c #FBFBFB",
+"LX c gray99",
+"PX c #FDFDFD",
+"IX c #FEFEFE",
+"UX c white",
+/* pixels */
+"lXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlX",
+"lXlXlXlXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXkXlXlXlXlX",
+"lXlXlXzXnXbXbXbXbXbXbXmXmXmXmXmXmXnXmXmXmXmXmXmXMXMXMXMXMXMXnXbXbXbXbXbXnXMXMXMXMXMXMXnXlXlXlXlX",
+"lXlXzXgX4X5X5X5X5X5X4X<X,X,X,X,X,X<X,X,X,X,X,X<X=X*X*X*X*X&X1X6X5X5X5X5X4X=X*X*X*X*X&X3XlXlXlXlX",
+"lXkXnX4XL S F D D S K &.*.*.*.*.&.+.*.*.*.*.*.&.v.V.N.N.M.A.| V F D F S P v.C.N.B.B.N.6.pXxXlXlX",
+"lXkXbX5XS z v c v z A a.d.d.d.d.s.q.f.d.d.d.d.p.0XgXaXsXpXcX>.a b c v l F 9XrXwXwXwXkXx.tXcXlXlX",
+"lXkXbX5XF v n n n c S t.i.u.u.i.y.7.i.u.u.u.u.t.,XwX8X9X7XiX;.g M n n x H -X4X9X7X0XaXj.yXcXlXlX",
+"lXkXbX5XD c n b n c S y.p.i.i.i.u.8.p.i.i.i.i.y.<XrX0XqX9XsX;.f m b n x H >X7XwX0XeXdXj.tXcXlXlX",
+"lXkXbX5XF v m n n v S t.i.u.u.i.y.7.p.i.i.i.i.y.,XwX9X9X7XpX-.f m b n x H ;X5X0X8XqXsXj.tXcXlXlX",
+"lXkXbX5XA k x z x k C p.d.s.s.s.a.8.p.i.i.i.i.y.7XsXiXiXyXlX>.d m v b z G 3XaXgXfXgXjXl.tXcXlXlX",
+"lXkXnX4XU K P L P K Y 2.5.4.4.4.2.3.i.y.y.y.u.w.~.}.].[.'.oX+.z V N B n J  X=X%X%X%X*Xs.yXcXlXlX",
+"lXkXMX=Xn.wX>X<X>X9X(.z b b b m d f.eX,X2X<X8X(.l z z z l z B m n n m v H #X<X>X>X>X2Xf.yXcXlXlX",
+"lXkXMX*XV.eX3X7X4XfX}.z n b b M s x.hXqXeXwXaX Xx c v v v m B x c c c x G 4XuXeXrXeXiXl.tXcXlXlX",
+"lXkXMX*XB.wX9XwX9XkX[.z n b b M d l.dX8X0X9XuX}.x v n b n m B c b n n v G 1XeX0XqX0XtXk.tXcXlXlX",
+"lXkXMX*XB.wX7X0X7XjX{.z n b b M s l.fX8XqX9XiX}.x c b b b m B x b b b v G 1XrX0XqX0XtXk.tXcXlXlX",
+"lXkXMX*XB.rXwXtXeXzX{.v M M M B g l.aX6X9X7XyX[.z v n b n m V n M M N m J ,XwX8X9X8XeXk.tXcXlXlX",
+"lXkXMX*Xm.pXeXtXrXdX_.p d d s g 9 x.bXuXaXiXkX+Xc m M m m M v a f f g p N 0XfXpXaXpXhXx.tXcXlXlX",
+"lXkXMX-Xf.&X|. X|.+XT.s.x.l.l.l.x.>.&.=.*.*.-.O.V B B B V v %.n.k.l.l.x.u.$.=.*.*.=.%.&.dXxXlXlX",
+"lXkXMX*XB.fXqXeXeXuX&X9XjXdXfXaXbX&.0 f s s s z m z c x n a c.mXaXfXsXxX%Xd f f f h u ( jXzXlXlX",
+"lXkXMX*XN.aX8X0X0XtX#X;XqX8X8X6XuX=.f N m m M V n c b b M f g.aX6X8X7XtX}.n m m m M x ' hXzXlXlX",
+"lXkXMX*XN.sX9XqX0XtX$X,XrXqXqX9XaX*.s m b b n N n c n b M f h.fX8XqX0XuX Xv b b b n k ` hXzXlXlX",
+"lXkXMX*XN.pX7X9X9XrX#X:XeX9X0X8XpX*.s m b b m B m c n b N g h.sX7X0X8XyX|.v n n b m l ` hXzXlXlX",
+"lXkXMX*XZ.lXyXiXuXdX;X4XaXyXuXrXjX=.a m b b b m c z v c m p k.zXrXuXtXfXOXx c c c v h _ hXzXlXlX",
+"lXkXMX-Xs.+X[.}.{..XR.`.+XXXoX.X&X$.n D A A Z F K J J J L V u.=X.XoXXX$XY.C A A A S N [ hXzXlXlX",
+"lXkXbX5XS l c c c l C 9.q.0.0.q.9.4.y.t.t.t.t.e.*X5X2X3X2X7Xk.6.q.0.0.w.4.0.y.t.t.y.r.,.sXxXlXlX",
+"lXkXbX5XD c n b n c S i.a.p.a.a.p.8.a.p.p.p.p.u.2XyXeXeXwXuXm.t.s.a.p.s.w.e.a.p.p.p.i.<.aXxXlXlX",
+"lXkXbX5XD c n b n c S y.i.i.i.i.u.7.p.i.i.i.i.y.<XeX0X0X0XrXb.e.p.i.i.p.0.e.p.i.i.i.u.<.aXxXlXlX",
+"lXkXbX5XD c n b n c S y.p.i.i.i.u.8.p.i.i.i.i.y.<XrX0XqX0XtXn.r.p.i.i.a.0.e.p.i.i.i.u.<.aXxXlXlX",
+"lXkXbX5XD c n b b c S t.i.u.u.u.y.8.s.p.a.a.a.u.<XeX0X0X9XrXn.r.p.i.i.a.q.e.p.i.i.p.u.<.aXxXlXlX",
+"lXkXbX5XD v n b n c S a.f.f.f.f.f.5.q.q.q.q.0.0.4XiXrXtXrXpXn.w.i.u.u.p.0.w.i.u.u.u.y.<.sXxXlXlX",
+"lXkXbX5XH M V B B N S ^ ^ ^ ^ / R 9.T.J.K.K.I.M.k.m.b.n.n.n.7.7.8.8.7.9.1.3.9.7.8.8.6.;.sXxXlXlX",
+"lXkXbX5XD c b b b v M x h j j z y x.cXtXiXuXgX$X6.t.e.r.r.w.7.a.i.p.i.a.q.e.a.p.p.p.i.<.aXxXlXlX",
+"lXkXbX5XD c n b b v N m v n n N g l.sX7X9X8XtXXXq.s.p.p.p.i.8.i.i.i.i.p.0.e.p.i.i.i.u.<.aXxXlXlX",
+"lXkXbX5XD c n b b v N n c n b M f l.fX8XqX0XuXoX0.a.i.i.i.u.8.p.i.i.i.a.0.e.p.i.i.i.u.<.aXxXlXlX",
+"lXkXbX5XF v n n n b B m c n b N g k.sX7X0X8XtXXX0.p.i.i.i.u.7.i.i.i.u.p.0.w.p.u.i.i.u.<.aXxXlXlX",
+"lXkXbX5XS l x x x z n b c b b M s c.xXtXuXyXfX$Xw.s.p.a.a.p.9.a.p.a.p.s.w.r.s.p.a.a.p.<.aXxXlXlX",
+"lXkXnX4XP F H H H G H A Z Z Z S v y.%X}. X|.OXY.4.w.0.0.q.0.1.q.0.0.0.w.4.6.q.0.0.q.9.:.sXxXlXlX",
+"lXkXMX=Xv.9X-X>X;X4X'.7.y.r.t.r.p.Q j v c c z C 0.e.e.e.e.w.3.e.e.e.w.r.6.8.r.e.e.e.q.>.sXxXlXlX",
+"lXkXMX*XC.rX4X7X4XdX+Xq.s.p.p.i.g.~ j v c v c A y.a.p.p.p.i.9.a.p.p.p.s.q.r.a.p.p.p.i.<.aXxXlXlX",
+"lXkXMX*XN.wX9XwX0XhXXX0.p.i.i.u.d.~ j n n n b A t.p.i.i.i.u.7.p.i.i.i.p.0.e.p.i.i.i.u.<.aXxXlXlX",
+"lXkXMX*XB.wX7X0X8XgXoX0.a.i.i.u.f.~ j n b b b A t.p.i.i.i.u.8.p.i.i.i.a.0.e.p.i.i.i.u.<.aXxXlXlX",
+"lXkXMX*XB.wX0XeXqXjXXX0.a.i.i.u.f.^ k M m m m S y.p.i.i.p.u.8.p.i.i.i.a.q.e.p.i.i.p.u.<.sXxXlXlX",
+"lXkXMX&XN.kXaXdXsXlXOX9.p.u.u.y.d.R d x z x k N r.i.u.u.u.y.6.i.u.u.u.p.9.q.i.u.u.u.y.:.aXxXlXlX",
+"lXkXnX3X6.x.j.j.j.l.i.:.<.<.<.<.1.X.) ` ` ` _ [ ,.<.<.<.<.<.;.<.<.<.<.<.:.>.<.<.<.<.:.4.fXzXlXlX",
+"lXlXlXlXpXtXyXtXtXtXuXsXaXaXaXsXaXgXjXhXhXhXjXhXsXaXaXaXaXsXsXaXaXaXaXaXsXsXaXaXaXsXaXfXlXlXlXlX",
+"lXlXlXlXxXcXcXcXcXcXcXxXxXxXxXxXxXzXzXzXzXzXzXzXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXzXlXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlX",
+"lXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlXlX"
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/unruly-web.png b/icons/unruly-web.png
new file mode 100644 (file)
index 0000000..f8fe159
Binary files /dev/null and b/icons/unruly-web.png differ
diff --git a/icons/unruly.ico b/icons/unruly.ico
new file mode 100644 (file)
index 0000000..0f645f1
Binary files /dev/null and b/icons/unruly.ico differ
diff --git a/icons/unruly.rc b/icons/unruly.rc
new file mode 100644 (file)
index 0000000..2aa7f40
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "unruly.ico"
diff --git a/icons/unruly.sav b/icons/unruly.sav
new file mode 100644 (file)
index 0000000..0f7ca1b
--- /dev/null
@@ -0,0 +1,22 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :6:Unruly
+PARAMS  :5:6x6de
+CPARAMS :5:6x6de
+DESC    :10:faCADAJeBd
+NSTATES :2:15
+STATEPOS:2:15
+MOVE    :6:P0,1,2
+MOVE    :6:P0,4,2
+MOVE    :6:P0,3,3
+MOVE    :6:P0,3,0
+MOVE    :6:P0,5,1
+MOVE    :6:P0,2,1
+MOVE    :6:P1,4,0
+MOVE    :6:P1,1,1
+MOVE    :6:P1,5,2
+MOVE    :6:P0,0,2
+MOVE    :6:P1,0,3
+MOVE    :6:P1,0,0
+MOVE    :6:P1,0,4
+MOVE    :6:P0,2,4
diff --git a/icons/untangle-16d24.png b/icons/untangle-16d24.png
new file mode 100644 (file)
index 0000000..7df6f04
Binary files /dev/null and b/icons/untangle-16d24.png differ
diff --git a/icons/untangle-16d4.png b/icons/untangle-16d4.png
new file mode 100644 (file)
index 0000000..87c2bbf
Binary files /dev/null and b/icons/untangle-16d4.png differ
diff --git a/icons/untangle-16d8.png b/icons/untangle-16d8.png
new file mode 100644 (file)
index 0000000..c250437
Binary files /dev/null and b/icons/untangle-16d8.png differ
diff --git a/icons/untangle-32d24.png b/icons/untangle-32d24.png
new file mode 100644 (file)
index 0000000..9990f39
Binary files /dev/null and b/icons/untangle-32d24.png differ
diff --git a/icons/untangle-32d4.png b/icons/untangle-32d4.png
new file mode 100644 (file)
index 0000000..b14a140
Binary files /dev/null and b/icons/untangle-32d4.png differ
diff --git a/icons/untangle-32d8.png b/icons/untangle-32d8.png
new file mode 100644 (file)
index 0000000..c561489
Binary files /dev/null and b/icons/untangle-32d8.png differ
diff --git a/icons/untangle-48d24.png b/icons/untangle-48d24.png
new file mode 100644 (file)
index 0000000..9a53fb3
Binary files /dev/null and b/icons/untangle-48d24.png differ
diff --git a/icons/untangle-48d4.png b/icons/untangle-48d4.png
new file mode 100644 (file)
index 0000000..10d8fcc
Binary files /dev/null and b/icons/untangle-48d4.png differ
diff --git a/icons/untangle-48d8.png b/icons/untangle-48d8.png
new file mode 100644 (file)
index 0000000..fa9b739
Binary files /dev/null and b/icons/untangle-48d8.png differ
diff --git a/icons/untangle-base.png b/icons/untangle-base.png
new file mode 100644 (file)
index 0000000..0079d73
Binary files /dev/null and b/icons/untangle-base.png differ
diff --git a/icons/untangle-ibase.png b/icons/untangle-ibase.png
new file mode 100644 (file)
index 0000000..32dd6c7
Binary files /dev/null and b/icons/untangle-ibase.png differ
diff --git a/icons/untangle-ibase4.png b/icons/untangle-ibase4.png
new file mode 100644 (file)
index 0000000..b97cfc9
Binary files /dev/null and b/icons/untangle-ibase4.png differ
diff --git a/icons/untangle-icon.c b/icons/untangle-icon.c
new file mode 100644 (file)
index 0000000..b958ef5
--- /dev/null
@@ -0,0 +1,792 @@
+/* XPM */
+static const char *const xpm_icon_0[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 235 2 ",
+"   c #D5D5D5",
+".  c #D5D5D5",
+"X  c #D4D4D5",
+"o  c #DBDBD4",
+"O  c #E0E0D2",
+"+  c #D5D5D6",
+"@  c gray83",
+"#  c #D5D5D5",
+"$  c #D5D5D5",
+"%  c #D5D5D5",
+"&  c gray84",
+"*  c #D2D2D2",
+"=  c #D5D5D5",
+"-  c #D5D5D5",
+";  c #D3D3D5",
+":  c #E0E0D6",
+">  c #9D9DCD",
+",  c #6363C6",
+"<  c #DFDFD3",
+"1  c #D6D6D8",
+"2  c #D5D5D5",
+"3  c #D3D3D4",
+"4  c #D6D6D8",
+"5  c #CDCDCC",
+"6  c #C3C3C3",
+"7  c #D7D7D7",
+"8  c #D5D5D5",
+"9  c #D4D4D5",
+"0  c #DCDCD6",
+"q  c #B1B1CD",
+"w  c #8484BF",
+"e  c #C8C8C1",
+"r  c #C6C6C8",
+"t  c #D2D2D4",
+"y  c #E0E0D9",
+"u  c #D9D9D1",
+"i  c #BDBDBF",
+"p  c #D6D6D5",
+"a  c #D5D5D5",
+"s  c #D5D5D5",
+"d  c #D5D5D5",
+"f  c #D5D5D7",
+"g  c #D5D5CF",
+"h  c #D1D1C5",
+"j  c #D6D6D8",
+"k  c #C8C8C9",
+"l  c #C9C9C3",
+"z  c #A3A3C8",
+"x  c #9999C1",
+"c  c #D8D8D1",
+"v  c #D5D5D6",
+"b  c #D5D5D5",
+"n  c #D5D5D5",
+"m  c #D5D5D5",
+"M  c #D5D5D5",
+"N  c gray84",
+"B  c #CECED0",
+"V  c #C3C3C5",
+"C  c #DBDBDA",
+"Z  c #D4D4D6",
+"A  c #D3D3C5",
+"S  c #7070BE",
+"D  c #6E6EC4",
+"F  c #E3E3D5",
+"G  c #D3D3D5",
+"H  c #D5D5D5",
+"J  c #D5D5D5",
+"K  c #D5D5D5",
+"L  c #D4D4D5",
+"P  c gray83",
+"I  c #D7D7D7",
+"U  c gray83",
+"Y  c gray76",
+"T  c #C8C8C8",
+"R  c #C0C0C0",
+"E  c #C9C9CB",
+"W  c #DFDFD6",
+"Q  c #D4D4CA",
+"!  c #BFBFC0",
+"~  c #D7D7D7",
+"^  c #D5D5D5",
+"/  c #D5D5D5",
+"(  c #D5D5D5",
+")  c #D7D7D5",
+"_  c #DBDBD6",
+"`  c #D7D7D7",
+"'  c LightGray",
+"]  c gray76",
+"[  c #B1B1B1",
+"{  c #CDCDCD",
+"}  c gray84",
+"|  c #D8D8D7",
+" . c #D3D3D5",
+".. c #D7D7D9",
+"X. c #CBCBCA",
+"o. c #C1C1C1",
+"O. c gray84",
+"+. c #D5D5D5",
+"@. c #D7D7D5",
+"#. c #CECED3",
+"$. c #BDBDCF",
+"%. c #C9C9C7",
+"&. c gray77",
+"*. c gray79",
+"=. c gray77",
+"-. c gray85",
+";. c gray83",
+":. c #D5D5D5",
+">. c #D5D5D5",
+",. c gray83",
+"<. c #D8D8D8",
+"1. c #CACACA",
+"2. c #D0D0D0",
+"3. c gray84",
+"4. c #E0E0D6",
+"5. c #A7A7D0",
+"6. c #3333BD",
+"7. c #C3C3B9",
+"8. c #CFCFD1",
+"9. c gray83",
+"0. c #C5C5C5",
+"q. c #D8D8D8",
+"w. c #D5D5D5",
+"e. c gray83",
+"r. c #D5D5D5",
+"t. c #D5D5D5",
+"y. c #D5D5D5",
+"u. c #D7D7D7",
+"i. c gray84",
+"p. c #D5D5D5",
+"a. c #D5D5D4",
+"s. c #D7D7D7",
+"d. c #B1B1B4",
+"f. c gray75",
+"g. c gray79",
+"h. c gray75",
+"j. c #B4B4B4",
+"k. c #CECECE",
+"l. c gray83",
+"z. c #D8D8D8",
+"x. c #D7D7D7",
+"c. c #D5D5D5",
+"v. c gray83",
+"b. c gray83",
+"n. c #D5D5D5",
+"m. c #D5D5D5",
+"M. c gray83",
+"N. c #D8D8D8",
+"B. c #CDCDCC",
+"V. c #B5B5B8",
+"C. c #D3D3D4",
+"Z. c gray84",
+"A. c #BCBCBC",
+"S. c gray78",
+"D. c #C1C1C1",
+"F. c gray77",
+"G. c gray80",
+"H. c gray83",
+"J. c #D8D8D8",
+"K. c #D7D7D7",
+"L. c #D5D5D5",
+"P. c #D5D5D5",
+"I. c #D5D5D5",
+"U. c gray84",
+"Y. c #D0D0D2",
+"T. c #CFCFC0",
+"R. c #C7C7C1",
+"E. c #D2D2D3",
+"W. c #C3C3C4",
+"Q. c #D8D8D8",
+"!. c #D5D5D5",
+"~. c gray81",
+"^. c #C6C6C6",
+"/. c gray76",
+"(. c gray77",
+"). c gray80",
+"_. c #D5D5D5",
+"`. c #D5D5D5",
+"'. c #D5D5D5",
+"]. c #D4D4D5",
+"[. c #DDDDD3",
+"{. c #6666C6",
+"}. c #9F9FC4",
+"|. c #CECEC5",
+" X c #CACAC6",
+".X c #D5D5D4",
+"XX c #D5D5D5",
+"oX c #D7D7D7",
+"OX c #D8D8D8",
+"+X c gray84",
+"@X c gray81",
+"#X c gray78",
+"$X c #D2D2D2",
+"%X c gray84",
+"&X c #D5D5D5",
+"*X c #D4D4D5",
+"=X c #DBDBD4",
+"-X c #8484CE",
+";X c #A7A7C4",
+":X c #BABAB4",
+">X c #9D9DB7",
+",X c #D4D4D6",
+"<X c #D5D5D5",
+"1X c #D5D5D5",
+"2X c gray83",
+"3X c #D5D5D5",
+"4X c gray84",
+"5X c #D8D8D8",
+"6X c #D5D5D5",
+"7X c #D5D5D5",
+"8X c #D5D5D5",
+"9X c #D4D4D5",
+"0X c #E2E2D5",
+"qX c #DFDFD8",
+"wX c #C1C1C9",
+"eX c #3838C7",
+"rX c #C3C3D0",
+"tX c #DADAD6",
+"yX c #D4D4D5",
+"uX c #D5D5D5",
+"iX c #D5D5D5",
+"pX c gray83",
+"aX c #D5D5D5",
+"sX c #D5D5D5",
+"dX c #D5D5D5",
+"fX c #D5D5D5",
+"gX c #D5D5D5",
+"hX c #D2D2D5",
+"jX c #D3D3D5",
+"kX c #D7D7D7",
+"lX c #D2D2D1",
+"zX c #D5D5D5",
+"xX c #D5D5D5",
+"cX c #D5D5D5",
+"vX c white",
+/* pixels */
+"      . X o O + @ # $ % & * = - ",
+"      ; : > , < 1 2 3 4 5 6 7 8 ",
+"      9 0 q w e r t y u i p a s ",
+"      d f g h j k l z x c v b n ",
+"    m M N B V C Z A S D F G H n ",
+"J K L P I U Y T R E W Q ! ~ ^ / ",
+"( ) _ ` ' ] [ { } |  ...X.o.O.+.",
+"@.#.$.%.&.*.=.-.;.:.>.,.<.1.2.3.",
+"4.5.6.7.8.9.0.q.w.e.r.t.y.u.i.p.",
+"a.s.d.f.g.h.j.k.l.z.x.c.v.b.n.m.",
+"M.N.B.V.C.Z.A.S.D.F.G.H.J.K.L.P.",
+"I.U.Y.T.R.E.W.Q.!.~.^./.(.)._.`.",
+"'.].[.{.}.|. X.XXXoXOX+X@X#X$X%X",
+"&X*X=X-X;X:X>X,X<X1X2X3X4X5X6Xs ",
+"7X8X9X0XqXwXeXrXtXyXm uXiXpXaXsX",
+"dXfXgXhXjXkXlXzXxXcX            "
+};
+
+/* XPM */
+static const char *const xpm_icon_1[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 181 2 ",
+"   c #73739F",
+".  c #4444AD",
+"X  c #5E5EA8",
+"o  c #4848B0",
+"O  c #6B6BA9",
+"+  c #7777AD",
+"@  c #6161BB",
+"#  c #6D6DBB",
+"$  c #7878B3",
+"%  c #7878B8",
+"&  c #0101D2",
+"*  c #0505D5",
+"=  c #0B0BD1",
+"-  c #0808D9",
+";  c #2121C4",
+":  c #2E2EC8",
+">  c #0101E4",
+",  c #0000E8",
+"<  c #0000F2",
+"1  c #0000F5",
+"2  c #0000F9",
+"3  c #4545C1",
+"4  c #4141C7",
+"5  c #6666C0",
+"6  c #868695",
+"7  c #959595",
+"8  c #999990",
+"9  c #9B9B9B",
+"0  c gray62",
+"q  c #8E8EA1",
+"w  c #9292AA",
+"e  c #8080BE",
+"r  c #8888BD",
+"t  c #9C9CB1",
+"y  c #9292BF",
+"u  c #9494BF",
+"i  c #A9A9A9",
+"p  c #AAAAAA",
+"a  c #AAAAAB",
+"s  c gray67",
+"d  c #AEAEA9",
+"f  c #A9A9AD",
+"g  c #ABABAC",
+"h  c #ACACAC",
+"j  c gray68",
+"k  c #AEAEAE",
+"l  c #AFAFAF",
+"z  c #B8B8A5",
+"x  c #B1B1AC",
+"c  c #B2B2AC",
+"v  c #B5B5AD",
+"b  c #A6A6B7",
+"n  c #AFAFB0",
+"m  c #AFAFB3",
+"M  c gray69",
+"N  c #B1B1B1",
+"B  c #B2B2B1",
+"V  c #B2B2B2",
+"C  c #B2B2B3",
+"Z  c gray70",
+"A  c #B6B6B1",
+"S  c #B1B1B4",
+"D  c #B3B3B4",
+"F  c #B4B4B4",
+"G  c gray71",
+"H  c #B6B6B6",
+"J  c #B7B7B7",
+"K  c #BDBDB7",
+"L  c #B7B7B8",
+"P  c #B7B7BC",
+"I  c gray72",
+"U  c #B9B9B9",
+"Y  c gray73",
+"T  c #BBBBBB",
+"R  c #BABABF",
+"E  c #BCBCBC",
+"W  c gray74",
+"Q  c #BEBEBF",
+"!  c gray75",
+"~  c #C0C0B4",
+"^  c #C1C1B4",
+"/  c #C1C1B6",
+"(  c #C7C7B7",
+")  c #C2C2BE",
+"_  c #C2C2BF",
+"`  c #BFBFC0",
+"'  c #BCBCC5",
+"]  c #C0C0C0",
+"[  c #C0C0C1",
+"{  c #C1C1C1",
+"}  c gray76",
+"|  c #C3C3C3",
+" . c #C5C5C1",
+".. c #C7C7C2",
+"X. c gray77",
+"o. c #C5C5C5",
+"O. c gray78",
+"+. c #C9C9C7",
+"@. c #C7C7C8",
+"#. c gray79",
+"$. c #CACAC9",
+"%. c #C9C9CA",
+"&. c #CACACA",
+"*. c #CBCBCB",
+"=. c gray80",
+"-. c #CDCDCD",
+";. c #CECECD",
+":. c #CCCCCE",
+">. c #CECECE",
+",. c #CECECF",
+"<. c gray81",
+"1. c #D0D0C2",
+"2. c #D7D7CF",
+"3. c #D8D8CA",
+"4. c #D9D9CC",
+"5. c #DEDECE",
+"6. c #D0D0D0",
+"7. c #D1D1D0",
+"8. c gray82",
+"9. c #D1D1D3",
+"0. c #D2D2D2",
+"q. c LightGray",
+"w. c #D4D4D0",
+"e. c #D7D7D1",
+"r. c #D1D1D4",
+"t. c #D2D2D4",
+"y. c #D3D3D4",
+"u. c #D2D2D5",
+"i. c #D3D3D5",
+"p. c #D3D3D6",
+"a. c gray83",
+"s. c #D4D4D5",
+"d. c #D5D5D5",
+"f. c #D6D6D5",
+"g. c #D5D5D6",
+"h. c #D4D4D7",
+"j. c gray84",
+"k. c #D6D6D7",
+"l. c #D7D7D7",
+"z. c #DADAD1",
+"x. c #DCDCD2",
+"c. c #DBDBD4",
+"v. c #D8D8D7",
+"b. c #DADAD7",
+"n. c #DCDCD4",
+"m. c #DFDFD5",
+"M. c #D5D5D8",
+"N. c #D7D7D8",
+"B. c #D6D6D9",
+"V. c #D7D7D9",
+"C. c #D8D8D8",
+"Z. c #D9D9D8",
+"A. c #D8D8D9",
+"S. c gray85",
+"D. c #DADAD8",
+"F. c #DBDBD9",
+"G. c #D9D9DA",
+"H. c #DADADA",
+"J. c #DADADB",
+"K. c gray86",
+"L. c #DDDDD8",
+"P. c #DFDFD8",
+"I. c #DCDCDA",
+"U. c #DFDFDA",
+"Y. c #DBDBDC",
+"T. c #DADADE",
+"R. c gainsboro",
+"E. c gray87",
+"W. c #E0E0CF",
+"Q. c #E2E2D4",
+"!. c #E3E3D5",
+"~. c #E0E0D6",
+"^. c #E0E0D7",
+"/. c #E4E4D6",
+"(. c #E6E6DB",
+"). c #E0E0DC",
+"_. c #E8E8DB",
+"`. c #EDEDDE",
+"'. c gray88",
+"]. c #E1E1E1",
+"[. c gray89",
+/* pixels */
+"d.d.d.d.d.d.d.d.d.d.d.d.d.d.d.d.d.d.d.d.d.d.d.d.d.d.d.C.d.d.d.d.",
+"d.d.d.d.d.d.d.d.d.d.d.C.x.M.d.d.d.d.d.d.d.d.d.d.d.d.d.-.d.d.d.d.",
+"d.d.d.d.d.d.d.d.d.w.C.' + q.d.d.d.d.d.d.d.d.d.d.d.C.N o.C.d.d.d.",
+"d.d.d.d.d.d.d.d.d.r.Q.3 < + (.d.q.d.d.d.d.d.d.d.R.T H C.d.d.d.d.",
+"d.d.d.d.d.d.d.d.d.d.m.e ; w Z %.C.C.d.d.d.d.d.C.o.h C.d.d.d.d.d.",
+"d.d.d.d.d.d.d.d.d.d.M.z.z ).%.Z h %.C.C.d.r.M.-.s C.d.d.d.d.d.d.",
+"d.d.d.d.d.d.d.d.d.d.d.r.N d.v.C.-.N h o.C.!.5.s -.C.d.d.d.d.d.d.",
+"d.d.d.d.d.d.d.d.d.d.d.w.N d.d.d.C.R.-.H v 5 o _ C.d.d.d.d.d.d.d.",
+"d.d.d.d.d.d.d.d.d.d.d.d.N d.d.d.d.d.C.[.d * > R U.r.d.d.d.d.d.d.",
+"d.d.d.d.d.d.d.d.d.d.d.d.h d.d.d.C.C.{ s K y + Z Y.d.d.d.d.d.d.d.",
+"d.d.d.d.d.d.d.d.d.d.d.d.h d.R.d.{ h H q.Y.U.(.L H C.d.d.d.d.d.d.",
+"d.d.d.d.d.d.d.d.d.d.q.C.H q.{ h H q.R.d.r.q.r.U.D T R.r.d.d.d.d.",
+"d.d.d.d.d.d.d.d.d.d.C.C.0 s T q.Y.d.d.d.d.d.d.d.Y.N W R.d.d.d.d.",
+"d.d.d.d.d.d.d.d.C.q.W N 9 -.R.d.d.d.d.d.d.d.d.d.r.U.h { C.d.d.d.",
+"d.d.d.d.U.C.C.q.W h T C.H q.d.d.d.d.d.d.d.d.d.d.d.d.C.h *.C.d.d.",
+"d.d.d.w.b %.W s W d.C.C.N -.d.d.d.d.d.d.d.d.d.d.d.d.d.d.q.d.d.d.",
+"d.r.!.% 1 . ( Y.).d.r.C.N -.C.d.d.d.d.d.d.d.d.d.d.d.d.d.d.d.d.d.",
+"d.r.!.u & X / D o.d.C.R.H %.v.d.d.d.d.d.d.d.d.d.d.d.d.d.d.d.d.d.",
+"d.d.r.)./ 8 :.*.H h N %.Z 2.R.d.d.d.d.d.d.d.d.d.d.d.d.d.d.d.d.d.",
+"d.d.d.d.M.s h C.R.M.o.R 7 i o.r.C.C.M.w.d.d.d.d.d.d.d.d.d.d.d.d.",
+"d.d.d.d.C.T H N C.d.C.[.I T K h N | q.C.C.C.d.r.d.d.d.d.d.d.d.d.",
+"d.d.d.d.C.{ W T { C.q.v.T %.E.M.%.T N N { q.C.Y.d.d.d.d.d.d.d.d.",
+"d.d.d.d.d.q.h C.s q.d.C.T { C.d.C.C.C.%.T N N { q.v.C.M.d.d.d.d.",
+"d.d.d.d.d.C.H w.-.h C.C.W { C.d.d.d.d.C.C.C.*.K h N W q.d.d.d.d.",
+"d.d.d.d.d.C.{ q C.K T [.T { C.d.d.d.d.d.d.d.C.C.C.-.W n -.d.d.d.",
+"d.d.d.d.r.!.# < @ `.f -.` W C.d.d.d.d.d.d.d.d.d.d.C.Y.C.d.d.d.d.",
+"d.d.d.d.r.m.r - O / :.D  . .C.d.d.d.d.d.d.d.d.d.d.d.d.w.d.d.d.d.",
+"d.d.d.d.d.d.m.1.v.{ n c 6 t U.r.d.d.d.d.d.d.d.d.d.d.d.d.d.d.d.d.",
+"d.d.d.d.d.d.d.M.d.C.C.c = > P U.r.d.d.d.d.d.d.d.d.d.d.d.d.d.d.d.",
+"d.d.d.d.d.d.d.d.d.d.M.4.4 : +.M.d.d.d.d.d.d.d.d.d.d.d.d.d.d.d.d.",
+"d.d.d.d.d.d.d.d.d.d.d.M.4.3.v.d.d.d.d.d.d.d.d.d.d.d.d.d.d.d.d.d.",
+"d.d.d.d.d.d.d.d.d.d.d.d.r.M.d.d.d.d.d.d.d.d.d.d.d.d.d.d.d.d.d.d."
+};
+
+/* XPM */
+static const char *const xpm_icon_2[] = {
+/* columns rows colors chars-per-pixel */
+"48 48 253 2 ",
+"   c #7B7B7B",
+".  c gray50",
+"X  c #222297",
+"o  c #0C0CBF",
+"O  c #0E0EBF",
+"+  c #1919B7",
+"@  c #1111BF",
+"#  c #1E1EB8",
+"$  c #2525A5",
+"%  c #3939AD",
+"&  c #2B2BBA",
+"*  c #3030B0",
+"=  c #3D3DB2",
+"-  c #454597",
+";  c #414199",
+":  c #4D4D98",
+">  c #666687",
+",  c #727288",
+"<  c #6C6C90",
+"1  c #4545A8",
+"2  c #4949A9",
+"3  c #5D5DA8",
+"4  c #6464A2",
+"5  c #6A6AA6",
+"6  c #6B6BAA",
+"7  c #0000C3",
+"8  c #0C0CC4",
+"9  c #0000CB",
+"0  c #0000CC",
+"q  c #1111C0",
+"w  c #1010C6",
+"e  c #0000DD",
+"r  c #0000E1",
+"t  c #0000E9",
+"y  c #0000EB",
+"u  c #0000F1",
+"i  c #0000F5",
+"p  c #0000F6",
+"a  c #0000FC",
+"s  c #0000FD",
+"d  c blue",
+"f  c #82828F",
+"g  c #909083",
+"h  c #90908E",
+"j  c #8A8A90",
+"k  c #929292",
+"l  c #959595",
+"z  c gray59",
+"x  c #979797",
+"c  c #989898",
+"v  c #9A9A9A",
+"b  c #9B9B9B",
+"n  c #9E9E9B",
+"m  c gray61",
+"M  c #9C9C9D",
+"N  c #9D9D9D",
+"B  c #9E9E9D",
+"V  c gray62",
+"C  c #9F9F9F",
+"Z  c #8181AC",
+"A  c #8383AF",
+"S  c #8E8EAE",
+"D  c #8F8FAE",
+"F  c #9696A0",
+"G  c #9696A5",
+"H  c #9090AE",
+"J  c #9494AF",
+"K  c #9A9AB0",
+"L  c #9E9EB1",
+"P  c #9E9EB2",
+"I  c #A0A0A0",
+"U  c gray63",
+"Y  c #A2A2A2",
+"T  c gray64",
+"R  c #A4A4A0",
+"E  c #A4A4A3",
+"W  c #A6A6A2",
+"Q  c #A4A4A4",
+"!  c #A4A4A5",
+"~  c #A5A5A5",
+"^  c gray65",
+"/  c #A7A7A7",
+"(  c #A9A9A1",
+")  c #A9A9A3",
+"_  c #A9A9A6",
+"`  c #ACACA4",
+"'  c #AFAFA4",
+"]  c #ACACA7",
+"[  c #A7A7A8",
+"{  c #A5A5AF",
+"}  c gray66",
+"|  c #A9A9A9",
+" . c #AAAAAA",
+".. c #AAAAAB",
+"X. c gray67",
+"o. c #AEAEAA",
+"O. c #ACACAC",
+"+. c gray68",
+"@. c #AEAEAE",
+"#. c #AFAFAF",
+"$. c #B0B0A4",
+"%. c #B2B2A5",
+"&. c #B0B0A9",
+"*. c #B4B4AF",
+"=. c #A7A7B4",
+"-. c #A8A8B3",
+";. c #B1B1B1",
+":. c #B0B0B3",
+">. c #B2B2B2",
+",. c #B4B4B5",
+"<. c gray71",
+"1. c #B6B6B5",
+"2. c #B6B6B6",
+"3. c #B6B6B7",
+"4. c #B7B7B7",
+"5. c #B4B4BA",
+"6. c #B5B5BA",
+"7. c gray72",
+"8. c #B8B8B9",
+"9. c #B9B9B9",
+"0. c gray73",
+"q. c #BBBBBB",
+"w. c #BCBCBB",
+"e. c #BBBBBC",
+"r. c #BBBBBD",
+"t. c #BCBCBC",
+"y. c gray74",
+"u. c gray",
+"i. c #BFBFBE",
+"p. c #BEBEBF",
+"a. c gray75",
+"s. c #CACABE",
+"d. c #C0C0C0",
+"f. c #C0C0C1",
+"g. c #C1C1C1",
+"h. c #C0C0C2",
+"j. c #C1C1C2",
+"k. c gray76",
+"l. c #C3C3C3",
+"z. c #C3C3C4",
+"x. c gray77",
+"c. c #C5C5C5",
+"v. c #C5C5C6",
+"b. c #C6C6C6",
+"n. c gray78",
+"m. c #CFCFC3",
+"M. c #C8C8C8",
+"N. c gray79",
+"B. c #C9C9CB",
+"V. c #CACACA",
+"C. c #CBCBCB",
+"Z. c #CACACC",
+"A. c #CBCBCD",
+"S. c gray80",
+"D. c #CDCDCD",
+"F. c #CECECE",
+"G. c gray81",
+"H. c #D2D2C3",
+"J. c #D6D6C5",
+"K. c #D1D1C9",
+"L. c #D6D6CD",
+"P. c #DADAC8",
+"I. c #DBDBCA",
+"U. c #D8D8CF",
+"Y. c #DDDDCC",
+"T. c #DDDDCD",
+"R. c #D0D0D0",
+"E. c gray82",
+"W. c #D2D2D2",
+"Q. c #D2D2D3",
+"!. c LightGray",
+"~. c #D4D4D3",
+"^. c #D1D1D4",
+"/. c #D2D2D4",
+"(. c #D3D3D4",
+"). c #D2D2D5",
+"_. c #D3D3D5",
+"`. c #D2D2D6",
+"'. c #D3D3D6",
+"]. c #D3D3D7",
+"[. c gray83",
+"{. c #D5D5D4",
+"}. c #D4D4D5",
+"|. c #D5D5D5",
+" X c #D4D4D6",
+".X c #D5D5D7",
+"XX c gray84",
+"oX c #D6D6D7",
+"OX c #D7D7D7",
+"+X c #D8D8D0",
+"@X c #D9D9D0",
+"#X c #DBDBD7",
+"$X c #DFDFD5",
+"%X c #DDDDD7",
+"&X c #D4D4D8",
+"*X c #D5D5D8",
+"=X c #D6D6D8",
+"-X c #D7D7D9",
+";X c #D8D8D8",
+":X c #D9D9D8",
+">X c gray85",
+",X c #DADAD9",
+"<X c #D8D8DA",
+"1X c #D9D9DA",
+"2X c #DADADA",
+"3X c #DBDBDA",
+"4X c #DADADB",
+"5X c gray86",
+"6X c #DCDCD8",
+"7X c #DDDDD8",
+"8X c #DCDCD9",
+"9X c #DEDED8",
+"0X c #DEDED9",
+"qX c #DFDFD9",
+"wX c #DDDDDA",
+"eX c #DEDEDB",
+"rX c #DADADC",
+"tX c #DBDBDC",
+"yX c #DBDBDD",
+"uX c gainsboro",
+"iX c #DCDCDD",
+"pX c #DDDDDD",
+"aX c #DFDFDD",
+"sX c gray87",
+"dX c #E0E0D2",
+"fX c #E2E2D2",
+"gX c #E2E2D5",
+"hX c #E4E4D6",
+"jX c #E8E8D7",
+"kX c #E1E1D9",
+"lX c #E1E1DB",
+"zX c #E2E2DA",
+"xX c #E5E5D8",
+"cX c #E0E0DC",
+"vX c #E1E1DC",
+"bX c #E3E3DC",
+"nX c #E3E3DD",
+"mX c #E0E0DF",
+"MX c #E2E2DF",
+"NX c #E4E4DC",
+"BX c #EBEBDE",
+"VX c #DDDDE0",
+"CX c #DFDFE0",
+"ZX c #DFDFE3",
+"AX c gray88",
+"SX c #E1E1E1",
+"DX c #E1E1E2",
+"FX c #E5E5E0",
+"GX c #E7E7E1",
+"HX c #E5E5E3",
+"JX c #E1E1E4",
+"KX c gray90",
+"LX c #E6E6E6",
+/* pixels */
+"|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.,X|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.&X(.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.S.|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.(.MX' $.MX!.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.!.uXU p.,X(.|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.(.0XP o + 5.,X!.|.|.|.|.|.|.|.|.|.|.|.|.|.!.uX>. .uX!.|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.(.fX2 d p 4 BX&X!.|.|.|.|.|.|.|.|.|.|.|.!.uXp.V ,X|.|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.(.0XH 9 w f _ b.uX,X!.(.|.|.|.|.|.|.|.(.,XS.x |.|.|.|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.(.NXn W FXa.U U l.tX,X(.|.|.|.|.(.|.|.,Xb M.,X|.|.|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.,X0.0.tX,XuXz.U U l.,X,X|.(.|.(.(.uXF w.uX~.|.|.|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.(.tX5.>.tX(.!.,XuXM.~ U p.,X,X&XMXFX .O.uX~.|.|.|.|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.,X0.>.,X|.|.|.|.|.uXM.~ V a.K.S v _ uX|.|.|.|.|.|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.tX0.>.uX|.|.|.|.|.~.&XuXZ.$.- y 7 -.uX|.|.|.|.|.|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.(.,Xp.O.,X!.|.|.|.|.|.(.|.JXJ.$ a e H xX!.|.|.|.|.|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.!.,Xp.O.uX|.|.|.|.~.,XuXM.U W G & % ' CX!.|.|.|.|.|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.!.,Xl. .uX!.|.!.,XuXM.~ U p.,XMXI.gX! >.uX!.|.|.|.|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.,Xl. .tX!.,XtXb.U U a.tX,X|.(.(.(.uXF 9.MX(.|.|.|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.!.,Xz.~ CX,Xl.U U b.uX,X|.!.|.|.|.|.|.tXn w.uX!.|.|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.(.,XS.X.n.V U b.,X,X(.(.|.|.|.|.|.|.|.|.|.x l.,X!.|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.,XuX<.   .b.,X,X(.|.|.|.|.|.|.|.|.|.|.(.,X|.x b.,X!.|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.|.|.|.|.|.|.,X|.p.V m V AX|.!.|.|.|.|.|.|.|.|.|.|.|.|.|.(.,X!.x Z.uX|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.!.|.!.|.uX|.p.V  .S.|.~ &X|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.(.,XS.x M.&X|.|.|.|.|.",
+"|.|.|.|.|.|.(.0X,X(.uX|.0.b  .S.uX,XS.U ,X~.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.!.,Xb.O.0X(.|.|.|.|.",
+"|.|.|.|.|.&X@X{ p.#X,.V  .!.uX|.(.|.!.U ,X|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.,X&X|.|.|.|.|.|.",
+"|.|.|.|.(.$X6 e q j <.!.0X|.!.|.|.|.!.U &X|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.",
+"|.|.|.|.&XI.* d y < @X&XtX,X|.|.(.|.!.U #X|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.",
+"|.|.|.|.(.0XJ @ X _ [ m X.z.|.uX,X|.!.U |.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.",
+"|.|.|.|.|.(.$XH.g ~ AXR.e.~ V O.l.|.,X~ |.(.(.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.(.CX-.n &.uX,X,X!.p.~ V  .k !.uX,X|.(.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.(.,Xp.>.~ l.,X!.|.,XuX|.p.. b  .a.|.uX,X|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.(.|.(.U !.x |.|.|.!.|.|.uX .S.l. .V [ p.|.,XuX|.!.(.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.(.tX~ n.a.U CX!.|.|.|.|.U !.,XuX~.l. .V ~ p.!.uX,X|.!.!.|.|.|.|.|.|.|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.(.tXp. .LXU 0.,X(.|.!.,XU S.&X!.|.,XuX|.l.O.V ~ 0.!.tX,X|.!.|.|.|.|.|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.|.!.V &X|.b !.|.|.(.,X~ S.,X|.|.(.!.|.,XuX|.z.O.V ~ 0.!.uXuX|.|.(.|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.!.uX~ z.uXb.b tX|.|.tX~ M.,X|.|.|.|.|.|.!.|.,XuX|.b.O.V ~ 0.R.,XuX&X&X|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.(.,Xp.&.FXuXO.O.uX!.tX~ M.,X|.|.|.|.|.|.|.|.|.(.|.,XuX,XM.&.F W <.K.#X|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.|.|.L., K @XuXm b.|.,X~ b.,X|.|.|.|.|.|.|.|.|.|.|.|.(.(.,XuX,XM. .0.,X(.|.|.|.|.",
+"|.|.|.|.|.|.|.|.(.kXZ r e A MXZ.b |.uX .z.,X|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.&XuX&X|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.(.gX3 a d : fXJX0.U LX_ l.&X(.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.(.,Xs.% = U V ,.AX[ a.5.z.,X|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.|.|.,XT.T.CX!. .V e.( h O.kX(.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.|.|.(.].(.(.|.uXS.&.> 0 8 -.,X(.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.(.&XhX1 d p 5 xX(.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.!.0XP q # 5.0X(.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.(.0Xs.m.0X|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.(.&X&X(.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.",
+"|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|.|."
+};
+
+const char *const *const xpm_icons[] = {
+    xpm_icon_0,
+    xpm_icon_1,
+    xpm_icon_2,
+};
+const int n_xpm_icons = 3;
diff --git a/icons/untangle-web.png b/icons/untangle-web.png
new file mode 100644 (file)
index 0000000..39a8bbc
Binary files /dev/null and b/icons/untangle-web.png differ
diff --git a/icons/untangle.ico b/icons/untangle.ico
new file mode 100644 (file)
index 0000000..c45d3c2
Binary files /dev/null and b/icons/untangle.ico differ
diff --git a/icons/untangle.rc b/icons/untangle.rc
new file mode 100644 (file)
index 0000000..517c71d
--- /dev/null
@@ -0,0 +1,2 @@
+#include "puzzles.rc2"
+200 ICON "untangle.ico"
diff --git a/icons/untangle.sav b/icons/untangle.sav
new file mode 100644 (file)
index 0000000..016318a
--- /dev/null
@@ -0,0 +1,16 @@
+SAVEFILE:41:Simon Tatham's Portable Puzzle Collection
+VERSION :1:1
+GAME    :8:Untangle
+PARAMS  :2:10
+CPARAMS :2:10
+SEED    :15:761628688787632
+DESC    :63:0-1,0-5,0-8,0-9,1-4,1-8,2-6,2-7,3-5,3-6,3-9,4-5,4-7,5-7,6-7,8-9
+AUXINFO :182:01bee8258e3164fe966f294b2837b6584b965b8d8e97571ba48f26c9bc0a91ac4b49fb4652bfaa5c340c82c57afbaa4620f2f6d49d7a7b330a66594d2b88c499d57c4093379b7dc322f2afa1ebab81004585751c39c19f8f9930c4
+NSTATES :1:7
+STATEPOS:1:6
+MOVE    :12:P8:168,16/64
+MOVE    :12:P0:186,85/64
+MOVE    :12:P2:47,254/64
+MOVE    :13:P5:131,153/64
+MOVE    :12:P3:75,126/64
+MOVE    :12:P7:93,303/64
diff --git a/icons/win16pal.xpm b/icons/win16pal.xpm
new file mode 100644 (file)
index 0000000..66fd60a
--- /dev/null
@@ -0,0 +1,23 @@
+/* XPM */
+static char *win16pal[] = {
+/* columns rows colors chars-per-pixel */
+"16 1 16 1",
+"  c #000000",
+". c #800000",
+"X c #008000",
+"o c #808000",
+"O c #000080",
+"+ c #800080",
+"@ c #008080",
+"# c #C0C0C0",
+"$ c #808080",
+"% c #FF0000",
+"& c #00FF00",
+"* c #FFFF00",
+"= c #0000FF",
+"- c #FF00FF",
+"; c #00FFFF",
+": c #FFFFFF",
+/* pixels */
+" .XoO+@#$%&*=-;:"
+};
diff --git a/inertia.R b/inertia.R
new file mode 100644 (file)
index 0000000..e6e86be
--- /dev/null
+++ b/inertia.R
@@ -0,0 +1,19 @@
+# -*- makefile -*-
+
+inertia  : [X] GTK COMMON inertia inertia-icon|no-icon
+
+inertia  : [G] WINDOWS COMMON inertia inertia.res|noicon.res
+
+ALL += inertia[COMBINED]
+
+!begin am gtk
+GAMES += inertia
+!end
+
+!begin >list.c
+    A(inertia) \
+!end
+
+!begin >gamedesc.txt
+inertia:inertia.exe:Inertia:Gem-collecting puzzle:Collect all the gems without running into any of the mines.
+!end
diff --git a/inertia.c b/inertia.c
new file mode 100644 (file)
index 0000000..4cbdd52
--- /dev/null
+++ b/inertia.c
@@ -0,0 +1,2249 @@
+/*
+ * inertia.c: Game involving navigating round a grid picking up
+ * gems.
+ * 
+ * Game rules and basic generator design by Ben Olmstead.
+ * This re-implementation was written by Simon Tatham.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+
+/* Used in the game_state */
+#define BLANK   'b'
+#define GEM     'g'
+#define MINE    'm'
+#define STOP    's'
+#define WALL    'w'
+
+/* Used in the game IDs */
+#define START   'S'
+
+/* Used in the game generation */
+#define POSSGEM 'G'
+
+/* Used only in the game_drawstate*/
+#define UNDRAWN '?'
+
+#define DIRECTIONS 8
+#define DP1 (DIRECTIONS+1)
+#define DX(dir) ( (dir) & 3 ? (((dir) & 7) > 4 ? -1 : +1) : 0 )
+#define DY(dir) ( DX((dir)+6) )
+
+/*
+ * Lvalue macro which expects x and y to be in range.
+ */
+#define LV_AT(w, h, grid, x, y) ( (grid)[(y)*(w)+(x)] )
+
+/*
+ * Rvalue macro which can cope with x and y being out of range.
+ */
+#define AT(w, h, grid, x, y) ( (x)<0 || (x)>=(w) || (y)<0 || (y)>=(h) ? \
+                              WALL : LV_AT(w, h, grid, x, y) )
+
+enum {
+    COL_BACKGROUND,
+    COL_OUTLINE,
+    COL_HIGHLIGHT,
+    COL_LOWLIGHT,
+    COL_PLAYER,
+    COL_DEAD_PLAYER,
+    COL_MINE,
+    COL_GEM,
+    COL_WALL,
+    COL_HINT,
+    NCOLOURS
+};
+
+struct game_params {
+    int w, h;
+};
+
+typedef struct soln {
+    int refcount;
+    int len;
+    unsigned char *list;
+} soln;
+
+struct game_state {
+    game_params p;
+    int px, py;
+    int gems;
+    char *grid;
+    int distance_moved;
+    int dead;
+    int cheated;
+    int solnpos;
+    soln *soln;
+};
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = 10;
+#ifdef PORTRAIT_SCREEN
+    ret->h = 10;
+#else
+    ret->h = 8;
+#endif
+    return ret;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+static const struct game_params inertia_presets[] = {
+#ifdef PORTRAIT_SCREEN
+    { 10, 10 },
+    { 12, 12 },
+    { 16, 16 },
+#else
+    { 10, 8 },
+    { 15, 12 },
+    { 20, 16 },
+#endif
+};
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params p, *ret;
+    char *retname;
+    char namebuf[80];
+
+    if (i < 0 || i >= lenof(inertia_presets))
+       return FALSE;
+
+    p = inertia_presets[i];
+    ret = dup_params(&p);
+    sprintf(namebuf, "%dx%d", ret->w, ret->h);
+    retname = dupstr(namebuf);
+
+    *params = ret;
+    *name = retname;
+    return TRUE;
+}
+
+static void decode_params(game_params *params, char const *string)
+{
+    params->w = params->h = atoi(string);
+    while (*string && isdigit((unsigned char)*string)) string++;
+    if (*string == 'x') {
+        string++;
+        params->h = atoi(string);
+    }
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char data[256];
+
+    sprintf(data, "%dx%d", params->w, params->h);
+
+    return dupstr(data);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+    char buf[80];
+
+    ret = snewn(3, config_item);
+
+    ret[0].name = "Width";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->w);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Height";
+    ret[1].type = C_STRING;
+    sprintf(buf, "%d", params->h);
+    ret[1].sval = dupstr(buf);
+    ret[1].ival = 0;
+
+    ret[2].name = NULL;
+    ret[2].type = C_END;
+    ret[2].sval = NULL;
+    ret[2].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = atoi(cfg[0].sval);
+    ret->h = atoi(cfg[1].sval);
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    /*
+     * Avoid completely degenerate cases which only have one
+     * row/column. We probably could generate completable puzzles
+     * of that shape, but they'd be forced to be extremely boring
+     * and at large sizes would take a while to happen upon at
+     * random as well.
+     */
+    if (params->w < 2 || params->h < 2)
+       return "Width and height must both be at least two";
+
+    /*
+     * The grid construction algorithm creates 1/5 as many gems as
+     * grid squares, and must create at least one gem to have an
+     * actual puzzle. However, an area-five grid is ruled out by
+     * the above constraint, so the practical minimum is six.
+     */
+    if (params->w * params->h < 6)
+       return "Grid area must be at least six squares";
+
+    return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Solver used by grid generator.
+ */
+
+struct solver_scratch {
+    unsigned char *reachable_from, *reachable_to;
+    int *positions;
+};
+
+static struct solver_scratch *new_scratch(int w, int h)
+{
+    struct solver_scratch *sc = snew(struct solver_scratch);
+
+    sc->reachable_from = snewn(w * h * DIRECTIONS, unsigned char);
+    sc->reachable_to = snewn(w * h * DIRECTIONS, unsigned char);
+    sc->positions = snewn(w * h * DIRECTIONS, int);
+
+    return sc;
+}
+
+static void free_scratch(struct solver_scratch *sc)
+{
+    sfree(sc->reachable_from);
+    sfree(sc->reachable_to);
+    sfree(sc->positions);
+    sfree(sc);
+}
+
+static int can_go(int w, int h, char *grid,
+                 int x1, int y1, int dir1, int x2, int y2, int dir2)
+{
+    /*
+     * Returns TRUE if we can transition directly from (x1,y1)
+     * going in direction dir1, to (x2,y2) going in direction dir2.
+     */
+
+    /*
+     * If we're actually in the middle of an unoccupyable square,
+     * we cannot make any move.
+     */
+    if (AT(w, h, grid, x1, y1) == WALL ||
+       AT(w, h, grid, x1, y1) == MINE)
+       return FALSE;
+
+    /*
+     * If a move is capable of stopping at x1,y1,dir1, and x2,y2 is
+     * the same coordinate as x1,y1, then we can make the
+     * transition (by stopping and changing direction).
+     * 
+     * For this to be the case, we have to either have a wall
+     * beyond x1,y1,dir1, or have a stop on x1,y1.
+     */
+    if (x2 == x1 && y2 == y1 &&
+       (AT(w, h, grid, x1, y1) == STOP ||
+        AT(w, h, grid, x1, y1) == START ||
+        AT(w, h, grid, x1+DX(dir1), y1+DY(dir1)) == WALL))
+       return TRUE;
+
+    /*
+     * If a move is capable of continuing here, then x1,y1,dir1 can
+     * move one space further on.
+     */
+    if (x2 == x1+DX(dir1) && y2 == y1+DY(dir1) && dir1 == dir2 &&
+       (AT(w, h, grid, x2, y2) == BLANK ||
+        AT(w, h, grid, x2, y2) == GEM ||
+        AT(w, h, grid, x2, y2) == STOP ||
+        AT(w, h, grid, x2, y2) == START))
+       return TRUE;
+
+    /*
+     * That's it.
+     */
+    return FALSE;
+}
+
+static int find_gem_candidates(int w, int h, char *grid,
+                              struct solver_scratch *sc)
+{
+    int wh = w*h;
+    int head, tail;
+    int sx, sy, gx, gy, gd, pass, possgems;
+
+    /*
+     * This function finds all the candidate gem squares, which are
+     * precisely those squares which can be picked up on a loop
+     * from the starting point back to the starting point. Doing
+     * this may involve passing through such a square in the middle
+     * of a move; so simple breadth-first search over the _squares_
+     * of the grid isn't quite adequate, because it might be that
+     * we can only reach a gem from the start by moving over it in
+     * one direction, but can only return to the start if we were
+     * moving over it in another direction.
+     * 
+     * Instead, we BFS over a space which mentions each grid square
+     * eight times - once for each direction. We also BFS twice:
+     * once to find out what square+direction pairs we can reach
+     * _from_ the start point, and once to find out what pairs we
+     * can reach the start point from. Then a square is reachable
+     * if any of the eight directions for that square has both
+     * flags set.
+     */
+
+    memset(sc->reachable_from, 0, wh * DIRECTIONS);
+    memset(sc->reachable_to, 0, wh * DIRECTIONS);
+
+    /*
+     * Find the starting square.
+     */
+    sx = -1;                          /* placate optimiser */
+    for (sy = 0; sy < h; sy++) {
+       for (sx = 0; sx < w; sx++)
+           if (AT(w, h, grid, sx, sy) == START)
+               break;
+       if (sx < w)
+           break;
+    }
+    assert(sy < h);
+
+    for (pass = 0; pass < 2; pass++) {
+       unsigned char *reachable = (pass == 0 ? sc->reachable_from :
+                                   sc->reachable_to);
+       int sign = (pass == 0 ? +1 : -1);
+       int dir;
+
+#ifdef SOLVER_DIAGNOSTICS
+       printf("starting pass %d\n", pass);
+#endif
+
+       /*
+        * `head' and `tail' are indices within sc->positions which
+        * track the list of board positions left to process.
+        */
+       head = tail = 0;
+       for (dir = 0; dir < DIRECTIONS; dir++) {
+           int index = (sy*w+sx)*DIRECTIONS+dir;
+           sc->positions[tail++] = index;
+           reachable[index] = TRUE;
+#ifdef SOLVER_DIAGNOSTICS
+           printf("starting point %d,%d,%d\n", sx, sy, dir);
+#endif
+       }
+
+       /*
+        * Now repeatedly pick an element off the list and process
+        * it.
+        */
+       while (head < tail) {
+           int index = sc->positions[head++];
+           int dir = index % DIRECTIONS;
+           int x = (index / DIRECTIONS) % w;
+           int y = index / (w * DIRECTIONS);
+           int n, x2, y2, d2, i2;
+
+#ifdef SOLVER_DIAGNOSTICS
+           printf("processing point %d,%d,%d\n", x, y, dir);
+#endif
+           /*
+            * The places we attempt to switch to here are:
+            *  - each possible direction change (all the other
+            *    directions in this square)
+            *  - one step further in the direction we're going (or
+            *    one step back, if we're in the reachable_to pass).
+            */
+           for (n = -1; n < DIRECTIONS; n++) {
+               if (n < 0) {
+                   x2 = x + sign * DX(dir);
+                   y2 = y + sign * DY(dir);
+                   d2 = dir;
+               } else {
+                   x2 = x;
+                   y2 = y;
+                   d2 = n;
+               }
+               i2 = (y2*w+x2)*DIRECTIONS+d2;
+               if (x2 >= 0 && x2 < w &&
+                   y2 >= 0 && y2 < h &&
+                   !reachable[i2]) {
+                   int ok;
+#ifdef SOLVER_DIAGNOSTICS
+                   printf("  trying point %d,%d,%d", x2, y2, d2);
+#endif
+                   if (pass == 0)
+                       ok = can_go(w, h, grid, x, y, dir, x2, y2, d2);
+                   else
+                       ok = can_go(w, h, grid, x2, y2, d2, x, y, dir);
+#ifdef SOLVER_DIAGNOSTICS
+                   printf(" - %sok\n", ok ? "" : "not ");
+#endif
+                   if (ok) {
+                       sc->positions[tail++] = i2;
+                       reachable[i2] = TRUE;
+                   }
+               }
+           }
+       }
+    }
+
+    /*
+     * And that should be it. Now all we have to do is find the
+     * squares for which there exists _some_ direction such that
+     * the square plus that direction form a tuple which is both
+     * reachable from the start and reachable to the start.
+     */
+    possgems = 0;
+    for (gy = 0; gy < h; gy++)
+       for (gx = 0; gx < w; gx++)
+           if (AT(w, h, grid, gx, gy) == BLANK) {
+               for (gd = 0; gd < DIRECTIONS; gd++) {
+                   int index = (gy*w+gx)*DIRECTIONS+gd;
+                   if (sc->reachable_from[index] && sc->reachable_to[index]) {
+#ifdef SOLVER_DIAGNOSTICS
+                       printf("space at %d,%d is reachable via"
+                              " direction %d\n", gx, gy, gd);
+#endif
+                       LV_AT(w, h, grid, gx, gy) = POSSGEM;
+                       possgems++;
+                       break;
+                   }
+               }
+           }
+
+    return possgems;
+}
+
+/* ----------------------------------------------------------------------
+ * Grid generation code.
+ */
+
+static char *gengrid(int w, int h, random_state *rs)
+{
+    int wh = w*h;
+    char *grid = snewn(wh+1, char);
+    struct solver_scratch *sc = new_scratch(w, h);
+    int maxdist_threshold, tries;
+
+    maxdist_threshold = 2;
+    tries = 0;
+
+    while (1) {
+       int i, j;
+       int possgems;
+       int *dist, *list, head, tail, maxdist;
+
+       /*
+        * We're going to fill the grid with the five basic piece
+        * types in about 1/5 proportion. For the moment, though,
+        * we leave out the gems, because we'll put those in
+        * _after_ we run the solver to tell us where the viable
+        * locations are.
+        */
+       i = 0;
+       for (j = 0; j < wh/5; j++)
+           grid[i++] = WALL;
+       for (j = 0; j < wh/5; j++)
+           grid[i++] = STOP;
+       for (j = 0; j < wh/5; j++)
+           grid[i++] = MINE;
+       assert(i < wh);
+       grid[i++] = START;
+       while (i < wh)
+           grid[i++] = BLANK;
+       shuffle(grid, wh, sizeof(*grid), rs);
+
+       /*
+        * Find the viable gem locations, and immediately give up
+        * and try again if there aren't enough of them.
+        */
+       possgems = find_gem_candidates(w, h, grid, sc);
+       if (possgems < wh/5)
+           continue;
+
+       /*
+        * We _could_ now select wh/5 of the POSSGEMs and set them
+        * to GEM, and have a viable level. However, there's a
+        * chance that a large chunk of the level will turn out to
+        * be unreachable, so first we test for that.
+        * 
+        * We do this by finding the largest distance from any
+        * square to the nearest POSSGEM, by breadth-first search.
+        * If this is above a critical threshold, we abort and try
+        * again.
+        * 
+        * (This search is purely geometric, without regard to
+        * walls and long ways round.)
+        */
+       dist = sc->positions;
+       list = sc->positions + wh;
+       for (i = 0; i < wh; i++)
+           dist[i] = -1;
+       head = tail = 0;
+       for (i = 0; i < wh; i++)
+           if (grid[i] == POSSGEM) {
+               dist[i] = 0;
+               list[tail++] = i;
+           }
+       maxdist = 0;
+       while (head < tail) {
+           int pos, x, y, d;
+
+           pos = list[head++];
+           if (maxdist < dist[pos])
+               maxdist = dist[pos];
+
+           x = pos % w;
+           y = pos / w;
+
+           for (d = 0; d < DIRECTIONS; d++) {
+               int x2, y2, p2;
+
+               x2 = x + DX(d);
+               y2 = y + DY(d);
+
+               if (x2 >= 0 && x2 < w && y2 >= 0 && y2 < h) {
+                   p2 = y2*w+x2;
+                   if (dist[p2] < 0) {
+                       dist[p2] = dist[pos] + 1;
+                       list[tail++] = p2;
+                   }
+               }
+           }
+       }
+       assert(head == wh && tail == wh);
+
+       /*
+        * Now abandon this grid and go round again if maxdist is
+        * above the required threshold.
+        * 
+        * We can safely start the threshold as low as 2. As we
+        * accumulate failed generation attempts, we gradually
+        * raise it as we get more desperate.
+        */
+       if (maxdist > maxdist_threshold) {
+           tries++;
+           if (tries == 50) {
+               maxdist_threshold++;
+               tries = 0;
+           }
+           continue;
+       }
+
+       /*
+        * Now our reachable squares are plausibly evenly
+        * distributed over the grid. I'm not actually going to
+        * _enforce_ that I place the gems in such a way as not to
+        * increase that maxdist value; I'm now just going to trust
+        * to the RNG to pick a sensible subset of the POSSGEMs.
+        */
+       j = 0;
+       for (i = 0; i < wh; i++)
+           if (grid[i] == POSSGEM)
+               list[j++] = i;
+       shuffle(list, j, sizeof(*list), rs);
+       for (i = 0; i < j; i++)
+           grid[list[i]] = (i < wh/5 ? GEM : BLANK);
+       break;
+    }
+
+    free_scratch(sc);
+
+    grid[wh] = '\0';
+
+    return grid;
+}
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                          char **aux, int interactive)
+{
+    return gengrid(params->w, params->h, rs);
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    int w = params->w, h = params->h, wh = w*h;
+    int starts = 0, gems = 0, i;
+
+    for (i = 0; i < wh; i++) {
+       if (!desc[i])
+           return "Not enough data to fill grid";
+       if (desc[i] != WALL && desc[i] != START && desc[i] != STOP &&
+           desc[i] != GEM && desc[i] != MINE && desc[i] != BLANK)
+           return "Unrecognised character in game description";
+       if (desc[i] == START)
+           starts++;
+       if (desc[i] == GEM)
+           gems++;
+    }
+    if (desc[i])
+       return "Too much data to fill grid";
+    if (starts < 1)
+       return "No starting square specified";
+    if (starts > 1)
+       return "More than one starting square specified";
+    if (gems < 1)
+       return "No gems specified";
+
+    return NULL;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    int w = params->w, h = params->h, wh = w*h;
+    int i;
+    game_state *state = snew(game_state);
+
+    state->p = *params;                       /* structure copy */
+
+    state->grid = snewn(wh, char);
+    assert(strlen(desc) == wh);
+    memcpy(state->grid, desc, wh);
+
+    state->px = state->py = -1;
+    state->gems = 0;
+    for (i = 0; i < wh; i++) {
+       if (state->grid[i] == START) {
+           state->grid[i] = STOP;
+           state->px = i % w;
+           state->py = i / w;
+       } else if (state->grid[i] == GEM) {
+           state->gems++;
+       }
+    }
+
+    assert(state->gems > 0);
+    assert(state->px >= 0 && state->py >= 0);
+
+    state->distance_moved = 0;
+    state->dead = FALSE;
+
+    state->cheated = FALSE;
+    state->solnpos = 0;
+    state->soln = NULL;
+
+    return state;
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    int w = state->p.w, h = state->p.h, wh = w*h;
+    game_state *ret = snew(game_state);
+
+    ret->p = state->p;
+    ret->px = state->px;
+    ret->py = state->py;
+    ret->gems = state->gems;
+    ret->grid = snewn(wh, char);
+    ret->distance_moved = state->distance_moved;
+    ret->dead = FALSE;
+    memcpy(ret->grid, state->grid, wh);
+    ret->cheated = state->cheated;
+    ret->soln = state->soln;
+    if (ret->soln)
+       ret->soln->refcount++;
+    ret->solnpos = state->solnpos;
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    if (state->soln && --state->soln->refcount == 0) {
+       sfree(state->soln->list);
+       sfree(state->soln);
+    }
+    sfree(state->grid);
+    sfree(state);
+}
+
+/*
+ * Internal function used by solver.
+ */
+static int move_goes_to(int w, int h, char *grid, int x, int y, int d)
+{
+    int dr;
+
+    /*
+     * See where we'd get to if we made this move.
+     */
+    dr = -1;                          /* placate optimiser */
+    while (1) {
+       if (AT(w, h, grid, x+DX(d), y+DY(d)) == WALL) {
+           dr = DIRECTIONS;           /* hit a wall, so end up stationary */
+           break;
+       }
+       x += DX(d);
+       y += DY(d);
+       if (AT(w, h, grid, x, y) == STOP) {
+           dr = DIRECTIONS;           /* hit a stop, so end up stationary */
+           break;
+       }
+       if (AT(w, h, grid, x, y) == GEM) {
+           dr = d;                    /* hit a gem, so we're still moving */
+           break;
+       }
+       if (AT(w, h, grid, x, y) == MINE)
+           return -1;                 /* hit a mine, so move is invalid */
+    }
+    assert(dr >= 0);
+    return (y*w+x)*DP1+dr;
+}
+
+static int compare_integers(const void *av, const void *bv)
+{
+    const int *a = (const int *)av;
+    const int *b = (const int *)bv;
+    if (*a < *b)
+       return -1;
+    else if (*a > *b)
+       return +1;
+    else
+       return 0;
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    int w = currstate->p.w, h = currstate->p.h, wh = w*h;
+    int *nodes, *nodeindex, *edges, *backedges, *edgei, *backedgei, *circuit;
+    int nedges;
+    int *dist, *dist2, *list;
+    int *unvisited;
+    int circuitlen, circuitsize;
+    int head, tail, pass, i, j, n, x, y, d, dd;
+    char *err, *soln, *p;
+
+    /*
+     * Before anything else, deal with the special case in which
+     * all the gems are already collected.
+     */
+    for (i = 0; i < wh; i++)
+       if (currstate->grid[i] == GEM)
+           break;
+    if (i == wh) {
+       *error = "Game is already solved";
+       return NULL;
+    }
+
+    /*
+     * Solving Inertia is a question of first building up the graph
+     * of where you can get to from where, and secondly finding a
+     * tour of the graph which takes in every gem.
+     * 
+     * This is of course a close cousin of the travelling salesman
+     * problem, which is NP-complete; so I rather doubt that any
+     * _optimal_ tour can be found in plausible time. Hence I'll
+     * restrict myself to merely finding a not-too-bad one.
+     * 
+     * First construct the graph, by bfsing out move by move from
+     * the current player position. Graph vertices will be
+     *         - every endpoint of a move (place the ball can be
+     *           stationary)
+     *         - every gem (place the ball can go through in motion).
+     *           Vertices of this type have an associated direction, since
+     *           if a gem can be collected by sliding through it in two
+     *           different directions it doesn't follow that you can
+     *           change direction at it.
+     * 
+     * I'm going to refer to a non-directional vertex as
+     * (y*w+x)*DP1+DIRECTIONS, and a directional one as
+     * (y*w+x)*DP1+d.
+     */
+
+    /*
+     * nodeindex[] maps node codes as shown above to numeric
+     * indices in the nodes[] array.
+     */
+    nodeindex = snewn(DP1*wh, int);
+    for (i = 0; i < DP1*wh; i++)
+       nodeindex[i] = -1;
+
+    /*
+     * Do the bfs to find all the interesting graph nodes.
+     */
+    nodes = snewn(DP1*wh, int);
+    head = tail = 0;
+
+    nodes[tail] = (currstate->py * w + currstate->px) * DP1 + DIRECTIONS;
+    nodeindex[nodes[0]] = tail;
+    tail++;
+
+    while (head < tail) {
+       int nc = nodes[head++], nnc;
+
+       d = nc % DP1;
+
+       /*
+        * Plot all possible moves from this node. If the node is
+        * directed, there's only one.
+        */
+       for (dd = 0; dd < DIRECTIONS; dd++) {
+           x = nc / DP1;
+           y = x / w;
+           x %= w;
+
+           if (d < DIRECTIONS && d != dd)
+               continue;
+
+           nnc = move_goes_to(w, h, currstate->grid, x, y, dd);
+           if (nnc >= 0 && nnc != nc) {
+               if (nodeindex[nnc] < 0) {
+                   nodes[tail] = nnc;
+                   nodeindex[nnc] = tail;
+                   tail++;
+               }
+           }
+       }
+    }
+    n = head;
+
+    /*
+     * Now we know how many nodes we have, allocate the edge array
+     * and go through setting up the edges.
+     */
+    edges = snewn(DIRECTIONS*n, int);
+    edgei = snewn(n+1, int);
+    nedges = 0;
+
+    for (i = 0; i < n; i++) {
+       int nc = nodes[i];
+
+       edgei[i] = nedges;
+
+       d = nc % DP1;
+       x = nc / DP1;
+       y = x / w;
+       x %= w;
+
+       for (dd = 0; dd < DIRECTIONS; dd++) {
+           int nnc;
+
+           if (d >= DIRECTIONS || d == dd) {
+               nnc = move_goes_to(w, h, currstate->grid, x, y, dd);
+
+               if (nnc >= 0 && nnc != nc)
+                   edges[nedges++] = nodeindex[nnc];
+           }
+       }
+    }
+    edgei[n] = nedges;
+
+    /*
+     * Now set up the backedges array.
+     */
+    backedges = snewn(nedges, int);
+    backedgei = snewn(n+1, int);
+    for (i = j = 0; i < nedges; i++) {
+       while (j+1 < n && i >= edgei[j+1])
+           j++;
+       backedges[i] = edges[i] * n + j;
+    }
+    qsort(backedges, nedges, sizeof(int), compare_integers);
+    backedgei[0] = 0;
+    for (i = j = 0; i < nedges; i++) {
+       int k = backedges[i] / n;
+       backedges[i] %= n;
+       while (j < k)
+           backedgei[++j] = i;
+    }
+    backedgei[n] = nedges;
+
+    /*
+     * Set up the initial tour. At all times, our tour is a circuit
+     * of graph vertices (which may, and probably will often,
+     * repeat vertices). To begin with, it's got exactly one vertex
+     * in it, which is the player's current starting point.
+     */
+    circuitsize = 256;
+    circuit = snewn(circuitsize, int);
+    circuitlen = 0;
+    circuit[circuitlen++] = 0;        /* node index 0 is the starting posn */
+
+    /*
+     * Track which gems are as yet unvisited.
+     */
+    unvisited = snewn(wh, int);
+    for (i = 0; i < wh; i++)
+       unvisited[i] = FALSE;
+    for (i = 0; i < wh; i++)
+       if (currstate->grid[i] == GEM)
+           unvisited[i] = TRUE;
+
+    /*
+     * Allocate space for doing bfses inside the main loop.
+     */
+    dist = snewn(n, int);
+    dist2 = snewn(n, int);
+    list = snewn(n, int);
+
+    err = NULL;
+    soln = NULL;
+
+    /*
+     * Now enter the main loop, in each iteration of which we
+     * extend the tour to take in an as yet uncollected gem.
+     */
+    while (1) {
+       int target, n1, n2, bestdist, extralen, targetpos;
+
+#ifdef TSP_DIAGNOSTICS
+       printf("circuit is");
+       for (i = 0; i < circuitlen; i++) {
+           int nc = nodes[circuit[i]];
+           printf(" (%d,%d,%d)", nc/DP1%w, nc/(DP1*w), nc%DP1);
+       }
+       printf("\n");
+       printf("moves are ");
+       x = nodes[circuit[0]] / DP1 % w;
+       y = nodes[circuit[0]] / DP1 / w;
+       for (i = 1; i < circuitlen; i++) {
+           int x2, y2, dx, dy;
+           if (nodes[circuit[i]] % DP1 != DIRECTIONS)
+               continue;
+           x2 = nodes[circuit[i]] / DP1 % w;
+           y2 = nodes[circuit[i]] / DP1 / w;
+           dx = (x2 > x ? +1 : x2 < x ? -1 : 0);
+           dy = (y2 > y ? +1 : y2 < y ? -1 : 0);
+           for (d = 0; d < DIRECTIONS; d++)
+               if (DX(d) == dx && DY(d) == dy)
+                   printf("%c", "89632147"[d]);
+           x = x2;
+           y = y2;
+       }
+       printf("\n");
+#endif
+
+       /*
+        * First, start a pair of bfses at _every_ vertex currently
+        * in the tour, and extend them outwards to find the
+        * nearest as yet unreached gem vertex.
+        * 
+        * This is largely a heuristic: we could pick _any_ doubly
+        * reachable node here and still get a valid tour as
+        * output. I hope that picking a nearby one will result in
+        * generally good tours.
+        */
+       for (pass = 0; pass < 2; pass++) {
+           int *ep = (pass == 0 ? edges : backedges);
+           int *ei = (pass == 0 ? edgei : backedgei);
+           int *dp = (pass == 0 ? dist : dist2);
+           head = tail = 0;
+           for (i = 0; i < n; i++)
+               dp[i] = -1;
+           for (i = 0; i < circuitlen; i++) {
+               int ni = circuit[i];
+               if (dp[ni] < 0) {
+                   dp[ni] = 0;
+                   list[tail++] = ni;
+               }
+           }
+           while (head < tail) {
+               int ni = list[head++];
+               for (i = ei[ni]; i < ei[ni+1]; i++) {
+                   int ti = ep[i];
+                   if (ti >= 0 && dp[ti] < 0) {
+                       dp[ti] = dp[ni] + 1;
+                       list[tail++] = ti;
+                   }
+               }
+           }
+       }
+       /* Now find the nearest unvisited gem. */
+       bestdist = -1;
+       target = -1;
+       for (i = 0; i < n; i++) {
+           if (unvisited[nodes[i] / DP1] &&
+               dist[i] >= 0 && dist2[i] >= 0) {
+               int thisdist = dist[i] + dist2[i];
+               if (bestdist < 0 || bestdist > thisdist) {
+                   bestdist = thisdist;
+                   target = i;
+               }
+           }
+       }
+
+       if (target < 0) {
+           /*
+            * If we get to here, we haven't found a gem we can get
+            * at all, which means we terminate this loop.
+            */
+           break;
+       }
+
+       /*
+        * Now we have a graph vertex at list[tail-1] which is an
+        * unvisited gem. We want to add that vertex to our tour.
+        * So we run two more breadth-first searches: one starting
+        * from that vertex and following forward edges, and
+        * another starting from the same vertex and following
+        * backward edges. This allows us to determine, for each
+        * node on the current tour, how quickly we can get both to
+        * and from the target vertex from that node.
+        */
+#ifdef TSP_DIAGNOSTICS
+       printf("target node is %d (%d,%d,%d)\n", target, nodes[target]/DP1%w,
+              nodes[target]/DP1/w, nodes[target]%DP1);
+#endif
+
+       for (pass = 0; pass < 2; pass++) {
+           int *ep = (pass == 0 ? edges : backedges);
+           int *ei = (pass == 0 ? edgei : backedgei);
+           int *dp = (pass == 0 ? dist : dist2);
+
+           for (i = 0; i < n; i++)
+               dp[i] = -1;
+           head = tail = 0;
+
+           dp[target] = 0;
+           list[tail++] = target;
+
+           while (head < tail) {
+               int ni = list[head++];
+               for (i = ei[ni]; i < ei[ni+1]; i++) {
+                   int ti = ep[i];
+                   if (ti >= 0 && dp[ti] < 0) {
+                       dp[ti] = dp[ni] + 1;
+/*printf("pass %d: set dist of vertex %d to %d (via %d)\n", pass, ti, dp[ti], ni);*/
+                       list[tail++] = ti;
+                   }
+               }
+           }
+       }
+
+       /*
+        * Now for every node n, dist[n] gives the length of the
+        * shortest path from the target vertex to n, and dist2[n]
+        * gives the length of the shortest path from n to the
+        * target vertex.
+        * 
+        * Our next step is to search linearly along the tour to
+        * find the optimum place to insert a trip to the target
+        * vertex and back. Our two options are either
+        *  (a) to find two adjacent vertices A,B in the tour and
+        *      replace the edge A->B with the path A->target->B
+        *  (b) to find a single vertex X in the tour and replace
+        *      it with the complete round trip X->target->X.
+        * We do whichever takes the fewest moves.
+        */
+       n1 = n2 = -1;
+       bestdist = -1;
+       for (i = 0; i < circuitlen; i++) {
+           int thisdist;
+
+           /*
+            * Try a round trip from vertex i.
+            */
+           if (dist[circuit[i]] >= 0 &&
+               dist2[circuit[i]] >= 0) {
+               thisdist = dist[circuit[i]] + dist2[circuit[i]];
+               if (bestdist < 0 || thisdist < bestdist) {
+                   bestdist = thisdist;
+                   n1 = n2 = i;
+               }
+           }
+
+           /*
+            * Try a trip from vertex i via target to vertex i+1.
+            */
+           if (i+1 < circuitlen &&
+               dist2[circuit[i]] >= 0 &&
+               dist[circuit[i+1]] >= 0) {
+               thisdist = dist2[circuit[i]] + dist[circuit[i+1]];
+               if (bestdist < 0 || thisdist < bestdist) {
+                   bestdist = thisdist;
+                   n1 = i;
+                   n2 = i+1;
+               }
+           }
+       }
+       if (bestdist < 0) {
+           /*
+            * We couldn't find a round trip taking in this gem _at
+            * all_. Give up.
+            */
+           err = "Unable to find a solution from this starting point";
+           break;
+       }
+#ifdef TSP_DIAGNOSTICS
+       printf("insertion point: n1=%d, n2=%d, dist=%d\n", n1, n2, bestdist);
+#endif
+
+#ifdef TSP_DIAGNOSTICS
+       printf("circuit before lengthening is");
+       for (i = 0; i < circuitlen; i++) {
+           printf(" %d", circuit[i]);
+       }
+       printf("\n");
+#endif
+
+       /*
+        * Now actually lengthen the tour to take in this round
+        * trip.
+        */
+       extralen = dist2[circuit[n1]] + dist[circuit[n2]];
+       if (n1 != n2)
+           extralen--;
+       circuitlen += extralen;
+       if (circuitlen >= circuitsize) {
+           circuitsize = circuitlen + 256;
+           circuit = sresize(circuit, circuitsize, int);
+       }
+       memmove(circuit + n2 + extralen, circuit + n2,
+               (circuitlen - n2 - extralen) * sizeof(int));
+       n2 += extralen;
+
+#ifdef TSP_DIAGNOSTICS
+       printf("circuit in middle of lengthening is");
+       for (i = 0; i < circuitlen; i++) {
+           printf(" %d", circuit[i]);
+       }
+       printf("\n");
+#endif
+
+       /*
+        * Find the shortest-path routes to and from the target,
+        * and write them into the circuit.
+        */
+       targetpos = n1 + dist2[circuit[n1]];
+       assert(targetpos - dist2[circuit[n1]] == n1);
+       assert(targetpos + dist[circuit[n2]] == n2);
+       for (pass = 0; pass < 2; pass++) {
+           int dir = (pass == 0 ? -1 : +1);
+           int *ep = (pass == 0 ? backedges : edges);
+           int *ei = (pass == 0 ? backedgei : edgei);
+           int *dp = (pass == 0 ? dist : dist2);
+           int nn = (pass == 0 ? n2 : n1);
+           int ni = circuit[nn], ti, dest = nn;
+
+           while (1) {
+               circuit[dest] = ni;
+               if (dp[ni] == 0)
+                   break;
+               dest += dir;
+               ti = -1;
+/*printf("pass %d: looking at vertex %d\n", pass, ni);*/
+               for (i = ei[ni]; i < ei[ni+1]; i++) {
+                   ti = ep[i];
+                   if (ti >= 0 && dp[ti] == dp[ni] - 1)
+                       break;
+               }
+               assert(i < ei[ni+1] && ti >= 0);
+               ni = ti;
+           }
+       }
+
+#ifdef TSP_DIAGNOSTICS
+       printf("circuit after lengthening is");
+       for (i = 0; i < circuitlen; i++) {
+           printf(" %d", circuit[i]);
+       }
+       printf("\n");
+#endif
+
+       /*
+        * Finally, mark all gems that the new piece of circuit
+        * passes through as visited.
+        */
+       for (i = n1; i <= n2; i++) {
+           int pos = nodes[circuit[i]] / DP1;
+           assert(pos >= 0 && pos < wh);
+           unvisited[pos] = FALSE;
+       }
+    }
+
+#ifdef TSP_DIAGNOSTICS
+    printf("before reduction, moves are ");
+    x = nodes[circuit[0]] / DP1 % w;
+    y = nodes[circuit[0]] / DP1 / w;
+    for (i = 1; i < circuitlen; i++) {
+       int x2, y2, dx, dy;
+       if (nodes[circuit[i]] % DP1 != DIRECTIONS)
+           continue;
+       x2 = nodes[circuit[i]] / DP1 % w;
+       y2 = nodes[circuit[i]] / DP1 / w;
+       dx = (x2 > x ? +1 : x2 < x ? -1 : 0);
+       dy = (y2 > y ? +1 : y2 < y ? -1 : 0);
+       for (d = 0; d < DIRECTIONS; d++)
+           if (DX(d) == dx && DY(d) == dy)
+               printf("%c", "89632147"[d]);
+       x = x2;
+       y = y2;
+    }
+    printf("\n");
+#endif
+
+    /*
+     * That's got a basic solution. Now optimise it by removing
+     * redundant sections of the circuit: it's entirely possible
+     * that a piece of circuit we carefully inserted at one stage
+     * to collect a gem has become pointless because the steps
+     * required to collect some _later_ gem necessarily passed
+     * through the same one.
+     * 
+     * So first we go through and work out how many times each gem
+     * is collected. Then we look for maximal sections of circuit
+     * which are redundant in the sense that their removal would
+     * not reduce any gem's collection count to zero, and replace
+     * each one with a bfs-derived fastest path between their
+     * endpoints.
+     */
+    while (1) {
+       int oldlen = circuitlen;
+       int dir;
+
+       for (dir = +1; dir >= -1; dir -= 2) {
+
+           for (i = 0; i < wh; i++)
+               unvisited[i] = 0;
+           for (i = 0; i < circuitlen; i++) {
+               int xy = nodes[circuit[i]] / DP1;
+               if (currstate->grid[xy] == GEM)
+                   unvisited[xy]++;
+           }
+
+           /*
+            * If there's any gem we didn't end up visiting at all,
+            * give up.
+            */
+           for (i = 0; i < wh; i++) {
+               if (currstate->grid[i] == GEM && unvisited[i] == 0) {
+                   err = "Unable to find a solution from this starting point";
+                   break;
+               }
+           }
+           if (i < wh)
+               break;
+
+           for (i = j = (dir > 0 ? 0 : circuitlen-1);
+                i < circuitlen && i >= 0;
+                i += dir) {
+               int xy = nodes[circuit[i]] / DP1;
+               if (currstate->grid[xy] == GEM && unvisited[xy] > 1) {
+                   unvisited[xy]--;
+               } else if (currstate->grid[xy] == GEM || i == circuitlen-1) {
+                   /*
+                    * circuit[i] collects a gem for the only time,
+                    * or is the last node in the circuit.
+                    * Therefore it cannot be removed; so we now
+                    * want to replace the path from circuit[j] to
+                    * circuit[i] with a bfs-shortest path.
+                    */
+                   int p, q, k, dest, ni, ti, thisdist;
+
+                   /*
+                    * Set up the upper and lower bounds of the
+                    * reduced section.
+                    */
+                   p = min(i, j);
+                   q = max(i, j);
+
+#ifdef TSP_DIAGNOSTICS
+                   printf("optimising section from %d - %d\n", p, q);
+#endif
+
+                   for (k = 0; k < n; k++)
+                       dist[k] = -1;
+                   head = tail = 0;
+
+                   dist[circuit[p]] = 0;
+                   list[tail++] = circuit[p];
+
+                   while (head < tail && dist[circuit[q]] < 0) {
+                       int ni = list[head++];
+                       for (k = edgei[ni]; k < edgei[ni+1]; k++) {
+                           int ti = edges[k];
+                           if (ti >= 0 && dist[ti] < 0) {
+                               dist[ti] = dist[ni] + 1;
+                               list[tail++] = ti;
+                           }
+                       }
+                   }
+
+                   thisdist = dist[circuit[q]];
+                   assert(thisdist >= 0 && thisdist <= q-p);
+
+                   memmove(circuit+p+thisdist, circuit+q,
+                           (circuitlen - q) * sizeof(int));
+                   circuitlen -= q-p;
+                   q = p + thisdist;
+                   circuitlen += q-p;
+
+                   if (dir > 0)
+                       i = q;         /* resume loop from the right place */
+
+#ifdef TSP_DIAGNOSTICS
+                   printf("new section runs from %d - %d\n", p, q);
+#endif
+
+                   dest = q;
+                   assert(dest >= 0);
+                   ni = circuit[q];
+
+                   while (1) {
+                       /* printf("dest=%d circuitlen=%d ni=%d dist[ni]=%d\n", dest, circuitlen, ni, dist[ni]); */
+                       circuit[dest] = ni;
+                       if (dist[ni] == 0)
+                           break;
+                       dest--;
+                       ti = -1;
+                       for (k = backedgei[ni]; k < backedgei[ni+1]; k++) {
+                           ti = backedges[k];
+                           if (ti >= 0 && dist[ti] == dist[ni] - 1)
+                               break;
+                       }
+                       assert(k < backedgei[ni+1] && ti >= 0);
+                       ni = ti;
+                   }
+
+                   /*
+                    * Now re-increment the visit counts for the
+                    * new path.
+                    */
+                   while (++p < q) {
+                       int xy = nodes[circuit[p]] / DP1;
+                       if (currstate->grid[xy] == GEM)
+                           unvisited[xy]++;
+                   }
+
+                   j = i;
+
+#ifdef TSP_DIAGNOSTICS
+                   printf("during reduction, circuit is");
+                   for (k = 0; k < circuitlen; k++) {
+                       int nc = nodes[circuit[k]];
+                       printf(" (%d,%d,%d)", nc/DP1%w, nc/(DP1*w), nc%DP1);
+                   }
+                   printf("\n");
+                   printf("moves are ");
+                   x = nodes[circuit[0]] / DP1 % w;
+                   y = nodes[circuit[0]] / DP1 / w;
+                   for (k = 1; k < circuitlen; k++) {
+                       int x2, y2, dx, dy;
+                       if (nodes[circuit[k]] % DP1 != DIRECTIONS)
+                           continue;
+                       x2 = nodes[circuit[k]] / DP1 % w;
+                       y2 = nodes[circuit[k]] / DP1 / w;
+                       dx = (x2 > x ? +1 : x2 < x ? -1 : 0);
+                       dy = (y2 > y ? +1 : y2 < y ? -1 : 0);
+                       for (d = 0; d < DIRECTIONS; d++)
+                           if (DX(d) == dx && DY(d) == dy)
+                               printf("%c", "89632147"[d]);
+                       x = x2;
+                       y = y2;
+                   }
+                   printf("\n");
+#endif
+               }
+           }
+
+#ifdef TSP_DIAGNOSTICS
+           printf("after reduction, moves are ");
+           x = nodes[circuit[0]] / DP1 % w;
+           y = nodes[circuit[0]] / DP1 / w;
+           for (i = 1; i < circuitlen; i++) {
+               int x2, y2, dx, dy;
+               if (nodes[circuit[i]] % DP1 != DIRECTIONS)
+                   continue;
+               x2 = nodes[circuit[i]] / DP1 % w;
+               y2 = nodes[circuit[i]] / DP1 / w;
+               dx = (x2 > x ? +1 : x2 < x ? -1 : 0);
+               dy = (y2 > y ? +1 : y2 < y ? -1 : 0);
+               for (d = 0; d < DIRECTIONS; d++)
+                   if (DX(d) == dx && DY(d) == dy)
+                       printf("%c", "89632147"[d]);
+               x = x2;
+               y = y2;
+           }
+           printf("\n");
+#endif
+       }
+
+       /*
+        * If we've managed an entire reduction pass in each
+        * direction and not made the solution any shorter, we're
+        * _really_ done.
+        */
+       if (circuitlen == oldlen)
+           break;
+    }
+
+    /*
+     * Encode the solution as a move string.
+     */
+    if (!err) {
+       soln = snewn(circuitlen+2, char);
+       p = soln;
+       *p++ = 'S';
+       x = nodes[circuit[0]] / DP1 % w;
+       y = nodes[circuit[0]] / DP1 / w;
+       for (i = 1; i < circuitlen; i++) {
+           int x2, y2, dx, dy;
+           if (nodes[circuit[i]] % DP1 != DIRECTIONS)
+               continue;
+           x2 = nodes[circuit[i]] / DP1 % w;
+           y2 = nodes[circuit[i]] / DP1 / w;
+           dx = (x2 > x ? +1 : x2 < x ? -1 : 0);
+           dy = (y2 > y ? +1 : y2 < y ? -1 : 0);
+           for (d = 0; d < DIRECTIONS; d++)
+               if (DX(d) == dx && DY(d) == dy) {
+                   *p++ = '0' + d;
+                   break;
+               }
+           assert(d < DIRECTIONS);
+           x = x2;
+           y = y2;
+       }
+       *p++ = '\0';
+       assert(p - soln < circuitlen+2);
+    }
+
+    sfree(list);
+    sfree(dist);
+    sfree(dist2);
+    sfree(unvisited);
+    sfree(circuit);
+    sfree(backedgei);
+    sfree(backedges);
+    sfree(edgei);
+    sfree(edges);
+    sfree(nodeindex);
+    sfree(nodes);
+
+    if (err)
+       *error = err;
+
+    return soln;
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    int w = state->p.w, h = state->p.h, r, c;
+    int cw = 4, ch = 2, gw = cw*w + 2, gh = ch * h + 1, len = gw * gh;
+    char *board = snewn(len + 1, char);
+
+    sprintf(board, "%*s+\n", len - 2, "");
+
+    for (r = 0; r < h; ++r) {
+       for (c = 0; c < w; ++c) {
+           int cell = r*ch*gw + cw*c, center = cell + gw*ch/2 + cw/2;
+           int i = r*w + c;
+           switch (state->grid[i]) {
+           case BLANK: break;
+           case GEM: board[center] = 'o'; break;
+           case MINE: board[center] = 'M'; break;
+           case STOP: board[center-1] = '('; board[center+1] = ')'; break;
+           case WALL: memset(board + center - 1, 'X', 3);
+           }
+
+           if (r == state->py && c == state->px) {
+               if (!state->dead) board[center] = '@';
+               else memcpy(board + center - 1, ":-(", 3);
+           }
+           board[cell] = '+';
+           memset(board + cell + 1, '-', cw - 1);
+           for (i = 1; i < ch; ++i) board[cell + i*gw] = '|';
+       }
+       for (c = 0; c < ch; ++c) {
+           board[(r*ch+c)*gw + gw - 2] = "|+"[!c];
+           board[(r*ch+c)*gw + gw - 1] = '\n';
+       }
+    }
+    memset(board + len - gw, '-', gw - 2);
+    for (c = 0; c < w; ++c) board[len - gw + cw*c] = '+';
+
+    return board;
+}
+
+struct game_ui {
+    float anim_length;
+    int flashtype;
+    int deaths;
+    int just_made_move;
+    int just_died;
+};
+
+static game_ui *new_ui(const game_state *state)
+{
+    game_ui *ui = snew(game_ui);
+    ui->anim_length = 0.0F;
+    ui->flashtype = 0;
+    ui->deaths = 0;
+    ui->just_made_move = FALSE;
+    ui->just_died = FALSE;
+    return ui;
+}
+
+static void free_ui(game_ui *ui)
+{
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    char buf[80];
+    /*
+     * The deaths counter needs preserving across a serialisation.
+     */
+    sprintf(buf, "D%d", ui->deaths);
+    return dupstr(buf);
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+    int p = 0;
+    sscanf(encoding, "D%d%n", &ui->deaths, &p);
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+    /*
+     * Increment the deaths counter. We only do this if
+     * ui->just_made_move is set (redoing a suicide move doesn't
+     * kill you _again_), and also we only do it if the game wasn't
+     * already completed (once you're finished, you can play).
+     */
+    if (!oldstate->dead && newstate->dead && ui->just_made_move &&
+       oldstate->gems) {
+       ui->deaths++;
+       ui->just_died = TRUE;
+    } else {
+       ui->just_died = FALSE;
+    }
+    ui->just_made_move = FALSE;
+}
+
+struct game_drawstate {
+    game_params p;
+    int tilesize;
+    int started;
+    unsigned short *grid;
+    blitter *player_background;
+    int player_bg_saved, pbgx, pbgy;
+};
+
+#define PREFERRED_TILESIZE 32
+#define TILESIZE (ds->tilesize)
+#ifdef SMALL_SCREEN
+#define BORDER    (TILESIZE / 4)
+#else
+#define BORDER    (TILESIZE)
+#endif
+#define HIGHLIGHT_WIDTH (TILESIZE / 10)
+#define COORD(x)  ( (x) * TILESIZE + BORDER )
+#define FROMCOORD(x)  ( ((x) - BORDER + TILESIZE) / TILESIZE - 1 )
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    int w = state->p.w, h = state->p.h /*, wh = w*h */;
+    int dir;
+    char buf[80];
+
+    dir = -1;
+
+    if (button == LEFT_BUTTON) {
+       /*
+        * Mouse-clicking near the target point (or, more
+        * accurately, in the appropriate octant) is an alternative
+        * way to input moves.
+        */
+
+       if (FROMCOORD(x) != state->px || FROMCOORD(y) != state->py) {
+           int dx, dy;
+           float angle;
+
+           dx = FROMCOORD(x) - state->px;
+           dy = FROMCOORD(y) - state->py;
+           /* I pass dx,dy rather than dy,dx so that the octants
+            * end up the right way round. */
+           angle = atan2(dx, -dy);
+
+           angle = (angle + (PI/8)) / (PI/4);
+           assert(angle > -16.0F);
+           dir = (int)(angle + 16.0F) & 7;
+       }
+    } else if (button == CURSOR_UP || button == (MOD_NUM_KEYPAD | '8'))
+        dir = 0;
+    else if (button == CURSOR_DOWN || button == (MOD_NUM_KEYPAD | '2'))
+        dir = 4;
+    else if (button == CURSOR_LEFT || button == (MOD_NUM_KEYPAD | '4'))
+        dir = 6;
+    else if (button == CURSOR_RIGHT || button == (MOD_NUM_KEYPAD | '6'))
+        dir = 2;
+    else if (button == (MOD_NUM_KEYPAD | '7'))
+        dir = 7;
+    else if (button == (MOD_NUM_KEYPAD | '1'))
+        dir = 5;
+    else if (button == (MOD_NUM_KEYPAD | '9'))
+        dir = 1;
+    else if (button == (MOD_NUM_KEYPAD | '3'))
+        dir = 3;
+    else if (IS_CURSOR_SELECT(button) &&
+             state->soln && state->solnpos < state->soln->len)
+       dir = state->soln->list[state->solnpos];
+
+    if (dir < 0)
+       return NULL;
+
+    /*
+     * Reject the move if we can't make it at all due to a wall
+     * being in the way.
+     */
+    if (AT(w, h, state->grid, state->px+DX(dir), state->py+DY(dir)) == WALL)
+       return NULL;
+
+    /*
+     * Reject the move if we're dead!
+     */
+    if (state->dead)
+       return NULL;
+
+    /*
+     * Otherwise, we can make the move. All we need to specify is
+     * the direction.
+     */
+    ui->just_made_move = TRUE;
+    sprintf(buf, "%d", dir);
+    return dupstr(buf);
+}
+
+static void install_new_solution(game_state *ret, const char *move)
+{
+    int i;
+    soln *sol;
+    assert (*move == 'S');
+    ++move;
+
+    sol = snew(soln);
+    sol->len = strlen(move);
+    sol->list = snewn(sol->len, unsigned char);
+    for (i = 0; i < sol->len; ++i) sol->list[i] = move[i] - '0';
+
+    if (ret->soln && --ret->soln->refcount == 0) {
+       sfree(ret->soln->list);
+       sfree(ret->soln);
+    }
+
+    ret->soln = sol;
+    sol->refcount = 1;
+
+    ret->cheated = TRUE;
+    ret->solnpos = 0;
+}
+
+static void discard_solution(game_state *ret)
+{
+    --ret->soln->refcount;
+    assert(ret->soln->refcount > 0); /* ret has a soln-pointing dup */
+    ret->soln = NULL;
+    ret->solnpos = 0;
+}
+
+static game_state *execute_move(const game_state *state, const char *move)
+{
+    int w = state->p.w, h = state->p.h /*, wh = w*h */;
+    int dir;
+    game_state *ret;
+
+    if (*move == 'S') {
+       /*
+        * This is a solve move, so we don't actually _change_ the
+        * grid but merely set up a stored solution path.
+        */
+       ret = dup_game(state);
+       install_new_solution(ret, move);
+       return ret;
+    }
+
+    dir = atoi(move);
+    if (dir < 0 || dir >= DIRECTIONS)
+       return NULL;                   /* huh? */
+
+    if (state->dead)
+       return NULL;
+
+    if (AT(w, h, state->grid, state->px+DX(dir), state->py+DY(dir)) == WALL)
+       return NULL;                   /* wall in the way! */
+
+    /*
+     * Now make the move.
+     */
+    ret = dup_game(state);
+    ret->distance_moved = 0;
+    while (1) {
+       ret->px += DX(dir);
+       ret->py += DY(dir);
+       ret->distance_moved++;
+
+       if (AT(w, h, ret->grid, ret->px, ret->py) == GEM) {
+           LV_AT(w, h, ret->grid, ret->px, ret->py) = BLANK;
+           ret->gems--;
+       }
+
+       if (AT(w, h, ret->grid, ret->px, ret->py) == MINE) {
+           ret->dead = TRUE;
+           break;
+       }
+
+       if (AT(w, h, ret->grid, ret->px, ret->py) == STOP ||
+           AT(w, h, ret->grid, ret->px+DX(dir),
+              ret->py+DY(dir)) == WALL)
+           break;
+    }
+
+    if (ret->soln) {
+       if (ret->dead || ret->gems == 0)
+           discard_solution(ret);
+       else if (ret->soln->list[ret->solnpos] == dir) {
+           ++ret->solnpos;
+           assert(ret->solnpos < ret->soln->len); /* or gems == 0 */
+           assert(!ret->dead); /* or not a solution */
+       } else {
+           char *error = NULL, *soln = solve_game(NULL, ret, NULL, &error);
+           if (!error) {
+               install_new_solution(ret, soln);
+               sfree(soln);
+           } else discard_solution(ret);
+       }
+    }
+
+    return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    struct { int tilesize; } ads, *ds = &ads;
+    ads.tilesize = tilesize;
+
+    *x = 2 * BORDER + 1 + params->w * TILESIZE;
+    *y = 2 * BORDER + 1 + params->h * TILESIZE;
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+
+    assert(!ds->player_background);    /* set_size is never called twice */
+    assert(!ds->player_bg_saved);
+
+    ds->player_background = blitter_new(dr, TILESIZE, TILESIZE);
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+    int i;
+
+    game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT);
+
+    ret[COL_OUTLINE * 3 + 0] = 0.0F;
+    ret[COL_OUTLINE * 3 + 1] = 0.0F;
+    ret[COL_OUTLINE * 3 + 2] = 0.0F;
+
+    ret[COL_PLAYER * 3 + 0] = 0.0F;
+    ret[COL_PLAYER * 3 + 1] = 1.0F;
+    ret[COL_PLAYER * 3 + 2] = 0.0F;
+
+    ret[COL_DEAD_PLAYER * 3 + 0] = 1.0F;
+    ret[COL_DEAD_PLAYER * 3 + 1] = 0.0F;
+    ret[COL_DEAD_PLAYER * 3 + 2] = 0.0F;
+
+    ret[COL_MINE * 3 + 0] = 0.0F;
+    ret[COL_MINE * 3 + 1] = 0.0F;
+    ret[COL_MINE * 3 + 2] = 0.0F;
+
+    ret[COL_GEM * 3 + 0] = 0.6F;
+    ret[COL_GEM * 3 + 1] = 1.0F;
+    ret[COL_GEM * 3 + 2] = 1.0F;
+
+    for (i = 0; i < 3; i++) {
+       ret[COL_WALL * 3 + i] = (3 * ret[COL_BACKGROUND * 3 + i] +
+                                1 * ret[COL_HIGHLIGHT * 3 + i]) / 4;
+    }
+
+    ret[COL_HINT * 3 + 0] = 1.0F;
+    ret[COL_HINT * 3 + 1] = 1.0F;
+    ret[COL_HINT * 3 + 2] = 0.0F;
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    int w = state->p.w, h = state->p.h, wh = w*h;
+    struct game_drawstate *ds = snew(struct game_drawstate);
+    int i;
+
+    ds->tilesize = 0;
+
+    /* We can't allocate the blitter rectangle for the player background
+     * until we know what size to make it. */
+    ds->player_background = NULL;
+    ds->player_bg_saved = FALSE;
+    ds->pbgx = ds->pbgy = -1;
+
+    ds->p = state->p;                 /* structure copy */
+    ds->started = FALSE;
+    ds->grid = snewn(wh, unsigned short);
+    for (i = 0; i < wh; i++)
+       ds->grid[i] = UNDRAWN;
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    if (ds->player_background)
+       blitter_free(dr, ds->player_background);
+    sfree(ds->grid);
+    sfree(ds);
+}
+
+static void draw_player(drawing *dr, game_drawstate *ds, int x, int y,
+                       int dead, int hintdir)
+{
+    if (dead) {
+       int coords[DIRECTIONS*4];
+       int d;
+
+       for (d = 0; d < DIRECTIONS; d++) {
+           float x1, y1, x2, y2, x3, y3, len;
+
+           x1 = DX(d);
+           y1 = DY(d);
+           len = sqrt(x1*x1+y1*y1); x1 /= len; y1 /= len;
+
+           x3 = DX(d+1);
+           y3 = DY(d+1);
+           len = sqrt(x3*x3+y3*y3); x3 /= len; y3 /= len;
+
+           x2 = (x1+x3) / 4;
+           y2 = (y1+y3) / 4;
+
+           coords[d*4+0] = x + TILESIZE/2 + (int)((TILESIZE*3/7) * x1);
+           coords[d*4+1] = y + TILESIZE/2 + (int)((TILESIZE*3/7) * y1);
+           coords[d*4+2] = x + TILESIZE/2 + (int)((TILESIZE*3/7) * x2);
+           coords[d*4+3] = y + TILESIZE/2 + (int)((TILESIZE*3/7) * y2);
+       }
+       draw_polygon(dr, coords, DIRECTIONS*2, COL_DEAD_PLAYER, COL_OUTLINE);
+    } else {
+       draw_circle(dr, x + TILESIZE/2, y + TILESIZE/2,
+                   TILESIZE/3, COL_PLAYER, COL_OUTLINE);
+    }
+
+    if (!dead && hintdir >= 0) {
+       float scale = (DX(hintdir) && DY(hintdir) ? 0.8F : 1.0F);
+       int ax = (TILESIZE*2/5) * scale * DX(hintdir);
+       int ay = (TILESIZE*2/5) * scale * DY(hintdir);
+       int px = -ay, py = ax;
+       int ox = x + TILESIZE/2, oy = y + TILESIZE/2;
+       int coords[14], *c;
+
+       c = coords;
+       *c++ = ox + px/9;
+       *c++ = oy + py/9;
+       *c++ = ox + px/9 + ax*2/3;
+       *c++ = oy + py/9 + ay*2/3;
+       *c++ = ox + px/3 + ax*2/3;
+       *c++ = oy + py/3 + ay*2/3;
+       *c++ = ox + ax;
+       *c++ = oy + ay;
+       *c++ = ox - px/3 + ax*2/3;
+       *c++ = oy - py/3 + ay*2/3;
+       *c++ = ox - px/9 + ax*2/3;
+       *c++ = oy - py/9 + ay*2/3;
+       *c++ = ox - px/9;
+       *c++ = oy - py/9;
+       draw_polygon(dr, coords, 7, COL_HINT, COL_OUTLINE);
+    }
+
+    draw_update(dr, x, y, TILESIZE, TILESIZE);
+}
+
+#define FLASH_DEAD 0x100
+#define FLASH_WIN  0x200
+#define FLASH_MASK 0x300
+
+static void draw_tile(drawing *dr, game_drawstate *ds, int x, int y, int v)
+{
+    int tx = COORD(x), ty = COORD(y);
+    int bg = (v & FLASH_DEAD ? COL_DEAD_PLAYER :
+             v & FLASH_WIN ? COL_HIGHLIGHT : COL_BACKGROUND);
+
+    v &= ~FLASH_MASK;
+
+    clip(dr, tx+1, ty+1, TILESIZE-1, TILESIZE-1);
+    draw_rect(dr, tx+1, ty+1, TILESIZE-1, TILESIZE-1, bg);
+
+    if (v == WALL) {
+       int coords[6];
+
+        coords[0] = tx + TILESIZE;
+        coords[1] = ty + TILESIZE;
+        coords[2] = tx + TILESIZE;
+        coords[3] = ty + 1;
+        coords[4] = tx + 1;
+        coords[5] = ty + TILESIZE;
+        draw_polygon(dr, coords, 3, COL_LOWLIGHT, COL_LOWLIGHT);
+
+        coords[0] = tx + 1;
+        coords[1] = ty + 1;
+        draw_polygon(dr, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT);
+
+        draw_rect(dr, tx + 1 + HIGHLIGHT_WIDTH, ty + 1 + HIGHLIGHT_WIDTH,
+                  TILESIZE - 2*HIGHLIGHT_WIDTH,
+                 TILESIZE - 2*HIGHLIGHT_WIDTH, COL_WALL);
+    } else if (v == MINE) {
+       int cx = tx + TILESIZE / 2;
+       int cy = ty + TILESIZE / 2;
+       int r = TILESIZE / 2 - 3;
+
+       draw_circle(dr, cx, cy, 5*r/6, COL_MINE, COL_MINE);
+       draw_rect(dr, cx - r/6, cy - r, 2*(r/6)+1, 2*r+1, COL_MINE);
+       draw_rect(dr, cx - r, cy - r/6, 2*r+1, 2*(r/6)+1, COL_MINE);
+       draw_rect(dr, cx-r/3, cy-r/3, r/3, r/4, COL_HIGHLIGHT);
+    } else if (v == STOP) {
+       draw_circle(dr, tx + TILESIZE/2, ty + TILESIZE/2,
+                   TILESIZE*3/7, -1, COL_OUTLINE);
+       draw_rect(dr, tx + TILESIZE*3/7, ty+1,
+                 TILESIZE - 2*(TILESIZE*3/7) + 1, TILESIZE-1, bg);
+       draw_rect(dr, tx+1, ty + TILESIZE*3/7,
+                 TILESIZE-1, TILESIZE - 2*(TILESIZE*3/7) + 1, bg);
+    } else if (v == GEM) {
+       int coords[8];
+
+       coords[0] = tx+TILESIZE/2;
+       coords[1] = ty+TILESIZE/2-TILESIZE*5/14;
+       coords[2] = tx+TILESIZE/2-TILESIZE*5/14;
+       coords[3] = ty+TILESIZE/2;
+       coords[4] = tx+TILESIZE/2;
+       coords[5] = ty+TILESIZE/2+TILESIZE*5/14;
+       coords[6] = tx+TILESIZE/2+TILESIZE*5/14;
+       coords[7] = ty+TILESIZE/2;
+
+       draw_polygon(dr, coords, 4, COL_GEM, COL_OUTLINE);
+    }
+
+    unclip(dr);
+    draw_update(dr, tx, ty, TILESIZE, TILESIZE);
+}
+
+#define BASE_ANIM_LENGTH 0.1F
+#define FLASH_LENGTH 0.3F
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int w = state->p.w, h = state->p.h /*, wh = w*h */;
+    int x, y;
+    float ap;
+    int player_dist;
+    int flashtype;
+    int gems, deaths;
+    char status[256];
+
+    if (flashtime &&
+       !((int)(flashtime * 3 / FLASH_LENGTH) % 2))
+       flashtype = ui->flashtype;
+    else
+       flashtype = 0;
+
+    /*
+     * Erase the player sprite.
+     */
+    if (ds->player_bg_saved) {
+       assert(ds->player_background);
+        blitter_load(dr, ds->player_background, ds->pbgx, ds->pbgy);
+        draw_update(dr, ds->pbgx, ds->pbgy, TILESIZE, TILESIZE);
+       ds->player_bg_saved = FALSE;
+    }
+
+    /*
+     * Initialise a fresh drawstate.
+     */
+    if (!ds->started) {
+       int wid, ht;
+
+       /*
+        * Blank out the window initially.
+        */
+       game_compute_size(&ds->p, TILESIZE, &wid, &ht);
+       draw_rect(dr, 0, 0, wid, ht, COL_BACKGROUND);
+       draw_update(dr, 0, 0, wid, ht);
+
+       /*
+        * Draw the grid lines.
+        */
+       for (y = 0; y <= h; y++)
+           draw_line(dr, COORD(0), COORD(y), COORD(w), COORD(y),
+                     COL_LOWLIGHT);
+       for (x = 0; x <= w; x++)
+           draw_line(dr, COORD(x), COORD(0), COORD(x), COORD(h),
+                     COL_LOWLIGHT);
+
+       ds->started = TRUE;
+    }
+
+    /*
+     * If we're in the process of animating a move, let's start by
+     * working out how far the player has moved from their _older_
+     * state.
+     */
+    if (oldstate) {
+       ap = animtime / ui->anim_length;
+       player_dist = ap * (dir > 0 ? state : oldstate)->distance_moved;
+    } else {
+       player_dist = 0;
+       ap = 0.0F;
+    }
+
+    /*
+     * Draw the grid contents.
+     * 
+     * We count the gems as we go round this loop, for the purposes
+     * of the status bar. Of course we have a gems counter in the
+     * game_state already, but if we do the counting in this loop
+     * then it tracks gems being picked up in a sliding move, and
+     * updates one by one.
+     */
+    gems = 0;
+    for (y = 0; y < h; y++)
+       for (x = 0; x < w; x++) {
+           unsigned short v = (unsigned char)state->grid[y*w+x];
+
+           /*
+            * Special case: if the player is in the process of
+            * moving over a gem, we draw the gem iff they haven't
+            * gone past it yet.
+            */
+           if (oldstate && oldstate->grid[y*w+x] != state->grid[y*w+x]) {
+               /*
+                * Compute the distance from this square to the
+                * original player position.
+                */
+               int dist = max(abs(x - oldstate->px), abs(y - oldstate->py));
+
+               /*
+                * If the player has reached here, use the new grid
+                * element. Otherwise use the old one.
+                */
+               if (player_dist < dist)
+                   v = oldstate->grid[y*w+x];
+               else
+                   v = state->grid[y*w+x];
+           }
+
+           /*
+            * Special case: erase the mine the dead player is
+            * sitting on. Only at the end of the move.
+            */
+           if (v == MINE && !oldstate && state->dead &&
+               x == state->px && y == state->py)
+               v = BLANK;
+
+           if (v == GEM)
+               gems++;
+
+           v |= flashtype;
+
+           if (ds->grid[y*w+x] != v) {
+               draw_tile(dr, ds, x, y, v);
+               ds->grid[y*w+x] = v;
+           }
+       }
+
+    /*
+     * Gem counter in the status bar. We replace it with
+     * `COMPLETED!' when it reaches zero ... or rather, when the
+     * _current state_'s gem counter is zero. (Thus, `Gems: 0' is
+     * shown between the collection of the last gem and the
+     * completion of the move animation that did it.)
+     */
+    if (state->dead && (!oldstate || oldstate->dead)) {
+       sprintf(status, "DEAD!");
+    } else if (state->gems || (oldstate && oldstate->gems)) {
+       if (state->cheated)
+           sprintf(status, "Auto-solver used. ");
+       else
+           *status = '\0';
+       sprintf(status + strlen(status), "Gems: %d", gems);
+    } else if (state->cheated) {
+       sprintf(status, "Auto-solved.");
+    } else {
+       sprintf(status, "COMPLETED!");
+    }
+    /* We subtract one from the visible death counter if we're still
+     * animating the move at the end of which the death took place. */
+    deaths = ui->deaths;
+    if (oldstate && ui->just_died) {
+       assert(deaths > 0);
+       deaths--;
+    }
+    if (deaths)
+       sprintf(status + strlen(status), "   Deaths: %d", deaths);
+    status_bar(dr, status);
+
+    /*
+     * Draw the player sprite.
+     */
+    assert(!ds->player_bg_saved);
+    assert(ds->player_background);
+    {
+       int ox, oy, nx, ny;
+       nx = COORD(state->px);
+       ny = COORD(state->py);
+       if (oldstate) {
+           ox = COORD(oldstate->px);
+           oy = COORD(oldstate->py);
+       } else {
+           ox = nx;
+           oy = ny;
+       }
+       ds->pbgx = ox + ap * (nx - ox);
+       ds->pbgy = oy + ap * (ny - oy);
+    }
+    blitter_save(dr, ds->player_background, ds->pbgx, ds->pbgy);
+    draw_player(dr, ds, ds->pbgx, ds->pbgy,
+               (state->dead && !oldstate),
+               (!oldstate && state->soln ?
+                state->soln->list[state->solnpos] : -1));
+    ds->player_bg_saved = TRUE;
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    int dist;
+    if (dir > 0)
+       dist = newstate->distance_moved;
+    else
+       dist = oldstate->distance_moved;
+    ui->anim_length = sqrt(dist) * BASE_ANIM_LENGTH;
+    return ui->anim_length;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    if (!oldstate->dead && newstate->dead) {
+       ui->flashtype = FLASH_DEAD;
+       return FLASH_LENGTH;
+    } else if (oldstate->gems && !newstate->gems) {
+       ui->flashtype = FLASH_WIN;
+       return FLASH_LENGTH;
+    }
+    return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    /*
+     * We never report the game as lost, on the grounds that if the
+     * player has died they're quite likely to want to undo and carry
+     * on.
+     */
+    return state->gems == 0 ? +1 : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+}
+
+#ifdef COMBINED
+#define thegame inertia
+#endif
+
+const struct game thegame = {
+    "Inertia", "games.inertia", "inertia",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    TRUE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILESIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    FALSE, FALSE, game_print_size, game_print,
+    TRUE,                             /* wants_statusbar */
+    FALSE, game_timing_state,
+    0,                                /* flags */
+};
diff --git a/install-sh b/install-sh
new file mode 100755 (executable)
index 0000000..59990a1
--- /dev/null
@@ -0,0 +1,508 @@
+#!/bin/sh
+# install - install a program, script, or datafile
+
+scriptversion=2014-09-12.12; # UTC
+
+# This originates from X11R5 (mit/util/scripts/install.sh), which was
+# later released in X11R6 (xc/config/util/install.sh) with the
+# following copyright and license.
+#
+# Copyright (C) 1994 X Consortium
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC-
+# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+# Except as contained in this notice, the name of the X Consortium shall not
+# be used in advertising or otherwise to promote the sale, use or other deal-
+# ings in this Software without prior written authorization from the X Consor-
+# tium.
+#
+#
+# FSF changes to this file are in the public domain.
+#
+# Calling this script install-sh is preferred over install.sh, to prevent
+# 'make' implicit rules from creating a file called install from it
+# when there is no Makefile.
+#
+# This script is compatible with the BSD install script, but was written
+# from scratch.
+
+tab='  '
+nl='
+'
+IFS=" $tab$nl"
+
+# Set DOITPROG to "echo" to test this script.
+
+doit=${DOITPROG-}
+doit_exec=${doit:-exec}
+
+# Put in absolute file names if you don't have them in your path;
+# or use environment vars.
+
+chgrpprog=${CHGRPPROG-chgrp}
+chmodprog=${CHMODPROG-chmod}
+chownprog=${CHOWNPROG-chown}
+cmpprog=${CMPPROG-cmp}
+cpprog=${CPPROG-cp}
+mkdirprog=${MKDIRPROG-mkdir}
+mvprog=${MVPROG-mv}
+rmprog=${RMPROG-rm}
+stripprog=${STRIPPROG-strip}
+
+posix_mkdir=
+
+# Desired mode of installed file.
+mode=0755
+
+chgrpcmd=
+chmodcmd=$chmodprog
+chowncmd=
+mvcmd=$mvprog
+rmcmd="$rmprog -f"
+stripcmd=
+
+src=
+dst=
+dir_arg=
+dst_arg=
+
+copy_on_change=false
+is_target_a_directory=possibly
+
+usage="\
+Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE
+   or: $0 [OPTION]... SRCFILES... DIRECTORY
+   or: $0 [OPTION]... -t DIRECTORY SRCFILES...
+   or: $0 [OPTION]... -d DIRECTORIES...
+
+In the 1st form, copy SRCFILE to DSTFILE.
+In the 2nd and 3rd, copy all SRCFILES to DIRECTORY.
+In the 4th, create DIRECTORIES.
+
+Options:
+     --help     display this help and exit.
+     --version  display version info and exit.
+
+  -c            (ignored)
+  -C            install only if different (preserve the last data modification time)
+  -d            create directories instead of installing files.
+  -g GROUP      $chgrpprog installed files to GROUP.
+  -m MODE       $chmodprog installed files to MODE.
+  -o USER       $chownprog installed files to USER.
+  -s            $stripprog installed files.
+  -t DIRECTORY  install into DIRECTORY.
+  -T            report an error if DSTFILE is a directory.
+
+Environment variables override the default commands:
+  CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG
+  RMPROG STRIPPROG
+"
+
+while test $# -ne 0; do
+  case $1 in
+    -c) ;;
+
+    -C) copy_on_change=true;;
+
+    -d) dir_arg=true;;
+
+    -g) chgrpcmd="$chgrpprog $2"
+        shift;;
+
+    --help) echo "$usage"; exit $?;;
+
+    -m) mode=$2
+        case $mode in
+          *' '* | *"$tab"* | *"$nl"* | *'*'* | *'?'* | *'['*)
+            echo "$0: invalid mode: $mode" >&2
+            exit 1;;
+        esac
+        shift;;
+
+    -o) chowncmd="$chownprog $2"
+        shift;;
+
+    -s) stripcmd=$stripprog;;
+
+    -t)
+        is_target_a_directory=always
+        dst_arg=$2
+        # Protect names problematic for 'test' and other utilities.
+        case $dst_arg in
+          -* | [=\(\)!]) dst_arg=./$dst_arg;;
+        esac
+        shift;;
+
+    -T) is_target_a_directory=never;;
+
+    --version) echo "$0 $scriptversion"; exit $?;;
+
+    --) shift
+        break;;
+
+    -*) echo "$0: invalid option: $1" >&2
+        exit 1;;
+
+    *)  break;;
+  esac
+  shift
+done
+
+# We allow the use of options -d and -T together, by making -d
+# take the precedence; this is for compatibility with GNU install.
+
+if test -n "$dir_arg"; then
+  if test -n "$dst_arg"; then
+    echo "$0: target directory not allowed when installing a directory." >&2
+    exit 1
+  fi
+fi
+
+if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then
+  # When -d is used, all remaining arguments are directories to create.
+  # When -t is used, the destination is already specified.
+  # Otherwise, the last argument is the destination.  Remove it from $@.
+  for arg
+  do
+    if test -n "$dst_arg"; then
+      # $@ is not empty: it contains at least $arg.
+      set fnord "$@" "$dst_arg"
+      shift # fnord
+    fi
+    shift # arg
+    dst_arg=$arg
+    # Protect names problematic for 'test' and other utilities.
+    case $dst_arg in
+      -* | [=\(\)!]) dst_arg=./$dst_arg;;
+    esac
+  done
+fi
+
+if test $# -eq 0; then
+  if test -z "$dir_arg"; then
+    echo "$0: no input file specified." >&2
+    exit 1
+  fi
+  # It's OK to call 'install-sh -d' without argument.
+  # This can happen when creating conditional directories.
+  exit 0
+fi
+
+if test -z "$dir_arg"; then
+  if test $# -gt 1 || test "$is_target_a_directory" = always; then
+    if test ! -d "$dst_arg"; then
+      echo "$0: $dst_arg: Is not a directory." >&2
+      exit 1
+    fi
+  fi
+fi
+
+if test -z "$dir_arg"; then
+  do_exit='(exit $ret); exit $ret'
+  trap "ret=129; $do_exit" 1
+  trap "ret=130; $do_exit" 2
+  trap "ret=141; $do_exit" 13
+  trap "ret=143; $do_exit" 15
+
+  # Set umask so as not to create temps with too-generous modes.
+  # However, 'strip' requires both read and write access to temps.
+  case $mode in
+    # Optimize common cases.
+    *644) cp_umask=133;;
+    *755) cp_umask=22;;
+
+    *[0-7])
+      if test -z "$stripcmd"; then
+        u_plus_rw=
+      else
+        u_plus_rw='% 200'
+      fi
+      cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;;
+    *)
+      if test -z "$stripcmd"; then
+        u_plus_rw=
+      else
+        u_plus_rw=,u+rw
+      fi
+      cp_umask=$mode$u_plus_rw;;
+  esac
+fi
+
+for src
+do
+  # Protect names problematic for 'test' and other utilities.
+  case $src in
+    -* | [=\(\)!]) src=./$src;;
+  esac
+
+  if test -n "$dir_arg"; then
+    dst=$src
+    dstdir=$dst
+    test -d "$dstdir"
+    dstdir_status=$?
+  else
+
+    # Waiting for this to be detected by the "$cpprog $src $dsttmp" command
+    # might cause directories to be created, which would be especially bad
+    # if $src (and thus $dsttmp) contains '*'.
+    if test ! -f "$src" && test ! -d "$src"; then
+      echo "$0: $src does not exist." >&2
+      exit 1
+    fi
+
+    if test -z "$dst_arg"; then
+      echo "$0: no destination specified." >&2
+      exit 1
+    fi
+    dst=$dst_arg
+
+    # If destination is a directory, append the input filename; won't work
+    # if double slashes aren't ignored.
+    if test -d "$dst"; then
+      if test "$is_target_a_directory" = never; then
+        echo "$0: $dst_arg: Is a directory" >&2
+        exit 1
+      fi
+      dstdir=$dst
+      dst=$dstdir/`basename "$src"`
+      dstdir_status=0
+    else
+      dstdir=`dirname "$dst"`
+      test -d "$dstdir"
+      dstdir_status=$?
+    fi
+  fi
+
+  obsolete_mkdir_used=false
+
+  if test $dstdir_status != 0; then
+    case $posix_mkdir in
+      '')
+        # Create intermediate dirs using mode 755 as modified by the umask.
+        # This is like FreeBSD 'install' as of 1997-10-28.
+        umask=`umask`
+        case $stripcmd.$umask in
+          # Optimize common cases.
+          *[2367][2367]) mkdir_umask=$umask;;
+          .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;;
+
+          *[0-7])
+            mkdir_umask=`expr $umask + 22 \
+              - $umask % 100 % 40 + $umask % 20 \
+              - $umask % 10 % 4 + $umask % 2
+            `;;
+          *) mkdir_umask=$umask,go-w;;
+        esac
+
+        # With -d, create the new directory with the user-specified mode.
+        # Otherwise, rely on $mkdir_umask.
+        if test -n "$dir_arg"; then
+          mkdir_mode=-m$mode
+        else
+          mkdir_mode=
+        fi
+
+        posix_mkdir=false
+        case $umask in
+          *[123567][0-7][0-7])
+            # POSIX mkdir -p sets u+wx bits regardless of umask, which
+            # is incompatible with FreeBSD 'install' when (umask & 300) != 0.
+            ;;
+          *)
+            # $RANDOM is not portable (e.g. dash);  use it when possible to
+            # lower collision chance
+            tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$
+            trap 'ret=$?; rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" 2>/dev/null; exit $ret' 0
+
+            # As "mkdir -p" follows symlinks and we work in /tmp possibly;  so
+            # create the $tmpdir first (and fail if unsuccessful) to make sure
+            # that nobody tries to guess the $tmpdir name.
+            if (umask $mkdir_umask &&
+                $mkdirprog $mkdir_mode "$tmpdir" &&
+                exec $mkdirprog $mkdir_mode -p -- "$tmpdir/a/b") >/dev/null 2>&1
+            then
+              if test -z "$dir_arg" || {
+                   # Check for POSIX incompatibilities with -m.
+                   # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or
+                   # other-writable bit of parent directory when it shouldn't.
+                   # FreeBSD 6.1 mkdir -m -p sets mode of existing directory.
+                   test_tmpdir="$tmpdir/a"
+                   ls_ld_tmpdir=`ls -ld "$test_tmpdir"`
+                   case $ls_ld_tmpdir in
+                     d????-?r-*) different_mode=700;;
+                     d????-?--*) different_mode=755;;
+                     *) false;;
+                   esac &&
+                   $mkdirprog -m$different_mode -p -- "$test_tmpdir" && {
+                     ls_ld_tmpdir_1=`ls -ld "$test_tmpdir"`
+                     test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1"
+                   }
+                 }
+              then posix_mkdir=:
+              fi
+              rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir"
+            else
+              # Remove any dirs left behind by ancient mkdir implementations.
+              rmdir ./$mkdir_mode ./-p ./-- "$tmpdir" 2>/dev/null
+            fi
+            trap '' 0;;
+        esac;;
+    esac
+
+    if
+      $posix_mkdir && (
+        umask $mkdir_umask &&
+        $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir"
+      )
+    then :
+    else
+
+      # The umask is ridiculous, or mkdir does not conform to POSIX,
+      # or it failed possibly due to a race condition.  Create the
+      # directory the slow way, step by step, checking for races as we go.
+
+      case $dstdir in
+        /*) prefix='/';;
+        [-=\(\)!]*) prefix='./';;
+        *)  prefix='';;
+      esac
+
+      oIFS=$IFS
+      IFS=/
+      set -f
+      set fnord $dstdir
+      shift
+      set +f
+      IFS=$oIFS
+
+      prefixes=
+
+      for d
+      do
+        test X"$d" = X && continue
+
+        prefix=$prefix$d
+        if test -d "$prefix"; then
+          prefixes=
+        else
+          if $posix_mkdir; then
+            (umask=$mkdir_umask &&
+             $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break
+            # Don't fail if two instances are running concurrently.
+            test -d "$prefix" || exit 1
+          else
+            case $prefix in
+              *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;;
+              *) qprefix=$prefix;;
+            esac
+            prefixes="$prefixes '$qprefix'"
+          fi
+        fi
+        prefix=$prefix/
+      done
+
+      if test -n "$prefixes"; then
+        # Don't fail if two instances are running concurrently.
+        (umask $mkdir_umask &&
+         eval "\$doit_exec \$mkdirprog $prefixes") ||
+          test -d "$dstdir" || exit 1
+        obsolete_mkdir_used=true
+      fi
+    fi
+  fi
+
+  if test -n "$dir_arg"; then
+    { test -z "$chowncmd" || $doit $chowncmd "$dst"; } &&
+    { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } &&
+    { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false ||
+      test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1
+  else
+
+    # Make a couple of temp file names in the proper directory.
+    dsttmp=$dstdir/_inst.$$_
+    rmtmp=$dstdir/_rm.$$_
+
+    # Trap to clean up those temp files at exit.
+    trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0
+
+    # Copy the file name to the temp name.
+    (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") &&
+
+    # and set any options; do chmod last to preserve setuid bits.
+    #
+    # If any of these fail, we abort the whole thing.  If we want to
+    # ignore errors from any of these, just make sure not to ignore
+    # errors from the above "$doit $cpprog $src $dsttmp" command.
+    #
+    { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } &&
+    { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } &&
+    { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } &&
+    { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } &&
+
+    # If -C, don't bother to copy if it wouldn't change the file.
+    if $copy_on_change &&
+       old=`LC_ALL=C ls -dlL "$dst"     2>/dev/null` &&
+       new=`LC_ALL=C ls -dlL "$dsttmp"  2>/dev/null` &&
+       set -f &&
+       set X $old && old=:$2:$4:$5:$6 &&
+       set X $new && new=:$2:$4:$5:$6 &&
+       set +f &&
+       test "$old" = "$new" &&
+       $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1
+    then
+      rm -f "$dsttmp"
+    else
+      # Rename the file to the real destination.
+      $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null ||
+
+      # The rename failed, perhaps because mv can't rename something else
+      # to itself, or perhaps because mv is so ancient that it does not
+      # support -f.
+      {
+        # Now remove or move aside any old file at destination location.
+        # We try this two ways since rm can't unlink itself on some
+        # systems and the destination file might be busy for other
+        # reasons.  In this case, the final cleanup might fail but the new
+        # file should still install successfully.
+        {
+          test ! -f "$dst" ||
+          $doit $rmcmd -f "$dst" 2>/dev/null ||
+          { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null &&
+            { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; }
+          } ||
+          { echo "$0: cannot unlink or rename $dst" >&2
+            (exit 1); exit 1
+          }
+        } &&
+
+        # Now rename the file to the real destination.
+        $doit $mvcmd "$dsttmp" "$dst"
+      }
+    fi || exit 1
+
+    trap '' 0
+  fi
+done
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC"
+# time-stamp-end: "; # UTC"
+# End:
diff --git a/keen.R b/keen.R
new file mode 100644 (file)
index 0000000..77609bc
--- /dev/null
+++ b/keen.R
@@ -0,0 +1,25 @@
+# -*- makefile -*-
+
+KEEN_LATIN_EXTRA = tree234 maxflow dsf
+KEEN_EXTRA = latin KEEN_LATIN_EXTRA
+
+keen    : [X] GTK COMMON keen KEEN_EXTRA keen-icon|no-icon
+
+keen    : [G] WINDOWS COMMON keen KEEN_EXTRA keen.res|noicon.res
+
+keensolver : [U] keen[STANDALONE_SOLVER] latin[STANDALONE_SOLVER] KEEN_LATIN_EXTRA STANDALONE
+keensolver : [C] keen[STANDALONE_SOLVER] latin[STANDALONE_SOLVER] KEEN_LATIN_EXTRA STANDALONE
+
+ALL += keen[COMBINED] KEEN_EXTRA
+
+!begin am gtk
+GAMES += keen
+!end
+
+!begin >list.c
+    A(keen) \
+!end
+
+!begin >gamedesc.txt
+keen:keen.exe:Keen:Arithmetic Latin square puzzle:Complete the latin square in accordance with the arithmetic clues.
+!end
diff --git a/keen.c b/keen.c
new file mode 100644 (file)
index 0000000..b91e95b
--- /dev/null
+++ b/keen.c
@@ -0,0 +1,2479 @@
+/*
+ * keen.c: an implementation of the Times's 'KenKen' puzzle, and
+ * also of Nikoli's very similar 'Inshi No Heya' puzzle.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+#include "latin.h"
+
+/*
+ * Difficulty levels. I do some macro ickery here to ensure that my
+ * enum and the various forms of my name list always match up.
+ */
+#define DIFFLIST(A) \
+    A(EASY,Easy,solver_easy,e) \
+    A(NORMAL,Normal,solver_normal,n) \
+    A(HARD,Hard,solver_hard,h) \
+    A(EXTREME,Extreme,NULL,x) \
+    A(UNREASONABLE,Unreasonable,NULL,u)
+#define ENUM(upper,title,func,lower) DIFF_ ## upper,
+#define TITLE(upper,title,func,lower) #title,
+#define ENCODE(upper,title,func,lower) #lower
+#define CONFIG(upper,title,func,lower) ":" #title
+enum { DIFFLIST(ENUM) DIFFCOUNT };
+static char const *const keen_diffnames[] = { DIFFLIST(TITLE) };
+static char const keen_diffchars[] = DIFFLIST(ENCODE);
+#define DIFFCONFIG DIFFLIST(CONFIG)
+
+/*
+ * Clue notation. Important here that ADD and MUL come before SUB
+ * and DIV, and that DIV comes last. 
+ */
+#define C_ADD 0x00000000L
+#define C_MUL 0x20000000L
+#define C_SUB 0x40000000L
+#define C_DIV 0x60000000L
+#define CMASK 0x60000000L
+#define CUNIT 0x20000000L
+
+/*
+ * Maximum size of any clue block. Very large ones are annoying in UI
+ * terms (if they're multiplicative you end up with too many digits to
+ * fit in the square) and also in solver terms (too many possibilities
+ * to iterate over).
+ */
+#define MAXBLK 6
+
+enum {
+    COL_BACKGROUND,
+    COL_GRID,
+    COL_USER,
+    COL_HIGHLIGHT,
+    COL_ERROR,
+    COL_PENCIL,
+    NCOLOURS
+};
+
+struct game_params {
+    int w, diff, multiplication_only;
+};
+
+struct clues {
+    int refcount;
+    int w;
+    int *dsf;
+    long *clues;
+};
+
+struct game_state {
+    game_params par;
+    struct clues *clues;
+    digit *grid;
+    int *pencil;                      /* bitmaps using bits 1<<1..1<<n */
+    int completed, cheated;
+};
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = 6;
+    ret->diff = DIFF_NORMAL;
+    ret->multiplication_only = FALSE;
+
+    return ret;
+}
+
+const static struct game_params keen_presets[] = {
+    {  4, DIFF_EASY,         FALSE },
+    {  5, DIFF_EASY,         FALSE },
+    {  5, DIFF_EASY,         TRUE  },
+    {  6, DIFF_EASY,         FALSE },
+    {  6, DIFF_NORMAL,       FALSE },
+    {  6, DIFF_NORMAL,       TRUE  },
+    {  6, DIFF_HARD,         FALSE },
+    {  6, DIFF_EXTREME,      FALSE },
+    {  6, DIFF_UNREASONABLE, FALSE },
+    {  9, DIFF_NORMAL,       FALSE },
+};
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params *ret;
+    char buf[80];
+
+    if (i < 0 || i >= lenof(keen_presets))
+        return FALSE;
+
+    ret = snew(game_params);
+    *ret = keen_presets[i]; /* structure copy */
+
+    sprintf(buf, "%dx%d %s%s", ret->w, ret->w, keen_diffnames[ret->diff],
+           ret->multiplication_only ? ", multiplication only" : "");
+
+    *name = dupstr(buf);
+    *params = ret;
+    return TRUE;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+static void decode_params(game_params *params, char const *string)
+{
+    char const *p = string;
+
+    params->w = atoi(p);
+    while (*p && isdigit((unsigned char)*p)) p++;
+
+    if (*p == 'd') {
+        int i;
+        p++;
+        params->diff = DIFFCOUNT+1; /* ...which is invalid */
+        if (*p) {
+            for (i = 0; i < DIFFCOUNT; i++) {
+                if (*p == keen_diffchars[i])
+                    params->diff = i;
+            }
+            p++;
+        }
+    }
+
+    if (*p == 'm') {
+       p++;
+       params->multiplication_only = TRUE;
+    }
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char ret[80];
+
+    sprintf(ret, "%d", params->w);
+    if (full)
+        sprintf(ret + strlen(ret), "d%c%s", keen_diffchars[params->diff],
+               params->multiplication_only ? "m" : "");
+
+    return dupstr(ret);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+    char buf[80];
+
+    ret = snewn(4, config_item);
+
+    ret[0].name = "Grid size";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->w);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Difficulty";
+    ret[1].type = C_CHOICES;
+    ret[1].sval = DIFFCONFIG;
+    ret[1].ival = params->diff;
+
+    ret[2].name = "Multiplication only";
+    ret[2].type = C_BOOLEAN;
+    ret[2].sval = NULL;
+    ret[2].ival = params->multiplication_only;
+
+    ret[3].name = NULL;
+    ret[3].type = C_END;
+    ret[3].sval = NULL;
+    ret[3].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = atoi(cfg[0].sval);
+    ret->diff = cfg[1].ival;
+    ret->multiplication_only = cfg[2].ival;
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    if (params->w < 3 || params->w > 9)
+        return "Grid size must be between 3 and 9";
+    if (params->diff >= DIFFCOUNT)
+        return "Unknown difficulty rating";
+    return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Solver.
+ */
+
+struct solver_ctx {
+    int w, diff;
+    int nboxes;
+    int *boxes, *boxlist, *whichbox;
+    long *clues;
+    digit *soln;
+    digit *dscratch;
+    int *iscratch;
+};
+
+static void solver_clue_candidate(struct solver_ctx *ctx, int diff, int box)
+{
+    int w = ctx->w;
+    int n = ctx->boxes[box+1] - ctx->boxes[box];
+    int j;
+
+    /*
+     * This function is called from the main clue-based solver
+     * routine when we discover a candidate layout for a given clue
+     * box consistent with everything we currently know about the
+     * digit constraints in that box. We expect to find the digits
+     * of the candidate layout in ctx->dscratch, and we update
+     * ctx->iscratch as appropriate.
+     *
+     * The contents of ctx->iscratch are completely different
+     * depending on whether diff == DIFF_HARD or not. This function
+     * uses iscratch completely differently between the two cases, and
+     * the code in solver_common() which consumes the result must
+     * likewise have an if statement with completely different
+     * branches for the two cases.
+     *
+     * In DIFF_EASY and DIFF_NORMAL modes, the valid entries in
+     * ctx->iscratch are 0,...,n-1, and each of those entries
+     * ctx->iscratch[i] gives a bitmap of the possible digits in the
+     * ith square of the clue box currently under consideration. So
+     * each entry of iscratch starts off as an empty bitmap, and we
+     * set bits in it as possible layouts for the clue box are
+     * considered (and the difference between DIFF_EASY and
+     * DIFF_NORMAL is just that in DIFF_EASY mode we deliberately set
+     * more bits than absolutely necessary, hence restricting our own
+     * knowledge).
+     *
+     * But in DIFF_HARD mode, the valid entries are 0,...,2*w-1 (at
+     * least outside *this* function - inside this function, we also
+     * use 2*w,...,4*w-1 as scratch space in the loop below); the
+     * first w of those give the possible digits in the intersection
+     * of the current clue box with each column of the puzzle, and the
+     * next w do the same for each row. In this mode, each iscratch
+     * entry starts off as a _full_ bitmap, and in this function we
+     * _clear_ bits for digits that are absent from a given row or
+     * column in each candidate layout, so that the only bits which
+     * remain set are those for digits which have to appear in a given
+     * row/column no matter how the clue box is laid out.
+     */
+    if (diff == DIFF_EASY) {
+       unsigned mask = 0;
+       /*
+        * Easy-mode clue deductions: we do not record information
+        * about which squares take which values, so we amalgamate
+        * all the values in dscratch and OR them all into
+        * everywhere.
+        */
+       for (j = 0; j < n; j++)
+           mask |= 1 << ctx->dscratch[j];
+       for (j = 0; j < n; j++)
+           ctx->iscratch[j] |= mask;
+    } else if (diff == DIFF_NORMAL) {
+       /*
+        * Normal-mode deductions: we process the information in
+        * dscratch in the obvious way.
+        */
+       for (j = 0; j < n; j++)
+           ctx->iscratch[j] |= 1 << ctx->dscratch[j];
+    } else if (diff == DIFF_HARD) {
+       /*
+        * Hard-mode deductions: instead of ruling things out
+        * _inside_ the clue box, we look for numbers which occur in
+        * a given row or column in all candidate layouts, and rule
+        * them out of all squares in that row or column that
+        * _aren't_ part of this clue box.
+        */
+       int *sq = ctx->boxlist + ctx->boxes[box];
+
+       for (j = 0; j < 2*w; j++)
+           ctx->iscratch[2*w+j] = 0;
+       for (j = 0; j < n; j++) {
+           int x = sq[j] / w, y = sq[j] % w;
+           ctx->iscratch[2*w+x] |= 1 << ctx->dscratch[j];
+           ctx->iscratch[3*w+y] |= 1 << ctx->dscratch[j];
+       }
+       for (j = 0; j < 2*w; j++)
+           ctx->iscratch[j] &= ctx->iscratch[2*w+j];
+    }
+}
+
+static int solver_common(struct latin_solver *solver, void *vctx, int diff)
+{
+    struct solver_ctx *ctx = (struct solver_ctx *)vctx;
+    int w = ctx->w;
+    int box, i, j, k;
+    int ret = 0, total;
+
+    /*
+     * Iterate over each clue box and deduce what we can.
+     */
+    for (box = 0; box < ctx->nboxes; box++) {
+       int *sq = ctx->boxlist + ctx->boxes[box];
+       int n = ctx->boxes[box+1] - ctx->boxes[box];
+       long value = ctx->clues[box] & ~CMASK;
+       long op = ctx->clues[box] & CMASK;
+
+        /*
+         * Initialise ctx->iscratch for this clue box. At different
+         * difficulty levels we must initialise a different amount of
+         * it to different things; see the comments in
+         * solver_clue_candidate explaining what each version does.
+         */
+       if (diff == DIFF_HARD) {
+           for (i = 0; i < 2*w; i++)
+               ctx->iscratch[i] = (1 << (w+1)) - (1 << 1);
+       } else {
+           for (i = 0; i < n; i++)
+               ctx->iscratch[i] = 0;
+       }
+
+       switch (op) {
+         case C_SUB:
+         case C_DIV:
+           /*
+            * These two clue types must always apply to a box of
+            * area 2. Also, the two digits in these boxes can never
+            * be the same (because any domino must have its two
+            * squares in either the same row or the same column).
+            * So we simply iterate over all possibilities for the
+            * two squares (both ways round), rule out any which are
+            * inconsistent with the digit constraints we already
+            * have, and update the digit constraints with any new
+            * information thus garnered.
+            */
+           assert(n == 2);
+
+           for (i = 1; i <= w; i++) {
+               j = (op == C_SUB ? i + value : i * value);
+               if (j > w) break;
+
+               /* (i,j) is a valid digit pair. Try it both ways round. */
+
+               if (solver->cube[sq[0]*w+i-1] &&
+                   solver->cube[sq[1]*w+j-1]) {
+                   ctx->dscratch[0] = i;
+                   ctx->dscratch[1] = j;
+                   solver_clue_candidate(ctx, diff, box);
+               }
+
+               if (solver->cube[sq[0]*w+j-1] &&
+                   solver->cube[sq[1]*w+i-1]) {
+                   ctx->dscratch[0] = j;
+                   ctx->dscratch[1] = i;
+                   solver_clue_candidate(ctx, diff, box);
+               }
+           }
+
+           break;
+
+         case C_ADD:
+         case C_MUL:
+           /*
+            * For these clue types, I have no alternative but to go
+            * through all possible number combinations.
+            *
+            * Instead of a tedious physical recursion, I iterate in
+            * the scratch array through all possibilities. At any
+            * given moment, i indexes the element of the box that
+            * will next be incremented.
+            */
+           i = 0;
+           ctx->dscratch[i] = 0;
+           total = value;             /* start with the identity */
+           while (1) {
+               if (i < n) {
+                   /*
+                    * Find the next valid value for cell i.
+                    */
+                   for (j = ctx->dscratch[i] + 1; j <= w; j++) {
+                       if (op == C_ADD ? (total < j) : (total % j != 0))
+                           continue;  /* this one won't fit */
+                       if (!solver->cube[sq[i]*w+j-1])
+                           continue;  /* this one is ruled out already */
+                       for (k = 0; k < i; k++)
+                           if (ctx->dscratch[k] == j &&
+                               (sq[k] % w == sq[i] % w ||
+                                sq[k] / w == sq[i] / w))
+                               break; /* clashes with another row/col */
+                       if (k < i)
+                           continue;
+
+                       /* Found one. */
+                       break;
+                   }
+
+                   if (j > w) {
+                       /* No valid values left; drop back. */
+                       i--;
+                       if (i < 0)
+                           break;     /* overall iteration is finished */
+                       if (op == C_ADD)
+                           total += ctx->dscratch[i];
+                       else
+                           total *= ctx->dscratch[i];
+                   } else {
+                       /* Got a valid value; store it and move on. */
+                       ctx->dscratch[i++] = j;
+                       if (op == C_ADD)
+                           total -= j;
+                       else
+                           total /= j;
+                       ctx->dscratch[i] = 0;
+                   }
+               } else {
+                   if (total == (op == C_ADD ? 0 : 1))
+                       solver_clue_candidate(ctx, diff, box);
+                   i--;
+                   if (op == C_ADD)
+                       total += ctx->dscratch[i];
+                   else
+                       total *= ctx->dscratch[i];
+               }
+           }
+
+           break;
+       }
+
+        /*
+         * Do deductions based on the information we've now
+         * accumulated in ctx->iscratch. See the comments above in
+         * solver_clue_candidate explaining what data is left in here,
+         * and how it differs between DIFF_HARD and lower difficulty
+         * levels (hence the big if statement here).
+         */
+       if (diff < DIFF_HARD) {
+#ifdef STANDALONE_SOLVER
+           char prefix[256];
+
+           if (solver_show_working)
+               sprintf(prefix, "%*susing clue at (%d,%d):\n",
+                       solver_recurse_depth*4, "",
+                       sq[0]/w+1, sq[0]%w+1);
+           else
+               prefix[0] = '\0';              /* placate optimiser */
+#endif
+
+           for (i = 0; i < n; i++)
+               for (j = 1; j <= w; j++) {
+                   if (solver->cube[sq[i]*w+j-1] &&
+                       !(ctx->iscratch[i] & (1 << j))) {
+#ifdef STANDALONE_SOLVER
+                       if (solver_show_working) {
+                           printf("%s%*s  ruling out %d at (%d,%d)\n",
+                                  prefix, solver_recurse_depth*4, "",
+                                  j, sq[i]/w+1, sq[i]%w+1);
+                           prefix[0] = '\0';
+                       }
+#endif
+                       solver->cube[sq[i]*w+j-1] = 0;
+                       ret = 1;
+                   }
+               }
+       } else {
+#ifdef STANDALONE_SOLVER
+           char prefix[256];
+
+           if (solver_show_working)
+               sprintf(prefix, "%*susing clue at (%d,%d):\n",
+                       solver_recurse_depth*4, "",
+                       sq[0]/w+1, sq[0]%w+1);
+           else
+               prefix[0] = '\0';              /* placate optimiser */
+#endif
+
+           for (i = 0; i < 2*w; i++) {
+               int start = (i < w ? i*w : i-w);
+               int step = (i < w ? 1 : w);
+               for (j = 1; j <= w; j++) if (ctx->iscratch[i] & (1 << j)) {
+#ifdef STANDALONE_SOLVER
+                   char prefix2[256];
+
+                   if (solver_show_working)
+                       sprintf(prefix2, "%*s  this clue requires %d in"
+                               " %s %d:\n", solver_recurse_depth*4, "",
+                               j, i < w ? "column" : "row", i%w+1);
+                   else
+                       prefix2[0] = '\0';   /* placate optimiser */
+#endif
+
+                   for (k = 0; k < w; k++) {
+                       int pos = start + k*step;
+                       if (ctx->whichbox[pos] != box &&
+                           solver->cube[pos*w+j-1]) {
+#ifdef STANDALONE_SOLVER
+                           if (solver_show_working) {
+                               printf("%s%s%*s   ruling out %d at (%d,%d)\n",
+                                      prefix, prefix2,
+                                      solver_recurse_depth*4, "",
+                                      j, pos/w+1, pos%w+1);
+                               prefix[0] = prefix2[0] = '\0';
+                           }
+#endif
+                           solver->cube[pos*w+j-1] = 0;
+                           ret = 1;
+                       }
+                   }
+               }
+           }
+
+           /*
+            * Once we find one block we can do something with in
+            * this way, revert to trying easier deductions, so as
+            * not to generate solver diagnostics that make the
+            * problem look harder than it is. (We have to do this
+            * for the Hard deductions but not the Easy/Normal ones,
+            * because only the Hard deductions are cross-box.)
+            */
+           if (ret)
+               return ret;
+       }
+    }
+
+    return ret;
+}
+
+static int solver_easy(struct latin_solver *solver, void *vctx)
+{
+    /*
+     * Omit the EASY deductions when solving at NORMAL level, since
+     * the NORMAL deductions are a superset of them anyway and it
+     * saves on time and confusing solver diagnostics.
+     *
+     * Note that this breaks the natural semantics of the return
+     * value of latin_solver. Without this hack, you could determine
+     * a puzzle's difficulty in one go by trying to solve it at
+     * maximum difficulty and seeing what difficulty value was
+     * returned; but with this hack, solving an Easy puzzle on
+     * Normal difficulty will typically return Normal. Hence the
+     * uses of the solver to determine difficulty are all arranged
+     * so as to double-check by re-solving at the next difficulty
+     * level down and making sure it failed.
+     */
+    struct solver_ctx *ctx = (struct solver_ctx *)vctx;
+    if (ctx->diff > DIFF_EASY)
+       return 0;
+    return solver_common(solver, vctx, DIFF_EASY);
+}
+
+static int solver_normal(struct latin_solver *solver, void *vctx)
+{
+    return solver_common(solver, vctx, DIFF_NORMAL);
+}
+
+static int solver_hard(struct latin_solver *solver, void *vctx)
+{
+    return solver_common(solver, vctx, DIFF_HARD);
+}
+
+#define SOLVER(upper,title,func,lower) func,
+static usersolver_t const keen_solvers[] = { DIFFLIST(SOLVER) };
+
+static int solver(int w, int *dsf, long *clues, digit *soln, int maxdiff)
+{
+    int a = w*w;
+    struct solver_ctx ctx;
+    int ret;
+    int i, j, n, m;
+    
+    ctx.w = w;
+    ctx.soln = soln;
+    ctx.diff = maxdiff;
+
+    /*
+     * Transform the dsf-formatted clue list into one over which we
+     * can iterate more easily.
+     *
+     * Also transpose the x- and y-coordinates at this point,
+     * because the 'cube' array in the general Latin square solver
+     * puts x first (oops).
+     */
+    for (ctx.nboxes = i = 0; i < a; i++)
+       if (dsf_canonify(dsf, i) == i)
+           ctx.nboxes++;
+    ctx.boxlist = snewn(a, int);
+    ctx.boxes = snewn(ctx.nboxes+1, int);
+    ctx.clues = snewn(ctx.nboxes, long);
+    ctx.whichbox = snewn(a, int);
+    for (n = m = i = 0; i < a; i++)
+       if (dsf_canonify(dsf, i) == i) {
+           ctx.clues[n] = clues[i];
+           ctx.boxes[n] = m;
+           for (j = 0; j < a; j++)
+               if (dsf_canonify(dsf, j) == i) {
+                   ctx.boxlist[m++] = (j % w) * w + (j / w);   /* transpose */
+                   ctx.whichbox[ctx.boxlist[m-1]] = n;
+               }
+           n++;
+       }
+    assert(n == ctx.nboxes);
+    assert(m == a);
+    ctx.boxes[n] = m;
+
+    ctx.dscratch = snewn(a+1, digit);
+    ctx.iscratch = snewn(max(a+1, 4*w), int);
+
+    ret = latin_solver(soln, w, maxdiff,
+                      DIFF_EASY, DIFF_HARD, DIFF_EXTREME,
+                      DIFF_EXTREME, DIFF_UNREASONABLE,
+                      keen_solvers, &ctx, NULL, NULL);
+
+    sfree(ctx.dscratch);
+    sfree(ctx.iscratch);
+    sfree(ctx.whichbox);
+    sfree(ctx.boxlist);
+    sfree(ctx.boxes);
+    sfree(ctx.clues);
+
+    return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * Grid generation.
+ */
+
+static char *encode_block_structure(char *p, int w, int *dsf)
+{
+    int i, currrun = 0;
+    char *orig, *q, *r, c;
+
+    orig = p;
+
+    /*
+     * Encode the block structure. We do this by encoding the
+     * pattern of dividing lines: first we iterate over the w*(w-1)
+     * internal vertical grid lines in ordinary reading order, then
+     * over the w*(w-1) internal horizontal ones in transposed
+     * reading order.
+     *
+     * We encode the number of non-lines between the lines; _ means
+     * zero (two adjacent divisions), a means 1, ..., y means 25,
+     * and z means 25 non-lines _and no following line_ (so that za
+     * means 26, zb 27 etc).
+     */
+    for (i = 0; i <= 2*w*(w-1); i++) {
+       int x, y, p0, p1, edge;
+
+       if (i == 2*w*(w-1)) {
+           edge = TRUE;       /* terminating virtual edge */
+       } else {
+           if (i < w*(w-1)) {
+               y = i/(w-1);
+               x = i%(w-1);
+               p0 = y*w+x;
+               p1 = y*w+x+1;
+           } else {
+               x = i/(w-1) - w;
+               y = i%(w-1);
+               p0 = y*w+x;
+               p1 = (y+1)*w+x;
+           }
+           edge = (dsf_canonify(dsf, p0) != dsf_canonify(dsf, p1));
+       }
+
+       if (edge) {
+           while (currrun > 25)
+               *p++ = 'z', currrun -= 25;
+           if (currrun)
+               *p++ = 'a'-1 + currrun;
+           else
+               *p++ = '_';
+           currrun = 0;
+       } else
+           currrun++;
+    }
+
+    /*
+     * Now go through and compress the string by replacing runs of
+     * the same letter with a single copy of that letter followed by
+     * a repeat count, where that makes it shorter. (This puzzle
+     * seems to generate enough long strings of _ to make this a
+     * worthwhile step.)
+     */
+    for (q = r = orig; r < p ;) {
+       *q++ = c = *r;
+
+       for (i = 0; r+i < p && r[i] == c; i++);
+       r += i;
+
+       if (i == 2) {
+           *q++ = c;
+       } else if (i > 2) {
+           q += sprintf(q, "%d", i);
+       }
+    }
+    
+    return q;
+}
+
+static char *parse_block_structure(const char **p, int w, int *dsf)
+{
+    int a = w*w;
+    int pos = 0;
+    int repc = 0, repn = 0;
+
+    dsf_init(dsf, a);
+
+    while (**p && (repn > 0 || **p != ',')) {
+       int c, adv;
+
+       if (repn > 0) {
+           repn--;
+           c = repc;
+       } else if (**p == '_' || (**p >= 'a' && **p <= 'z')) {
+           c = (**p == '_' ? 0 : **p - 'a' + 1);
+           (*p)++;
+           if (**p && isdigit((unsigned char)**p)) {
+               repc = c;
+               repn = atoi(*p)-1;
+               while (**p && isdigit((unsigned char)**p)) (*p)++;
+           }
+       } else
+           return "Invalid character in game description";
+
+       adv = (c != 25);               /* 'z' is a special case */
+
+       while (c-- > 0) {
+           int p0, p1;
+
+           /*
+            * Non-edge; merge the two dsf classes on either
+            * side of it.
+            */
+           if (pos >= 2*w*(w-1))
+               return "Too much data in block structure specification";
+           if (pos < w*(w-1)) {
+               int y = pos/(w-1);
+               int x = pos%(w-1);
+               p0 = y*w+x;
+               p1 = y*w+x+1;
+           } else {
+               int x = pos/(w-1) - w;
+               int y = pos%(w-1);
+               p0 = y*w+x;
+               p1 = (y+1)*w+x;
+           }
+           dsf_merge(dsf, p0, p1);
+
+           pos++;
+       }
+       if (adv) {
+           pos++;
+           if (pos > 2*w*(w-1)+1)
+               return "Too much data in block structure specification";
+       }
+    }
+
+    /*
+     * When desc is exhausted, we expect to have gone exactly
+     * one space _past_ the end of the grid, due to the dummy
+     * edge at the end.
+     */
+    if (pos != 2*w*(w-1)+1)
+       return "Not enough data in block structure specification";
+
+    return NULL;
+}
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                          char **aux, int interactive)
+{
+    int w = params->w, a = w*w;
+    digit *grid, *soln;
+    int *order, *revorder, *singletons, *dsf;
+    long *clues, *cluevals;
+    int i, j, k, n, x, y, ret;
+    int diff = params->diff;
+    char *desc, *p;
+
+    /*
+     * Difficulty exceptions: 3x3 puzzles at difficulty Hard or
+     * higher are currently not generable - the generator will spin
+     * forever looking for puzzles of the appropriate difficulty. We
+     * dial each of these down to the next lower difficulty.
+     *
+     * Remember to re-test this whenever a change is made to the
+     * solver logic!
+     *
+     * I tested it using the following shell command:
+
+for d in e n h x u; do
+  for i in {3..9}; do
+    echo ./keen --generate 1 ${i}d${d}
+    perl -e 'alarm 30; exec @ARGV' ./keen --generate 5 ${i}d${d} >/dev/null \
+      || echo broken
+  done
+done
+
+     * Of course, it's better to do that after taking the exceptions
+     * _out_, so as to detect exceptions that should be removed as
+     * well as those which should be added.
+     */
+    if (w == 3 && diff > DIFF_NORMAL)
+       diff = DIFF_NORMAL;
+
+    grid = NULL;
+
+    order = snewn(a, int);
+    revorder = snewn(a, int);
+    singletons = snewn(a, int);
+    dsf = snew_dsf(a);
+    clues = snewn(a, long);
+    cluevals = snewn(a, long);
+    soln = snewn(a, digit);
+
+    while (1) {
+       /*
+        * First construct a latin square to be the solution.
+        */
+       sfree(grid);
+       grid = latin_generate(w, rs);
+
+       /*
+        * Divide the grid into arbitrarily sized blocks, but so as
+        * to arrange plenty of dominoes which can be SUB/DIV clues.
+        * We do this by first placing dominoes at random for a
+        * while, then tying the remaining singletons one by one
+        * into neighbouring blocks.
+        */
+       for (i = 0; i < a; i++)
+           order[i] = i;
+       shuffle(order, a, sizeof(*order), rs);
+       for (i = 0; i < a; i++)
+           revorder[order[i]] = i;
+
+       for (i = 0; i < a; i++)
+           singletons[i] = TRUE;
+
+       dsf_init(dsf, a);
+
+       /* Place dominoes. */
+       for (i = 0; i < a; i++) {
+           if (singletons[i]) {
+               int best = -1;
+
+               x = i % w;
+               y = i / w;
+
+               if (x > 0 && singletons[i-1] &&
+                   (best == -1 || revorder[i-1] < revorder[best]))
+                   best = i-1;
+               if (x+1 < w && singletons[i+1] &&
+                   (best == -1 || revorder[i+1] < revorder[best]))
+                   best = i+1;
+               if (y > 0 && singletons[i-w] &&
+                   (best == -1 || revorder[i-w] < revorder[best]))
+                   best = i-w;
+               if (y+1 < w && singletons[i+w] &&
+                   (best == -1 || revorder[i+w] < revorder[best]))
+                   best = i+w;
+
+               /*
+                * When we find a potential domino, we place it with
+                * probability 3/4, which seems to strike a decent
+                * balance between plenty of dominoes and leaving
+                * enough singletons to make interesting larger
+                * shapes.
+                */
+               if (best >= 0 && random_upto(rs, 4)) {
+                   singletons[i] = singletons[best] = FALSE;
+                   dsf_merge(dsf, i, best);
+               }
+           }
+       }
+
+       /* Fold in singletons. */
+       for (i = 0; i < a; i++) {
+           if (singletons[i]) {
+               int best = -1;
+
+               x = i % w;
+               y = i / w;
+
+               if (x > 0 && dsf_size(dsf, i-1) < MAXBLK &&
+                   (best == -1 || revorder[i-1] < revorder[best]))
+                   best = i-1;
+               if (x+1 < w && dsf_size(dsf, i+1) < MAXBLK &&
+                   (best == -1 || revorder[i+1] < revorder[best]))
+                   best = i+1;
+               if (y > 0 && dsf_size(dsf, i-w) < MAXBLK &&
+                   (best == -1 || revorder[i-w] < revorder[best]))
+                   best = i-w;
+               if (y+1 < w && dsf_size(dsf, i+w) < MAXBLK &&
+                   (best == -1 || revorder[i+w] < revorder[best]))
+                   best = i+w;
+
+               if (best >= 0) {
+                   singletons[i] = singletons[best] = FALSE;
+                   dsf_merge(dsf, i, best);
+               }
+           }
+       }
+
+        /* Quit and start again if we have any singletons left over
+         * which we weren't able to do anything at all with. */
+       for (i = 0; i < a; i++)
+           if (singletons[i])
+                break;
+        if (i < a)
+            continue;
+
+       /*
+        * Decide what would be acceptable clues for each block.
+        *
+        * Blocks larger than 2 have free choice of ADD or MUL;
+        * blocks of size 2 can be anything in principle (except
+        * that they can only be DIV if the two numbers have an
+        * integer quotient, of course), but we rule out (or try to
+        * avoid) some clues because they're of low quality.
+        *
+        * Hence, we iterate once over the grid, stopping at the
+        * canonical element of every >2 block and the _non_-
+        * canonical element of every 2-block; the latter means that
+        * we can make our decision about a 2-block in the knowledge
+        * of both numbers in it.
+        *
+        * We reuse the 'singletons' array (finished with in the
+        * above loop) to hold information about which blocks are
+        * suitable for what.
+        */
+#define F_ADD     0x01
+#define F_SUB     0x02
+#define F_MUL     0x04
+#define F_DIV     0x08
+#define BAD_SHIFT 4
+
+       for (i = 0; i < a; i++) {
+           singletons[i] = 0;
+           j = dsf_canonify(dsf, i);
+           k = dsf_size(dsf, j);
+           if (params->multiplication_only)
+               singletons[j] = F_MUL;
+           else if (j == i && k > 2) {
+               singletons[j] |= F_ADD | F_MUL;
+           } else if (j != i && k == 2) {
+               /* Fetch the two numbers and sort them into order. */
+               int p = grid[j], q = grid[i], v;
+               if (p < q) {
+                   int t = p; p = q; q = t;
+               }
+
+               /*
+                * Addition clues are always allowed, but we try to
+                * avoid sums of 3, 4, (2w-1) and (2w-2) if we can,
+                * because they're too easy - they only leave one
+                * option for the pair of numbers involved.
+                */
+               v = p + q;
+               if (v > 4 && v < 2*w-2)
+                   singletons[j] |= F_ADD;
+                else
+                   singletons[j] |= F_ADD << BAD_SHIFT;
+
+               /*
+                * Multiplication clues: above Normal difficulty, we
+                * prefer (but don't absolutely insist on) clues of
+                * this type which leave multiple options open.
+                */
+               v = p * q;
+               n = 0;
+               for (k = 1; k <= w; k++)
+                   if (v % k == 0 && v / k <= w && v / k != k)
+                       n++;
+               if (n <= 2 && diff > DIFF_NORMAL)
+                   singletons[j] |= F_MUL << BAD_SHIFT;
+                else
+                   singletons[j] |= F_MUL;
+
+               /*
+                * Subtraction: we completely avoid a difference of
+                * w-1.
+                */
+               v = p - q;
+               if (v < w-1)
+                   singletons[j] |= F_SUB;
+
+               /*
+                * Division: for a start, the quotient must be an
+                * integer or the clue type is impossible. Also, we
+                * never use quotients strictly greater than w/2,
+                * because they're not only too easy but also
+                * inelegant.
+                */
+               if (p % q == 0 && 2 * (p / q) <= w)
+                   singletons[j] |= F_DIV;
+           }
+       }
+
+       /*
+        * Actually choose a clue for each block, trying to keep the
+        * numbers of each type even, and starting with the
+        * preferred candidates for each type where possible.
+        *
+        * I'm sure there should be a faster algorithm for doing
+        * this, but I can't be bothered: O(N^2) is good enough when
+        * N is at most the number of dominoes that fits into a 9x9
+        * square.
+        */
+       shuffle(order, a, sizeof(*order), rs);
+       for (i = 0; i < a; i++)
+           clues[i] = 0;
+       while (1) {
+           int done_something = FALSE;
+
+           for (k = 0; k < 4; k++) {
+               long clue;
+               int good, bad;
+               switch (k) {
+                 case 0:                clue = C_DIV; good = F_DIV; break;
+                 case 1:                clue = C_SUB; good = F_SUB; break;
+                 case 2:                clue = C_MUL; good = F_MUL; break;
+                 default /* case 3 */ : clue = C_ADD; good = F_ADD; break;
+               }
+
+               for (i = 0; i < a; i++) {
+                   j = order[i];
+                   if (singletons[j] & good) {
+                       clues[j] = clue;
+                       singletons[j] = 0;
+                       break;
+                   }
+               }
+               if (i == a) {
+                   /* didn't find a nice one, use a nasty one */
+                    bad = good << BAD_SHIFT;
+                   for (i = 0; i < a; i++) {
+                       j = order[i];
+                       if (singletons[j] & bad) {
+                           clues[j] = clue;
+                           singletons[j] = 0;
+                           break;
+                       }
+                   }
+               }
+               if (i < a)
+                   done_something = TRUE;
+           }
+
+           if (!done_something)
+               break;
+       }
+#undef F_ADD
+#undef F_SUB
+#undef F_MUL
+#undef F_DIV
+#undef BAD_SHIFT
+
+       /*
+        * Having chosen the clue types, calculate the clue values.
+        */
+       for (i = 0; i < a; i++) {
+           j = dsf_canonify(dsf, i);
+           if (j == i) {
+               cluevals[j] = grid[i];
+           } else {
+               switch (clues[j]) {
+                 case C_ADD:
+                   cluevals[j] += grid[i];
+                   break;
+                 case C_MUL:
+                   cluevals[j] *= grid[i];
+                   break;
+                 case C_SUB:
+                   cluevals[j] = abs(cluevals[j] - grid[i]);
+                   break;
+                 case C_DIV:
+                   {
+                       int d1 = cluevals[j], d2 = grid[i];
+                       if (d1 == 0 || d2 == 0)
+                           cluevals[j] = 0;
+                       else
+                           cluevals[j] = d2/d1 + d1/d2;/* one is 0 :-) */
+                   }
+                   break;
+               }
+           }
+       }
+
+       for (i = 0; i < a; i++) {
+           j = dsf_canonify(dsf, i);
+           if (j == i) {
+               clues[j] |= cluevals[j];
+           }
+       }
+
+       /*
+        * See if the game can be solved at the specified difficulty
+        * level, but not at the one below.
+        */
+       if (diff > 0) {
+           memset(soln, 0, a);
+           ret = solver(w, dsf, clues, soln, diff-1);
+           if (ret <= diff-1)
+               continue;
+       }
+       memset(soln, 0, a);
+       ret = solver(w, dsf, clues, soln, diff);
+       if (ret != diff)
+           continue;                  /* go round again */
+
+       /*
+        * I wondered if at this point it would be worth trying to
+        * merge adjacent blocks together, to make the puzzle
+        * gradually more difficult if it's currently easier than
+        * specced, increasing the chance of a given generation run
+        * being successful.
+        *
+        * It doesn't seem to be critical for the generation speed,
+        * though, so for the moment I'm leaving it out.
+        */
+
+       /*
+        * We've got a usable puzzle!
+        */
+       break;
+    }
+
+    /*
+     * Encode the puzzle description.
+     */
+    desc = snewn(40*a, char);
+    p = desc;
+    p = encode_block_structure(p, w, dsf);
+    *p++ = ',';
+    for (i = 0; i < a; i++) {
+       j = dsf_canonify(dsf, i);
+       if (j == i) {
+           switch (clues[j] & CMASK) {
+             case C_ADD: *p++ = 'a'; break;
+             case C_SUB: *p++ = 's'; break;
+             case C_MUL: *p++ = 'm'; break;
+             case C_DIV: *p++ = 'd'; break;
+           }
+           p += sprintf(p, "%ld", clues[j] & ~CMASK);
+       }
+    }
+    *p++ = '\0';
+    desc = sresize(desc, p - desc, char);
+
+    /*
+     * Encode the solution.
+     */
+    assert(memcmp(soln, grid, a) == 0);
+    *aux = snewn(a+2, char);
+    (*aux)[0] = 'S';
+    for (i = 0; i < a; i++)
+       (*aux)[i+1] = '0' + soln[i];
+    (*aux)[a+1] = '\0';
+
+    sfree(grid);
+    sfree(order);
+    sfree(revorder);
+    sfree(singletons);
+    sfree(dsf);
+    sfree(clues);
+    sfree(cluevals);
+    sfree(soln);
+
+    return desc;
+}
+
+/* ----------------------------------------------------------------------
+ * Gameplay.
+ */
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    int w = params->w, a = w*w;
+    int *dsf;
+    char *ret;
+    const char *p = desc;
+    int i;
+
+    /*
+     * Verify that the block structure makes sense.
+     */
+    dsf = snew_dsf(a);
+    ret = parse_block_structure(&p, w, dsf);
+    if (ret) {
+       sfree(dsf);
+       return ret;
+    }
+
+    if (*p != ',')
+       return "Expected ',' after block structure description";
+    p++;
+
+    /*
+     * Verify that the right number of clues are given, and that SUB
+     * and DIV clues don't apply to blocks of the wrong size.
+     */
+    for (i = 0; i < a; i++) {
+       if (dsf_canonify(dsf, i) == i) {
+           if (*p == 'a' || *p == 'm') {
+               /* these clues need no validation */
+           } else if (*p == 'd' || *p == 's') {
+               if (dsf_size(dsf, i) != 2)
+                   return "Subtraction and division blocks must have area 2";
+           } else if (!*p) {
+               return "Too few clues for block structure";
+           } else {
+               return "Unrecognised clue type";
+           }
+           p++;
+           while (*p && isdigit((unsigned char)*p)) p++;
+       }
+    }
+    if (*p)
+       return "Too many clues for block structure";
+
+    return NULL;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    int w = params->w, a = w*w;
+    game_state *state = snew(game_state);
+    const char *p = desc;
+    int i;
+
+    state->par = *params;             /* structure copy */
+    state->clues = snew(struct clues);
+    state->clues->refcount = 1;
+    state->clues->w = w;
+    state->clues->dsf = snew_dsf(a);
+    parse_block_structure(&p, w, state->clues->dsf);
+
+    assert(*p == ',');
+    p++;
+
+    state->clues->clues = snewn(a, long);
+    for (i = 0; i < a; i++) {
+       if (dsf_canonify(state->clues->dsf, i) == i) {
+           long clue = 0;
+           switch (*p) {
+             case 'a':
+               clue = C_ADD;
+               break;
+             case 'm':
+               clue = C_MUL;
+               break;
+             case 's':
+               clue = C_SUB;
+               assert(dsf_size(state->clues->dsf, i) == 2);
+               break;
+             case 'd':
+               clue = C_DIV;
+               assert(dsf_size(state->clues->dsf, i) == 2);
+               break;
+             default:
+               assert(!"Bad description in new_game");
+           }
+           p++;
+           clue |= atol(p);
+           while (*p && isdigit((unsigned char)*p)) p++;
+           state->clues->clues[i] = clue;
+       } else
+           state->clues->clues[i] = 0;
+    }
+
+    state->grid = snewn(a, digit);
+    state->pencil = snewn(a, int);
+    for (i = 0; i < a; i++) {
+       state->grid[i] = 0;
+       state->pencil[i] = 0;
+    }
+
+    state->completed = state->cheated = FALSE;
+
+    return state;
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    int w = state->par.w, a = w*w;
+    game_state *ret = snew(game_state);
+
+    ret->par = state->par;            /* structure copy */
+
+    ret->clues = state->clues;
+    ret->clues->refcount++;
+
+    ret->grid = snewn(a, digit);
+    ret->pencil = snewn(a, int);
+    memcpy(ret->grid, state->grid, a*sizeof(digit));
+    memcpy(ret->pencil, state->pencil, a*sizeof(int));
+
+    ret->completed = state->completed;
+    ret->cheated = state->cheated;
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    sfree(state->grid);
+    sfree(state->pencil);
+    if (--state->clues->refcount <= 0) {
+       sfree(state->clues->dsf);
+       sfree(state->clues->clues);
+       sfree(state->clues);
+    }
+    sfree(state);
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    int w = state->par.w, a = w*w;
+    int i, ret;
+    digit *soln;
+    char *out;
+
+    if (aux)
+       return dupstr(aux);
+
+    soln = snewn(a, digit);
+    memset(soln, 0, a);
+
+    ret = solver(w, state->clues->dsf, state->clues->clues,
+                soln, DIFFCOUNT-1);
+
+    if (ret == diff_impossible) {
+       *error = "No solution exists for this puzzle";
+       out = NULL;
+    } else if (ret == diff_ambiguous) {
+       *error = "Multiple solutions exist for this puzzle";
+       out = NULL;
+    } else {
+       out = snewn(a+2, char);
+       out[0] = 'S';
+       for (i = 0; i < a; i++)
+           out[i+1] = '0' + soln[i];
+       out[a+1] = '\0';
+    }
+
+    sfree(soln);
+    return out;
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    return NULL;
+}
+
+struct game_ui {
+    /*
+     * These are the coordinates of the currently highlighted
+     * square on the grid, if hshow = 1.
+     */
+    int hx, hy;
+    /*
+     * This indicates whether the current highlight is a
+     * pencil-mark one or a real one.
+     */
+    int hpencil;
+    /*
+     * This indicates whether or not we're showing the highlight
+     * (used to be hx = hy = -1); important so that when we're
+     * using the cursor keys it doesn't keep coming back at a
+     * fixed position. When hshow = 1, pressing a valid number
+     * or letter key or Space will enter that number or letter in the grid.
+     */
+    int hshow;
+    /*
+     * This indicates whether we're using the highlight as a cursor;
+     * it means that it doesn't vanish on a keypress, and that it is
+     * allowed on immutable squares.
+     */
+    int hcursor;
+};
+
+static game_ui *new_ui(const game_state *state)
+{
+    game_ui *ui = snew(game_ui);
+
+    ui->hx = ui->hy = 0;
+    ui->hpencil = ui->hshow = ui->hcursor = 0;
+
+    return ui;
+}
+
+static void free_ui(game_ui *ui)
+{
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+    int w = newstate->par.w;
+    /*
+     * We prevent pencil-mode highlighting of a filled square, unless
+     * we're using the cursor keys. So if the user has just filled in
+     * a square which we had a pencil-mode highlight in (by Undo, or
+     * by Redo, or by Solve), then we cancel the highlight.
+     */
+    if (ui->hshow && ui->hpencil && !ui->hcursor &&
+        newstate->grid[ui->hy * w + ui->hx] != 0) {
+        ui->hshow = 0;
+    }
+}
+
+#define PREFERRED_TILESIZE 48
+#define TILESIZE (ds->tilesize)
+#define BORDER (TILESIZE / 2)
+#define GRIDEXTRA max((TILESIZE / 32),1)
+#define COORD(x) ((x)*TILESIZE + BORDER)
+#define FROMCOORD(x) (((x)+(TILESIZE-BORDER)) / TILESIZE - 1)
+
+#define FLASH_TIME 0.4F
+
+#define DF_PENCIL_SHIFT 16
+#define DF_ERR_LATIN 0x8000
+#define DF_ERR_CLUE 0x4000
+#define DF_HIGHLIGHT 0x2000
+#define DF_HIGHLIGHT_PENCIL 0x1000
+#define DF_DIGIT_MASK 0x000F
+
+struct game_drawstate {
+    int tilesize;
+    int started;
+    long *tiles;
+    long *errors;
+    char *minus_sign, *times_sign, *divide_sign;
+};
+
+static int check_errors(const game_state *state, long *errors)
+{
+    int w = state->par.w, a = w*w;
+    int i, j, x, y, errs = FALSE;
+    long *cluevals;
+    int *full;
+
+    cluevals = snewn(a, long);
+    full = snewn(a, int);
+
+    if (errors)
+       for (i = 0; i < a; i++) {
+           errors[i] = 0;
+           full[i] = TRUE;
+       }
+
+    for (i = 0; i < a; i++) {
+       long clue;
+
+       j = dsf_canonify(state->clues->dsf, i);
+       if (j == i) {
+           cluevals[i] = state->grid[i];
+       } else {
+           clue = state->clues->clues[j] & CMASK;
+
+           switch (clue) {
+             case C_ADD:
+               cluevals[j] += state->grid[i];
+               break;
+             case C_MUL:
+               cluevals[j] *= state->grid[i];
+               break;
+             case C_SUB:
+               cluevals[j] = abs(cluevals[j] - state->grid[i]);
+               break;
+             case C_DIV:
+               {
+                   int d1 = min(cluevals[j], state->grid[i]);
+                   int d2 = max(cluevals[j], state->grid[i]);
+                   if (d1 == 0 || d2 % d1 != 0)
+                       cluevals[j] = 0;
+                   else
+                       cluevals[j] = d2 / d1;
+               }
+               break;
+           }
+       }
+
+       if (!state->grid[i])
+           full[j] = FALSE;
+    }
+
+    for (i = 0; i < a; i++) {
+       j = dsf_canonify(state->clues->dsf, i);
+       if (j == i) {
+           if ((state->clues->clues[j] & ~CMASK) != cluevals[i]) {
+               errs = TRUE;
+               if (errors && full[j])
+                   errors[j] |= DF_ERR_CLUE;
+           }
+       }
+    }
+
+    sfree(cluevals);
+    sfree(full);
+
+    for (y = 0; y < w; y++) {
+       int mask = 0, errmask = 0;
+       for (x = 0; x < w; x++) {
+           int bit = 1 << state->grid[y*w+x];
+           errmask |= (mask & bit);
+           mask |= bit;
+       }
+
+       if (mask != (1 << (w+1)) - (1 << 1)) {
+           errs = TRUE;
+           errmask &= ~1;
+           if (errors) {
+               for (x = 0; x < w; x++)
+                   if (errmask & (1 << state->grid[y*w+x]))
+                       errors[y*w+x] |= DF_ERR_LATIN;
+           }
+       }
+    }
+
+    for (x = 0; x < w; x++) {
+       int mask = 0, errmask = 0;
+       for (y = 0; y < w; y++) {
+           int bit = 1 << state->grid[y*w+x];
+           errmask |= (mask & bit);
+           mask |= bit;
+       }
+
+       if (mask != (1 << (w+1)) - (1 << 1)) {
+           errs = TRUE;
+           errmask &= ~1;
+           if (errors) {
+               for (y = 0; y < w; y++)
+                   if (errmask & (1 << state->grid[y*w+x]))
+                       errors[y*w+x] |= DF_ERR_LATIN;
+           }
+       }
+    }
+
+    return errs;
+}
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    int w = state->par.w;
+    int tx, ty;
+    char buf[80];
+
+    button &= ~MOD_MASK;
+
+    tx = FROMCOORD(x);
+    ty = FROMCOORD(y);
+
+    if (tx >= 0 && tx < w && ty >= 0 && ty < w) {
+        if (button == LEFT_BUTTON) {
+           if (tx == ui->hx && ty == ui->hy &&
+               ui->hshow && ui->hpencil == 0) {
+                ui->hshow = 0;
+            } else {
+                ui->hx = tx;
+                ui->hy = ty;
+                ui->hshow = 1;
+                ui->hpencil = 0;
+            }
+            ui->hcursor = 0;
+            return "";                /* UI activity occurred */
+        }
+        if (button == RIGHT_BUTTON) {
+            /*
+             * Pencil-mode highlighting for non filled squares.
+             */
+            if (state->grid[ty*w+tx] == 0) {
+                if (tx == ui->hx && ty == ui->hy &&
+                    ui->hshow && ui->hpencil) {
+                    ui->hshow = 0;
+                } else {
+                    ui->hpencil = 1;
+                    ui->hx = tx;
+                    ui->hy = ty;
+                    ui->hshow = 1;
+                }
+            } else {
+                ui->hshow = 0;
+            }
+            ui->hcursor = 0;
+            return "";                /* UI activity occurred */
+        }
+    }
+    if (IS_CURSOR_MOVE(button)) {
+        move_cursor(button, &ui->hx, &ui->hy, w, w, 0);
+        ui->hshow = ui->hcursor = 1;
+        return "";
+    }
+    if (ui->hshow &&
+        (button == CURSOR_SELECT)) {
+        ui->hpencil = 1 - ui->hpencil;
+        ui->hcursor = 1;
+        return "";
+    }
+
+    if (ui->hshow &&
+       ((button >= '0' && button <= '9' && button - '0' <= w) ||
+        button == CURSOR_SELECT2 || button == '\b')) {
+       int n = button - '0';
+       if (button == CURSOR_SELECT2 || button == '\b')
+           n = 0;
+
+        /*
+         * Can't make pencil marks in a filled square. This can only
+         * become highlighted if we're using cursor keys.
+         */
+        if (ui->hpencil && state->grid[ui->hy*w+ui->hx])
+            return NULL;
+
+       sprintf(buf, "%c%d,%d,%d",
+               (char)(ui->hpencil && n > 0 ? 'P' : 'R'), ui->hx, ui->hy, n);
+
+        if (!ui->hcursor) ui->hshow = 0;
+
+       return dupstr(buf);
+    }
+
+    if (button == 'M' || button == 'm')
+        return dupstr("M");
+
+    return NULL;
+}
+
+static game_state *execute_move(const game_state *from, const char *move)
+{
+    int w = from->par.w, a = w*w;
+    game_state *ret;
+    int x, y, i, n;
+
+    if (move[0] == 'S') {
+       ret = dup_game(from);
+       ret->completed = ret->cheated = TRUE;
+
+       for (i = 0; i < a; i++) {
+           if (move[i+1] < '1' || move[i+1] > '0'+w) {
+               free_game(ret);
+               return NULL;
+           }
+           ret->grid[i] = move[i+1] - '0';
+           ret->pencil[i] = 0;
+       }
+
+       if (move[a+1] != '\0') {
+           free_game(ret);
+           return NULL;
+       }
+
+       return ret;
+    } else if ((move[0] == 'P' || move[0] == 'R') &&
+       sscanf(move+1, "%d,%d,%d", &x, &y, &n) == 3 &&
+       x >= 0 && x < w && y >= 0 && y < w && n >= 0 && n <= w) {
+
+       ret = dup_game(from);
+        if (move[0] == 'P' && n > 0) {
+            ret->pencil[y*w+x] ^= 1 << n;
+        } else {
+            ret->grid[y*w+x] = n;
+            ret->pencil[y*w+x] = 0;
+
+            if (!ret->completed && !check_errors(ret, NULL))
+                ret->completed = TRUE;
+        }
+       return ret;
+    } else if (move[0] == 'M') {
+       /*
+        * Fill in absolutely all pencil marks everywhere. (I
+        * wouldn't use this for actual play, but it's a handy
+        * starting point when following through a set of
+        * diagnostics output by the standalone solver.)
+        */
+       ret = dup_game(from);
+       for (i = 0; i < a; i++) {
+           if (!ret->grid[i])
+               ret->pencil[i] = (1 << (w+1)) - (1 << 1);
+       }
+       return ret;
+    } else
+       return NULL;                   /* couldn't parse move string */
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+#define SIZE(w) ((w) * TILESIZE + 2*BORDER)
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    struct { int tilesize; } ads, *ds = &ads;
+    ads.tilesize = tilesize;
+
+    *x = *y = SIZE(params->w);
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+
+    frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
+
+    ret[COL_GRID * 3 + 0] = 0.0F;
+    ret[COL_GRID * 3 + 1] = 0.0F;
+    ret[COL_GRID * 3 + 2] = 0.0F;
+
+    ret[COL_USER * 3 + 0] = 0.0F;
+    ret[COL_USER * 3 + 1] = 0.6F * ret[COL_BACKGROUND * 3 + 1];
+    ret[COL_USER * 3 + 2] = 0.0F;
+
+    ret[COL_HIGHLIGHT * 3 + 0] = 0.78F * ret[COL_BACKGROUND * 3 + 0];
+    ret[COL_HIGHLIGHT * 3 + 1] = 0.78F * ret[COL_BACKGROUND * 3 + 1];
+    ret[COL_HIGHLIGHT * 3 + 2] = 0.78F * ret[COL_BACKGROUND * 3 + 2];
+
+    ret[COL_ERROR * 3 + 0] = 1.0F;
+    ret[COL_ERROR * 3 + 1] = 0.0F;
+    ret[COL_ERROR * 3 + 2] = 0.0F;
+
+    ret[COL_PENCIL * 3 + 0] = 0.5F * ret[COL_BACKGROUND * 3 + 0];
+    ret[COL_PENCIL * 3 + 1] = 0.5F * ret[COL_BACKGROUND * 3 + 1];
+    ret[COL_PENCIL * 3 + 2] = ret[COL_BACKGROUND * 3 + 2];
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static const char *const minus_signs[] = { "\xE2\x88\x92", "-" };
+static const char *const times_signs[] = { "\xC3\x97", "*" };
+static const char *const divide_signs[] = { "\xC3\xB7", "/" };
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    int w = state->par.w, a = w*w;
+    struct game_drawstate *ds = snew(struct game_drawstate);
+    int i;
+
+    ds->tilesize = 0;
+    ds->started = FALSE;
+    ds->tiles = snewn(a, long);
+    for (i = 0; i < a; i++)
+       ds->tiles[i] = -1;
+    ds->errors = snewn(a, long);
+    ds->minus_sign = text_fallback(dr, minus_signs, lenof(minus_signs));
+    ds->times_sign = text_fallback(dr, times_signs, lenof(times_signs));
+    ds->divide_sign = text_fallback(dr, divide_signs, lenof(divide_signs));
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds->tiles);
+    sfree(ds->errors);
+    sfree(ds->minus_sign);
+    sfree(ds->times_sign);
+    sfree(ds->divide_sign);
+    sfree(ds);
+}
+
+static void draw_tile(drawing *dr, game_drawstate *ds, struct clues *clues,
+                     int x, int y, long tile, int only_one_op)
+{
+    int w = clues->w /* , a = w*w */;
+    int tx, ty, tw, th;
+    int cx, cy, cw, ch;
+    char str[64];
+
+    tx = BORDER + x * TILESIZE + 1 + GRIDEXTRA;
+    ty = BORDER + y * TILESIZE + 1 + GRIDEXTRA;
+
+    cx = tx;
+    cy = ty;
+    cw = tw = TILESIZE-1-2*GRIDEXTRA;
+    ch = th = TILESIZE-1-2*GRIDEXTRA;
+
+    if (x > 0 && dsf_canonify(clues->dsf, y*w+x) == dsf_canonify(clues->dsf, y*w+x-1))
+       cx -= GRIDEXTRA, cw += GRIDEXTRA;
+    if (x+1 < w && dsf_canonify(clues->dsf, y*w+x) == dsf_canonify(clues->dsf, y*w+x+1))
+       cw += GRIDEXTRA;
+    if (y > 0 && dsf_canonify(clues->dsf, y*w+x) == dsf_canonify(clues->dsf, (y-1)*w+x))
+       cy -= GRIDEXTRA, ch += GRIDEXTRA;
+    if (y+1 < w && dsf_canonify(clues->dsf, y*w+x) == dsf_canonify(clues->dsf, (y+1)*w+x))
+       ch += GRIDEXTRA;
+
+    clip(dr, cx, cy, cw, ch);
+
+    /* background needs erasing */
+    draw_rect(dr, cx, cy, cw, ch,
+             (tile & DF_HIGHLIGHT) ? COL_HIGHLIGHT : COL_BACKGROUND);
+
+    /* pencil-mode highlight */
+    if (tile & DF_HIGHLIGHT_PENCIL) {
+        int coords[6];
+        coords[0] = cx;
+        coords[1] = cy;
+        coords[2] = cx+cw/2;
+        coords[3] = cy;
+        coords[4] = cx;
+        coords[5] = cy+ch/2;
+        draw_polygon(dr, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT);
+    }
+
+    /*
+     * Draw the corners of thick lines in corner-adjacent squares,
+     * which jut into this square by one pixel.
+     */
+    if (x > 0 && y > 0 && dsf_canonify(clues->dsf, y*w+x) != dsf_canonify(clues->dsf, (y-1)*w+x-1))
+       draw_rect(dr, tx-GRIDEXTRA, ty-GRIDEXTRA, GRIDEXTRA, GRIDEXTRA, COL_GRID);
+    if (x+1 < w && y > 0 && dsf_canonify(clues->dsf, y*w+x) != dsf_canonify(clues->dsf, (y-1)*w+x+1))
+       draw_rect(dr, tx+TILESIZE-1-2*GRIDEXTRA, ty-GRIDEXTRA, GRIDEXTRA, GRIDEXTRA, COL_GRID);
+    if (x > 0 && y+1 < w && dsf_canonify(clues->dsf, y*w+x) != dsf_canonify(clues->dsf, (y+1)*w+x-1))
+       draw_rect(dr, tx-GRIDEXTRA, ty+TILESIZE-1-2*GRIDEXTRA, GRIDEXTRA, GRIDEXTRA, COL_GRID);
+    if (x+1 < w && y+1 < w && dsf_canonify(clues->dsf, y*w+x) != dsf_canonify(clues->dsf, (y+1)*w+x+1))
+       draw_rect(dr, tx+TILESIZE-1-2*GRIDEXTRA, ty+TILESIZE-1-2*GRIDEXTRA, GRIDEXTRA, GRIDEXTRA, COL_GRID);
+
+    /* Draw the box clue. */
+    if (dsf_canonify(clues->dsf, y*w+x) == y*w+x) {
+       long clue = clues->clues[y*w+x];
+       long cluetype = clue & CMASK, clueval = clue & ~CMASK;
+       int size = dsf_size(clues->dsf, y*w+x);
+       /*
+        * Special case of clue-drawing: a box with only one square
+        * is written as just the number, with no operation, because
+        * it doesn't matter whether the operation is ADD or MUL.
+        * The generation code above should never produce puzzles
+        * containing such a thing - I think they're inelegant - but
+        * it's possible to type in game IDs from elsewhere, so I
+        * want to display them right if so.
+        */
+       sprintf (str, "%ld%s", clueval,
+                (size == 1 || only_one_op ? "" :
+                 cluetype == C_ADD ? "+" :
+                 cluetype == C_SUB ? ds->minus_sign :
+                 cluetype == C_MUL ? ds->times_sign :
+                 /* cluetype == C_DIV ? */ ds->divide_sign));
+       draw_text(dr, tx + GRIDEXTRA * 2, ty + GRIDEXTRA * 2 + TILESIZE/4,
+                 FONT_VARIABLE, TILESIZE/4, ALIGN_VNORMAL | ALIGN_HLEFT,
+                 (tile & DF_ERR_CLUE ? COL_ERROR : COL_GRID), str);
+    }
+
+    /* new number needs drawing? */
+    if (tile & DF_DIGIT_MASK) {
+       str[1] = '\0';
+       str[0] = (tile & DF_DIGIT_MASK) + '0';
+       draw_text(dr, tx + TILESIZE/2, ty + TILESIZE/2,
+                 FONT_VARIABLE, TILESIZE/2, ALIGN_VCENTRE | ALIGN_HCENTRE,
+                 (tile & DF_ERR_LATIN) ? COL_ERROR : COL_USER, str);
+    } else {
+        int i, j, npencil;
+       int pl, pr, pt, pb;
+       float bestsize;
+       int pw, ph, minph, pbest, fontsize;
+
+        /* Count the pencil marks required. */
+        for (i = 1, npencil = 0; i <= w; i++)
+            if (tile & (1L << (i + DF_PENCIL_SHIFT)))
+               npencil++;
+       if (npencil) {
+
+           minph = 2;
+
+           /*
+            * Determine the bounding rectangle within which we're going
+            * to put the pencil marks.
+            */
+           /* Start with the whole square */
+           pl = tx + GRIDEXTRA;
+           pr = pl + TILESIZE - GRIDEXTRA;
+           pt = ty + GRIDEXTRA;
+           pb = pt + TILESIZE - GRIDEXTRA;
+           if (dsf_canonify(clues->dsf, y*w+x) == y*w+x) {
+               /*
+                * Make space for the clue text.
+                */
+               pt += TILESIZE/4;
+               /* minph--; */
+           }
+
+           /*
+            * We arrange our pencil marks in a grid layout, with
+            * the number of rows and columns adjusted to allow the
+            * maximum font size.
+            *
+            * So now we work out what the grid size ought to be.
+            */
+           bestsize = 0.0;
+           pbest = 0;
+           /* Minimum */
+           for (pw = 3; pw < max(npencil,4); pw++) {
+               float fw, fh, fs;
+
+               ph = (npencil + pw - 1) / pw;
+               ph = max(ph, minph);
+               fw = (pr - pl) / (float)pw;
+               fh = (pb - pt) / (float)ph;
+               fs = min(fw, fh);
+               if (fs > bestsize) {
+                   bestsize = fs;
+                   pbest = pw;
+               }
+           }
+           assert(pbest > 0);
+           pw = pbest;
+           ph = (npencil + pw - 1) / pw;
+           ph = max(ph, minph);
+
+           /*
+            * Now we've got our grid dimensions, work out the pixel
+            * size of a grid element, and round it to the nearest
+            * pixel. (We don't want rounding errors to make the
+            * grid look uneven at low pixel sizes.)
+            */
+           fontsize = min((pr - pl) / pw, (pb - pt) / ph);
+
+           /*
+            * Centre the resulting figure in the square.
+            */
+           pl = tx + (TILESIZE - fontsize * pw) / 2;
+           pt = ty + (TILESIZE - fontsize * ph) / 2;
+
+           /*
+            * And move it down a bit if it's collided with some
+            * clue text.
+            */
+           if (dsf_canonify(clues->dsf, y*w+x) == y*w+x) {
+               pt = max(pt, ty + GRIDEXTRA * 3 + TILESIZE/4);
+           }
+
+           /*
+            * Now actually draw the pencil marks.
+            */
+           for (i = 1, j = 0; i <= w; i++)
+               if (tile & (1L << (i + DF_PENCIL_SHIFT))) {
+                   int dx = j % pw, dy = j / pw;
+
+                   str[1] = '\0';
+                   str[0] = i + '0';
+                   draw_text(dr, pl + fontsize * (2*dx+1) / 2,
+                             pt + fontsize * (2*dy+1) / 2,
+                             FONT_VARIABLE, fontsize,
+                             ALIGN_VCENTRE | ALIGN_HCENTRE, COL_PENCIL, str);
+                   j++;
+               }
+       }
+    }
+
+    unclip(dr);
+
+    draw_update(dr, cx, cy, cw, ch);
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int w = state->par.w /*, a = w*w */;
+    int x, y;
+
+    if (!ds->started) {
+       /*
+        * The initial contents of the window are not guaranteed and
+        * can vary with front ends. To be on the safe side, all
+        * games should start by drawing a big background-colour
+        * rectangle covering the whole window.
+        */
+       draw_rect(dr, 0, 0, SIZE(w), SIZE(w), COL_BACKGROUND);
+
+       /*
+        * Big containing rectangle.
+        */
+       draw_rect(dr, COORD(0) - GRIDEXTRA, COORD(0) - GRIDEXTRA,
+                 w*TILESIZE+1+GRIDEXTRA*2, w*TILESIZE+1+GRIDEXTRA*2,
+                 COL_GRID);
+
+       draw_update(dr, 0, 0, SIZE(w), SIZE(w));
+
+       ds->started = TRUE;
+    }
+
+    check_errors(state, ds->errors);
+
+    for (y = 0; y < w; y++) {
+       for (x = 0; x < w; x++) {
+           long tile = 0L;
+
+           if (state->grid[y*w+x])
+               tile = state->grid[y*w+x];
+           else
+               tile = (long)state->pencil[y*w+x] << DF_PENCIL_SHIFT;
+
+           if (ui->hshow && ui->hx == x && ui->hy == y)
+               tile |= (ui->hpencil ? DF_HIGHLIGHT_PENCIL : DF_HIGHLIGHT);
+
+            if (flashtime > 0 &&
+                (flashtime <= FLASH_TIME/3 ||
+                 flashtime >= FLASH_TIME*2/3))
+                tile |= DF_HIGHLIGHT;  /* completion flash */
+
+           tile |= ds->errors[y*w+x];
+
+           if (ds->tiles[y*w+x] != tile) {
+               ds->tiles[y*w+x] = tile;
+               draw_tile(dr, ds, state->clues, x, y, tile,
+                         state->par.multiplication_only);
+           }
+       }
+    }
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return 0.0F;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    if (!oldstate->completed && newstate->completed &&
+       !oldstate->cheated && !newstate->cheated)
+        return FLASH_TIME;
+    return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    return state->completed ? +1 : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    if (state->completed)
+       return FALSE;
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+    int pw, ph;
+
+    /*
+     * We use 9mm squares by default, like Solo.
+     */
+    game_compute_size(params, 900, &pw, &ph);
+    *x = pw / 100.0F;
+    *y = ph / 100.0F;
+}
+
+/*
+ * Subfunction to draw the thick lines between cells. In order to do
+ * this using the line-drawing rather than rectangle-drawing API (so
+ * as to get line thicknesses to scale correctly) and yet have
+ * correctly mitred joins between lines, we must do this by tracing
+ * the boundary of each sub-block and drawing it in one go as a
+ * single polygon.
+ */
+static void outline_block_structure(drawing *dr, game_drawstate *ds,
+                                   int w, int *dsf, int ink)
+{
+    int a = w*w;
+    int *coords;
+    int i, n;
+    int x, y, dx, dy, sx, sy, sdx, sdy;
+
+    coords = snewn(4*a, int);
+
+    /*
+     * Iterate over all the blocks.
+     */
+    for (i = 0; i < a; i++) {
+       if (dsf_canonify(dsf, i) != i)
+           continue;
+
+       /*
+        * For each block, we need a starting square within it which
+        * has a boundary at the left. Conveniently, we have one
+        * right here, by construction.
+        */
+       x = i % w;
+       y = i / w;
+       dx = -1;
+       dy = 0;
+
+       /*
+        * Now begin tracing round the perimeter. At all
+        * times, (x,y) describes some square within the
+        * block, and (x+dx,y+dy) is some adjacent square
+        * outside it; so the edge between those two squares
+        * is always an edge of the block.
+        */
+       sx = x, sy = y, sdx = dx, sdy = dy;   /* save starting position */
+       n = 0;
+       do {
+           int cx, cy, tx, ty, nin;
+
+           /*
+            * Advance to the next edge, by looking at the two
+            * squares beyond it. If they're both outside the block,
+            * we turn right (by leaving x,y the same and rotating
+            * dx,dy clockwise); if they're both inside, we turn
+            * left (by rotating dx,dy anticlockwise and contriving
+            * to leave x+dx,y+dy unchanged); if one of each, we go
+            * straight on (and may enforce by assertion that
+            * they're one of each the _right_ way round).
+            */
+           nin = 0;
+           tx = x - dy + dx;
+           ty = y + dx + dy;
+           nin += (tx >= 0 && tx < w && ty >= 0 && ty < w &&
+                   dsf_canonify(dsf, ty*w+tx) == i);
+           tx = x - dy;
+           ty = y + dx;
+           nin += (tx >= 0 && tx < w && ty >= 0 && ty < w &&
+                   dsf_canonify(dsf, ty*w+tx) == i);
+           if (nin == 0) {
+               /*
+                * Turn right.
+                */
+               int tmp;
+               tmp = dx;
+               dx = -dy;
+               dy = tmp;
+           } else if (nin == 2) {
+               /*
+                * Turn left.
+                */
+               int tmp;
+
+               x += dx;
+               y += dy;
+
+               tmp = dx;
+               dx = dy;
+               dy = -tmp;
+
+               x -= dx;
+               y -= dy;
+           } else {
+               /*
+                * Go straight on.
+                */
+               x -= dy;
+               y += dx;
+           }
+
+           /*
+            * Now enforce by assertion that we ended up
+            * somewhere sensible.
+            */
+           assert(x >= 0 && x < w && y >= 0 && y < w &&
+                  dsf_canonify(dsf, y*w+x) == i);
+           assert(x+dx < 0 || x+dx >= w || y+dy < 0 || y+dy >= w ||
+                  dsf_canonify(dsf, (y+dy)*w+(x+dx)) != i);
+
+           /*
+            * Record the point we just went past at one end of the
+            * edge. To do this, we translate (x,y) down and right
+            * by half a unit (so they're describing a point in the
+            * _centre_ of the square) and then translate back again
+            * in a manner rotated by dy and dx.
+            */
+           assert(n < 2*w+2);
+           cx = ((2*x+1) + dy + dx) / 2;
+           cy = ((2*y+1) - dx + dy) / 2;
+           coords[2*n+0] = BORDER + cx * TILESIZE;
+           coords[2*n+1] = BORDER + cy * TILESIZE;
+           n++;
+
+       } while (x != sx || y != sy || dx != sdx || dy != sdy);
+
+       /*
+        * That's our polygon; now draw it.
+        */
+       draw_polygon(dr, coords, n, -1, ink);
+    }
+
+    sfree(coords);
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+    int w = state->par.w;
+    int ink = print_mono_colour(dr, 0);
+    int x, y;
+    char *minus_sign, *times_sign, *divide_sign;
+
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    game_drawstate ads, *ds = &ads;
+    game_set_size(dr, ds, NULL, tilesize);
+
+    minus_sign = text_fallback(dr, minus_signs, lenof(minus_signs));
+    times_sign = text_fallback(dr, times_signs, lenof(times_signs));
+    divide_sign = text_fallback(dr, divide_signs, lenof(divide_signs));
+
+    /*
+     * Border.
+     */
+    print_line_width(dr, 3 * TILESIZE / 40);
+    draw_rect_outline(dr, BORDER, BORDER, w*TILESIZE, w*TILESIZE, ink);
+
+    /*
+     * Main grid.
+     */
+    for (x = 1; x < w; x++) {
+       print_line_width(dr, TILESIZE / 40);
+       draw_line(dr, BORDER+x*TILESIZE, BORDER,
+                 BORDER+x*TILESIZE, BORDER+w*TILESIZE, ink);
+    }
+    for (y = 1; y < w; y++) {
+       print_line_width(dr, TILESIZE / 40);
+       draw_line(dr, BORDER, BORDER+y*TILESIZE,
+                 BORDER+w*TILESIZE, BORDER+y*TILESIZE, ink);
+    }
+
+    /*
+     * Thick lines between cells.
+     */
+    print_line_width(dr, 3 * TILESIZE / 40);
+    outline_block_structure(dr, ds, w, state->clues->dsf, ink);
+
+    /*
+     * Clues.
+     */
+    for (y = 0; y < w; y++)
+       for (x = 0; x < w; x++)
+           if (dsf_canonify(state->clues->dsf, y*w+x) == y*w+x) {
+               long clue = state->clues->clues[y*w+x];
+               long cluetype = clue & CMASK, clueval = clue & ~CMASK;
+               int size = dsf_size(state->clues->dsf, y*w+x);
+               char str[64];
+
+               /*
+                * As in the drawing code, we omit the operator for
+                * blocks of area 1.
+                */
+               sprintf (str, "%ld%s", clueval,
+                        (size == 1 ? "" :
+                         cluetype == C_ADD ? "+" :
+                         cluetype == C_SUB ? minus_sign :
+                         cluetype == C_MUL ? times_sign :
+                         /* cluetype == C_DIV ? */ divide_sign));
+
+               draw_text(dr,
+                         BORDER+x*TILESIZE + 5*TILESIZE/80,
+                         BORDER+y*TILESIZE + 20*TILESIZE/80,
+                         FONT_VARIABLE, TILESIZE/4,
+                         ALIGN_VNORMAL | ALIGN_HLEFT,
+                         ink, str);
+           }
+
+    /*
+     * Numbers for the solution, if any.
+     */
+    for (y = 0; y < w; y++)
+       for (x = 0; x < w; x++)
+           if (state->grid[y*w+x]) {
+               char str[2];
+               str[1] = '\0';
+               str[0] = state->grid[y*w+x] + '0';
+               draw_text(dr, BORDER + x*TILESIZE + TILESIZE/2,
+                         BORDER + y*TILESIZE + TILESIZE/2,
+                         FONT_VARIABLE, TILESIZE/2,
+                         ALIGN_VCENTRE | ALIGN_HCENTRE, ink, str);
+           }
+
+    sfree(minus_sign);
+    sfree(times_sign);
+    sfree(divide_sign);
+}
+
+#ifdef COMBINED
+#define thegame keen
+#endif
+
+const struct game thegame = {
+    "Keen", "games.keen", "keen",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    FALSE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILESIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    TRUE, FALSE, game_print_size, game_print,
+    FALSE,                            /* wants_statusbar */
+    FALSE, game_timing_state,
+    REQUIRE_RBUTTON | REQUIRE_NUMPAD,  /* flags */
+};
+
+#ifdef STANDALONE_SOLVER
+
+#include <stdarg.h>
+
+int main(int argc, char **argv)
+{
+    game_params *p;
+    game_state *s;
+    char *id = NULL, *desc, *err;
+    int grade = FALSE;
+    int ret, diff, really_show_working = FALSE;
+
+    while (--argc > 0) {
+        char *p = *++argv;
+        if (!strcmp(p, "-v")) {
+            really_show_working = TRUE;
+        } else if (!strcmp(p, "-g")) {
+            grade = TRUE;
+        } else if (*p == '-') {
+            fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p);
+            return 1;
+        } else {
+            id = p;
+        }
+    }
+
+    if (!id) {
+        fprintf(stderr, "usage: %s [-g | -v] <game_id>\n", argv[0]);
+        return 1;
+    }
+
+    desc = strchr(id, ':');
+    if (!desc) {
+        fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]);
+        return 1;
+    }
+    *desc++ = '\0';
+
+    p = default_params();
+    decode_params(p, id);
+    err = validate_desc(p, desc);
+    if (err) {
+        fprintf(stderr, "%s: %s\n", argv[0], err);
+        return 1;
+    }
+    s = new_game(NULL, p, desc);
+
+    /*
+     * When solving an Easy puzzle, we don't want to bother the
+     * user with Hard-level deductions. For this reason, we grade
+     * the puzzle internally before doing anything else.
+     */
+    ret = -1;                         /* placate optimiser */
+    solver_show_working = FALSE;
+    for (diff = 0; diff < DIFFCOUNT; diff++) {
+       memset(s->grid, 0, p->w * p->w);
+       ret = solver(p->w, s->clues->dsf, s->clues->clues,
+                    s->grid, diff);
+       if (ret <= diff)
+           break;
+    }
+
+    if (diff == DIFFCOUNT) {
+       if (grade)
+           printf("Difficulty rating: ambiguous\n");
+       else
+           printf("Unable to find a unique solution\n");
+    } else {
+       if (grade) {
+           if (ret == diff_impossible)
+               printf("Difficulty rating: impossible (no solution exists)\n");
+           else
+               printf("Difficulty rating: %s\n", keen_diffnames[ret]);
+       } else {
+           solver_show_working = really_show_working;
+           memset(s->grid, 0, p->w * p->w);
+           ret = solver(p->w, s->clues->dsf, s->clues->clues,
+                        s->grid, diff);
+           if (ret != diff)
+               printf("Puzzle is inconsistent\n");
+           else {
+               /*
+                * We don't have a game_text_format for this game,
+                * so we have to output the solution manually.
+                */
+               int x, y;
+               for (y = 0; y < p->w; y++) {
+                   for (x = 0; x < p->w; x++) {
+                       printf("%s%c", x>0?" ":"", '0' + s->grid[y*p->w+x]);
+                   }
+                   putchar('\n');
+               }
+           }
+       }
+    }
+
+    return 0;
+}
+
+#endif
+
+/* vim: set shiftwidth=4 tabstop=8: */
diff --git a/latin.c b/latin.c
new file mode 100644 (file)
index 0000000..03d78af
--- /dev/null
+++ b/latin.c
@@ -0,0 +1,1436 @@
+#include <assert.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "puzzles.h"
+#include "tree234.h"
+#include "maxflow.h"
+
+#ifdef STANDALONE_LATIN_TEST
+#define STANDALONE_SOLVER
+#endif
+
+#include "latin.h"
+
+/* --------------------------------------------------------
+ * Solver.
+ */
+
+static int latin_solver_top(struct latin_solver *solver, int maxdiff,
+                           int diff_simple, int diff_set_0, int diff_set_1,
+                           int diff_forcing, int diff_recursive,
+                           usersolver_t const *usersolvers, void *ctx,
+                           ctxnew_t ctxnew, ctxfree_t ctxfree);
+
+#ifdef STANDALONE_SOLVER
+int solver_show_working, solver_recurse_depth;
+#endif
+
+/*
+ * Function called when we are certain that a particular square has
+ * a particular number in it. The y-coordinate passed in here is
+ * transformed.
+ */
+void latin_solver_place(struct latin_solver *solver, int x, int y, int n)
+{
+    int i, o = solver->o;
+
+    assert(n <= o);
+    assert(cube(x,y,n));
+
+    /*
+     * Rule out all other numbers in this square.
+     */
+    for (i = 1; i <= o; i++)
+       if (i != n)
+            cube(x,y,i) = FALSE;
+
+    /*
+     * Rule out this number in all other positions in the row.
+     */
+    for (i = 0; i < o; i++)
+       if (i != y)
+            cube(x,i,n) = FALSE;
+
+    /*
+     * Rule out this number in all other positions in the column.
+     */
+    for (i = 0; i < o; i++)
+       if (i != x)
+            cube(i,y,n) = FALSE;
+
+    /*
+     * Enter the number in the result grid.
+     */
+    solver->grid[y*o+x] = n;
+
+    /*
+     * Cross out this number from the list of numbers left to place
+     * in its row, its column and its block.
+     */
+    solver->row[y*o+n-1] = solver->col[x*o+n-1] = TRUE;
+}
+
+int latin_solver_elim(struct latin_solver *solver, int start, int step
+#ifdef STANDALONE_SOLVER
+                     , char *fmt, ...
+#endif
+                     )
+{
+    int o = solver->o;
+#ifdef STANDALONE_SOLVER
+    char **names = solver->names;
+#endif
+    int fpos, m, i;
+
+    /*
+     * Count the number of set bits within this section of the
+     * cube.
+     */
+    m = 0;
+    fpos = -1;
+    for (i = 0; i < o; i++)
+       if (solver->cube[start+i*step]) {
+           fpos = start+i*step;
+           m++;
+       }
+
+    if (m == 1) {
+       int x, y, n;
+       assert(fpos >= 0);
+
+       n = 1 + fpos % o;
+       y = fpos / o;
+       x = y / o;
+       y %= o;
+
+        if (!solver->grid[y*o+x]) {
+#ifdef STANDALONE_SOLVER
+            if (solver_show_working) {
+                va_list ap;
+               printf("%*s", solver_recurse_depth*4, "");
+                va_start(ap, fmt);
+                vprintf(fmt, ap);
+                va_end(ap);
+                printf(":\n%*s  placing %s at (%d,%d)\n",
+                       solver_recurse_depth*4, "", names[n-1],
+                      x+1, y+1);
+            }
+#endif
+            latin_solver_place(solver, x, y, n);
+            return +1;
+        }
+    } else if (m == 0) {
+#ifdef STANDALONE_SOLVER
+       if (solver_show_working) {
+           va_list ap;
+           printf("%*s", solver_recurse_depth*4, "");
+           va_start(ap, fmt);
+           vprintf(fmt, ap);
+           va_end(ap);
+           printf(":\n%*s  no possibilities available\n",
+                  solver_recurse_depth*4, "");
+       }
+#endif
+        return -1;
+    }
+
+    return 0;
+}
+
+struct latin_solver_scratch {
+    unsigned char *grid, *rowidx, *colidx, *set;
+    int *neighbours, *bfsqueue;
+#ifdef STANDALONE_SOLVER
+    int *bfsprev;
+#endif
+};
+
+int latin_solver_set(struct latin_solver *solver,
+                     struct latin_solver_scratch *scratch,
+                     int start, int step1, int step2
+#ifdef STANDALONE_SOLVER
+                     , char *fmt, ...
+#endif
+                     )
+{
+    int o = solver->o;
+#ifdef STANDALONE_SOLVER
+    char **names = solver->names;
+#endif
+    int i, j, n, count;
+    unsigned char *grid = scratch->grid;
+    unsigned char *rowidx = scratch->rowidx;
+    unsigned char *colidx = scratch->colidx;
+    unsigned char *set = scratch->set;
+
+    /*
+     * We are passed a o-by-o matrix of booleans. Our first job
+     * is to winnow it by finding any definite placements - i.e.
+     * any row with a solitary 1 - and discarding that row and the
+     * column containing the 1.
+     */
+    memset(rowidx, TRUE, o);
+    memset(colidx, TRUE, o);
+    for (i = 0; i < o; i++) {
+        int count = 0, first = -1;
+        for (j = 0; j < o; j++)
+            if (solver->cube[start+i*step1+j*step2])
+                first = j, count++;
+
+       if (count == 0) return -1;
+        if (count == 1)
+            rowidx[i] = colidx[first] = FALSE;
+    }
+
+    /*
+     * Convert each of rowidx/colidx from a list of 0s and 1s to a
+     * list of the indices of the 1s.
+     */
+    for (i = j = 0; i < o; i++)
+        if (rowidx[i])
+            rowidx[j++] = i;
+    n = j;
+    for (i = j = 0; i < o; i++)
+        if (colidx[i])
+            colidx[j++] = i;
+    assert(n == j);
+
+    /*
+     * And create the smaller matrix.
+     */
+    for (i = 0; i < n; i++)
+        for (j = 0; j < n; j++)
+            grid[i*o+j] = solver->cube[start+rowidx[i]*step1+colidx[j]*step2];
+
+    /*
+     * Having done that, we now have a matrix in which every row
+     * has at least two 1s in. Now we search to see if we can find
+     * a rectangle of zeroes (in the set-theoretic sense of
+     * `rectangle', i.e. a subset of rows crossed with a subset of
+     * columns) whose width and height add up to n.
+     */
+
+    memset(set, 0, n);
+    count = 0;
+    while (1) {
+        /*
+         * We have a candidate set. If its size is <=1 or >=n-1
+         * then we move on immediately.
+         */
+        if (count > 1 && count < n-1) {
+            /*
+             * The number of rows we need is n-count. See if we can
+             * find that many rows which each have a zero in all
+             * the positions listed in `set'.
+             */
+            int rows = 0;
+            for (i = 0; i < n; i++) {
+                int ok = TRUE;
+                for (j = 0; j < n; j++)
+                    if (set[j] && grid[i*o+j]) {
+                        ok = FALSE;
+                        break;
+                    }
+                if (ok)
+                    rows++;
+            }
+
+            /*
+             * We expect never to be able to get _more_ than
+             * n-count suitable rows: this would imply that (for
+             * example) there are four numbers which between them
+             * have at most three possible positions, and hence it
+             * indicates a faulty deduction before this point or
+             * even a bogus clue.
+             */
+            if (rows > n - count) {
+#ifdef STANDALONE_SOLVER
+               if (solver_show_working) {
+                   va_list ap;
+                   printf("%*s", solver_recurse_depth*4,
+                          "");
+                   va_start(ap, fmt);
+                   vprintf(fmt, ap);
+                   va_end(ap);
+                   printf(":\n%*s  contradiction reached\n",
+                          solver_recurse_depth*4, "");
+               }
+#endif
+               return -1;
+           }
+
+            if (rows >= n - count) {
+                int progress = FALSE;
+
+                /*
+                 * We've got one! Now, for each row which _doesn't_
+                 * satisfy the criterion, eliminate all its set
+                 * bits in the positions _not_ listed in `set'.
+                 * Return +1 (meaning progress has been made) if we
+                 * successfully eliminated anything at all.
+                 *
+                 * This involves referring back through
+                 * rowidx/colidx in order to work out which actual
+                 * positions in the cube to meddle with.
+                 */
+                for (i = 0; i < n; i++) {
+                    int ok = TRUE;
+                    for (j = 0; j < n; j++)
+                        if (set[j] && grid[i*o+j]) {
+                            ok = FALSE;
+                            break;
+                        }
+                    if (!ok) {
+                        for (j = 0; j < n; j++)
+                            if (!set[j] && grid[i*o+j]) {
+                                int fpos = (start+rowidx[i]*step1+
+                                            colidx[j]*step2);
+#ifdef STANDALONE_SOLVER
+                                if (solver_show_working) {
+                                    int px, py, pn;
+
+                                    if (!progress) {
+                                        va_list ap;
+                                       printf("%*s", solver_recurse_depth*4,
+                                              "");
+                                        va_start(ap, fmt);
+                                        vprintf(fmt, ap);
+                                        va_end(ap);
+                                        printf(":\n");
+                                    }
+
+                                    pn = 1 + fpos % o;
+                                    py = fpos / o;
+                                    px = py / o;
+                                    py %= o;
+
+                                    printf("%*s  ruling out %s at (%d,%d)\n",
+                                          solver_recurse_depth*4, "",
+                                           names[pn-1], px+1, py+1);
+                                }
+#endif
+                                progress = TRUE;
+                                solver->cube[fpos] = FALSE;
+                            }
+                    }
+                }
+
+                if (progress) {
+                    return +1;
+                }
+            }
+        }
+
+        /*
+         * Binary increment: change the rightmost 0 to a 1, and
+         * change all 1s to the right of it to 0s.
+         */
+        i = n;
+        while (i > 0 && set[i-1])
+            set[--i] = 0, count--;
+        if (i > 0)
+            set[--i] = 1, count++;
+        else
+            break;                     /* done */
+    }
+
+    return 0;
+}
+
+/*
+ * Look for forcing chains. A forcing chain is a path of
+ * pairwise-exclusive squares (i.e. each pair of adjacent squares
+ * in the path are in the same row, column or block) with the
+ * following properties:
+ *
+ *  (a) Each square on the path has precisely two possible numbers.
+ *
+ *  (b) Each pair of squares which are adjacent on the path share
+ *      at least one possible number in common.
+ *
+ *  (c) Each square in the middle of the path shares _both_ of its
+ *      numbers with at least one of its neighbours (not the same
+ *      one with both neighbours).
+ *
+ * These together imply that at least one of the possible number
+ * choices at one end of the path forces _all_ the rest of the
+ * numbers along the path. In order to make real use of this, we
+ * need further properties:
+ *
+ *  (c) Ruling out some number N from the square at one end
+ *      of the path forces the square at the other end to
+ *      take number N.
+ *
+ *  (d) The two end squares are both in line with some third
+ *      square.
+ *
+ *  (e) That third square currently has N as a possibility.
+ *
+ * If we can find all of that lot, we can deduce that at least one
+ * of the two ends of the forcing chain has number N, and that
+ * therefore the mutually adjacent third square does not.
+ *
+ * To find forcing chains, we're going to start a bfs at each
+ * suitable square, once for each of its two possible numbers.
+ */
+int latin_solver_forcing(struct latin_solver *solver,
+                         struct latin_solver_scratch *scratch)
+{
+    int o = solver->o;
+#ifdef STANDALONE_SOLVER
+    char **names = solver->names;
+#endif
+    int *bfsqueue = scratch->bfsqueue;
+#ifdef STANDALONE_SOLVER
+    int *bfsprev = scratch->bfsprev;
+#endif
+    unsigned char *number = scratch->grid;
+    int *neighbours = scratch->neighbours;
+    int x, y;
+
+    for (y = 0; y < o; y++)
+        for (x = 0; x < o; x++) {
+            int count, t, n;
+
+            /*
+             * If this square doesn't have exactly two candidate
+             * numbers, don't try it.
+             *
+             * In this loop we also sum the candidate numbers,
+             * which is a nasty hack to allow us to quickly find
+             * `the other one' (since we will shortly know there
+             * are exactly two).
+             */
+            for (count = t = 0, n = 1; n <= o; n++)
+                if (cube(x, y, n))
+                    count++, t += n;
+            if (count != 2)
+                continue;
+
+            /*
+             * Now attempt a bfs for each candidate.
+             */
+            for (n = 1; n <= o; n++)
+                if (cube(x, y, n)) {
+                    int orign, currn, head, tail;
+
+                    /*
+                     * Begin a bfs.
+                     */
+                    orign = n;
+
+                    memset(number, o+1, o*o);
+                    head = tail = 0;
+                    bfsqueue[tail++] = y*o+x;
+#ifdef STANDALONE_SOLVER
+                    bfsprev[y*o+x] = -1;
+#endif
+                    number[y*o+x] = t - n;
+
+                    while (head < tail) {
+                        int xx, yy, nneighbours, xt, yt, i;
+
+                        xx = bfsqueue[head++];
+                        yy = xx / o;
+                        xx %= o;
+
+                        currn = number[yy*o+xx];
+
+                        /*
+                         * Find neighbours of yy,xx.
+                         */
+                        nneighbours = 0;
+                        for (yt = 0; yt < o; yt++)
+                            neighbours[nneighbours++] = yt*o+xx;
+                        for (xt = 0; xt < o; xt++)
+                            neighbours[nneighbours++] = yy*o+xt;
+
+                        /*
+                         * Try visiting each of those neighbours.
+                         */
+                        for (i = 0; i < nneighbours; i++) {
+                            int cc, tt, nn;
+
+                            xt = neighbours[i] % o;
+                            yt = neighbours[i] / o;
+
+                            /*
+                             * We need this square to not be
+                             * already visited, and to include
+                             * currn as a possible number.
+                             */
+                            if (number[yt*o+xt] <= o)
+                                continue;
+                            if (!cube(xt, yt, currn))
+                                continue;
+
+                            /*
+                             * Don't visit _this_ square a second
+                             * time!
+                             */
+                            if (xt == xx && yt == yy)
+                                continue;
+
+                            /*
+                             * To continue with the bfs, we need
+                             * this square to have exactly two
+                             * possible numbers.
+                             */
+                            for (cc = tt = 0, nn = 1; nn <= o; nn++)
+                                if (cube(xt, yt, nn))
+                                    cc++, tt += nn;
+                            if (cc == 2) {
+                                bfsqueue[tail++] = yt*o+xt;
+#ifdef STANDALONE_SOLVER
+                                bfsprev[yt*o+xt] = yy*o+xx;
+#endif
+                                number[yt*o+xt] = tt - currn;
+                            }
+
+                            /*
+                             * One other possibility is that this
+                             * might be the square in which we can
+                             * make a real deduction: if it's
+                             * adjacent to x,y, and currn is equal
+                             * to the original number we ruled out.
+                             */
+                            if (currn == orign &&
+                                (xt == x || yt == y)) {
+#ifdef STANDALONE_SOLVER
+                                if (solver_show_working) {
+                                    char *sep = "";
+                                    int xl, yl;
+                                    printf("%*sforcing chain, %s at ends of ",
+                                           solver_recurse_depth*4, "",
+                                          names[orign-1]);
+                                    xl = xx;
+                                    yl = yy;
+                                    while (1) {
+                                        printf("%s(%d,%d)", sep, xl+1,
+                                               yl+1);
+                                        xl = bfsprev[yl*o+xl];
+                                        if (xl < 0)
+                                            break;
+                                        yl = xl / o;
+                                        xl %= o;
+                                        sep = "-";
+                                    }
+                                    printf("\n%*s  ruling out %s at (%d,%d)\n",
+                                           solver_recurse_depth*4, "",
+                                           names[orign-1],
+                                          xt+1, yt+1);
+                                }
+#endif
+                                cube(xt, yt, orign) = FALSE;
+                                return 1;
+                            }
+                        }
+                    }
+                }
+        }
+
+    return 0;
+}
+
+struct latin_solver_scratch *latin_solver_new_scratch(struct latin_solver *solver)
+{
+    struct latin_solver_scratch *scratch = snew(struct latin_solver_scratch);
+    int o = solver->o;
+    scratch->grid = snewn(o*o, unsigned char);
+    scratch->rowidx = snewn(o, unsigned char);
+    scratch->colidx = snewn(o, unsigned char);
+    scratch->set = snewn(o, unsigned char);
+    scratch->neighbours = snewn(3*o, int);
+    scratch->bfsqueue = snewn(o*o, int);
+#ifdef STANDALONE_SOLVER
+    scratch->bfsprev = snewn(o*o, int);
+#endif
+    return scratch;
+}
+
+void latin_solver_free_scratch(struct latin_solver_scratch *scratch)
+{
+#ifdef STANDALONE_SOLVER
+    sfree(scratch->bfsprev);
+#endif
+    sfree(scratch->bfsqueue);
+    sfree(scratch->neighbours);
+    sfree(scratch->set);
+    sfree(scratch->colidx);
+    sfree(scratch->rowidx);
+    sfree(scratch->grid);
+    sfree(scratch);
+}
+
+void latin_solver_alloc(struct latin_solver *solver, digit *grid, int o)
+{
+    int x, y;
+
+    solver->o = o;
+    solver->cube = snewn(o*o*o, unsigned char);
+    solver->grid = grid;               /* write straight back to the input */
+    memset(solver->cube, TRUE, o*o*o);
+
+    solver->row = snewn(o*o, unsigned char);
+    solver->col = snewn(o*o, unsigned char);
+    memset(solver->row, FALSE, o*o);
+    memset(solver->col, FALSE, o*o);
+
+    for (x = 0; x < o; x++)
+       for (y = 0; y < o; y++)
+           if (grid[y*o+x])
+               latin_solver_place(solver, x, y, grid[y*o+x]);
+
+#ifdef STANDALONE_SOLVER
+    solver->names = NULL;
+#endif
+}
+
+void latin_solver_free(struct latin_solver *solver)
+{
+    sfree(solver->cube);
+    sfree(solver->row);
+    sfree(solver->col);
+}
+
+int latin_solver_diff_simple(struct latin_solver *solver)
+{
+    int x, y, n, ret, o = solver->o;
+#ifdef STANDALONE_SOLVER
+    char **names = solver->names;
+#endif
+
+    /*
+     * Row-wise positional elimination.
+     */
+    for (y = 0; y < o; y++)
+        for (n = 1; n <= o; n++)
+            if (!solver->row[y*o+n-1]) {
+                ret = latin_solver_elim(solver, cubepos(0,y,n), o*o
+#ifdef STANDALONE_SOLVER
+                                       , "positional elimination,"
+                                       " %s in row %d", names[n-1],
+                                       y+1
+#endif
+                                       );
+                if (ret != 0) return ret;
+            }
+    /*
+     * Column-wise positional elimination.
+     */
+    for (x = 0; x < o; x++)
+        for (n = 1; n <= o; n++)
+            if (!solver->col[x*o+n-1]) {
+                ret = latin_solver_elim(solver, cubepos(x,0,n), o
+#ifdef STANDALONE_SOLVER
+                                       , "positional elimination,"
+                                       " %s in column %d", names[n-1], x+1
+#endif
+                                       );
+                if (ret != 0) return ret;
+            }
+
+    /*
+     * Numeric elimination.
+     */
+    for (x = 0; x < o; x++)
+        for (y = 0; y < o; y++)
+            if (!solver->grid[y*o+x]) {
+                ret = latin_solver_elim(solver, cubepos(x,y,1), 1
+#ifdef STANDALONE_SOLVER
+                                       , "numeric elimination at (%d,%d)",
+                                       x+1, y+1
+#endif
+                                       );
+                if (ret != 0) return ret;
+            }
+    return 0;
+}
+
+int latin_solver_diff_set(struct latin_solver *solver,
+                          struct latin_solver_scratch *scratch,
+                          int extreme)
+{
+    int x, y, n, ret, o = solver->o;
+#ifdef STANDALONE_SOLVER
+    char **names = solver->names;
+#endif
+
+    if (!extreme) {
+        /*
+         * Row-wise set elimination.
+         */
+        for (y = 0; y < o; y++) {
+            ret = latin_solver_set(solver, scratch, cubepos(0,y,1), o*o, 1
+#ifdef STANDALONE_SOLVER
+                                   , "set elimination, row %d", y+1
+#endif
+                                  );
+            if (ret != 0) return ret;
+        }
+        /*
+         * Column-wise set elimination.
+         */
+        for (x = 0; x < o; x++) {
+            ret = latin_solver_set(solver, scratch, cubepos(x,0,1), o, 1
+#ifdef STANDALONE_SOLVER
+                                   , "set elimination, column %d", x+1
+#endif
+                                  );
+            if (ret != 0) return ret;
+        }
+    } else {
+        /*
+         * Row-vs-column set elimination on a single number
+         * (much tricker for a human to do!)
+         */
+        for (n = 1; n <= o; n++) {
+            ret = latin_solver_set(solver, scratch, cubepos(0,0,n), o*o, o
+#ifdef STANDALONE_SOLVER
+                                   , "positional set elimination on %s",
+                                  names[n-1]
+#endif
+                                  );
+            if (ret != 0) return ret;
+        }
+    }
+    return 0;
+}
+
+/*
+ * Returns:
+ * 0 for 'didn't do anything' implying it was already solved.
+ * -1 for 'impossible' (no solution)
+ * 1 for 'single solution'
+ * >1 for 'multiple solutions' (you don't get to know how many, and
+ *     the first such solution found will be set.
+ *
+ * and this function may well assert if given an impossible board.
+ */
+static int latin_solver_recurse
+    (struct latin_solver *solver, int diff_simple, int diff_set_0,
+     int diff_set_1, int diff_forcing, int diff_recursive,
+     usersolver_t const *usersolvers, void *ctx,
+     ctxnew_t ctxnew, ctxfree_t ctxfree)
+{
+    int best, bestcount;
+    int o = solver->o, x, y, n;
+#ifdef STANDALONE_SOLVER
+    char **names = solver->names;
+#endif
+
+    best = -1;
+    bestcount = o+1;
+
+    for (y = 0; y < o; y++)
+        for (x = 0; x < o; x++)
+            if (!solver->grid[y*o+x]) {
+                int count;
+
+                /*
+                 * An unfilled square. Count the number of
+                 * possible digits in it.
+                 */
+                count = 0;
+                for (n = 1; n <= o; n++)
+                    if (cube(x,y,n))
+                        count++;
+
+                /*
+                 * We should have found any impossibilities
+                 * already, so this can safely be an assert.
+                 */
+                assert(count > 1);
+
+                if (count < bestcount) {
+                    bestcount = count;
+                    best = y*o+x;
+                }
+            }
+
+    if (best == -1)
+        /* we were complete already. */
+        return 0;
+    else {
+        int i, j;
+        digit *list, *ingrid, *outgrid;
+        int diff = diff_impossible;    /* no solution found yet */
+
+        /*
+         * Attempt recursion.
+         */
+        y = best / o;
+        x = best % o;
+
+        list = snewn(o, digit);
+        ingrid = snewn(o*o, digit);
+        outgrid = snewn(o*o, digit);
+        memcpy(ingrid, solver->grid, o*o);
+
+        /* Make a list of the possible digits. */
+        for (j = 0, n = 1; n <= o; n++)
+            if (cube(x,y,n))
+                list[j++] = n;
+
+#ifdef STANDALONE_SOLVER
+        if (solver_show_working) {
+            char *sep = "";
+            printf("%*srecursing on (%d,%d) [",
+                   solver_recurse_depth*4, "", x+1, y+1);
+            for (i = 0; i < j; i++) {
+                printf("%s%s", sep, names[list[i]-1]);
+                sep = " or ";
+            }
+            printf("]\n");
+        }
+#endif
+
+        /*
+         * And step along the list, recursing back into the
+         * main solver at every stage.
+         */
+        for (i = 0; i < j; i++) {
+            int ret;
+           void *newctx;
+           struct latin_solver subsolver;
+
+            memcpy(outgrid, ingrid, o*o);
+            outgrid[y*o+x] = list[i];
+
+#ifdef STANDALONE_SOLVER
+            if (solver_show_working)
+                printf("%*sguessing %s at (%d,%d)\n",
+                       solver_recurse_depth*4, "", names[list[i]-1], x+1, y+1);
+            solver_recurse_depth++;
+#endif
+
+           if (ctxnew) {
+               newctx = ctxnew(ctx);
+           } else {
+               newctx = ctx;
+           }
+           latin_solver_alloc(&subsolver, outgrid, o);
+#ifdef STANDALONE_SOLVER
+           subsolver.names = solver->names;
+#endif
+            ret = latin_solver_top(&subsolver, diff_recursive,
+                                  diff_simple, diff_set_0, diff_set_1,
+                                  diff_forcing, diff_recursive,
+                                  usersolvers, newctx, ctxnew, ctxfree);
+           latin_solver_free(&subsolver);
+           if (ctxnew)
+               ctxfree(newctx);
+
+#ifdef STANDALONE_SOLVER
+            solver_recurse_depth--;
+            if (solver_show_working) {
+                printf("%*sretracting %s at (%d,%d)\n",
+                       solver_recurse_depth*4, "", names[list[i]-1], x+1, y+1);
+            }
+#endif
+            /* we recurse as deep as we can, so we should never find
+             * find ourselves giving up on a puzzle without declaring it
+             * impossible.  */
+            assert(ret != diff_unfinished);
+
+            /*
+             * If we have our first solution, copy it into the
+             * grid we will return.
+             */
+            if (diff == diff_impossible && ret != diff_impossible)
+                memcpy(solver->grid, outgrid, o*o);
+
+            if (ret == diff_ambiguous)
+                diff = diff_ambiguous;
+            else if (ret == diff_impossible)
+                /* do not change our return value */;
+            else {
+                /* the recursion turned up exactly one solution */
+                if (diff == diff_impossible)
+                    diff = diff_recursive;
+                else
+                    diff = diff_ambiguous;
+            }
+
+            /*
+             * As soon as we've found more than one solution,
+             * give up immediately.
+             */
+            if (diff == diff_ambiguous)
+                break;
+        }
+
+        sfree(outgrid);
+        sfree(ingrid);
+        sfree(list);
+
+        if (diff == diff_impossible)
+            return -1;
+        else if (diff == diff_ambiguous)
+            return 2;
+        else {
+            assert(diff == diff_recursive);
+            return 1;
+        }
+    }
+}
+
+static int latin_solver_top(struct latin_solver *solver, int maxdiff,
+                           int diff_simple, int diff_set_0, int diff_set_1,
+                           int diff_forcing, int diff_recursive,
+                           usersolver_t const *usersolvers, void *ctx,
+                           ctxnew_t ctxnew, ctxfree_t ctxfree)
+{
+    struct latin_solver_scratch *scratch = latin_solver_new_scratch(solver);
+    int ret, diff = diff_simple;
+
+    assert(maxdiff <= diff_recursive);
+    /*
+     * Now loop over the grid repeatedly trying all permitted modes
+     * of reasoning. The loop terminates if we complete an
+     * iteration without making any progress; we then return
+     * failure or success depending on whether the grid is full or
+     * not.
+     */
+    while (1) {
+       int i;
+
+       cont:
+
+        latin_solver_debug(solver->cube, solver->o);
+
+       for (i = 0; i <= maxdiff; i++) {
+           if (usersolvers[i])
+               ret = usersolvers[i](solver, ctx);
+           else
+               ret = 0;
+           if (ret == 0 && i == diff_simple)
+               ret = latin_solver_diff_simple(solver);
+           if (ret == 0 && i == diff_set_0)
+               ret = latin_solver_diff_set(solver, scratch, 0);
+           if (ret == 0 && i == diff_set_1)
+               ret = latin_solver_diff_set(solver, scratch, 1);
+           if (ret == 0 && i == diff_forcing)
+               ret = latin_solver_forcing(solver, scratch);
+
+           if (ret < 0) {
+               diff = diff_impossible;
+               goto got_result;
+           } else if (ret > 0) {
+               diff = max(diff, i);
+               goto cont;
+           }
+       }
+
+        /*
+         * If we reach here, we have made no deductions in this
+         * iteration, so the algorithm terminates.
+         */
+        break;
+    }
+
+    /*
+     * Last chance: if we haven't fully solved the puzzle yet, try
+     * recursing based on guesses for a particular square. We pick
+     * one of the most constrained empty squares we can find, which
+     * has the effect of pruning the search tree as much as
+     * possible.
+     */
+    if (maxdiff == diff_recursive) {
+        int nsol = latin_solver_recurse(solver,
+                                       diff_simple, diff_set_0, diff_set_1,
+                                       diff_forcing, diff_recursive,
+                                       usersolvers, ctx, ctxnew, ctxfree);
+        if (nsol < 0) diff = diff_impossible;
+        else if (nsol == 1) diff = diff_recursive;
+        else if (nsol > 1) diff = diff_ambiguous;
+        /* if nsol == 0 then we were complete anyway
+         * (and thus don't need to change diff) */
+    } else {
+        /*
+         * We're forbidden to use recursion, so we just see whether
+         * our grid is fully solved, and return diff_unfinished
+         * otherwise.
+         */
+        int x, y, o = solver->o;
+
+        for (y = 0; y < o; y++)
+            for (x = 0; x < o; x++)
+                if (!solver->grid[y*o+x])
+                    diff = diff_unfinished;
+    }
+
+    got_result:
+
+#ifdef STANDALONE_SOLVER
+    if (solver_show_working)
+        printf("%*s%s found\n",
+               solver_recurse_depth*4, "",
+               diff == diff_impossible ? "no solution (impossible)" :
+               diff == diff_unfinished ? "no solution (unfinished)" :
+               diff == diff_ambiguous ? "multiple solutions" :
+               "one solution");
+#endif
+
+    latin_solver_free_scratch(scratch);
+
+    return diff;
+}
+
+int latin_solver_main(struct latin_solver *solver, int maxdiff,
+                     int diff_simple, int diff_set_0, int diff_set_1,
+                     int diff_forcing, int diff_recursive,
+                     usersolver_t const *usersolvers, void *ctx,
+                     ctxnew_t ctxnew, ctxfree_t ctxfree)
+{
+    int diff;
+#ifdef STANDALONE_SOLVER
+    int o = solver->o;
+    char *text = NULL, **names = NULL;
+#endif
+
+#ifdef STANDALONE_SOLVER
+    if (!solver->names) {
+       char *p;
+       int i;
+
+       text = snewn(40 * o, char);
+       p = text;
+
+       solver->names = snewn(o, char *);
+
+       for (i = 0; i < o; i++) {
+           solver->names[i] = p;
+           p += 1 + sprintf(p, "%d", i+1);
+       }
+    }
+#endif
+
+    diff = latin_solver_top(solver, maxdiff,
+                           diff_simple, diff_set_0, diff_set_1,
+                           diff_forcing, diff_recursive,
+                           usersolvers, ctx, ctxnew, ctxfree);
+
+#ifdef STANDALONE_SOLVER
+    sfree(names);
+    sfree(text);
+#endif
+
+    return diff;
+}
+
+int latin_solver(digit *grid, int o, int maxdiff,
+                int diff_simple, int diff_set_0, int diff_set_1,
+                int diff_forcing, int diff_recursive,
+                usersolver_t const *usersolvers, void *ctx,
+                ctxnew_t ctxnew, ctxfree_t ctxfree)
+{
+    struct latin_solver solver;
+    int diff;
+
+    latin_solver_alloc(&solver, grid, o);
+    diff = latin_solver_main(&solver, maxdiff,
+                            diff_simple, diff_set_0, diff_set_1,
+                            diff_forcing, diff_recursive,
+                            usersolvers, ctx, ctxnew, ctxfree);
+    latin_solver_free(&solver);
+    return diff;
+}
+
+void latin_solver_debug(unsigned char *cube, int o)
+{
+#ifdef STANDALONE_SOLVER
+    if (solver_show_working > 1) {
+        struct latin_solver ls, *solver = &ls;
+        char *dbg;
+        int x, y, i, c = 0;
+
+        ls.cube = cube; ls.o = o; /* for cube() to work */
+
+        dbg = snewn(3*o*o*o, char);
+        for (y = 0; y < o; y++) {
+            for (x = 0; x < o; x++) {
+                for (i = 1; i <= o; i++) {
+                    if (cube(x,y,i))
+                        dbg[c++] = i + '0';
+                    else
+                        dbg[c++] = '.';
+                }
+                dbg[c++] = ' ';
+            }
+            dbg[c++] = '\n';
+        }
+        dbg[c++] = '\n';
+        dbg[c++] = '\0';
+
+        printf("%s", dbg);
+        sfree(dbg);
+    }
+#endif
+}
+
+void latin_debug(digit *sq, int o)
+{
+#ifdef STANDALONE_SOLVER
+    if (solver_show_working) {
+        int x, y;
+
+        for (y = 0; y < o; y++) {
+            for (x = 0; x < o; x++) {
+                printf("%2d ", sq[y*o+x]);
+            }
+            printf("\n");
+        }
+        printf("\n");
+    }
+#endif
+}
+
+/* --------------------------------------------------------
+ * Generation.
+ */
+
+digit *latin_generate(int o, random_state *rs)
+{
+    digit *sq;
+    int *edges, *backedges, *capacity, *flow;
+    void *scratch;
+    int ne, scratchsize;
+    int i, j, k;
+    digit *row, *col, *numinv, *num;
+
+    /*
+     * To efficiently generate a latin square in such a way that
+     * all possible squares are possible outputs from the function,
+     * we make use of a theorem which states that any r x n latin
+     * rectangle, with r < n, can be extended into an (r+1) x n
+     * latin rectangle. In other words, we can reliably generate a
+     * latin square row by row, by at every stage writing down any
+     * row at all which doesn't conflict with previous rows, and
+     * the theorem guarantees that we will never have to backtrack.
+     *
+     * To find a viable row at each stage, we can make use of the
+     * support functions in maxflow.c.
+     */
+
+    sq = snewn(o*o, digit);
+
+    /*
+     * In case this method of generation introduces a really subtle
+     * top-to-bottom directional bias, we'll generate the rows in
+     * random order.
+     */
+    row = snewn(o, digit);
+    col = snewn(o, digit);
+    numinv = snewn(o, digit);
+    num = snewn(o, digit);
+    for (i = 0; i < o; i++)
+       row[i] = i;
+    shuffle(row, i, sizeof(*row), rs);
+
+    /*
+     * Set up the infrastructure for the maxflow algorithm.
+     */
+    scratchsize = maxflow_scratch_size(o * 2 + 2);
+    scratch = smalloc(scratchsize);
+    backedges = snewn(o*o + 2*o, int);
+    edges = snewn((o*o + 2*o) * 2, int);
+    capacity = snewn(o*o + 2*o, int);
+    flow = snewn(o*o + 2*o, int);
+    /* Set up the edge array, and the initial capacities. */
+    ne = 0;
+    for (i = 0; i < o; i++) {
+       /* Each LHS vertex is connected to all RHS vertices. */
+       for (j = 0; j < o; j++) {
+           edges[ne*2] = i;
+           edges[ne*2+1] = j+o;
+           /* capacity for this edge is set later on */
+           ne++;
+       }
+    }
+    for (i = 0; i < o; i++) {
+       /* Each RHS vertex is connected to the distinguished sink vertex. */
+       edges[ne*2] = i+o;
+       edges[ne*2+1] = o*2+1;
+       capacity[ne] = 1;
+       ne++;
+    }
+    for (i = 0; i < o; i++) {
+       /* And the distinguished source vertex connects to each LHS vertex. */
+       edges[ne*2] = o*2;
+       edges[ne*2+1] = i;
+       capacity[ne] = 1;
+       ne++;
+    }
+    assert(ne == o*o + 2*o);
+    /* Now set up backedges. */
+    maxflow_setup_backedges(ne, edges, backedges);
+    
+    /*
+     * Now generate each row of the latin square.
+     */
+    for (i = 0; i < o; i++) {
+       /*
+        * To prevent maxflow from behaving deterministically, we
+        * separately permute the columns and the digits for the
+        * purposes of the algorithm, differently for every row.
+        */
+       for (j = 0; j < o; j++)
+           col[j] = num[j] = j;
+       shuffle(col, j, sizeof(*col), rs);
+       shuffle(num, j, sizeof(*num), rs);
+       /* We need the num permutation in both forward and inverse forms. */
+       for (j = 0; j < o; j++)
+           numinv[num[j]] = j;
+
+       /*
+        * Set up the capacities for the maxflow run, by examining
+        * the existing latin square.
+        */
+       for (j = 0; j < o*o; j++)
+           capacity[j] = 1;
+       for (j = 0; j < i; j++)
+           for (k = 0; k < o; k++) {
+               int n = num[sq[row[j]*o + col[k]] - 1];
+               capacity[k*o+n] = 0;
+           }
+
+       /*
+        * Run maxflow.
+        */
+       j = maxflow_with_scratch(scratch, o*2+2, 2*o, 2*o+1, ne,
+                                edges, backedges, capacity, flow, NULL);
+       assert(j == o);   /* by the above theorem, this must have succeeded */
+
+       /*
+        * And examine the flow array to pick out the new row of
+        * the latin square.
+        */
+       for (j = 0; j < o; j++) {
+           for (k = 0; k < o; k++) {
+               if (flow[j*o+k])
+                   break;
+           }
+           assert(k < o);
+           sq[row[i]*o + col[j]] = numinv[k] + 1;
+       }
+    }
+
+    /*
+     * Done. Free our internal workspaces...
+     */
+    sfree(flow);
+    sfree(capacity);
+    sfree(edges);
+    sfree(backedges);
+    sfree(scratch);
+    sfree(numinv);
+    sfree(num);
+    sfree(col);
+    sfree(row);
+
+    /*
+     * ... and return our completed latin square.
+     */
+    return sq;
+}
+
+digit *latin_generate_rect(int w, int h, random_state *rs)
+{
+    int o = max(w, h), x, y;
+    digit *latin, *latin_rect;
+
+    latin = latin_generate(o, rs);
+    latin_rect = snewn(w*h, digit);
+
+    for (x = 0; x < w; x++) {
+        for (y = 0; y < h; y++) {
+            latin_rect[y*w + x] = latin[y*o + x];
+        }
+    }
+
+    sfree(latin);
+    return latin_rect;
+}
+
+/* --------------------------------------------------------
+ * Checking.
+ */
+
+typedef struct lcparams {
+    digit elt;
+    int count;
+} lcparams;
+
+static int latin_check_cmp(void *v1, void *v2)
+{
+    lcparams *lc1 = (lcparams *)v1;
+    lcparams *lc2 = (lcparams *)v2;
+
+    if (lc1->elt < lc2->elt) return -1;
+    if (lc1->elt > lc2->elt) return 1;
+    return 0;
+}
+
+#define ELT(sq,x,y) (sq[((y)*order)+(x)])
+
+/* returns non-zero if sq is not a latin square. */
+int latin_check(digit *sq, int order)
+{
+    tree234 *dict = newtree234(latin_check_cmp);
+    int c, r;
+    int ret = 0;
+    lcparams *lcp, lc, *aret;
+
+    /* Use a tree234 as a simple hash table, go through the square
+     * adding elements as we go or incrementing their counts. */
+    for (c = 0; c < order; c++) {
+       for (r = 0; r < order; r++) {
+           lc.elt = ELT(sq, c, r); lc.count = 0;
+           lcp = find234(dict, &lc, NULL);
+           if (!lcp) {
+               lcp = snew(lcparams);
+               lcp->elt = ELT(sq, c, r);
+               lcp->count = 1;
+                aret = add234(dict, lcp);
+               assert(aret == lcp);
+           } else {
+               lcp->count++;
+           }
+       }
+    }
+
+    /* There should be precisely 'order' letters in the alphabet,
+     * each occurring 'order' times (making the OxO tree) */
+    if (count234(dict) != order) ret = 1;
+    else {
+       for (c = 0; (lcp = index234(dict, c)) != NULL; c++) {
+           if (lcp->count != order) ret = 1;
+       }
+    }
+    for (c = 0; (lcp = index234(dict, c)) != NULL; c++)
+       sfree(lcp);
+    freetree234(dict);
+
+    return ret;
+}
+
+
+/* --------------------------------------------------------
+ * Testing (and printing).
+ */
+
+#ifdef STANDALONE_LATIN_TEST
+
+#include <stdio.h>
+#include <time.h>
+
+const char *quis;
+
+static void latin_print(digit *sq, int order)
+{
+    int x, y;
+
+    for (y = 0; y < order; y++) {
+       for (x = 0; x < order; x++) {
+           printf("%2u ", ELT(sq, x, y));
+       }
+       printf("\n");
+    }
+    printf("\n");
+}
+
+static void gen(int order, random_state *rs, int debug)
+{
+    digit *sq;
+
+    solver_show_working = debug;
+
+    sq = latin_generate(order, rs);
+    latin_print(sq, order);
+    if (latin_check(sq, order)) {
+       fprintf(stderr, "Square is not a latin square!");
+       exit(1);
+    }
+
+    sfree(sq);
+}
+
+void test_soak(int order, random_state *rs)
+{
+    digit *sq;
+    int n = 0;
+    time_t tt_start, tt_now, tt_last;
+
+    solver_show_working = 0;
+    tt_now = tt_start = time(NULL);
+
+    while(1) {
+        sq = latin_generate(order, rs);
+        sfree(sq);
+        n++;
+
+        tt_last = time(NULL);
+        if (tt_last > tt_now) {
+            tt_now = tt_last;
+            printf("%d total, %3.1f/s\n", n,
+                   (double)n / (double)(tt_now - tt_start));
+        }
+    }
+}
+
+void usage_exit(const char *msg)
+{
+    if (msg)
+        fprintf(stderr, "%s: %s\n", quis, msg);
+    fprintf(stderr, "Usage: %s [--seed SEED] --soak <params> | [game_id [game_id ...]]\n", quis);
+    exit(1);
+}
+
+int main(int argc, char *argv[])
+{
+    int i, soak = 0;
+    random_state *rs;
+    time_t seed = time(NULL);
+
+    quis = argv[0];
+    while (--argc > 0) {
+       const char *p = *++argv;
+       if (!strcmp(p, "--soak"))
+           soak = 1;
+       else if (!strcmp(p, "--seed")) {
+           if (argc == 0)
+               usage_exit("--seed needs an argument");
+           seed = (time_t)atoi(*++argv);
+           argc--;
+       } else if (*p == '-')
+               usage_exit("unrecognised option");
+       else
+           break; /* finished options */
+    }
+
+    rs = random_new((void*)&seed, sizeof(time_t));
+
+    if (soak == 1) {
+       if (argc != 1) usage_exit("only one argument for --soak");
+       test_soak(atoi(*argv), rs);
+    } else {
+       if (argc > 0) {
+           for (i = 0; i < argc; i++) {
+               gen(atoi(*argv++), rs, 1);
+           }
+       } else {
+           while (1) {
+               i = random_upto(rs, 20) + 1;
+               gen(i, rs, 0);
+           }
+       }
+    }
+    random_free(rs);
+    return 0;
+}
+
+#endif
+
+/* vim: set shiftwidth=4 tabstop=8: */
diff --git a/latin.h b/latin.h
new file mode 100644 (file)
index 0000000..4b09f16
--- /dev/null
+++ b/latin.h
@@ -0,0 +1,122 @@
+#ifndef LATIN_H
+#define LATIN_H
+
+#include "puzzles.h"
+
+typedef unsigned char digit;
+
+/* --- Solver structures, definitions --- */
+
+#ifdef STANDALONE_SOLVER
+extern int solver_show_working, solver_recurse_depth;
+#endif
+
+struct latin_solver {
+  int o;                /* order of latin square */
+  unsigned char *cube;  /* o^3, indexed by x, y, and digit:
+                           TRUE in that position indicates a possibility */
+  digit *grid;          /* o^2, indexed by x and y: for final deductions */
+
+  unsigned char *row;   /* o^2: row[y*cr+n-1] TRUE if n is in row y */
+  unsigned char *col;   /* o^2: col[x*cr+n-1] TRUE if n is in col x */
+
+#ifdef STANDALONE_SOLVER
+  char **names;         /* o: names[n-1] gives name of 'digit' n */
+#endif
+};
+#define cubepos(x,y,n) (((x)*solver->o+(y))*solver->o+(n)-1)
+#define cube(x,y,n) (solver->cube[cubepos(x,y,n)])
+
+#define gridpos(x,y) ((y)*solver->o+(x))
+#define grid(x,y) (solver->grid[gridpos(x,y)])
+
+
+/* --- Solver individual strategies --- */
+
+/* Place a value at a specific location. */
+void latin_solver_place(struct latin_solver *solver, int x, int y, int n);
+
+/* Positional elimination. */
+int latin_solver_elim(struct latin_solver *solver, int start, int step
+#ifdef STANDALONE_SOLVER
+                      , char *fmt, ...
+#endif
+                      );
+
+struct latin_solver_scratch; /* private to latin.c */
+/* Set elimination */
+int latin_solver_set(struct latin_solver *solver,
+                     struct latin_solver_scratch *scratch,
+                     int start, int step1, int step2
+#ifdef STANDALONE_SOLVER
+                     , char *fmt, ...
+#endif
+                     );
+
+/* Forcing chains */
+int latin_solver_forcing(struct latin_solver *solver,
+                         struct latin_solver_scratch *scratch);
+
+
+/* --- Solver allocation --- */
+
+/* Fills in (and allocates members for) a latin_solver struct.
+ * Will allocate members of snew, but not snew itself
+ * (allowing 'struct latin_solver' to be the first element in a larger
+ * struct, for example). */
+void latin_solver_alloc(struct latin_solver *solver, digit *grid, int o);
+void latin_solver_free(struct latin_solver *solver);
+
+/* Allocates scratch space (for _set and _forcing) */
+struct latin_solver_scratch *
+  latin_solver_new_scratch(struct latin_solver *solver);
+void latin_solver_free_scratch(struct latin_solver_scratch *scratch);
+
+
+/* --- Solver guts --- */
+
+/* Looped positional elimination */
+int latin_solver_diff_simple(struct latin_solver *solver);
+
+/* Looped set elimination; *extreme is set if it used
+ * the more difficult single-number elimination. */
+int latin_solver_diff_set(struct latin_solver *solver,
+                          struct latin_solver_scratch *scratch,
+                          int extreme);
+
+typedef int (*usersolver_t)(struct latin_solver *solver, void *ctx);
+typedef void *(*ctxnew_t)(void *ctx);
+typedef void (*ctxfree_t)(void *ctx);
+
+/* Individual puzzles should use their enumerations for their
+ * own difficulty levels, ensuring they don't clash with these. */
+enum { diff_impossible = 10, diff_ambiguous, diff_unfinished };
+
+/* Externally callable function that allocates and frees a latin_solver */
+int latin_solver(digit *grid, int o, int maxdiff,
+                int diff_simple, int diff_set_0, int diff_set_1,
+                int diff_forcing, int diff_recursive,
+                usersolver_t const *usersolvers, void *ctx,
+                ctxnew_t ctxnew, ctxfree_t ctxfree);
+
+/* Version you can call if you want to alloc and free latin_solver yourself */
+int latin_solver_main(struct latin_solver *solver, int maxdiff,
+                     int diff_simple, int diff_set_0, int diff_set_1,
+                     int diff_forcing, int diff_recursive,
+                     usersolver_t const *usersolvers, void *ctx,
+                     ctxnew_t ctxnew, ctxfree_t ctxfree);
+
+void latin_solver_debug(unsigned char *cube, int o);
+
+/* --- Generation and checking --- */
+
+digit *latin_generate(int o, random_state *rs);
+
+/* The order of the latin rectangle is max(w,h). */
+digit *latin_generate_rect(int w, int h, random_state *rs);
+
+int latin_check(digit *sq, int order); /* !0 => not a latin square */
+
+void latin_debug(digit *sq, int order);
+
+#endif
diff --git a/laydomino.c b/laydomino.c
new file mode 100644 (file)
index 0000000..cead5d5
--- /dev/null
@@ -0,0 +1,291 @@
+/*
+ * laydomino.c: code for performing a domino (2x1 tile) layout of
+ * a given area of code.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "puzzles.h"
+
+/*
+ * This function returns an array size w x h representing a grid:
+ * each grid[i] = j, where j is the other end of a 2x1 domino.
+ * If w*h is odd, one square will remain referring to itself.
+ */
+
+int *domino_layout(int w, int h, random_state *rs)
+{
+    int *grid, *grid2, *list;
+    int wh = w*h;
+
+    /*
+     * Allocate space in which to lay the grid out.
+     */
+    grid = snewn(wh, int);
+    grid2 = snewn(wh, int);
+    list = snewn(2*wh, int);
+
+    domino_layout_prealloc(w, h, rs, grid, grid2, list);
+
+    sfree(grid2);
+    sfree(list);
+
+    return grid;
+}
+
+/*
+ * As for domino_layout, but with preallocated buffers.
+ * grid and grid2 should be size w*h, and list size 2*w*h.
+ */
+void domino_layout_prealloc(int w, int h, random_state *rs,
+                            int *grid, int *grid2, int *list)
+{
+    int i, j, k, m, wh = w*h, todo, done;
+
+    /*
+     * To begin with, set grid[i] = i for all i to indicate
+     * that all squares are currently singletons. Later we'll
+     * set grid[i] to be the index of the other end of the
+     * domino on i.
+     */
+    for (i = 0; i < wh; i++)
+        grid[i] = i;
+
+    /*
+     * Now prepare a list of the possible domino locations. There
+     * are w*(h-1) possible vertical locations, and (w-1)*h
+     * horizontal ones, for a total of 2*wh - h - w.
+     *
+     * I'm going to denote the vertical domino placement with
+     * its top in square i as 2*i, and the horizontal one with
+     * its left half in square i as 2*i+1.
+     */
+    k = 0;
+    for (j = 0; j < h-1; j++)
+        for (i = 0; i < w; i++)
+            list[k++] = 2 * (j*w+i);   /* vertical positions */
+    for (j = 0; j < h; j++)
+        for (i = 0; i < w-1; i++)
+            list[k++] = 2 * (j*w+i) + 1;   /* horizontal positions */
+    assert(k == 2*wh - h - w);
+
+    /*
+     * Shuffle the list.
+     */
+    shuffle(list, k, sizeof(*list), rs);
+
+    /*
+     * Work down the shuffled list, placing a domino everywhere
+     * we can.
+     */
+    for (i = 0; i < k; i++) {
+        int horiz, xy, xy2;
+
+        horiz = list[i] % 2;
+        xy = list[i] / 2;
+        xy2 = xy + (horiz ? 1 : w);
+
+        if (grid[xy] == xy && grid[xy2] == xy2) {
+            /*
+             * We can place this domino. Do so.
+             */
+            grid[xy] = xy2;
+            grid[xy2] = xy;
+        }
+    }
+
+#ifdef GENERATION_DIAGNOSTICS
+    printf("generated initial layout\n");
+#endif
+
+    /*
+     * Now we've placed as many dominoes as we can immediately
+     * manage. There will be squares remaining, but they'll be
+     * singletons. So loop round and deal with the singletons
+     * two by two.
+     */
+    while (1) {
+#ifdef GENERATION_DIAGNOSTICS
+        for (j = 0; j < h; j++) {
+            for (i = 0; i < w; i++) {
+                int xy = j*w+i;
+                int v = grid[xy];
+                int c = (v == xy+1 ? '[' : v == xy-1 ? ']' :
+                         v == xy+w ? 'n' : v == xy-w ? 'U' : '.');
+                putchar(c);
+            }
+            putchar('\n');
+        }
+        putchar('\n');
+#endif
+
+        /*
+         * Our strategy is:
+         *
+         * First find a singleton square.
+         *
+         * Then breadth-first search out from the starting
+         * square. From that square (and any others we reach on
+         * the way), examine all four neighbours of the square.
+         * If one is an end of a domino, we move to the _other_
+         * end of that domino before looking at neighbours
+         * again. When we encounter another singleton on this
+         * search, stop.
+         *
+         * This will give us a path of adjacent squares such
+         * that all but the two ends are covered in dominoes.
+         * So we can now shuffle every domino on the path up by
+         * one.
+         *
+         * (Chessboard colours are mathematically important
+         * here: we always end up pairing each singleton with a
+         * singleton of the other colour. However, we never
+         * have to track this manually, since it's
+         * automatically taken care of by the fact that we
+         * always make an even number of orthogonal moves.)
+         */
+        k = 0;
+        for (j = 0; j < wh; j++) {
+            if (grid[j] == j) {
+                k++;
+                i = j;          /* start BFS here. */
+            }
+        }
+        if (k == (wh % 2))
+            break;              /* if area is even, we have no more singletons;
+                                   if area is odd, we have one singleton.
+                                   either way, we're done. */
+
+#ifdef GENERATION_DIAGNOSTICS
+        printf("starting b.f.s. at singleton %d\n", i);
+#endif
+        /*
+         * Set grid2 to -1 everywhere. It will hold our
+         * distance-from-start values, and also our
+         * backtracking data, during the b.f.s.
+         */
+        for (j = 0; j < wh; j++)
+            grid2[j] = -1;
+        grid2[i] = 0;              /* starting square has distance zero */
+
+        /*
+         * Start our to-do list of squares. It'll live in
+         * `list'; since the b.f.s can cover every square at
+         * most once there is no need for it to be circular.
+         * We'll just have two counters tracking the end of the
+         * list and the squares we've already dealt with.
+         */
+        done = 0;
+        todo = 1;
+        list[0] = i;
+
+        /*
+         * Now begin the b.f.s. loop.
+         */
+        while (done < todo) {
+            int d[4], nd, x, y;
+
+            i = list[done++];
+
+#ifdef GENERATION_DIAGNOSTICS
+            printf("b.f.s. iteration from %d\n", i);
+#endif
+            x = i % w;
+            y = i / w;
+            nd = 0;
+            if (x > 0)
+                d[nd++] = i - 1;
+            if (x+1 < w)
+                d[nd++] = i + 1;
+            if (y > 0)
+                d[nd++] = i - w;
+            if (y+1 < h)
+                d[nd++] = i + w;
+            /*
+             * To avoid directional bias, process the
+             * neighbours of this square in a random order.
+             */
+            shuffle(d, nd, sizeof(*d), rs);
+
+            for (j = 0; j < nd; j++) {
+                k = d[j];
+                if (grid[k] == k) {
+#ifdef GENERATION_DIAGNOSTICS
+                    printf("found neighbouring singleton %d\n", k);
+#endif
+                    grid2[k] = i;
+                    break;         /* found a target singleton! */
+                }
+
+                /*
+                 * We're moving through a domino here, so we
+                 * have two entries in grid2 to fill with
+                 * useful data. In grid[k] - the square
+                 * adjacent to where we came from - I'm going
+                 * to put the address _of_ the square we came
+                 * from. In the other end of the domino - the
+                 * square from which we will continue the
+                 * search - I'm going to put the distance.
+                 */
+                m = grid[k];
+
+                if (grid2[m] < 0 || grid2[m] > grid2[i]+1) {
+#ifdef GENERATION_DIAGNOSTICS
+                    printf("found neighbouring domino %d/%d\n", k, m);
+#endif
+                    grid2[m] = grid2[i]+1;
+                    grid2[k] = i;
+                    /*
+                     * And since we've now visited a new
+                     * domino, add m to the to-do list.
+                     */
+                    assert(todo < wh);
+                    list[todo++] = m;
+                }
+            }
+
+            if (j < nd) {
+                i = k;
+#ifdef GENERATION_DIAGNOSTICS
+                printf("terminating b.f.s. loop, i = %d\n", i);
+#endif
+                break;
+            }
+
+            i = -1;                /* just in case the loop terminates */
+        }
+
+        /*
+         * We expect this b.f.s. to have found us a target
+         * square.
+         */
+        assert(i >= 0);
+
+        /*
+         * Now we can follow the trail back to our starting
+         * singleton, re-laying dominoes as we go.
+         */
+        while (1) {
+            j = grid2[i];
+            assert(j >= 0 && j < wh);
+            k = grid[j];
+
+            grid[i] = j;
+            grid[j] = i;
+#ifdef GENERATION_DIAGNOSTICS
+            printf("filling in domino %d/%d (next %d)\n", i, j, k);
+#endif
+            if (j == k)
+                break;             /* we've reached the other singleton */
+            i = k;
+        }
+#ifdef GENERATION_DIAGNOSTICS
+        printf("fixup path completed\n");
+#endif
+    }
+}
+
+/* vim: set shiftwidth=4 :set textwidth=80: */
+
diff --git a/lightup.R b/lightup.R
new file mode 100644 (file)
index 0000000..a474de8
--- /dev/null
+++ b/lightup.R
@@ -0,0 +1,24 @@
+# -*- makefile -*-
+
+LIGHTUP_EXTRA = combi
+
+lightup  : [X] GTK COMMON lightup LIGHTUP_EXTRA lightup-icon|no-icon
+
+lightup  : [G] WINDOWS COMMON lightup LIGHTUP_EXTRA lightup.res|noicon.res
+
+lightupsolver : [U] lightup[STANDALONE_SOLVER] LIGHTUP_EXTRA STANDALONE
+lightupsolver : [C] lightup[STANDALONE_SOLVER] LIGHTUP_EXTRA STANDALONE
+
+ALL += lightup[COMBINED] LIGHTUP_EXTRA
+
+!begin am gtk
+GAMES += lightup
+!end
+
+!begin >list.c
+    A(lightup) \
+!end
+
+!begin >gamedesc.txt
+lightup:lightup.exe:Light Up:Light-bulb placing puzzle:Place bulbs to light up all the squares.
+!end
diff --git a/lightup.c b/lightup.c
new file mode 100644 (file)
index 0000000..bfc6980
--- /dev/null
+++ b/lightup.c
@@ -0,0 +1,2405 @@
+/*
+ * lightup.c: Implementation of the Nikoli game 'Light Up'.
+ *
+ * Possible future solver enhancements:
+ *
+ *  - In a situation where two clues are diagonally adjacent, you can
+ *    deduce bounds on the number of lights shared between them. For
+ *    instance, suppose a 3 clue is diagonally adjacent to a 1 clue:
+ *    of the two squares adjacent to both clues, at least one must be
+ *    a light (or the 3 would be unsatisfiable) and yet at most one
+ *    must be a light (or the 1 would be overcommitted), so in fact
+ *    _exactly_ one must be a light, and hence the other two squares
+ *    adjacent to the 3 must also be lights and the other two adjacent
+ *    to the 1 must not. Likewise if the 3 is replaced with a 2 but
+ *    one of its other two squares is known not to be a light, and so
+ *    on.
+ *
+ *  - In a situation where two clues are orthogonally separated (not
+ *    necessarily directly adjacent), you may be able to deduce
+ *    something about the squares that align with each other. For
+ *    instance, suppose two clues are vertically adjacent. Consider
+ *    the pair of squares A,B horizontally adjacent to the top clue,
+ *    and the pair C,D horizontally adjacent to the bottom clue.
+ *    Assuming no intervening obstacles, A and C align with each other
+ *    and hence at most one of them can be a light, and B and D
+ *    likewise, so we must have at most two lights between the four
+ *    squares. So if the clues indicate that there are at _least_ two
+ *    lights in those four squares because the top clue requires at
+ *    least one of AB to be a light and the bottom one requires at
+ *    least one of CD, then we can in fact deduce that there are
+ *    _exactly_ two lights between the four squares, and fill in the
+ *    other squares adjacent to each clue accordingly. For instance,
+ *    if both clues are 3s, then we instantly deduce that all four of
+ *    the squares _vertically_ adjacent to the two clues must be
+ *    lights. (For that to happen, of course, there'd also have to be
+ *    a black square in between the clues, so the two inner lights
+ *    don't light each other.)
+ *
+ *  - I haven't thought it through carefully, but there's always the
+ *    possibility that both of the above deductions are special cases
+ *    of some more general pattern which can be made computationally
+ *    feasible...
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+
+/*
+ * In standalone solver mode, `verbose' is a variable which can be
+ * set by command-line option; in debugging mode it's simply always
+ * true.
+ */
+#if defined STANDALONE_SOLVER
+#define SOLVER_DIAGNOSTICS
+int verbose = 0;
+#undef debug
+#define debug(x) printf x
+#elif defined SOLVER_DIAGNOSTICS
+#define verbose 2
+#endif
+
+/* --- Constants, structure definitions, etc. --- */
+
+#define PREFERRED_TILE_SIZE 32
+#define TILE_SIZE       (ds->tilesize)
+#define BORDER          (TILE_SIZE / 2)
+#define TILE_RADIUS     (ds->crad)
+
+#define COORD(x)  ( (x) * TILE_SIZE + BORDER )
+#define FROMCOORD(x)  ( ((x) - BORDER + TILE_SIZE) / TILE_SIZE - 1 )
+
+#define FLASH_TIME 0.30F
+
+enum {
+    COL_BACKGROUND,
+    COL_GRID,
+    COL_BLACK,                        /* black */
+    COL_LIGHT,                        /* white */
+    COL_LIT,                          /* yellow */
+    COL_ERROR,                        /* red */
+    COL_CURSOR,
+    NCOLOURS
+};
+
+enum { SYMM_NONE, SYMM_REF2, SYMM_ROT2, SYMM_REF4, SYMM_ROT4, SYMM_MAX };
+
+#define DIFFCOUNT 2
+
+struct game_params {
+    int w, h;
+    int blackpc;        /* %age of black squares */
+    int symm;
+    int difficulty;     /* 0 to DIFFCOUNT */
+};
+
+#define F_BLACK         1
+
+/* flags for black squares */
+#define F_NUMBERED      2       /* it has a number attached */
+#define F_NUMBERUSED    4       /* this number was useful for solving */
+
+/* flags for non-black squares */
+#define F_IMPOSSIBLE    8       /* can't put a light here */
+#define F_LIGHT         16
+
+#define F_MARK          32
+
+struct game_state {
+    int w, h, nlights;
+    int *lights;        /* For black squares, (optionally) the number
+                           of surrounding lights. For non-black squares,
+                           the number of times it's lit. size h*w*/
+    unsigned int *flags;        /* size h*w */
+    int completed, used_solve;
+};
+
+#define GRID(gs,grid,x,y) (gs->grid[(y)*((gs)->w) + (x)])
+
+/* A ll_data holds information about which lights would be lit by
+ * a particular grid location's light (or conversely, which locations
+ * could light a specific other location). */
+/* most things should consider this struct opaque. */
+typedef struct {
+    int ox,oy;
+    int minx, maxx, miny, maxy;
+    int include_origin;
+} ll_data;
+
+/* Macro that executes 'block' once per light in lld, including
+ * the origin if include_origin is specified. 'block' can use
+ * lx and ly as the coords. */
+#define FOREACHLIT(lld,block) do {                              \
+  int lx,ly;                                                    \
+  ly = (lld)->oy;                                               \
+  for (lx = (lld)->minx; lx <= (lld)->maxx; lx++) {             \
+    if (lx == (lld)->ox) continue;                              \
+    block                                                       \
+  }                                                             \
+  lx = (lld)->ox;                                               \
+  for (ly = (lld)->miny; ly <= (lld)->maxy; ly++) {             \
+    if (!(lld)->include_origin && ly == (lld)->oy) continue;    \
+    block                                                       \
+  }                                                             \
+} while(0)
+
+
+typedef struct {
+    struct { int x, y; unsigned int f; } points[4];
+    int npoints;
+} surrounds;
+
+/* Fills in (doesn't allocate) a surrounds structure with the grid locations
+ * around a given square, taking account of the edges. */
+static void get_surrounds(const game_state *state, int ox, int oy,
+                          surrounds *s)
+{
+    assert(ox >= 0 && ox < state->w && oy >= 0 && oy < state->h);
+    s->npoints = 0;
+#define ADDPOINT(cond,nx,ny) do {\
+    if (cond) { \
+        s->points[s->npoints].x = (nx); \
+        s->points[s->npoints].y = (ny); \
+        s->points[s->npoints].f = 0; \
+        s->npoints++; \
+    } } while(0)
+    ADDPOINT(ox > 0,            ox-1, oy);
+    ADDPOINT(ox < (state->w-1), ox+1, oy);
+    ADDPOINT(oy > 0,            ox,   oy-1);
+    ADDPOINT(oy < (state->h-1), ox,   oy+1);
+}
+
+/* --- Game parameter functions --- */
+
+#define DEFAULT_PRESET 0
+
+const struct game_params lightup_presets[] = {
+    { 7, 7, 20, SYMM_ROT4, 0 },
+    { 7, 7, 20, SYMM_ROT4, 1 },
+    { 7, 7, 20, SYMM_ROT4, 2 },
+    { 10, 10, 20, SYMM_ROT2, 0 },
+    { 10, 10, 20, SYMM_ROT2, 1 },
+#ifdef SLOW_SYSTEM
+    { 12, 12, 20, SYMM_ROT2, 0 },
+    { 12, 12, 20, SYMM_ROT2, 1 },
+#else
+    { 10, 10, 20, SYMM_ROT2, 2 },
+    { 14, 14, 20, SYMM_ROT2, 0 },
+    { 14, 14, 20, SYMM_ROT2, 1 },
+    { 14, 14, 20, SYMM_ROT2, 2 }
+#endif
+};
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+    *ret = lightup_presets[DEFAULT_PRESET];
+
+    return ret;
+}
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params *ret;
+    char buf[80];
+
+    if (i < 0 || i >= lenof(lightup_presets))
+        return FALSE;
+
+    ret = default_params();
+    *ret = lightup_presets[i];
+    *params = ret;
+
+    sprintf(buf, "%dx%d %s",
+            ret->w, ret->h,
+            ret->difficulty == 2 ? "hard" :
+            ret->difficulty == 1 ? "tricky" : "easy");
+    *name = dupstr(buf);
+
+    return TRUE;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+#define EATNUM(x) do { \
+    (x) = atoi(string); \
+    while (*string && isdigit((unsigned char)*string)) string++; \
+} while(0)
+
+static void decode_params(game_params *params, char const *string)
+{
+    EATNUM(params->w);
+    if (*string == 'x') {
+        string++;
+        EATNUM(params->h);
+    }
+    if (*string == 'b') {
+        string++;
+        EATNUM(params->blackpc);
+    }
+    if (*string == 's') {
+        string++;
+        EATNUM(params->symm);
+    } else {
+        /* cope with user input such as '18x10' by ensuring symmetry
+         * is not selected by default to be incompatible with dimensions */
+        if (params->symm == SYMM_ROT4 && params->w != params->h)
+            params->symm = SYMM_ROT2;
+    }
+    params->difficulty = 0;
+    /* cope with old params */
+    if (*string == 'r') {
+        params->difficulty = 2;
+        string++;
+    }
+    if (*string == 'd') {
+        string++;
+        EATNUM(params->difficulty);
+    }
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char buf[80];
+
+    if (full) {
+        sprintf(buf, "%dx%db%ds%dd%d",
+                params->w, params->h, params->blackpc,
+                params->symm,
+                params->difficulty);
+    } else {
+        sprintf(buf, "%dx%d", params->w, params->h);
+    }
+    return dupstr(buf);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+    char buf[80];
+
+    ret = snewn(6, config_item);
+
+    ret[0].name = "Width";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->w);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Height";
+    ret[1].type = C_STRING;
+    sprintf(buf, "%d", params->h);
+    ret[1].sval = dupstr(buf);
+    ret[1].ival = 0;
+
+    ret[2].name = "%age of black squares";
+    ret[2].type = C_STRING;
+    sprintf(buf, "%d", params->blackpc);
+    ret[2].sval = dupstr(buf);
+    ret[2].ival = 0;
+
+    ret[3].name = "Symmetry";
+    ret[3].type = C_CHOICES;
+    ret[3].sval = ":None"
+                  ":2-way mirror:2-way rotational"
+                  ":4-way mirror:4-way rotational";
+    ret[3].ival = params->symm;
+
+    ret[4].name = "Difficulty";
+    ret[4].type = C_CHOICES;
+    ret[4].sval = ":Easy:Tricky:Hard";
+    ret[4].ival = params->difficulty;
+
+    ret[5].name = NULL;
+    ret[5].type = C_END;
+    ret[5].sval = NULL;
+    ret[5].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w =       atoi(cfg[0].sval);
+    ret->h =       atoi(cfg[1].sval);
+    ret->blackpc = atoi(cfg[2].sval);
+    ret->symm =    cfg[3].ival;
+    ret->difficulty = cfg[4].ival;
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    if (params->w < 2 || params->h < 2)
+        return "Width and height must be at least 2";
+    if (full) {
+        if (params->blackpc < 5 || params->blackpc > 100)
+            return "Percentage of black squares must be between 5% and 100%";
+        if (params->w != params->h) {
+            if (params->symm == SYMM_ROT4)
+                return "4-fold symmetry is only available with square grids";
+        }
+        if (params->symm < 0 || params->symm >= SYMM_MAX)
+            return "Unknown symmetry type";
+        if (params->difficulty < 0 || params->difficulty > DIFFCOUNT)
+            return "Unknown difficulty level";
+    }
+    return NULL;
+}
+
+/* --- Game state construction/freeing helper functions --- */
+
+static game_state *new_state(const game_params *params)
+{
+    game_state *ret = snew(game_state);
+
+    ret->w = params->w;
+    ret->h = params->h;
+    ret->lights = snewn(ret->w * ret->h, int);
+    ret->nlights = 0;
+    memset(ret->lights, 0, ret->w * ret->h * sizeof(int));
+    ret->flags = snewn(ret->w * ret->h, unsigned int);
+    memset(ret->flags, 0, ret->w * ret->h * sizeof(unsigned int));
+    ret->completed = ret->used_solve = 0;
+    return ret;
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    game_state *ret = snew(game_state);
+
+    ret->w = state->w;
+    ret->h = state->h;
+
+    ret->lights = snewn(ret->w * ret->h, int);
+    memcpy(ret->lights, state->lights, ret->w * ret->h * sizeof(int));
+    ret->nlights = state->nlights;
+
+    ret->flags = snewn(ret->w * ret->h, unsigned int);
+    memcpy(ret->flags, state->flags, ret->w * ret->h * sizeof(unsigned int));
+
+    ret->completed = state->completed;
+    ret->used_solve = state->used_solve;
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    sfree(state->lights);
+    sfree(state->flags);
+    sfree(state);
+}
+
+static void debug_state(game_state *state)
+{
+    int x, y;
+    char c = '?';
+
+    for (y = 0; y < state->h; y++) {
+        for (x = 0; x < state->w; x++) {
+            c = '.';
+            if (GRID(state, flags, x, y) & F_BLACK) {
+                if (GRID(state, flags, x, y) & F_NUMBERED)
+                    c = GRID(state, lights, x, y) + '0';
+                else
+                    c = '#';
+            } else {
+                if (GRID(state, flags, x, y) & F_LIGHT)
+                    c = 'O';
+                else if (GRID(state, flags, x, y) & F_IMPOSSIBLE)
+                    c = 'X';
+            }
+            debug(("%c", (int)c));
+        }
+        debug(("     "));
+        for (x = 0; x < state->w; x++) {
+            if (GRID(state, flags, x, y) & F_BLACK)
+                c = '#';
+            else {
+                c = (GRID(state, flags, x, y) & F_LIGHT) ? 'A' : 'a';
+                c += GRID(state, lights, x, y);
+            }
+            debug(("%c", (int)c));
+        }
+        debug(("\n"));
+    }
+}
+
+/* --- Game completion test routines. --- */
+
+/* These are split up because occasionally functions are only
+ * interested in one particular aspect. */
+
+/* Returns non-zero if all grid spaces are lit. */
+static int grid_lit(game_state *state)
+{
+    int x, y;
+
+    for (x = 0; x < state->w; x++) {
+        for (y = 0; y < state->h; y++) {
+            if (GRID(state,flags,x,y) & F_BLACK) continue;
+            if (GRID(state,lights,x,y) == 0)
+                return 0;
+        }
+    }
+    return 1;
+}
+
+/* Returns non-zero if any lights are lit by other lights. */
+static int grid_overlap(game_state *state)
+{
+    int x, y;
+
+    for (x = 0; x < state->w; x++) {
+        for (y = 0; y < state->h; y++) {
+            if (!(GRID(state, flags, x, y) & F_LIGHT)) continue;
+            if (GRID(state, lights, x, y) > 1)
+                return 1;
+        }
+    }
+    return 0;
+}
+
+static int number_wrong(const game_state *state, int x, int y)
+{
+    surrounds s;
+    int i, n, empty, lights = GRID(state, lights, x, y);
+
+    /*
+     * This function computes the display hint for a number: we
+     * turn the number red if it is definitely wrong. This means
+     * that either
+     * 
+     *  (a) it has too many lights around it, or
+     *         (b) it would have too few lights around it even if all the
+     *             plausible squares (not black, lit or F_IMPOSSIBLE) were
+     *             filled with lights.
+     */
+
+    assert(GRID(state, flags, x, y) & F_NUMBERED);
+    get_surrounds(state, x, y, &s);
+
+    empty = n = 0;
+    for (i = 0; i < s.npoints; i++) {
+       if (GRID(state,flags,s.points[i].x,s.points[i].y) & F_LIGHT) {
+           n++;
+           continue;
+       }
+       if (GRID(state,flags,s.points[i].x,s.points[i].y) & F_BLACK)
+           continue;
+       if (GRID(state,flags,s.points[i].x,s.points[i].y) & F_IMPOSSIBLE)
+           continue;
+       if (GRID(state,lights,s.points[i].x,s.points[i].y))
+           continue;
+       empty++;
+    }
+    return (n > lights || (n + empty < lights));
+}
+
+static int number_correct(game_state *state, int x, int y)
+{
+    surrounds s;
+    int n = 0, i, lights = GRID(state, lights, x, y);
+
+    assert(GRID(state, flags, x, y) & F_NUMBERED);
+    get_surrounds(state, x, y, &s);
+    for (i = 0; i < s.npoints; i++) {
+        if (GRID(state,flags,s.points[i].x,s.points[i].y) & F_LIGHT)
+            n++;
+    }
+    return (n == lights) ? 1 : 0;
+}
+
+/* Returns non-zero if any numbers add up incorrectly. */
+static int grid_addsup(game_state *state)
+{
+    int x, y;
+
+    for (x = 0; x < state->w; x++) {
+        for (y = 0; y < state->h; y++) {
+            if (!(GRID(state, flags, x, y) & F_NUMBERED)) continue;
+            if (!number_correct(state, x, y)) return 0;
+        }
+    }
+    return 1;
+}
+
+static int grid_correct(game_state *state)
+{
+    if (grid_lit(state) &&
+        !grid_overlap(state) &&
+        grid_addsup(state)) return 1;
+    return 0;
+}
+
+/* --- Board initial setup (blacks, lights, numbers) --- */
+
+static void clean_board(game_state *state, int leave_blacks)
+{
+    int x,y;
+    for (x = 0; x < state->w; x++) {
+        for (y = 0; y < state->h; y++) {
+            if (leave_blacks)
+                GRID(state, flags, x, y) &= F_BLACK;
+            else
+                GRID(state, flags, x, y) = 0;
+            GRID(state, lights, x, y) = 0;
+        }
+    }
+    state->nlights = 0;
+}
+
+static void set_blacks(game_state *state, const game_params *params,
+                       random_state *rs)
+{
+    int x, y, degree = 0, rotate = 0, nblack;
+    int rh, rw, i;
+    int wodd = (state->w % 2) ? 1 : 0;
+    int hodd = (state->h % 2) ? 1 : 0;
+    int xs[4], ys[4];
+
+    switch (params->symm) {
+    case SYMM_NONE: degree = 1; rotate = 0; break;
+    case SYMM_ROT2: degree = 2; rotate = 1; break;
+    case SYMM_REF2: degree = 2; rotate = 0; break;
+    case SYMM_ROT4: degree = 4; rotate = 1; break;
+    case SYMM_REF4: degree = 4; rotate = 0; break;
+    default: assert(!"Unknown symmetry type");
+    }
+    if (params->symm == SYMM_ROT4 && (state->h != state->w))
+        assert(!"4-fold symmetry unavailable without square grid");
+
+    if (degree == 4) {
+        rw = state->w/2;
+        rh = state->h/2;
+        if (!rotate) rw += wodd; /* ... but see below. */
+        rh += hodd;
+    } else if (degree == 2) {
+        rw = state->w;
+        rh = state->h/2;
+        rh += hodd;
+    } else {
+        rw = state->w;
+        rh = state->h;
+    }
+
+    /* clear, then randomise, required region. */
+    clean_board(state, 0);
+    nblack = (rw * rh * params->blackpc) / 100;
+    for (i = 0; i < nblack; i++) {
+        do {
+            x = random_upto(rs,rw);
+            y = random_upto(rs,rh);
+        } while (GRID(state,flags,x,y) & F_BLACK);
+        GRID(state, flags, x, y) |= F_BLACK;
+    }
+
+    /* Copy required region. */
+    if (params->symm == SYMM_NONE) return;
+
+    for (x = 0; x < rw; x++) {
+        for (y = 0; y < rh; y++) {
+            if (degree == 4) {
+                xs[0] = x;
+                ys[0] = y;
+                xs[1] = state->w - 1 - (rotate ? y : x);
+                ys[1] = rotate ? x : y;
+                xs[2] = rotate ? (state->w - 1 - x) : x;
+                ys[2] = state->h - 1 - y;
+                xs[3] = rotate ? y : (state->w - 1 - x);
+                ys[3] = state->h - 1 - (rotate ? x : y);
+            } else {
+                xs[0] = x;
+                ys[0] = y;
+                xs[1] = rotate ? (state->w - 1 - x) : x;
+                ys[1] = state->h - 1 - y;
+            }
+            for (i = 1; i < degree; i++) {
+                GRID(state, flags, xs[i], ys[i]) =
+                    GRID(state, flags, xs[0], ys[0]);
+            }
+        }
+    }
+    /* SYMM_ROT4 misses the middle square above; fix that here. */
+    if (degree == 4 && rotate && wodd &&
+        (random_upto(rs,100) <= (unsigned int)params->blackpc))
+        GRID(state,flags,
+             state->w/2 + wodd - 1, state->h/2 + hodd - 1) |= F_BLACK;
+
+#ifdef SOLVER_DIAGNOSTICS
+    if (verbose) debug_state(state);
+#endif
+}
+
+/* Fills in (does not allocate) a ll_data with all the tiles that would
+ * be illuminated by a light at point (ox,oy). If origin=1 then the
+ * origin is included in this list. */
+static void list_lights(game_state *state, int ox, int oy, int origin,
+                        ll_data *lld)
+{
+    int x,y;
+
+    lld->ox = lld->minx = lld->maxx = ox;
+    lld->oy = lld->miny = lld->maxy = oy;
+    lld->include_origin = origin;
+
+    y = oy;
+    for (x = ox-1; x >= 0; x--) {
+        if (GRID(state, flags, x, y) & F_BLACK) break;
+        if (x < lld->minx) lld->minx = x;
+    }
+    for (x = ox+1; x < state->w; x++) {
+        if (GRID(state, flags, x, y) & F_BLACK) break;
+        if (x > lld->maxx) lld->maxx = x;
+    }
+
+    x = ox;
+    for (y = oy-1; y >= 0; y--) {
+        if (GRID(state, flags, x, y) & F_BLACK) break;
+        if (y < lld->miny) lld->miny = y;
+    }
+    for (y = oy+1; y < state->h; y++) {
+        if (GRID(state, flags, x, y) & F_BLACK) break;
+        if (y > lld->maxy) lld->maxy = y;
+    }
+}
+
+/* Makes sure a light is the given state, editing the lights table to suit the
+ * new state if necessary. */
+static void set_light(game_state *state, int ox, int oy, int on)
+{
+    ll_data lld;
+    int diff = 0;
+
+    assert(!(GRID(state,flags,ox,oy) & F_BLACK));
+
+    if (!on && GRID(state,flags,ox,oy) & F_LIGHT) {
+        diff = -1;
+        GRID(state,flags,ox,oy) &= ~F_LIGHT;
+        state->nlights--;
+    } else if (on && !(GRID(state,flags,ox,oy) & F_LIGHT)) {
+        diff = 1;
+        GRID(state,flags,ox,oy) |= F_LIGHT;
+        state->nlights++;
+    }
+
+    if (diff != 0) {
+        list_lights(state,ox,oy,1,&lld);
+        FOREACHLIT(&lld, GRID(state,lights,lx,ly) += diff; );
+    }
+}
+
+/* Returns 1 if removing a light at (x,y) would cause a square to go dark. */
+static int check_dark(game_state *state, int x, int y)
+{
+    ll_data lld;
+
+    list_lights(state, x, y, 1, &lld);
+    FOREACHLIT(&lld, if (GRID(state,lights,lx,ly) == 1) { return 1; } );
+    return 0;
+}
+
+/* Sets up an initial random correct position (i.e. every
+ * space lit, and no lights lit by other lights) by filling the
+ * grid with lights and then removing lights one by one at random. */
+static void place_lights(game_state *state, random_state *rs)
+{
+    int i, x, y, n, *numindices, wh = state->w*state->h;
+    ll_data lld;
+
+    numindices = snewn(wh, int);
+    for (i = 0; i < wh; i++) numindices[i] = i;
+    shuffle(numindices, wh, sizeof(*numindices), rs);
+
+    /* Place a light on all grid squares without lights. */
+    for (x = 0; x < state->w; x++) {
+        for (y = 0; y < state->h; y++) {
+            GRID(state, flags, x, y) &= ~F_MARK; /* we use this later. */
+            if (GRID(state, flags, x, y) & F_BLACK) continue;
+            set_light(state, x, y, 1);
+        }
+    }
+
+    for (i = 0; i < wh; i++) {
+        y = numindices[i] / state->w;
+        x = numindices[i] % state->w;
+        if (!(GRID(state, flags, x, y) & F_LIGHT)) continue;
+        if (GRID(state, flags, x, y) & F_MARK) continue;
+        list_lights(state, x, y, 0, &lld);
+
+        /* If we're not lighting any lights ourself, don't remove anything. */
+        n = 0;
+        FOREACHLIT(&lld, if (GRID(state,flags,lx,ly) & F_LIGHT) { n += 1; } );
+        if (n == 0) continue; /* [1] */
+
+        /* Check whether removing lights we're lighting would cause anything
+         * to go dark. */
+        n = 0;
+        FOREACHLIT(&lld, if (GRID(state,flags,lx,ly) & F_LIGHT) { n += check_dark(state,lx,ly); } );
+        if (n == 0) {
+            /* No, it wouldn't, so we can remove them all. */
+            FOREACHLIT(&lld, set_light(state,lx,ly, 0); );
+            GRID(state,flags,x,y) |= F_MARK;
+        }
+
+        if (!grid_overlap(state)) {
+            sfree(numindices);
+            return; /* we're done. */
+        }
+        assert(grid_lit(state));
+    }
+    /* could get here if the line at [1] continue'd out of the loop. */
+    if (grid_overlap(state)) {
+        debug_state(state);
+        assert(!"place_lights failed to resolve overlapping lights!");
+    }
+    sfree(numindices);
+}
+
+/* Fills in all black squares with numbers of adjacent lights. */
+static void place_numbers(game_state *state)
+{
+    int x, y, i, n;
+    surrounds s;
+
+    for (x = 0; x < state->w; x++) {
+        for (y = 0; y < state->h; y++) {
+            if (!(GRID(state,flags,x,y) & F_BLACK)) continue;
+            get_surrounds(state, x, y, &s);
+            n = 0;
+            for (i = 0; i < s.npoints; i++) {
+                if (GRID(state,flags,s.points[i].x, s.points[i].y) & F_LIGHT)
+                    n++;
+            }
+            GRID(state,flags,x,y) |= F_NUMBERED;
+            GRID(state,lights,x,y) = n;
+        }
+    }
+}
+
+/* --- Actual solver, with helper subroutines. --- */
+
+static void tsl_callback(game_state *state,
+                         int lx, int ly, int *x, int *y, int *n)
+{
+    if (GRID(state,flags,lx,ly) & F_IMPOSSIBLE) return;
+    if (GRID(state,lights,lx,ly) > 0) return;
+    *x = lx; *y = ly; (*n)++;
+}
+
+static int try_solve_light(game_state *state, int ox, int oy,
+                           unsigned int flags, int lights)
+{
+    ll_data lld;
+    int sx = 0, sy = 0, n = 0;
+
+    if (lights > 0) return 0;
+    if (flags & F_BLACK) return 0;
+
+    /* We have an unlit square; count how many ways there are left to
+     * place a light that lights us (including this square); if only
+     * one, we must put a light there. Squares that could light us
+     * are, of course, the same as the squares we would light... */
+    list_lights(state, ox, oy, 1, &lld);
+    FOREACHLIT(&lld, { tsl_callback(state, lx, ly, &sx, &sy, &n); });
+    if (n == 1) {
+        set_light(state, sx, sy, 1);
+#ifdef SOLVER_DIAGNOSTICS
+        debug(("(%d,%d) can only be lit from (%d,%d); setting to LIGHT\n",
+                ox,oy,sx,sy));
+        if (verbose) debug_state(state);
+#endif
+        return 1;
+    }
+
+    return 0;
+}
+
+static int could_place_light(unsigned int flags, int lights)
+{
+    if (flags & (F_BLACK | F_IMPOSSIBLE)) return 0;
+    return (lights > 0) ? 0 : 1;
+}
+
+static int could_place_light_xy(game_state *state, int x, int y)
+{
+    int lights = GRID(state,lights,x,y);
+    unsigned int flags = GRID(state,flags,x,y);
+    return (could_place_light(flags, lights)) ? 1 : 0;
+}
+
+/* For a given number square, determine whether we have enough info
+ * to unambiguously place its lights. */
+static int try_solve_number(game_state *state, int nx, int ny,
+                            unsigned int nflags, int nlights)
+{
+    surrounds s;
+    int x, y, nl, ns, i, ret = 0, lights;
+    unsigned int flags;
+
+    if (!(nflags & F_NUMBERED)) return 0;
+    nl = nlights;
+    get_surrounds(state,nx,ny,&s);
+    ns = s.npoints;
+
+    /* nl is no. of lights we need to place, ns is no. of spaces we
+     * have to place them in. Try and narrow these down, and mark
+     * points we can ignore later. */
+    for (i = 0; i < s.npoints; i++) {
+        x = s.points[i].x; y = s.points[i].y;
+        flags = GRID(state,flags,x,y);
+        lights = GRID(state,lights,x,y);
+        if (flags & F_LIGHT) {
+            /* light here already; one less light for one less place. */
+            nl--; ns--;
+            s.points[i].f |= F_MARK;
+        } else if (!could_place_light(flags, lights)) {
+            ns--;
+            s.points[i].f |= F_MARK;
+        }
+    }
+    if (ns == 0) return 0; /* nowhere to put anything. */
+    if (nl == 0) {
+        /* we have placed all lights we need to around here; all remaining
+         * surrounds are therefore IMPOSSIBLE. */
+        GRID(state,flags,nx,ny) |= F_NUMBERUSED;
+        for (i = 0; i < s.npoints; i++) {
+            if (!(s.points[i].f & F_MARK)) {
+                GRID(state,flags,s.points[i].x,s.points[i].y) |= F_IMPOSSIBLE;
+                ret = 1;
+            }
+        }
+#ifdef SOLVER_DIAGNOSTICS
+        printf("Clue at (%d,%d) full; setting unlit to IMPOSSIBLE.\n",
+               nx,ny);
+        if (verbose) debug_state(state);
+#endif
+    } else if (nl == ns) {
+        /* we have as many lights to place as spaces; fill them all. */
+        GRID(state,flags,nx,ny) |= F_NUMBERUSED;
+        for (i = 0; i < s.npoints; i++) {
+            if (!(s.points[i].f & F_MARK)) {
+                set_light(state, s.points[i].x,s.points[i].y, 1);
+                ret = 1;
+            }
+        }
+#ifdef SOLVER_DIAGNOSTICS
+        printf("Clue at (%d,%d) trivial; setting unlit to LIGHT.\n",
+               nx,ny);
+        if (verbose) debug_state(state);
+#endif
+    }
+    return ret;
+}
+
+struct setscratch {
+    int x, y;
+    int n;
+};
+
+#define SCRATCHSZ (state->w+state->h)
+
+/* New solver algorithm: overlapping sets can add IMPOSSIBLE flags.
+ * Algorithm thanks to Simon:
+ *
+ * (a) Any square where you can place a light has a set of squares
+ *     which would become non-lights as a result. (This includes
+ *     squares lit by the first square, and can also include squares
+ *     adjacent to the same clue square if the new light is the last
+ *     one around that clue.) Call this MAKESDARK(x,y) with (x,y) being
+ *     the square you place a light.
+
+ * (b) Any unlit square has a set of squares on which you could place
+ *     a light to illuminate it. (Possibly including itself, of
+ *     course.) This set of squares has the property that _at least
+ *     one_ of them must contain a light. Sets of this type also arise
+ *     from clue squares. Call this MAKESLIGHT(x,y), again with (x,y)
+ *     the square you would place a light.
+
+ * (c) If there exists (dx,dy) and (lx,ly) such that MAKESDARK(dx,dy) is
+ *     a superset of MAKESLIGHT(lx,ly), this implies that placing a light at
+ *     (dx,dy) would either leave no remaining way to illuminate a certain
+ *     square, or would leave no remaining way to fulfill a certain clue
+ *     (at lx,ly). In either case, a light can be ruled out at that position.
+ *
+ * So, we construct all possible MAKESLIGHT sets, both from unlit squares
+ * and clue squares, and then we look for plausible MAKESDARK sets that include
+ * our (lx,ly) to see if we can find a (dx,dy) to rule out. By the time we have
+ * constructed the MAKESLIGHT set we don't care about (lx,ly), just the set
+ * members.
+ *
+ * Once we have such a set, Simon came up with a Cunning Plan to find
+ * the most sensible MAKESDARK candidate:
+ *
+ * (a) for each square S in your set X, find all the squares which _would_
+ *     rule it out. That means any square which would light S, plus
+ *     any square adjacent to the same clue square as S (provided
+ *     that clue square has only one remaining light to be placed).
+ *     It's not hard to make this list. Don't do anything with this
+ *     data at the moment except _count_ the squares.
+
+ * (b) Find the square S_min in the original set which has the
+ *     _smallest_ number of other squares which would rule it out.
+
+ * (c) Find all the squares that rule out S_min (it's probably
+ *     better to recompute this than to have stored it during step
+ *     (a), since the CPU requirement is modest but the storage
+ *     cost would get ugly.) For each of these squares, see if it
+ *     rules out everything else in the set X. Any which does can
+ *     be marked as not-a-light.
+ *
+ */
+
+typedef void (*trl_cb)(game_state *state, int dx, int dy,
+                       struct setscratch *scratch, int n, void *ctx);
+
+static void try_rule_out(game_state *state, int x, int y,
+                         struct setscratch *scratch, int n,
+                         trl_cb cb, void *ctx);
+
+static void trl_callback_search(game_state *state, int dx, int dy,
+                       struct setscratch *scratch, int n, void *ignored)
+{
+    int i;
+
+#ifdef SOLVER_DIAGNOSTICS
+    if (verbose) debug(("discount cb: light at (%d,%d)\n", dx, dy));
+#endif
+
+    for (i = 0; i < n; i++) {
+        if (dx == scratch[i].x && dy == scratch[i].y) {
+            scratch[i].n = 1;
+            return;
+        }
+    }
+}
+
+static void trl_callback_discount(game_state *state, int dx, int dy,
+                       struct setscratch *scratch, int n, void *ctx)
+{
+    int *didsth = (int *)ctx;
+    int i;
+
+    if (GRID(state,flags,dx,dy) & F_IMPOSSIBLE) {
+#ifdef SOLVER_DIAGNOSTICS
+        debug(("Square at (%d,%d) already impossible.\n", dx,dy));
+#endif
+        return;
+    }
+
+    /* Check whether a light at (dx,dy) rules out everything
+     * in scratch, and mark (dx,dy) as IMPOSSIBLE if it does.
+     * We can use try_rule_out for this as well, as the set of
+     * squares which would rule out (x,y) is the same as the
+     * set of squares which (x,y) would rule out. */
+
+#ifdef SOLVER_DIAGNOSTICS
+    if (verbose) debug(("Checking whether light at (%d,%d) rules out everything in scratch.\n", dx, dy));
+#endif
+
+    for (i = 0; i < n; i++)
+        scratch[i].n = 0;
+    try_rule_out(state, dx, dy, scratch, n, trl_callback_search, NULL);
+    for (i = 0; i < n; i++) {
+        if (scratch[i].n == 0) return;
+    }
+    /* The light ruled out everything in scratch. Yay. */
+    GRID(state,flags,dx,dy) |= F_IMPOSSIBLE;
+#ifdef SOLVER_DIAGNOSTICS
+    debug(("Set reduction discounted square at (%d,%d):\n", dx,dy));
+    if (verbose) debug_state(state);
+#endif
+
+    *didsth = 1;
+}
+
+static void trl_callback_incn(game_state *state, int dx, int dy,
+                       struct setscratch *scratch, int n, void *ctx)
+{
+    struct setscratch *s = (struct setscratch *)ctx;
+    s->n++;
+}
+
+static void try_rule_out(game_state *state, int x, int y,
+                         struct setscratch *scratch, int n,
+                         trl_cb cb, void *ctx)
+{
+    /* XXX Find all the squares which would rule out (x,y); anything
+     * that would light it as well as squares adjacent to same clues
+     * as X assuming that clue only has one remaining light.
+     * Call the callback with each square. */
+    ll_data lld;
+    surrounds s, ss;
+    int i, j, curr_lights, tot_lights;
+
+    /* Find all squares that would rule out a light at (x,y) and call trl_cb
+     * with them: anything that would light (x,y)... */
+
+    list_lights(state, x, y, 0, &lld);
+    FOREACHLIT(&lld, { if (could_place_light_xy(state, lx, ly)) { cb(state, lx, ly, scratch, n, ctx); } });
+
+    /* ... as well as any empty space (that isn't x,y) next to any clue square
+     * next to (x,y) that only has one light left to place. */
+
+    get_surrounds(state, x, y, &s);
+    for (i = 0; i < s.npoints; i++) {
+        if (!(GRID(state,flags,s.points[i].x,s.points[i].y) & F_NUMBERED))
+            continue;
+        /* we have an adjacent clue square; find /its/ surrounds
+         * and count the remaining lights it needs. */
+        get_surrounds(state,s.points[i].x,s.points[i].y,&ss);
+        curr_lights = 0;
+        for (j = 0; j < ss.npoints; j++) {
+            if (GRID(state,flags,ss.points[j].x,ss.points[j].y) & F_LIGHT)
+                curr_lights++;
+        }
+        tot_lights = GRID(state, lights, s.points[i].x, s.points[i].y);
+        /* We have a clue with tot_lights to fill, and curr_lights currently
+         * around it. If adding a light at (x,y) fills up the clue (i.e.
+         * curr_lights + 1 = tot_lights) then we need to discount all other
+         * unlit squares around the clue. */
+        if ((curr_lights + 1) == tot_lights) {
+            for (j = 0; j < ss.npoints; j++) {
+                int lx = ss.points[j].x, ly = ss.points[j].y;
+                if (lx == x && ly == y) continue;
+                if (could_place_light_xy(state, lx, ly))
+                    cb(state, lx, ly, scratch, n, ctx);
+            }
+        }
+    }
+}
+
+#ifdef SOLVER_DIAGNOSTICS
+static void debug_scratch(const char *msg, struct setscratch *scratch, int n)
+{
+    int i;
+    debug(("%s scratch (%d elements):\n", msg, n));
+    for (i = 0; i < n; i++) {
+        debug(("  (%d,%d) n%d\n", scratch[i].x, scratch[i].y, scratch[i].n));
+    }
+}
+#endif
+
+static int discount_set(game_state *state,
+                        struct setscratch *scratch, int n)
+{
+    int i, besti, bestn, didsth = 0;
+
+#ifdef SOLVER_DIAGNOSTICS
+    if (verbose > 1) debug_scratch("discount_set", scratch, n);
+#endif
+    if (n == 0) return 0;
+
+    for (i = 0; i < n; i++) {
+        try_rule_out(state, scratch[i].x, scratch[i].y, scratch, n,
+                     trl_callback_incn, (void*)&(scratch[i]));
+    }
+#ifdef SOLVER_DIAGNOSTICS
+    if (verbose > 1) debug_scratch("discount_set after count", scratch, n);
+#endif
+
+    besti = -1; bestn = SCRATCHSZ;
+    for (i = 0; i < n; i++) {
+        if (scratch[i].n < bestn) {
+            bestn = scratch[i].n;
+            besti = i;
+        }
+    }
+#ifdef SOLVER_DIAGNOSTICS
+    if (verbose > 1) debug(("best square (%d,%d) with n%d.\n",
+           scratch[besti].x, scratch[besti].y, scratch[besti].n));
+#endif
+    try_rule_out(state, scratch[besti].x, scratch[besti].y, scratch, n,
+                 trl_callback_discount, (void*)&didsth);
+#ifdef SOLVER_DIAGNOSTICS
+    if (didsth) debug((" [from square (%d,%d)]\n",
+                       scratch[besti].x, scratch[besti].y));
+#endif
+
+    return didsth;
+}
+
+static void discount_clear(game_state *state, struct setscratch *scratch, int *n)
+{
+    *n = 0;
+    memset(scratch, 0, SCRATCHSZ * sizeof(struct setscratch));
+}
+
+static void unlit_cb(game_state *state, int lx, int ly,
+                     struct setscratch *scratch, int *n)
+{
+    if (could_place_light_xy(state, lx, ly)) {
+        scratch[*n].x = lx; scratch[*n].y = ly; (*n)++;
+    }
+}
+
+/* Construct a MAKESLIGHT set from an unlit square. */
+static int discount_unlit(game_state *state, int x, int y,
+                          struct setscratch *scratch)
+{
+    ll_data lld;
+    int n, didsth;
+
+#ifdef SOLVER_DIAGNOSTICS
+    if (verbose) debug(("Trying to discount for unlit square at (%d,%d).\n", x, y));
+    if (verbose > 1) debug_state(state);
+#endif
+
+    discount_clear(state, scratch, &n);
+
+    list_lights(state, x, y, 1, &lld);
+    FOREACHLIT(&lld, { unlit_cb(state, lx, ly, scratch, &n); });
+    didsth = discount_set(state, scratch, n);
+#ifdef SOLVER_DIAGNOSTICS
+    if (didsth) debug(("  [from unlit square at (%d,%d)].\n", x, y));
+#endif
+    return didsth;
+
+}
+
+/* Construct a series of MAKESLIGHT sets from a clue square.
+ *  for a clue square with N remaining spaces that must contain M lights, every
+ *  subset of size N-M+1 of those N spaces forms such a set.
+ */
+
+static int discount_clue(game_state *state, int x, int y,
+                          struct setscratch *scratch)
+{
+    int slen, m = GRID(state, lights, x, y), n, i, didsth = 0, lights;
+    unsigned int flags;
+    surrounds s, sempty;
+    combi_ctx *combi;
+
+    if (m == 0) return 0;
+
+#ifdef SOLVER_DIAGNOSTICS
+    if (verbose) debug(("Trying to discount for sets at clue (%d,%d).\n", x, y));
+    if (verbose > 1) debug_state(state);
+#endif
+
+    /* m is no. of lights still to place; starts off at the clue value
+     * and decreases when we find a light already down.
+     * n is no. of spaces left; starts off at 0 and goes up when we find
+     * a plausible space. */
+
+    get_surrounds(state, x, y, &s);
+    memset(&sempty, 0, sizeof(surrounds));
+    for (i = 0; i < s.npoints; i++) {
+        int lx = s.points[i].x, ly = s.points[i].y;
+        flags = GRID(state,flags,lx,ly);
+        lights = GRID(state,lights,lx,ly);
+
+        if (flags & F_LIGHT) m--;
+
+        if (could_place_light(flags, lights)) {
+            sempty.points[sempty.npoints].x = lx;
+            sempty.points[sempty.npoints].y = ly;
+            sempty.npoints++;
+        }
+    }
+    n = sempty.npoints; /* sempty is now a surrounds of only blank squares. */
+    if (n == 0) return 0; /* clue is full already. */
+
+    if (m < 0 || m > n) return 0; /* become impossible. */
+
+    combi = new_combi(n - m + 1, n);
+    while (next_combi(combi)) {
+        discount_clear(state, scratch, &slen);
+        for (i = 0; i < combi->r; i++) {
+            scratch[slen].x = sempty.points[combi->a[i]].x;
+            scratch[slen].y = sempty.points[combi->a[i]].y;
+            slen++;
+        }
+        if (discount_set(state, scratch, slen)) didsth = 1;
+    }
+    free_combi(combi);
+#ifdef SOLVER_DIAGNOSTICS
+    if (didsth) debug(("  [from clue at (%d,%d)].\n", x, y));
+#endif
+    return didsth;
+}
+
+#define F_SOLVE_FORCEUNIQUE     1
+#define F_SOLVE_DISCOUNTSETS    2
+#define F_SOLVE_ALLOWRECURSE    4
+
+static unsigned int flags_from_difficulty(int difficulty)
+{
+    unsigned int sflags = F_SOLVE_FORCEUNIQUE;
+    assert(difficulty <= DIFFCOUNT);
+    if (difficulty >= 1) sflags |= F_SOLVE_DISCOUNTSETS;
+    if (difficulty >= 2) sflags |= F_SOLVE_ALLOWRECURSE;
+    return sflags;
+}
+
+#define MAXRECURSE 5
+
+static int solve_sub(game_state *state,
+                     unsigned int solve_flags, int depth,
+                     int *maxdepth)
+{
+    unsigned int flags;
+    int x, y, didstuff, ncanplace, lights;
+    int bestx, besty, n, bestn, copy_soluble, self_soluble, ret, maxrecurse = 0;
+    game_state *scopy;
+    ll_data lld;
+    struct setscratch *sscratch = NULL;
+
+#ifdef SOLVER_DIAGNOSTICS
+    printf("solve_sub: depth = %d\n", depth);
+#endif
+    if (maxdepth && *maxdepth < depth) *maxdepth = depth;
+    if (solve_flags & F_SOLVE_ALLOWRECURSE) maxrecurse = MAXRECURSE;
+
+    while (1) {
+        if (grid_overlap(state)) {
+            /* Our own solver, from scratch, should never cause this to happen
+             * (assuming a soluble grid). However, if we're trying to solve
+             * from a half-completed *incorrect* grid this might occur; we
+             * just return the 'no solutions' code in this case. */
+            ret = 0; goto done;
+        }
+
+        if (grid_correct(state)) { ret = 1; goto done; }
+
+        ncanplace = 0;
+        didstuff = 0;
+        /* These 2 loops, and the functions they call, are the critical loops
+         * for timing; any optimisations should look here first. */
+        for (x = 0; x < state->w; x++) {
+            for (y = 0; y < state->h; y++) {
+                flags = GRID(state,flags,x,y);
+                lights = GRID(state,lights,x,y);
+                ncanplace += could_place_light(flags, lights);
+
+                if (try_solve_light(state, x, y, flags, lights)) didstuff = 1;
+                if (try_solve_number(state, x, y, flags, lights)) didstuff = 1;
+            }
+        }
+        if (didstuff) continue;
+        if (!ncanplace) {
+            /* nowhere to put a light, puzzle is unsoluble. */
+            ret = 0; goto done;
+        }
+
+        if (solve_flags & F_SOLVE_DISCOUNTSETS) {
+            if (!sscratch) sscratch = snewn(SCRATCHSZ, struct setscratch);
+            /* Try a more cunning (and more involved) way... more details above. */
+            for (x = 0; x < state->w; x++) {
+                for (y = 0; y < state->h; y++) {
+                    flags = GRID(state,flags,x,y);
+                    lights = GRID(state,lights,x,y);
+
+                    if (!(flags & F_BLACK) && lights == 0) {
+                        if (discount_unlit(state, x, y, sscratch)) {
+                            didstuff = 1;
+                            goto reduction_success;
+                        }
+                    } else if (flags & F_NUMBERED) {
+                        if (discount_clue(state, x, y, sscratch)) {
+                            didstuff = 1;
+                            goto reduction_success;
+                        }
+                    }
+                }
+            }
+        }
+reduction_success:
+        if (didstuff) continue;
+
+        /* We now have to make a guess; we have places to put lights but
+         * no definite idea about where they can go. */
+        if (depth >= maxrecurse) {
+            /* mustn't delve any deeper. */
+            ret = -1; goto done;
+        }
+        /* Of all the squares that we could place a light, pick the one
+         * that would light the most currently unlit squares. */
+        /* This heuristic was just plucked from the air; there may well be
+         * a more efficient way of choosing a square to flip to minimise
+         * recursion. */
+        bestn = 0;
+        bestx = besty = -1; /* suyb */
+        for (x = 0; x < state->w; x++) {
+            for (y = 0; y < state->h; y++) {
+                flags = GRID(state,flags,x,y);
+                lights = GRID(state,lights,x,y);
+                if (!could_place_light(flags, lights)) continue;
+
+                n = 0;
+                list_lights(state, x, y, 1, &lld);
+                FOREACHLIT(&lld, { if (GRID(state,lights,lx,ly) == 0) n++; });
+                if (n > bestn) {
+                    bestn = n; bestx = x; besty = y;
+                }
+            }
+        }
+        assert(bestn > 0);
+       assert(bestx >= 0 && besty >= 0);
+
+        /* Now we've chosen a plausible (x,y), try to solve it once as 'lit'
+         * and once as 'impossible'; we need to make one copy to do this. */
+
+        scopy = dup_game(state);
+#ifdef SOLVER_DIAGNOSTICS
+        debug(("Recursing #1: trying (%d,%d) as IMPOSSIBLE\n", bestx, besty));
+#endif
+        GRID(state,flags,bestx,besty) |= F_IMPOSSIBLE;
+        self_soluble = solve_sub(state, solve_flags,  depth+1, maxdepth);
+
+        if (!(solve_flags & F_SOLVE_FORCEUNIQUE) && self_soluble > 0) {
+            /* we didn't care about finding all solutions, and we just
+             * found one; return with it immediately. */
+            free_game(scopy);
+            ret = self_soluble;
+            goto done;
+        }
+
+#ifdef SOLVER_DIAGNOSTICS
+        debug(("Recursing #2: trying (%d,%d) as LIGHT\n", bestx, besty));
+#endif
+        set_light(scopy, bestx, besty, 1);
+        copy_soluble = solve_sub(scopy, solve_flags, depth+1, maxdepth);
+
+        /* If we wanted a unique solution but we hit our recursion limit
+         * (on either branch) then we have to assume we didn't find possible
+         * extra solutions, and return 'not soluble'. */
+        if ((solve_flags & F_SOLVE_FORCEUNIQUE) &&
+            ((copy_soluble < 0) || (self_soluble < 0))) {
+            ret = -1;
+        /* Make sure that whether or not it was self or copy (or both) that
+         * were soluble, that we return a solved state in self. */
+        } else if (copy_soluble <= 0) {
+            /* copy wasn't soluble; keep self state and return that result. */
+            ret = self_soluble;
+        } else if (self_soluble <= 0) {
+            /* copy solved and we didn't, so copy in copy's (now solved)
+             * flags and light state. */
+            memcpy(state->lights, scopy->lights,
+                   scopy->w * scopy->h * sizeof(int));
+            memcpy(state->flags, scopy->flags,
+                   scopy->w * scopy->h * sizeof(unsigned int));
+            ret = copy_soluble;
+        } else {
+            ret = copy_soluble + self_soluble;
+        }
+        free_game(scopy);
+        goto done;
+    }
+done:
+    if (sscratch) sfree(sscratch);
+#ifdef SOLVER_DIAGNOSTICS
+    if (ret < 0)
+        debug(("solve_sub: depth = %d returning, ran out of recursion.\n",
+               depth));
+    else
+        debug(("solve_sub: depth = %d returning, %d solutions.\n",
+               depth, ret));
+#endif
+    return ret;
+}
+
+/* Fills in the (possibly partially-complete) game_state as far as it can,
+ * returning the number of possible solutions. If it returns >0 then the
+ * game_state will be in a solved state, but you won't know which one. */
+static int dosolve(game_state *state, int solve_flags, int *maxdepth)
+{
+    int x, y, nsol;
+
+    for (x = 0; x < state->w; x++) {
+        for (y = 0; y < state->h; y++) {
+            GRID(state,flags,x,y) &= ~F_NUMBERUSED;
+        }
+    }
+    nsol = solve_sub(state, solve_flags, 0, maxdepth);
+    return nsol;
+}
+
+static int strip_unused_nums(game_state *state)
+{
+    int x,y,n=0;
+    for (x = 0; x < state->w; x++) {
+        for (y = 0; y < state->h; y++) {
+            if ((GRID(state,flags,x,y) & F_NUMBERED) &&
+                !(GRID(state,flags,x,y) & F_NUMBERUSED)) {
+                GRID(state,flags,x,y) &= ~F_NUMBERED;
+                GRID(state,lights,x,y) = 0;
+                n++;
+            }
+        }
+    }
+    debug(("Stripped %d unused numbers.\n", n));
+    return n;
+}
+
+static void unplace_lights(game_state *state)
+{
+    int x,y;
+    for (x = 0; x < state->w; x++) {
+        for (y = 0; y < state->h; y++) {
+            if (GRID(state,flags,x,y) & F_LIGHT)
+                set_light(state,x,y,0);
+            GRID(state,flags,x,y) &= ~F_IMPOSSIBLE;
+            GRID(state,flags,x,y) &= ~F_NUMBERUSED;
+        }
+    }
+}
+
+static int puzzle_is_good(game_state *state, int difficulty)
+{
+    int nsol, mdepth = 0;
+    unsigned int sflags = flags_from_difficulty(difficulty);
+
+    unplace_lights(state);
+
+#ifdef SOLVER_DIAGNOSTICS
+    debug(("Trying to solve with difficulty %d (0x%x):\n",
+           difficulty, sflags));
+    if (verbose) debug_state(state);
+#endif
+
+    nsol = dosolve(state, sflags, &mdepth);
+    /* if we wanted an easy puzzle, make sure we didn't need recursion. */
+    if (!(sflags & F_SOLVE_ALLOWRECURSE) && mdepth > 0) {
+        debug(("Ignoring recursive puzzle.\n"));
+        return 0;
+    }
+
+    debug(("%d solutions found.\n", nsol));
+    if (nsol <= 0) return 0;
+    if (nsol > 1) return 0;
+    return 1;
+}
+
+/* --- New game creation and user input code. --- */
+
+/* The basic algorithm here is to generate the most complex grid possible
+ * while honouring two restrictions:
+ *
+ *  * we require a unique solution, and
+ *  * either we require solubility with no recursion (!params->recurse)
+ *  * or we require some recursion. (params->recurse).
+ *
+ * The solver helpfully keeps track of the numbers it needed to use to
+ * get its solution, so we use that to remove an initial set of numbers
+ * and check we still satsify our requirements (on uniqueness and
+ * non-recursiveness, if applicable; we don't check explicit recursiveness
+ * until the end).
+ *
+ * Then we try to remove all numbers in a random order, and see if we
+ * still satisfy requirements (putting them back if we didn't).
+ *
+ * Removing numbers will always, in general terms, make a puzzle require
+ * more recursion but it may also mean a puzzle becomes non-unique.
+ *
+ * Once we're done, if we wanted a recursive puzzle but the most difficult
+ * puzzle we could come up with was non-recursive, we give up and try a new
+ * grid. */
+
+#define MAX_GRIDGEN_TRIES 20
+
+static char *new_game_desc(const game_params *params_in, random_state *rs,
+                          char **aux, int interactive)
+{
+    game_params params_copy = *params_in; /* structure copy */
+    game_params *params = &params_copy;
+    game_state *news = new_state(params), *copys;
+    int i, j, run, x, y, wh = params->w*params->h, num;
+    char *ret, *p;
+    int *numindices;
+
+    /* Construct a shuffled list of grid positions; we only
+     * do this once, because if it gets used more than once it'll
+     * be on a different grid layout. */
+    numindices = snewn(wh, int);
+    for (j = 0; j < wh; j++) numindices[j] = j;
+    shuffle(numindices, wh, sizeof(*numindices), rs);
+
+    while (1) {
+        for (i = 0; i < MAX_GRIDGEN_TRIES; i++) {
+            set_blacks(news, params, rs); /* also cleans board. */
+
+            /* set up lights and then the numbers, and remove the lights */
+            place_lights(news, rs);
+            debug(("Generating initial grid.\n"));
+            place_numbers(news);
+            if (!puzzle_is_good(news, params->difficulty)) continue;
+
+            /* Take a copy, remove numbers we didn't use and check there's
+             * still a unique solution; if so, use the copy subsequently. */
+            copys = dup_game(news);
+            strip_unused_nums(copys);
+            if (!puzzle_is_good(copys, params->difficulty)) {
+                debug(("Stripped grid is not good, reverting.\n"));
+                free_game(copys);
+            } else {
+                free_game(news);
+                news = copys;
+            }
+
+            /* Go through grid removing numbers at random one-by-one and
+             * trying to solve again; if it ceases to be good put the number back. */
+            for (j = 0; j < wh; j++) {
+                y = numindices[j] / params->w;
+                x = numindices[j] % params->w;
+                if (!(GRID(news, flags, x, y) & F_NUMBERED)) continue;
+                num = GRID(news, lights, x, y);
+                GRID(news, lights, x, y) = 0;
+                GRID(news, flags, x, y) &= ~F_NUMBERED;
+                if (!puzzle_is_good(news, params->difficulty)) {
+                    GRID(news, lights, x, y) = num;
+                    GRID(news, flags, x, y) |= F_NUMBERED;
+                } else
+                    debug(("Removed (%d,%d) still soluble.\n", x, y));
+            }
+            if (params->difficulty > 0) {
+                /* Was the maximally-difficult puzzle difficult enough?
+                 * Check we can't solve it with a more simplistic solver. */
+                if (puzzle_is_good(news, params->difficulty-1)) {
+                    debug(("Maximally-hard puzzle still not hard enough, skipping.\n"));
+                    continue;
+                }
+            }
+
+            goto goodpuzzle;
+        }
+        /* Couldn't generate a good puzzle in however many goes. Ramp up the
+         * %age of black squares (if we didn't already have lots; in which case
+         * why couldn't we generate a puzzle?) and try again. */
+        if (params->blackpc < 90) params->blackpc += 5;
+        debug(("New black layout %d%%.\n", params->blackpc));
+    }
+goodpuzzle:
+    /* Game is encoded as a long string one character per square;
+     * 'S' is a space
+     * 'B' is a black square with no number
+     * '0', '1', '2', '3', '4' is a black square with a number. */
+    ret = snewn((params->w * params->h) + 1, char);
+    p = ret;
+    run = 0;
+    for (y = 0; y < params->h; y++) {
+       for (x = 0; x < params->w; x++) {
+            if (GRID(news,flags,x,y) & F_BLACK) {
+               if (run) {
+                   *p++ = ('a'-1) + run;
+                   run = 0;
+               }
+                if (GRID(news,flags,x,y) & F_NUMBERED)
+                    *p++ = '0' + GRID(news,lights,x,y);
+                else
+                    *p++ = 'B';
+            } else {
+               if (run == 26) {
+                   *p++ = ('a'-1) + run;
+                   run = 0;
+               }
+               run++;
+           }
+        }
+    }
+    if (run) {
+       *p++ = ('a'-1) + run;
+       run = 0;
+    }
+    *p = '\0';
+    assert(p - ret <= params->w * params->h);
+    free_game(news);
+    sfree(numindices);
+
+    return ret;
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    int i;
+    for (i = 0; i < params->w*params->h; i++) {
+        if (*desc >= '0' && *desc <= '4')
+            /* OK */;
+        else if (*desc == 'B')
+            /* OK */;
+        else if (*desc >= 'a' && *desc <= 'z')
+            i += *desc - 'a';         /* and the i++ will add another one */
+        else if (!*desc)
+            return "Game description shorter than expected";
+        else
+            return "Game description contained unexpected character";
+        desc++;
+    }
+    if (*desc || i > params->w*params->h)
+        return "Game description longer than expected";
+
+    return NULL;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    game_state *ret = new_state(params);
+    int x,y;
+    int run = 0;
+
+    for (y = 0; y < params->h; y++) {
+       for (x = 0; x < params->w; x++) {
+            char c = '\0';
+
+           if (run == 0) {
+               c = *desc++;
+               assert(c != 'S');
+               if (c >= 'a' && c <= 'z')
+                   run = c - 'a' + 1;
+           }
+
+           if (run > 0) {
+               c = 'S';
+               run--;
+           }
+
+            switch (c) {
+             case '0': case '1': case '2': case '3': case '4':
+                GRID(ret,flags,x,y) |= F_NUMBERED;
+                GRID(ret,lights,x,y) = (c - '0');
+                /* run-on... */
+
+             case 'B':
+                GRID(ret,flags,x,y) |= F_BLACK;
+                break;
+
+             case 'S':
+               /* empty square */
+                break;
+
+             default:
+               assert(!"Malformed desc.");
+               break;
+            }
+        }
+    }
+    if (*desc) assert(!"Over-long desc.");
+
+    return ret;
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    game_state *solved;
+    char *move = NULL, buf[80];
+    int movelen, movesize, x, y, len;
+    unsigned int oldflags, solvedflags, sflags;
+
+    /* We don't care here about non-unique puzzles; if the
+     * user entered one themself then I doubt they care. */
+
+    sflags = F_SOLVE_ALLOWRECURSE | F_SOLVE_DISCOUNTSETS;
+
+    /* Try and solve from where we are now (for non-unique
+     * puzzles this may produce a different answer). */
+    solved = dup_game(currstate);
+    if (dosolve(solved, sflags, NULL) > 0) goto solved;
+    free_game(solved);
+
+    /* That didn't work; try solving from the clean puzzle. */
+    solved = dup_game(state);
+    if (dosolve(solved, sflags, NULL) > 0) goto solved;
+    *error = "Unable to find a solution to this puzzle.";
+    goto done;
+
+solved:
+    movesize = 256;
+    move = snewn(movesize, char);
+    movelen = 0;
+    move[movelen++] = 'S';
+    move[movelen] = '\0';
+    for (x = 0; x < currstate->w; x++) {
+        for (y = 0; y < currstate->h; y++) {
+            len = 0;
+            oldflags = GRID(currstate, flags, x, y);
+            solvedflags = GRID(solved, flags, x, y);
+            if ((oldflags & F_LIGHT) != (solvedflags & F_LIGHT))
+                len = sprintf(buf, ";L%d,%d", x, y);
+            else if ((oldflags & F_IMPOSSIBLE) != (solvedflags & F_IMPOSSIBLE))
+                len = sprintf(buf, ";I%d,%d", x, y);
+            if (len) {
+                if (movelen + len >= movesize) {
+                    movesize = movelen + len + 256;
+                    move = sresize(move, movesize, char);
+                }
+                strcpy(move + movelen, buf);
+                movelen += len;
+            }
+        }
+    }
+
+done:
+    free_game(solved);
+    return move;
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+/* 'borrowed' from slant.c, mainly. I could have printed it one
+ * character per cell (like debug_state) but that comes out tiny.
+ * 'L' is used for 'light here' because 'O' looks too much like '0'
+ * (black square with no surrounding lights). */
+static char *game_text_format(const game_state *state)
+{
+    int w = state->w, h = state->h, W = w+1, H = h+1;
+    int x, y, len, lights;
+    unsigned int flags;
+    char *ret, *p;
+
+    len = (h+H) * (w+W+1) + 1;
+    ret = snewn(len, char);
+    p = ret;
+
+    for (y = 0; y < H; y++) {
+        for (x = 0; x < W; x++) {
+            *p++ = '+';
+            if (x < w)
+                *p++ = '-';
+        }
+        *p++ = '\n';
+        if (y < h) {
+            for (x = 0; x < W; x++) {
+                *p++ = '|';
+                if (x < w) {
+                    /* actual interesting bit. */
+                    flags = GRID(state, flags, x, y);
+                    lights = GRID(state, lights, x, y);
+                    if (flags & F_BLACK) {
+                        if (flags & F_NUMBERED)
+                            *p++ = '0' + lights;
+                        else
+                            *p++ = '#';
+                    } else {
+                        if (flags & F_LIGHT)
+                            *p++ = 'L';
+                        else if (flags & F_IMPOSSIBLE)
+                            *p++ = 'x';
+                        else if (lights > 0)
+                            *p++ = '.';
+                        else
+                            *p++ = ' ';
+                    }
+                }
+            }
+            *p++ = '\n';
+        }
+    }
+    *p++ = '\0';
+
+    assert(p - ret == len);
+    return ret;
+}
+
+struct game_ui {
+    int cur_x, cur_y, cur_visible;
+};
+
+static game_ui *new_ui(const game_state *state)
+{
+    game_ui *ui = snew(game_ui);
+    ui->cur_x = ui->cur_y = ui->cur_visible = 0;
+    return ui;
+}
+
+static void free_ui(game_ui *ui)
+{
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    /* nothing to encode. */
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+    /* nothing to decode. */
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+    if (newstate->completed)
+        ui->cur_visible = 0;
+}
+
+#define DF_BLACK        1       /* black square */
+#define DF_NUMBERED     2       /* black square with number */
+#define DF_LIT          4       /* display (white) square lit up */
+#define DF_LIGHT        8       /* display light in square */
+#define DF_OVERLAP      16      /* display light as overlapped */
+#define DF_CURSOR       32      /* display cursor */
+#define DF_NUMBERWRONG  64      /* display black numbered square as error. */
+#define DF_FLASH        128     /* background flash is on. */
+#define DF_IMPOSSIBLE   256     /* display non-light little square */
+
+struct game_drawstate {
+    int tilesize, crad;
+    int w, h;
+    unsigned int *flags;         /* width * height */
+    int started;
+};
+
+
+/* Believe it or not, this empty = "" hack is needed to get around a bug in
+ * the prc-tools gcc when optimisation is turned on; before, it produced:
+    lightup-sect.c: In function `interpret_move':
+    lightup-sect.c:1416: internal error--unrecognizable insn:
+    (insn 582 580 583 (set (reg:SI 134)
+            (pc)) -1 (nil)
+        (nil))
+ */
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    enum { NONE, FLIP_LIGHT, FLIP_IMPOSSIBLE } action = NONE;
+    int cx = -1, cy = -1;
+    unsigned int flags;
+    char buf[80], *nullret = NULL, *empty = "", c;
+
+    if (button == LEFT_BUTTON || button == RIGHT_BUTTON) {
+        if (ui->cur_visible)
+            nullret = empty;
+        ui->cur_visible = 0;
+        cx = FROMCOORD(x);
+        cy = FROMCOORD(y);
+        action = (button == LEFT_BUTTON) ? FLIP_LIGHT : FLIP_IMPOSSIBLE;
+    } else if (IS_CURSOR_SELECT(button) ||
+               button == 'i' || button == 'I' ||
+               button == ' ' || button == '\r' || button == '\n') {
+        if (ui->cur_visible) {
+            /* Only allow cursor-effect operations if the cursor is visible
+             * (otherwise you have no idea which square it might be affecting) */
+            cx = ui->cur_x;
+            cy = ui->cur_y;
+            action = (button == 'i' || button == 'I' || button == CURSOR_SELECT2) ?
+                FLIP_IMPOSSIBLE : FLIP_LIGHT;
+        }
+        ui->cur_visible = 1;
+    } else if (IS_CURSOR_MOVE(button)) {
+        move_cursor(button, &ui->cur_x, &ui->cur_y, state->w, state->h, 0);
+        ui->cur_visible = 1;
+        nullret = empty;
+    } else
+        return NULL;
+
+    switch (action) {
+    case FLIP_LIGHT:
+    case FLIP_IMPOSSIBLE:
+        if (cx < 0 || cy < 0 || cx >= state->w || cy >= state->h)
+            return nullret;
+        flags = GRID(state, flags, cx, cy);
+        if (flags & F_BLACK)
+            return nullret;
+        if (action == FLIP_LIGHT) {
+#ifdef STYLUS_BASED
+            if (flags & F_IMPOSSIBLE || flags & F_LIGHT) c = 'I'; else c = 'L';
+#else
+            if (flags & F_IMPOSSIBLE) return nullret;
+            c = 'L';
+#endif
+        } else {
+#ifdef STYLUS_BASED
+            if (flags & F_IMPOSSIBLE || flags & F_LIGHT) c = 'L'; else c = 'I';
+#else
+            if (flags & F_LIGHT) return nullret;
+            c = 'I';
+#endif
+        }
+        sprintf(buf, "%c%d,%d", (int)c, cx, cy);
+        break;
+
+    case NONE:
+        return nullret;
+
+    default:
+        assert(!"Shouldn't get here!");
+    }
+    return dupstr(buf);
+}
+
+static game_state *execute_move(const game_state *state, const char *move)
+{
+    game_state *ret = dup_game(state);
+    int x, y, n, flags;
+    char c;
+
+    if (!*move) goto badmove;
+
+    while (*move) {
+        c = *move;
+        if (c == 'S') {
+            ret->used_solve = TRUE;
+            move++;
+        } else if (c == 'L' || c == 'I') {
+            move++;
+            if (sscanf(move, "%d,%d%n", &x, &y, &n) != 2 ||
+                x < 0 || y < 0 || x >= ret->w || y >= ret->h)
+                goto badmove;
+
+            flags = GRID(ret, flags, x, y);
+            if (flags & F_BLACK) goto badmove;
+
+            /* LIGHT and IMPOSSIBLE are mutually exclusive. */
+            if (c == 'L') {
+                GRID(ret, flags, x, y) &= ~F_IMPOSSIBLE;
+                set_light(ret, x, y, (flags & F_LIGHT) ? 0 : 1);
+            } else {
+                set_light(ret, x, y, 0);
+                GRID(ret, flags, x, y) ^= F_IMPOSSIBLE;
+            }
+            move += n;
+        } else goto badmove;
+
+        if (*move == ';')
+            move++;
+        else if (*move) goto badmove;
+    }
+    if (grid_correct(ret)) ret->completed = 1;
+    return ret;
+
+badmove:
+    free_game(ret);
+    return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+/* XXX entirely cloned from fifteen.c; separate out? */
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    struct { int tilesize; } ads, *ds = &ads;
+    ads.tilesize = tilesize;
+
+    *x = TILE_SIZE * params->w + 2 * BORDER;
+    *y = TILE_SIZE * params->h + 2 * BORDER;
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+    ds->crad = 3*(tilesize-1)/8;
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+    int i;
+
+    frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
+
+    for (i = 0; i < 3; i++) {
+        ret[COL_BLACK * 3 + i] = 0.0F;
+        ret[COL_LIGHT * 3 + i] = 1.0F;
+        ret[COL_CURSOR * 3 + i] = ret[COL_BACKGROUND * 3 + i] / 2.0F;
+        ret[COL_GRID * 3 + i] = ret[COL_BACKGROUND * 3 + i] / 1.5F;
+
+    }
+
+    ret[COL_ERROR * 3 + 0] = 1.0F;
+    ret[COL_ERROR * 3 + 1] = 0.25F;
+    ret[COL_ERROR * 3 + 2] = 0.25F;
+
+    ret[COL_LIT * 3 + 0] = 1.0F;
+    ret[COL_LIT * 3 + 1] = 1.0F;
+    ret[COL_LIT * 3 + 2] = 0.0F;
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    struct game_drawstate *ds = snew(struct game_drawstate);
+    int i;
+
+    ds->tilesize = ds->crad = 0;
+    ds->w = state->w; ds->h = state->h;
+
+    ds->flags = snewn(ds->w*ds->h, unsigned int);
+    for (i = 0; i < ds->w*ds->h; i++)
+        ds->flags[i] = -1;
+
+    ds->started = 0;
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds->flags);
+    sfree(ds);
+}
+
+/* At some stage we should put these into a real options struct.
+ * Note that tile_redraw has no #ifdeffery; it relies on tile_flags not
+ * to put those flags in. */
+#define HINT_LIGHTS
+#define HINT_OVERLAPS
+#define HINT_NUMBERS
+
+static unsigned int tile_flags(game_drawstate *ds, const game_state *state,
+                               const game_ui *ui, int x, int y, int flashing)
+{
+    unsigned int flags = GRID(state, flags, x, y);
+    int lights = GRID(state, lights, x, y);
+    unsigned int ret = 0;
+
+    if (flashing) ret |= DF_FLASH;
+    if (ui && ui->cur_visible && x == ui->cur_x && y == ui->cur_y)
+        ret |= DF_CURSOR;
+
+    if (flags & F_BLACK) {
+        ret |= DF_BLACK;
+        if (flags & F_NUMBERED) {
+#ifdef HINT_NUMBERS
+            if (number_wrong(state, x, y))
+               ret |= DF_NUMBERWRONG;
+#endif
+            ret |= DF_NUMBERED;
+        }
+    } else {
+#ifdef HINT_LIGHTS
+        if (lights > 0) ret |= DF_LIT;
+#endif
+        if (flags & F_LIGHT) {
+            ret |= DF_LIGHT;
+#ifdef HINT_OVERLAPS
+            if (lights > 1) ret |= DF_OVERLAP;
+#endif
+        }
+        if (flags & F_IMPOSSIBLE) ret |= DF_IMPOSSIBLE;
+    }
+    return ret;
+}
+
+static void tile_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *state, int x, int y)
+{
+    unsigned int ds_flags = GRID(ds, flags, x, y);
+    int dx = COORD(x), dy = COORD(y);
+    int lit = (ds_flags & DF_FLASH) ? COL_GRID : COL_LIT;
+
+    if (ds_flags & DF_BLACK) {
+        draw_rect(dr, dx, dy, TILE_SIZE, TILE_SIZE, COL_BLACK);
+        if (ds_flags & DF_NUMBERED) {
+            int ccol = (ds_flags & DF_NUMBERWRONG) ? COL_ERROR : COL_LIGHT;
+            char str[32];
+
+            /* We know that this won't change over the course of the game
+             * so it's OK to ignore this when calculating whether or not
+             * to redraw the tile. */
+            sprintf(str, "%d", GRID(state, lights, x, y));
+            draw_text(dr, dx + TILE_SIZE/2, dy + TILE_SIZE/2,
+                      FONT_VARIABLE, TILE_SIZE*3/5,
+                     ALIGN_VCENTRE | ALIGN_HCENTRE, ccol, str);
+        }
+    } else {
+        draw_rect(dr, dx, dy, TILE_SIZE, TILE_SIZE,
+                  (ds_flags & DF_LIT) ? lit : COL_BACKGROUND);
+        draw_rect_outline(dr, dx, dy, TILE_SIZE, TILE_SIZE, COL_GRID);
+        if (ds_flags & DF_LIGHT) {
+            int lcol = (ds_flags & DF_OVERLAP) ? COL_ERROR : COL_LIGHT;
+            draw_circle(dr, dx + TILE_SIZE/2, dy + TILE_SIZE/2, TILE_RADIUS,
+                        lcol, COL_BLACK);
+        } else if ((ds_flags & DF_IMPOSSIBLE)) {
+            static int draw_blobs_when_lit = -1;
+            if (draw_blobs_when_lit < 0) {
+               char *env = getenv("LIGHTUP_LIT_BLOBS");
+               draw_blobs_when_lit = (!env || (env[0] == 'y' ||
+                                                env[0] == 'Y'));
+            }
+            if (!(ds_flags & DF_LIT) || draw_blobs_when_lit) {
+                int rlen = TILE_SIZE / 4;
+                draw_rect(dr, dx + TILE_SIZE/2 - rlen/2,
+                          dy + TILE_SIZE/2 - rlen/2,
+                          rlen, rlen, COL_BLACK);
+            }
+        }
+    }
+
+    if (ds_flags & DF_CURSOR) {
+        int coff = TILE_SIZE/8;
+        draw_rect_outline(dr, dx + coff, dy + coff,
+                          TILE_SIZE - coff*2, TILE_SIZE - coff*2, COL_CURSOR);
+    }
+
+    draw_update(dr, dx, dy, TILE_SIZE, TILE_SIZE);
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int flashing = FALSE;
+    int x,y;
+
+    if (flashtime) flashing = (int)(flashtime * 3 / FLASH_TIME) != 1;
+
+    if (!ds->started) {
+        draw_rect(dr, 0, 0,
+                  TILE_SIZE * ds->w + 2 * BORDER,
+                  TILE_SIZE * ds->h + 2 * BORDER, COL_BACKGROUND);
+
+        draw_rect_outline(dr, COORD(0)-1, COORD(0)-1,
+                          TILE_SIZE * ds->w + 2,
+                          TILE_SIZE * ds->h + 2,
+                          COL_GRID);
+
+        draw_update(dr, 0, 0,
+                    TILE_SIZE * ds->w + 2 * BORDER,
+                    TILE_SIZE * ds->h + 2 * BORDER);
+        ds->started = 1;
+    }
+
+    for (x = 0; x < ds->w; x++) {
+        for (y = 0; y < ds->h; y++) {
+            unsigned int ds_flags = tile_flags(ds, state, ui, x, y, flashing);
+            if (ds_flags != GRID(ds, flags, x, y)) {
+                GRID(ds, flags, x, y) = ds_flags;
+                tile_redraw(dr, ds, state, x, y);
+            }
+        }
+    }
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return 0.0F;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    if (!oldstate->completed && newstate->completed &&
+        !oldstate->used_solve && !newstate->used_solve)
+        return FLASH_TIME;
+    return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    return state->completed ? +1 : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+    int pw, ph;
+
+    /*
+     * I'll use 6mm squares by default.
+     */
+    game_compute_size(params, 600, &pw, &ph);
+    *x = pw / 100.0F;
+    *y = ph / 100.0F;
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+    int w = state->w, h = state->h;
+    int ink = print_mono_colour(dr, 0);
+    int paper = print_mono_colour(dr, 1);
+    int x, y;
+
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    game_drawstate ads, *ds = &ads;
+    game_set_size(dr, ds, NULL, tilesize);
+
+    /*
+     * Border.
+     */
+    print_line_width(dr, TILE_SIZE / 16);
+    draw_rect_outline(dr, COORD(0), COORD(0),
+                     TILE_SIZE * w, TILE_SIZE * h, ink);
+
+    /*
+     * Grid.
+     */
+    print_line_width(dr, TILE_SIZE / 24);
+    for (x = 1; x < w; x++)
+       draw_line(dr, COORD(x), COORD(0), COORD(x), COORD(h), ink);
+    for (y = 1; y < h; y++)
+       draw_line(dr, COORD(0), COORD(y), COORD(w), COORD(y), ink);
+
+    /*
+     * Grid contents.
+     */
+    for (y = 0; y < h; y++)
+       for (x = 0; x < w; x++) {
+            unsigned int ds_flags = tile_flags(ds, state, NULL, x, y, FALSE);
+           int dx = COORD(x), dy = COORD(y);
+           if (ds_flags & DF_BLACK) {
+               draw_rect(dr, dx, dy, TILE_SIZE, TILE_SIZE, ink);
+               if (ds_flags & DF_NUMBERED) {
+                   char str[32];
+                   sprintf(str, "%d", GRID(state, lights, x, y));
+                   draw_text(dr, dx + TILE_SIZE/2, dy + TILE_SIZE/2,
+                             FONT_VARIABLE, TILE_SIZE*3/5,
+                             ALIGN_VCENTRE | ALIGN_HCENTRE, paper, str);
+               }
+           } else if (ds_flags & DF_LIGHT) {
+               draw_circle(dr, dx + TILE_SIZE/2, dy + TILE_SIZE/2,
+                           TILE_RADIUS, -1, ink);
+           }
+       }
+}
+
+#ifdef COMBINED
+#define thegame lightup
+#endif
+
+const struct game thegame = {
+    "Light Up", "games.lightup", "lightup",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    TRUE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    TRUE, FALSE, game_print_size, game_print,
+    FALSE,                            /* wants_statusbar */
+    FALSE, game_timing_state,
+    0,                                /* flags */
+};
+
+#ifdef STANDALONE_SOLVER
+
+int main(int argc, char **argv)
+{
+    game_params *p;
+    game_state *s;
+    char *id = NULL, *desc, *err, *result;
+    int nsol, diff, really_verbose = 0;
+    unsigned int sflags;
+
+    while (--argc > 0) {
+        char *p = *++argv;
+        if (!strcmp(p, "-v")) {
+            really_verbose++;
+        } else if (*p == '-') {
+            fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p);
+            return 1;
+        } else {
+            id = p;
+        }
+    }
+
+    if (!id) {
+        fprintf(stderr, "usage: %s [-v] <game_id>\n", argv[0]);
+        return 1;
+    }
+
+    desc = strchr(id, ':');
+    if (!desc) {
+        fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]);
+        return 1;
+    }
+    *desc++ = '\0';
+
+    p = default_params();
+    decode_params(p, id);
+    err = validate_desc(p, desc);
+    if (err) {
+        fprintf(stderr, "%s: %s\n", argv[0], err);
+        return 1;
+    }
+    s = new_game(NULL, p, desc);
+
+    /* Run the solvers easiest to hardest until we find one that
+     * can solve our puzzle. If it's soluble we know that the
+     * hardest (recursive) solver will always find the solution. */
+    nsol = sflags = 0;
+    for (diff = 0; diff <= DIFFCOUNT; diff++) {
+        printf("\nSolving with difficulty %d.\n", diff);
+        sflags = flags_from_difficulty(diff);
+        unplace_lights(s);
+        nsol = dosolve(s, sflags, NULL);
+        if (nsol == 1) break;
+    }
+
+    printf("\n");
+    if (nsol == 0) {
+        printf("Puzzle has no solution.\n");
+    } else if (nsol < 0) {
+        printf("Unable to find a unique solution.\n");
+    } else if (nsol > 1) {
+        printf("Puzzle has multiple solutions.\n");
+    } else {
+        verbose = really_verbose;
+        unplace_lights(s);
+        printf("Puzzle has difficulty %d: solving...\n", diff);
+        dosolve(s, sflags, NULL); /* sflags from last successful solve */
+        result = game_text_format(s);
+        printf("%s", result);
+        sfree(result);
+    }
+
+    return 0;
+}
+
+#endif
+
+/* vim: set shiftwidth=4 tabstop=8: */
diff --git a/list.c b/list.c
new file mode 100644 (file)
index 0000000..ec019c3
--- /dev/null
+++ b/list.c
@@ -0,0 +1,55 @@
+/*
+ * list.c: List of pointers to puzzle structures, for monolithic
+ * platforms.
+ *
+ * This file is automatically generated by mkfiles.pl. Do not edit
+ * it directly, or the changes will be lost next time mkfiles.pl runs.
+ * Instead, edit Recipe and/or its *.R subfiles.
+ */
+#include "puzzles.h"
+#define GAMELIST(A) \
+    A(blackbox) \
+    A(bridges) \
+    A(cube) \
+    A(dominosa) \
+    A(fifteen) \
+    A(filling) \
+    A(flip) \
+    A(flood) \
+    A(galaxies) \
+    A(guess) \
+    A(inertia) \
+    A(keen) \
+    A(lightup) \
+    A(loopy) \
+    A(magnets) \
+    A(map) \
+    A(mines) \
+    A(net) \
+    A(netslide) \
+    A(palisade) \
+    A(pattern) \
+    A(pearl) \
+    A(pegs) \
+    A(range) \
+    A(rect) \
+    A(samegame) \
+    A(signpost) \
+    A(singles) \
+    A(sixteen) \
+    A(slant) \
+    A(solo) \
+    A(tents) \
+    A(towers) \
+    A(tracks) \
+    A(twiddle) \
+    A(undead) \
+    A(unequal) \
+    A(unruly) \
+    A(untangle) \
+
+#define DECL(x) extern const game x;
+#define REF(x) &x,
+GAMELIST(DECL)
+const game *gamelist[] = { GAMELIST(REF) };
+const int gamecount = lenof(gamelist);
diff --git a/loopgen.c b/loopgen.c
new file mode 100644 (file)
index 0000000..0b69904
--- /dev/null
+++ b/loopgen.c
@@ -0,0 +1,536 @@
+/*
+ * loopgen.c: loop generation functions for grid.[ch].
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+#include "tree234.h"
+#include "grid.h"
+#include "loopgen.h"
+
+
+/* We're going to store lists of current candidate faces for colouring black
+ * or white.
+ * Each face gets a 'score', which tells us how adding that face right
+ * now would affect the curliness of the solution loop.  We're trying to
+ * maximise that quantity so will bias our random selection of faces to
+ * colour those with high scores */
+struct face_score {
+    int white_score;
+    int black_score;
+    unsigned long random;
+    /* No need to store a grid_face* here.  The 'face_scores' array will
+     * be a list of 'face_score' objects, one for each face of the grid, so
+     * the position (index) within the 'face_scores' array will determine
+     * which face corresponds to a particular face_score.
+     * Having a single 'face_scores' array for all faces simplifies memory
+     * management, and probably improves performance, because we don't have to 
+     * malloc/free each individual face_score, and we don't have to maintain
+     * a mapping from grid_face* pointers to face_score* pointers.
+     */
+};
+
+static int generic_sort_cmpfn(void *v1, void *v2, size_t offset)
+{
+    struct face_score *f1 = v1;
+    struct face_score *f2 = v2;
+    int r;
+
+    r = *(int *)((char *)f2 + offset) - *(int *)((char *)f1 + offset);
+    if (r) {
+        return r;
+    }
+
+    if (f1->random < f2->random)
+        return -1;
+    else if (f1->random > f2->random)
+        return 1;
+
+    /*
+     * It's _just_ possible that two faces might have been given
+     * the same random value. In that situation, fall back to
+     * comparing based on the positions within the face_scores list.
+     * This introduces a tiny directional bias, but not a significant one.
+     */
+    return f1 - f2;
+}
+
+static int white_sort_cmpfn(void *v1, void *v2)
+{
+    return generic_sort_cmpfn(v1, v2, offsetof(struct face_score,white_score));
+}
+
+static int black_sort_cmpfn(void *v1, void *v2)
+{
+    return generic_sort_cmpfn(v1, v2, offsetof(struct face_score,black_score));
+}
+
+/* 'board' is an array of enum face_colour, indicating which faces are
+ * currently black/white/grey.  'colour' is FACE_WHITE or FACE_BLACK.
+ * Returns whether it's legal to colour the given face with this colour. */
+static int can_colour_face(grid *g, char* board, int face_index,
+                           enum face_colour colour)
+{
+    int i, j;
+    grid_face *test_face = g->faces + face_index;
+    grid_face *starting_face, *current_face;
+    grid_dot *starting_dot;
+    int transitions;
+    int current_state, s; /* booleans: equal or not-equal to 'colour' */
+    int found_same_coloured_neighbour = FALSE;
+    assert(board[face_index] != colour);
+
+    /* Can only consider a face for colouring if it's adjacent to a face
+     * with the same colour. */
+    for (i = 0; i < test_face->order; i++) {
+        grid_edge *e = test_face->edges[i];
+        grid_face *f = (e->face1 == test_face) ? e->face2 : e->face1;
+        if (FACE_COLOUR(f) == colour) {
+            found_same_coloured_neighbour = TRUE;
+            break;
+        }
+    }
+    if (!found_same_coloured_neighbour)
+        return FALSE;
+
+    /* Need to avoid creating a loop of faces of this colour around some
+     * differently-coloured faces.
+     * Also need to avoid meeting a same-coloured face at a corner, with
+     * other-coloured faces in between.  Here's a simple test that (I believe)
+     * takes care of both these conditions:
+     *
+     * Take the circular path formed by this face's edges, and inflate it
+     * slightly outwards.  Imagine walking around this path and consider
+     * the faces that you visit in sequence.  This will include all faces
+     * touching the given face, either along an edge or just at a corner.
+     * Count the number of 'colour'/not-'colour' transitions you encounter, as
+     * you walk along the complete loop.  This will obviously turn out to be
+     * an even number.
+     * If 0, we're either in the middle of an "island" of this colour (should
+     * be impossible as we're not supposed to create black or white loops),
+     * or we're about to start a new island - also not allowed.
+     * If 4 or greater, there are too many separate coloured regions touching
+     * this face, and colouring it would create a loop or a corner-violation.
+     * The only allowed case is when the count is exactly 2. */
+
+    /* i points to a dot around the test face.
+     * j points to a face around the i^th dot.
+     * The current face will always be:
+     *     test_face->dots[i]->faces[j]
+     * We assume dots go clockwise around the test face,
+     * and faces go clockwise around dots. */
+
+    /*
+     * The end condition is slightly fiddly. In sufficiently strange
+     * degenerate grids, our test face may be adjacent to the same
+     * other face multiple times (typically if it's the exterior
+     * face). Consider this, in particular:
+     * 
+     *   +--+
+     *   |  |
+     *   +--+--+
+     *   |  |  |
+     *   +--+--+
+     * 
+     * The bottom left face there is adjacent to the exterior face
+     * twice, so we can't just terminate our iteration when we reach
+     * the same _face_ we started at. Furthermore, we can't
+     * condition on having the same (i,j) pair either, because
+     * several (i,j) pairs identify the bottom left contiguity with
+     * the exterior face! We canonicalise the (i,j) pair by taking
+     * one step around before we set the termination tracking.
+     */
+
+    i = j = 0;
+    current_face = test_face->dots[0]->faces[0];
+    if (current_face == test_face) {
+        j = 1;
+        current_face = test_face->dots[0]->faces[1];
+    }
+    transitions = 0;
+    current_state = (FACE_COLOUR(current_face) == colour);
+    starting_dot = NULL;
+    starting_face = NULL;
+    while (TRUE) {
+        /* Advance to next face.
+         * Need to loop here because it might take several goes to
+         * find it. */
+        while (TRUE) {
+            j++;
+            if (j == test_face->dots[i]->order)
+                j = 0;
+
+            if (test_face->dots[i]->faces[j] == test_face) {
+                /* Advance to next dot round test_face, then
+                 * find current_face around new dot
+                 * and advance to the next face clockwise */
+                i++;
+                if (i == test_face->order)
+                    i = 0;
+                for (j = 0; j < test_face->dots[i]->order; j++) {
+                    if (test_face->dots[i]->faces[j] == current_face)
+                        break;
+                }
+                /* Must actually find current_face around new dot,
+                 * or else something's wrong with the grid. */
+                assert(j != test_face->dots[i]->order);
+                /* Found, so advance to next face and try again */
+            } else {
+                break;
+            }
+        }
+        /* (i,j) are now advanced to next face */
+        current_face = test_face->dots[i]->faces[j];
+        s = (FACE_COLOUR(current_face) == colour);
+       if (!starting_dot) {
+           starting_dot = test_face->dots[i];
+           starting_face = current_face;
+           current_state = s;
+       } else {
+           if (s != current_state) {
+               ++transitions;
+               current_state = s;
+               if (transitions > 2)
+                   break;
+           }
+           if (test_face->dots[i] == starting_dot &&
+               current_face == starting_face)
+               break;
+        }
+    }
+
+    return (transitions == 2) ? TRUE : FALSE;
+}
+
+/* Count the number of neighbours of 'face', having colour 'colour' */
+static int face_num_neighbours(grid *g, char *board, grid_face *face,
+                               enum face_colour colour)
+{
+    int colour_count = 0;
+    int i;
+    grid_face *f;
+    grid_edge *e;
+    for (i = 0; i < face->order; i++) {
+        e = face->edges[i];
+        f = (e->face1 == face) ? e->face2 : e->face1;
+        if (FACE_COLOUR(f) == colour)
+            ++colour_count;
+    }
+    return colour_count;
+}
+
+/* The 'score' of a face reflects its current desirability for selection
+ * as the next face to colour white or black.  We want to encourage moving
+ * into grey areas and increasing loopiness, so we give scores according to
+ * how many of the face's neighbours are currently coloured the same as the
+ * proposed colour. */
+static int face_score(grid *g, char *board, grid_face *face,
+                      enum face_colour colour)
+{
+    /* Simple formula: score = 0 - num. same-coloured neighbours,
+     * so a higher score means fewer same-coloured neighbours. */
+    return -face_num_neighbours(g, board, face, colour);
+}
+
+/*
+ * Generate a new complete random closed loop for the given grid.
+ *
+ * The method is to generate a WHITE/BLACK colouring of all the faces,
+ * such that the WHITE faces will define the inside of the path, and the
+ * BLACK faces define the outside.
+ * To do this, we initially colour all faces GREY.  The infinite space outside
+ * the grid is coloured BLACK, and we choose a random face to colour WHITE.
+ * Then we gradually grow the BLACK and the WHITE regions, eliminating GREY
+ * faces, until the grid is filled with BLACK/WHITE.  As we grow the regions,
+ * we avoid creating loops of a single colour, to preserve the topological
+ * shape of the WHITE and BLACK regions.
+ * We also try to make the boundary as loopy and twisty as possible, to avoid
+ * generating paths that are uninteresting.
+ * The algorithm works by choosing a BLACK/WHITE colour, then choosing a GREY
+ * face that can be coloured with that colour (without violating the
+ * topological shape of that region).  It's not obvious, but I think this
+ * algorithm is guaranteed to terminate without leaving any GREY faces behind.
+ * Indeed, if there are any GREY faces at all, both the WHITE and BLACK
+ * regions can be grown.
+ * This is checked using assert()ions, and I haven't seen any failures yet.
+ *
+ * Hand-wavy proof: imagine what can go wrong...
+ *
+ * Could the white faces get completely cut off by the black faces, and still
+ * leave some grey faces remaining?
+ * No, because then the black faces would form a loop around both the white
+ * faces and the grey faces, which is disallowed because we continually
+ * maintain the correct topological shape of the black region.
+ * Similarly, the black faces can never get cut off by the white faces.  That
+ * means both the WHITE and BLACK regions always have some room to grow into
+ * the GREY regions.
+ * Could it be that we can't colour some GREY face, because there are too many
+ * WHITE/BLACK transitions as we walk round the face? (see the
+ * can_colour_face() function for details)
+ * No.  Imagine otherwise, and we see WHITE/BLACK/WHITE/BLACK as we walk
+ * around the face.  The two WHITE faces would be connected by a WHITE path,
+ * and the BLACK faces would be connected by a BLACK path.  These paths would
+ * have to cross, which is impossible.
+ * Another thing that could go wrong: perhaps we can't find any GREY face to
+ * colour WHITE, because it would create a loop-violation or a corner-violation
+ * with the other WHITE faces?
+ * This is a little bit tricky to prove impossible.  Imagine you have such a
+ * GREY face (that is, if you coloured it WHITE, you would create a WHITE loop
+ * or corner violation).
+ * That would cut all the non-white area into two blobs.  One of those blobs
+ * must be free of BLACK faces (because the BLACK stuff is a connected blob).
+ * So we have a connected GREY area, completely surrounded by WHITE
+ * (including the GREY face we've tentatively coloured WHITE).
+ * A well-known result in graph theory says that you can always find a GREY
+ * face whose removal leaves the remaining GREY area connected.  And it says
+ * there are at least two such faces, so we can always choose the one that
+ * isn't the "tentative" GREY face.  Colouring that face WHITE leaves
+ * everything nice and connected, including that "tentative" GREY face which
+ * acts as a gateway to the rest of the non-WHITE grid.
+ */
+void generate_loop(grid *g, char *board, random_state *rs,
+                   loopgen_bias_fn_t bias, void *biasctx)
+{
+    int i, j;
+    int num_faces = g->num_faces;
+    struct face_score *face_scores; /* Array of face_score objects */
+    struct face_score *fs; /* Points somewhere in the above list */
+    struct grid_face *cur_face;
+    tree234 *lightable_faces_sorted;
+    tree234 *darkable_faces_sorted;
+    int *face_list;
+    int do_random_pass;
+
+    /* Make a board */
+    memset(board, FACE_GREY, num_faces);
+    
+    /* Create and initialise the list of face_scores */
+    face_scores = snewn(num_faces, struct face_score);
+    for (i = 0; i < num_faces; i++) {
+        face_scores[i].random = random_bits(rs, 31);
+        face_scores[i].black_score = face_scores[i].white_score = 0;
+    }
+    
+    /* Colour a random, finite face white.  The infinite face is implicitly
+     * coloured black.  Together, they will seed the random growth process
+     * for the black and white areas. */
+    i = random_upto(rs, num_faces);
+    board[i] = FACE_WHITE;
+
+    /* We need a way of favouring faces that will increase our loopiness.
+     * We do this by maintaining a list of all candidate faces sorted by
+     * their score and choose randomly from that with appropriate skew.
+     * In order to avoid consistently biasing towards particular faces, we
+     * need the sort order _within_ each group of scores to be completely
+     * random.  But it would be abusing the hospitality of the tree234 data
+     * structure if our comparison function were nondeterministic :-).  So with
+     * each face we associate a random number that does not change during a
+     * particular run of the generator, and use that as a secondary sort key.
+     * Yes, this means we will be biased towards particular random faces in
+     * any one run but that doesn't actually matter. */
+
+    lightable_faces_sorted = newtree234(white_sort_cmpfn);
+    darkable_faces_sorted = newtree234(black_sort_cmpfn);
+
+    /* Initialise the lists of lightable and darkable faces.  This is
+     * slightly different from the code inside the while-loop, because we need
+     * to check every face of the board (the grid structure does not keep a
+     * list of the infinite face's neighbours). */
+    for (i = 0; i < num_faces; i++) {
+        grid_face *f = g->faces + i;
+        struct face_score *fs = face_scores + i;
+        if (board[i] != FACE_GREY) continue;
+        /* We need the full colourability check here, it's not enough simply
+         * to check neighbourhood.  On some grids, a neighbour of the infinite
+         * face is not necessarily darkable. */
+        if (can_colour_face(g, board, i, FACE_BLACK)) {
+            fs->black_score = face_score(g, board, f, FACE_BLACK);
+            add234(darkable_faces_sorted, fs);
+        }
+        if (can_colour_face(g, board, i, FACE_WHITE)) {
+            fs->white_score = face_score(g, board, f, FACE_WHITE);
+            add234(lightable_faces_sorted, fs);
+        }
+    }
+
+    /* Colour faces one at a time until no more faces are colourable. */
+    while (TRUE)
+    {
+        enum face_colour colour;
+        tree234 *faces_to_pick;
+        int c_lightable = count234(lightable_faces_sorted);
+        int c_darkable = count234(darkable_faces_sorted);
+        if (c_lightable == 0 && c_darkable == 0) {
+            /* No more faces we can use at all. */
+            break;
+        }
+       assert(c_lightable != 0 && c_darkable != 0);
+
+        /* Choose a colour, and colour the best available face
+         * with that colour. */
+        colour = random_upto(rs, 2) ? FACE_WHITE : FACE_BLACK;
+
+        if (colour == FACE_WHITE)
+            faces_to_pick = lightable_faces_sorted;
+        else
+            faces_to_pick = darkable_faces_sorted;
+        if (bias) {
+            /*
+             * Go through all the candidate faces and pick the one the
+             * bias function likes best, breaking ties using the
+             * ordering in our tree234 (which is why we replace only
+             * if score > bestscore, not >=).
+             */
+            int j, k;
+            struct face_score *best = NULL;
+            int score, bestscore = 0;
+
+            for (j = 0;
+                 (fs = (struct face_score *)index234(faces_to_pick, j))!=NULL;
+                 j++) {
+
+                assert(fs);
+                k = fs - face_scores;
+                assert(board[k] == FACE_GREY);
+                board[k] = colour;
+                score = bias(biasctx, board, k);
+                board[k] = FACE_GREY;
+                bias(biasctx, board, k); /* let bias know we put it back */
+
+                if (!best || score > bestscore) {
+                    bestscore = score;
+                    best = fs;
+                }
+            }
+            fs = best;
+        } else {
+            fs = (struct face_score *)index234(faces_to_pick, 0);
+        }
+        assert(fs);
+        i = fs - face_scores;
+        assert(board[i] == FACE_GREY);
+        board[i] = colour;
+        if (bias)
+            bias(biasctx, board, i); /* notify bias function of the change */
+
+        /* Remove this newly-coloured face from the lists.  These lists should
+         * only contain grey faces. */
+        del234(lightable_faces_sorted, fs);
+        del234(darkable_faces_sorted, fs);
+
+        /* Remember which face we've just coloured */
+        cur_face = g->faces + i;
+
+        /* The face we've just coloured potentially affects the colourability
+         * and the scores of any neighbouring faces (touching at a corner or
+         * edge).  So the search needs to be conducted around all faces
+         * touching the one we've just lit.  Iterate over its corners, then
+         * over each corner's faces.  For each such face, we remove it from
+         * the lists, recalculate any scores, then add it back to the lists
+         * (depending on whether it is lightable, darkable or both). */
+        for (i = 0; i < cur_face->order; i++) {
+            grid_dot *d = cur_face->dots[i];
+            for (j = 0; j < d->order; j++) {
+                grid_face *f = d->faces[j];
+                int fi; /* face index of f */
+
+                if (f == NULL)
+                    continue;
+                if (f == cur_face)
+                    continue;
+                
+                /* If the face is already coloured, it won't be on our
+                 * lightable/darkable lists anyway, so we can skip it without 
+                 * bothering with the removal step. */
+                if (FACE_COLOUR(f) != FACE_GREY) continue; 
+
+                /* Find the face index and face_score* corresponding to f */
+                fi = f - g->faces;                
+                fs = face_scores + fi;
+
+                /* Remove from lightable list if it's in there.  We do this,
+                 * even if it is still lightable, because the score might
+                 * be different, and we need to remove-then-add to maintain
+                 * correct sort order. */
+                del234(lightable_faces_sorted, fs);
+                if (can_colour_face(g, board, fi, FACE_WHITE)) {
+                    fs->white_score = face_score(g, board, f, FACE_WHITE);
+                    add234(lightable_faces_sorted, fs);
+                }
+                /* Do the same for darkable list. */
+                del234(darkable_faces_sorted, fs);
+                if (can_colour_face(g, board, fi, FACE_BLACK)) {
+                    fs->black_score = face_score(g, board, f, FACE_BLACK);
+                    add234(darkable_faces_sorted, fs);
+                }
+            }
+        }
+    }
+
+    /* Clean up */
+    freetree234(lightable_faces_sorted);
+    freetree234(darkable_faces_sorted);
+    sfree(face_scores);
+
+    /* The next step requires a shuffled list of all faces */
+    face_list = snewn(num_faces, int);
+    for (i = 0; i < num_faces; ++i) {
+        face_list[i] = i;
+    }
+    shuffle(face_list, num_faces, sizeof(int), rs);
+
+    /* The above loop-generation algorithm can often leave large clumps
+     * of faces of one colour.  In extreme cases, the resulting path can be 
+     * degenerate and not very satisfying to solve.
+     * This next step alleviates this problem:
+     * Go through the shuffled list, and flip the colour of any face we can
+     * legally flip, and which is adjacent to only one face of the opposite
+     * colour - this tends to grow 'tendrils' into any clumps.
+     * Repeat until we can find no more faces to flip.  This will
+     * eventually terminate, because each flip increases the loop's
+     * perimeter, which cannot increase for ever.
+     * The resulting path will have maximal loopiness (in the sense that it
+     * cannot be improved "locally".  Unfortunately, this allows a player to
+     * make some illicit deductions.  To combat this (and make the path more
+     * interesting), we do one final pass making random flips. */
+
+    /* Set to TRUE for final pass */
+    do_random_pass = FALSE;
+
+    while (TRUE) {
+        /* Remember whether a flip occurred during this pass */
+        int flipped = FALSE;
+
+        for (i = 0; i < num_faces; ++i) {
+            int j = face_list[i];
+            enum face_colour opp =
+                (board[j] == FACE_WHITE) ? FACE_BLACK : FACE_WHITE;
+            if (can_colour_face(g, board, j, opp)) {
+                grid_face *face = g->faces +j;
+                if (do_random_pass) {
+                    /* final random pass */
+                    if (!random_upto(rs, 10))
+                        board[j] = opp;
+                } else {
+                    /* normal pass - flip when neighbour count is 1 */
+                    if (face_num_neighbours(g, board, face, opp) == 1) {
+                        board[j] = opp;
+                        flipped = TRUE;
+                    }
+                }
+            }
+        }
+
+        if (do_random_pass) break;
+        if (!flipped) do_random_pass = TRUE;
+    }
+
+    sfree(face_list);
+}
diff --git a/loopgen.h b/loopgen.h
new file mode 100644 (file)
index 0000000..079c87c
--- /dev/null
+++ b/loopgen.h
@@ -0,0 +1,35 @@
+/*
+ * loopgen.h: interface file for loop generation functions for grid.[ch].
+ */
+
+#ifndef _LOOPGEN_H
+#define _LOOPGEN_H
+
+#include "puzzles.h"
+#include "grid.h"
+
+enum face_colour { FACE_WHITE, FACE_GREY, FACE_BLACK };
+
+/* face should be of type grid_face* here. */
+#define FACE_COLOUR(face) \
+    ( (face) == NULL ? FACE_BLACK : \
+         board[(face) - g->faces] )
+
+typedef int (*loopgen_bias_fn_t)(void *ctx, char *board, int face);
+
+/* 'board' should be a char array whose length is the same as
+ * g->num_faces: this will be filled in with FACE_WHITE or FACE_BLACK
+ * after loop generation.
+ *
+ * If 'bias' is non-null, it should be a user-provided function which
+ * rates a half-finished board (i.e. may include some FACE_GREYs) for
+ * desirability; this will cause the loop generator to bias in favour
+ * of loops with a high return value from that function. The 'face'
+ * parameter to the bias function indicates which face of the grid has
+ * been modified since the last call; it is guaranteed that only one
+ * will have been (so that bias functions can work incrementally
+ * rather than re-scanning the whole grid on every call). */
+extern void generate_loop(grid *g, char *board, random_state *rs,
+                          loopgen_bias_fn_t bias, void *biasctx);
+
+#endif
diff --git a/loopy.R b/loopy.R
new file mode 100644 (file)
index 0000000..f445600
--- /dev/null
+++ b/loopy.R
@@ -0,0 +1,31 @@
+# -*- makefile -*-
+
+LOOPY_EXTRA = tree234 dsf grid penrose loopgen
+
+loopy     : [X] GTK COMMON loopy LOOPY_EXTRA loopy-icon|no-icon
+
+loopy     : [G] WINDOWS COMMON loopy LOOPY_EXTRA loopy.res|noicon.res
+
+loopysolver :   [U] loopy[STANDALONE_SOLVER] LOOPY_EXTRA STANDALONE m.lib
+loopysolver :   [C] loopy[STANDALONE_SOLVER] LOOPY_EXTRA STANDALONE
+
+#penrose :    [U] penrose[TEST_PENROSE] STANDALONE m.lib
+#penrose :    [C] penrose[TEST_PENROSE] STANDALONE
+
+#test-basis : [U] penrose[TEST_VECTORS] tree234 STANDALONE m.lib
+#test-basis : [C] penrose[TEST_VECTORS] tree234 STANDALONE
+
+
+ALL += loopy[COMBINED] LOOPY_EXTRA
+
+!begin am gtk
+GAMES += loopy
+!end
+
+!begin >list.c
+    A(loopy) \
+!end
+
+!begin >gamedesc.txt
+loopy:loopy.exe:Loopy:Loop-drawing puzzle:Draw a single closed loop, given clues about number of adjacent edges.
+!end
diff --git a/loopy.c b/loopy.c
new file mode 100644 (file)
index 0000000..44d51ee
--- /dev/null
+++ b/loopy.c
@@ -0,0 +1,3688 @@
+/*
+ * loopy.c:
+ *
+ * An implementation of the Nikoli game 'Loop the loop'.
+ * (c) Mike Pinna, 2005, 2006
+ * Substantially rewritten to allowing for more general types of grid.
+ * (c) Lambros Lambrou 2008
+ *
+ * vim: set shiftwidth=4 :set textwidth=80:
+ */
+
+/*
+ * Possible future solver enhancements:
+ * 
+ *  - There's an interesting deductive technique which makes use
+ *    of topology rather than just graph theory. Each _face_ in
+ *    the grid is either inside or outside the loop; you can tell
+ *    that two faces are on the same side of the loop if they're
+ *    separated by a LINE_NO (or, more generally, by a path
+ *    crossing no LINE_UNKNOWNs and an even number of LINE_YESes),
+ *    and on the opposite side of the loop if they're separated by
+ *    a LINE_YES (or an odd number of LINE_YESes and no
+ *    LINE_UNKNOWNs). Oh, and any face separated from the outside
+ *    of the grid by a LINE_YES or a LINE_NO is on the inside or
+ *    outside respectively. So if you can track this for all
+ *    faces, you figure out the state of the line between a pair
+ *    once their relative insideness is known.
+ *     + The way I envisage this working is simply to keep an edsf
+ *      of all _faces_, which indicates whether they're on
+ *      opposite sides of the loop from one another. We also
+ *      include a special entry in the edsf for the infinite
+ *      exterior "face".
+ *     + So, the simple way to do this is to just go through the
+ *      edges: every time we see an edge in a state other than
+ *      LINE_UNKNOWN which separates two faces that aren't in the
+ *      same edsf class, we can rectify that by merging the
+ *      classes. Then, conversely, an edge in LINE_UNKNOWN state
+ *      which separates two faces that _are_ in the same edsf
+ *      class can immediately have its state determined.
+ *     + But you can go one better, if you're prepared to loop
+ *      over all _pairs_ of edges. Suppose we have edges A and B,
+ *      which respectively separate faces A1,A2 and B1,B2.
+ *      Suppose that A,B are in the same edge-edsf class and that
+ *      A1,B1 (wlog) are in the same face-edsf class; then we can
+ *      immediately place A2,B2 into the same face-edsf class (as
+ *      each other, not as A1 and A2) one way round or the other.
+ *      And conversely again, if A1,B1 are in the same face-edsf
+ *      class and so are A2,B2, then we can put A,B into the same
+ *      face-edsf class.
+ *       * Of course, this deduction requires a quadratic-time
+ *         loop over all pairs of edges in the grid, so it should
+ *         be reserved until there's nothing easier left to be
+ *         done.
+ * 
+ *  - The generalised grid support has made me (SGT) notice a
+ *    possible extension to the loop-avoidance code. When you have
+ *    a path of connected edges such that no other edges at all
+ *    are incident on any vertex in the middle of the path - or,
+ *    alternatively, such that any such edges are already known to
+ *    be LINE_NO - then you know those edges are either all
+ *    LINE_YES or all LINE_NO. Hence you can mentally merge the
+ *    entire path into a single long curly edge for the purposes
+ *    of loop avoidance, and look directly at whether or not the
+ *    extreme endpoints of the path are connected by some other
+ *    route. I find this coming up fairly often when I play on the
+ *    octagonal grid setting, so it might be worth implementing in
+ *    the solver.
+ *
+ *  - (Just a speed optimisation.)  Consider some todo list queue where every
+ *    time we modify something we mark it for consideration by other bits of
+ *    the solver, to save iteration over things that have already been done.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+#include "tree234.h"
+#include "grid.h"
+#include "loopgen.h"
+
+/* Debugging options */
+
+/*
+#define DEBUG_CACHES
+#define SHOW_WORKING
+#define DEBUG_DLINES
+*/
+
+/* ----------------------------------------------------------------------
+ * Struct, enum and function declarations
+ */
+
+enum {
+    COL_BACKGROUND,
+    COL_FOREGROUND,
+    COL_LINEUNKNOWN,
+    COL_HIGHLIGHT,
+    COL_MISTAKE,
+    COL_SATISFIED,
+    COL_FAINT,
+    NCOLOURS
+};
+
+struct game_state {
+    grid *game_grid; /* ref-counted (internally) */
+
+    /* Put -1 in a face that doesn't get a clue */
+    signed char *clues;
+
+    /* Array of line states, to store whether each line is
+     * YES, NO or UNKNOWN */
+    char *lines;
+
+    unsigned char *line_errors;
+    int exactly_one_loop;
+
+    int solved;
+    int cheated;
+
+    /* Used in game_text_format(), so that it knows what type of
+     * grid it's trying to render as ASCII text. */
+    int grid_type;
+};
+
+enum solver_status {
+    SOLVER_SOLVED,    /* This is the only solution the solver could find */
+    SOLVER_MISTAKE,   /* This is definitely not a solution */
+    SOLVER_AMBIGUOUS, /* This _might_ be an ambiguous solution */
+    SOLVER_INCOMPLETE /* This may be a partial solution */
+};
+
+/* ------ Solver state ------ */
+typedef struct solver_state {
+    game_state *state;
+    enum solver_status solver_status;
+    /* NB looplen is the number of dots that are joined together at a point, ie a
+     * looplen of 1 means there are no lines to a particular dot */
+    int *looplen;
+
+    /* Difficulty level of solver.  Used by solver functions that want to
+     * vary their behaviour depending on the requested difficulty level. */
+    int diff;
+
+    /* caches */
+    char *dot_yes_count;
+    char *dot_no_count;
+    char *face_yes_count;
+    char *face_no_count;
+    char *dot_solved, *face_solved;
+    int *dotdsf;
+
+    /* Information for Normal level deductions:
+     * For each dline, store a bitmask for whether we know:
+     * (bit 0) at least one is YES
+     * (bit 1) at most one is YES */
+    char *dlines;
+
+    /* Hard level information */
+    int *linedsf;
+} solver_state;
+
+/*
+ * Difficulty levels. I do some macro ickery here to ensure that my
+ * enum and the various forms of my name list always match up.
+ */
+
+#define DIFFLIST(A) \
+    A(EASY,Easy,e) \
+    A(NORMAL,Normal,n) \
+    A(TRICKY,Tricky,t) \
+    A(HARD,Hard,h)
+#define ENUM(upper,title,lower) DIFF_ ## upper,
+#define TITLE(upper,title,lower) #title,
+#define ENCODE(upper,title,lower) #lower
+#define CONFIG(upper,title,lower) ":" #title
+enum { DIFFLIST(ENUM) DIFF_MAX };
+static char const *const diffnames[] = { DIFFLIST(TITLE) };
+static char const diffchars[] = DIFFLIST(ENCODE);
+#define DIFFCONFIG DIFFLIST(CONFIG)
+
+/*
+ * Solver routines, sorted roughly in order of computational cost.
+ * The solver will run the faster deductions first, and slower deductions are
+ * only invoked when the faster deductions are unable to make progress.
+ * Each function is associated with a difficulty level, so that the generated
+ * puzzles are solvable by applying only the functions with the chosen
+ * difficulty level or lower.
+ */
+#define SOLVERLIST(A) \
+    A(trivial_deductions, DIFF_EASY) \
+    A(dline_deductions, DIFF_NORMAL) \
+    A(linedsf_deductions, DIFF_HARD) \
+    A(loop_deductions, DIFF_EASY)
+#define SOLVER_FN_DECL(fn,diff) static int fn(solver_state *);
+#define SOLVER_FN(fn,diff) &fn,
+#define SOLVER_DIFF(fn,diff) diff,
+SOLVERLIST(SOLVER_FN_DECL)
+static int (*(solver_fns[]))(solver_state *) = { SOLVERLIST(SOLVER_FN) };
+static int const solver_diffs[] = { SOLVERLIST(SOLVER_DIFF) };
+static const int NUM_SOLVERS = sizeof(solver_diffs)/sizeof(*solver_diffs);
+
+struct game_params {
+    int w, h;
+    int diff;
+    int type;
+};
+
+/* line_drawstate is the same as line_state, but with the extra ERROR
+ * possibility.  The drawing code copies line_state to line_drawstate,
+ * except in the case that the line is an error. */
+enum line_state { LINE_YES, LINE_UNKNOWN, LINE_NO };
+enum line_drawstate { DS_LINE_YES, DS_LINE_UNKNOWN,
+                      DS_LINE_NO, DS_LINE_ERROR };
+
+#define OPP(line_state) \
+    (2 - line_state)
+
+
+struct game_drawstate {
+    int started;
+    int tilesize;
+    int flashing;
+    int *textx, *texty;
+    char *lines;
+    char *clue_error;
+    char *clue_satisfied;
+};
+
+static char *validate_desc(const game_params *params, const char *desc);
+static int dot_order(const game_state* state, int i, char line_type);
+static int face_order(const game_state* state, int i, char line_type);
+static solver_state *solve_game_rec(const solver_state *sstate);
+
+#ifdef DEBUG_CACHES
+static void check_caches(const solver_state* sstate);
+#else
+#define check_caches(s)
+#endif
+
+/* ------- List of grid generators ------- */
+#define GRIDLIST(A) \
+    A(Squares,GRID_SQUARE,3,3) \
+    A(Triangular,GRID_TRIANGULAR,3,3) \
+    A(Honeycomb,GRID_HONEYCOMB,3,3) \
+    A(Snub-Square,GRID_SNUBSQUARE,3,3) \
+    A(Cairo,GRID_CAIRO,3,4) \
+    A(Great-Hexagonal,GRID_GREATHEXAGONAL,3,3) \
+    A(Octagonal,GRID_OCTAGONAL,3,3) \
+    A(Kites,GRID_KITE,3,3) \
+    A(Floret,GRID_FLORET,1,2) \
+    A(Dodecagonal,GRID_DODECAGONAL,2,2) \
+    A(Great-Dodecagonal,GRID_GREATDODECAGONAL,2,2) \
+    A(Penrose (kite/dart),GRID_PENROSE_P2,3,3) \
+    A(Penrose (rhombs),GRID_PENROSE_P3,3,3)
+
+#define GRID_NAME(title,type,amin,omin) #title,
+#define GRID_CONFIG(title,type,amin,omin) ":" #title
+#define GRID_TYPE(title,type,amin,omin) type,
+#define GRID_SIZES(title,type,amin,omin) \
+    {amin, omin, \
+     "Width and height for this grid type must both be at least " #amin, \
+     "At least one of width and height for this grid type must be at least " #omin,},
+static char const *const gridnames[] = { GRIDLIST(GRID_NAME) };
+#define GRID_CONFIGS GRIDLIST(GRID_CONFIG)
+static grid_type grid_types[] = { GRIDLIST(GRID_TYPE) };
+#define NUM_GRID_TYPES (sizeof(grid_types) / sizeof(grid_types[0]))
+static const struct {
+    int amin, omin;
+    char *aerr, *oerr;
+} grid_size_limits[] = { GRIDLIST(GRID_SIZES) };
+
+/* Generates a (dynamically allocated) new grid, according to the
+ * type and size requested in params.  Does nothing if the grid is already
+ * generated. */
+static grid *loopy_generate_grid(const game_params *params,
+                                 const char *grid_desc)
+{
+    return grid_new(grid_types[params->type], params->w, params->h, grid_desc);
+}
+
+/* ----------------------------------------------------------------------
+ * Preprocessor magic
+ */
+
+/* General constants */
+#define PREFERRED_TILE_SIZE 32
+#define BORDER(tilesize) ((tilesize) / 2)
+#define FLASH_TIME 0.5F
+
+#define BIT_SET(field, bit) ((field) & (1<<(bit)))
+
+#define SET_BIT(field, bit)  (BIT_SET(field, bit) ? FALSE : \
+                              ((field) |= (1<<(bit)), TRUE))
+
+#define CLEAR_BIT(field, bit) (BIT_SET(field, bit) ? \
+                               ((field) &= ~(1<<(bit)), TRUE) : FALSE)
+
+#define CLUE2CHAR(c) \
+    ((c < 0) ? ' ' : c < 10 ? c + '0' : c - 10 + 'A')
+
+/* ----------------------------------------------------------------------
+ * General struct manipulation and other straightforward code
+ */
+
+static game_state *dup_game(const game_state *state)
+{
+    game_state *ret = snew(game_state);
+
+    ret->game_grid = state->game_grid;
+    ret->game_grid->refcount++;
+
+    ret->solved = state->solved;
+    ret->cheated = state->cheated;
+
+    ret->clues = snewn(state->game_grid->num_faces, signed char);
+    memcpy(ret->clues, state->clues, state->game_grid->num_faces);
+
+    ret->lines = snewn(state->game_grid->num_edges, char);
+    memcpy(ret->lines, state->lines, state->game_grid->num_edges);
+
+    ret->line_errors = snewn(state->game_grid->num_edges, unsigned char);
+    memcpy(ret->line_errors, state->line_errors, state->game_grid->num_edges);
+    ret->exactly_one_loop = state->exactly_one_loop;
+
+    ret->grid_type = state->grid_type;
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    if (state) {
+        grid_free(state->game_grid);
+        sfree(state->clues);
+        sfree(state->lines);
+        sfree(state->line_errors);
+        sfree(state);
+    }
+}
+
+static solver_state *new_solver_state(const game_state *state, int diff) {
+    int i;
+    int num_dots = state->game_grid->num_dots;
+    int num_faces = state->game_grid->num_faces;
+    int num_edges = state->game_grid->num_edges;
+    solver_state *ret = snew(solver_state);
+
+    ret->state = dup_game(state);
+
+    ret->solver_status = SOLVER_INCOMPLETE;
+    ret->diff = diff;
+
+    ret->dotdsf = snew_dsf(num_dots);
+    ret->looplen = snewn(num_dots, int);
+
+    for (i = 0; i < num_dots; i++) {
+        ret->looplen[i] = 1;
+    }
+
+    ret->dot_solved = snewn(num_dots, char);
+    ret->face_solved = snewn(num_faces, char);
+    memset(ret->dot_solved, FALSE, num_dots);
+    memset(ret->face_solved, FALSE, num_faces);
+
+    ret->dot_yes_count = snewn(num_dots, char);
+    memset(ret->dot_yes_count, 0, num_dots);
+    ret->dot_no_count = snewn(num_dots, char);
+    memset(ret->dot_no_count, 0, num_dots);
+    ret->face_yes_count = snewn(num_faces, char);
+    memset(ret->face_yes_count, 0, num_faces);
+    ret->face_no_count = snewn(num_faces, char);
+    memset(ret->face_no_count, 0, num_faces);
+
+    if (diff < DIFF_NORMAL) {
+        ret->dlines = NULL;
+    } else {
+        ret->dlines = snewn(2*num_edges, char);
+        memset(ret->dlines, 0, 2*num_edges);
+    }
+
+    if (diff < DIFF_HARD) {
+        ret->linedsf = NULL;
+    } else {
+        ret->linedsf = snew_dsf(state->game_grid->num_edges);
+    }
+
+    return ret;
+}
+
+static void free_solver_state(solver_state *sstate) {
+    if (sstate) {
+        free_game(sstate->state);
+        sfree(sstate->dotdsf);
+        sfree(sstate->looplen);
+        sfree(sstate->dot_solved);
+        sfree(sstate->face_solved);
+        sfree(sstate->dot_yes_count);
+        sfree(sstate->dot_no_count);
+        sfree(sstate->face_yes_count);
+        sfree(sstate->face_no_count);
+
+        /* OK, because sfree(NULL) is a no-op */
+        sfree(sstate->dlines);
+        sfree(sstate->linedsf);
+
+        sfree(sstate);
+    }
+}
+
+static solver_state *dup_solver_state(const solver_state *sstate) {
+    game_state *state = sstate->state;
+    int num_dots = state->game_grid->num_dots;
+    int num_faces = state->game_grid->num_faces;
+    int num_edges = state->game_grid->num_edges;
+    solver_state *ret = snew(solver_state);
+
+    ret->state = state = dup_game(sstate->state);
+
+    ret->solver_status = sstate->solver_status;
+    ret->diff = sstate->diff;
+
+    ret->dotdsf = snewn(num_dots, int);
+    ret->looplen = snewn(num_dots, int);
+    memcpy(ret->dotdsf, sstate->dotdsf,
+           num_dots * sizeof(int));
+    memcpy(ret->looplen, sstate->looplen,
+           num_dots * sizeof(int));
+
+    ret->dot_solved = snewn(num_dots, char);
+    ret->face_solved = snewn(num_faces, char);
+    memcpy(ret->dot_solved, sstate->dot_solved, num_dots);
+    memcpy(ret->face_solved, sstate->face_solved, num_faces);
+
+    ret->dot_yes_count = snewn(num_dots, char);
+    memcpy(ret->dot_yes_count, sstate->dot_yes_count, num_dots);
+    ret->dot_no_count = snewn(num_dots, char);
+    memcpy(ret->dot_no_count, sstate->dot_no_count, num_dots);
+
+    ret->face_yes_count = snewn(num_faces, char);
+    memcpy(ret->face_yes_count, sstate->face_yes_count, num_faces);
+    ret->face_no_count = snewn(num_faces, char);
+    memcpy(ret->face_no_count, sstate->face_no_count, num_faces);
+
+    if (sstate->dlines) {
+        ret->dlines = snewn(2*num_edges, char);
+        memcpy(ret->dlines, sstate->dlines,
+               2*num_edges);
+    } else {
+        ret->dlines = NULL;
+    }
+
+    if (sstate->linedsf) {
+        ret->linedsf = snewn(num_edges, int);
+        memcpy(ret->linedsf, sstate->linedsf,
+               num_edges * sizeof(int));
+    } else {
+        ret->linedsf = NULL;
+    }
+
+    return ret;
+}
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+
+#ifdef SLOW_SYSTEM
+    ret->h = 7;
+    ret->w = 7;
+#else
+    ret->h = 10;
+    ret->w = 10;
+#endif
+    ret->diff = DIFF_EASY;
+    ret->type = 0;
+
+    return ret;
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+
+    *ret = *params;                       /* structure copy */
+    return ret;
+}
+
+static const game_params presets[] = {
+#ifdef SMALL_SCREEN
+    {  7,  7, DIFF_EASY, 0 },
+    {  7,  7, DIFF_NORMAL, 0 },
+    {  7,  7, DIFF_HARD, 0 },
+    {  7,  7, DIFF_HARD, 1 },
+    {  7,  7, DIFF_HARD, 2 },
+    {  5,  5, DIFF_HARD, 3 },
+    {  7,  7, DIFF_HARD, 4 },
+    {  5,  4, DIFF_HARD, 5 },
+    {  5,  5, DIFF_HARD, 6 },
+    {  5,  5, DIFF_HARD, 7 },
+    {  3,  3, DIFF_HARD, 8 },
+    {  3,  3, DIFF_HARD, 9 },
+    {  3,  3, DIFF_HARD, 10 },
+    {  6,  6, DIFF_HARD, 11 },
+    {  6,  6, DIFF_HARD, 12 },
+#else
+    {  7,  7, DIFF_EASY, 0 },
+    {  10,  10, DIFF_EASY, 0 },
+    {  7,  7, DIFF_NORMAL, 0 },
+    {  10,  10, DIFF_NORMAL, 0 },
+    {  7,  7, DIFF_HARD, 0 },
+    {  10,  10, DIFF_HARD, 0 },
+    {  10,  10, DIFF_HARD, 1 },
+    {  12,  10, DIFF_HARD, 2 },
+    {  7,  7, DIFF_HARD, 3 },
+    {  9,  9, DIFF_HARD, 4 },
+    {  5,  4, DIFF_HARD, 5 },
+    {  7,  7, DIFF_HARD, 6 },
+    {  5,  5, DIFF_HARD, 7 },
+    {  5,  5, DIFF_HARD, 8 },
+    {  5,  4, DIFF_HARD, 9 },
+    {  5,  4, DIFF_HARD, 10 },
+    {  10, 10, DIFF_HARD, 11 },
+    {  10, 10, DIFF_HARD, 12 }
+#endif
+};
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params *tmppar;
+    char buf[80];
+
+    if (i < 0 || i >= lenof(presets))
+        return FALSE;
+
+    tmppar = snew(game_params);
+    *tmppar = presets[i];
+    *params = tmppar;
+    sprintf(buf, "%dx%d %s - %s", tmppar->h, tmppar->w,
+            gridnames[tmppar->type], diffnames[tmppar->diff]);
+    *name = dupstr(buf);
+
+    return TRUE;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static void decode_params(game_params *params, char const *string)
+{
+    params->h = params->w = atoi(string);
+    params->diff = DIFF_EASY;
+    while (*string && isdigit((unsigned char)*string)) string++;
+    if (*string == 'x') {
+        string++;
+        params->h = atoi(string);
+        while (*string && isdigit((unsigned char)*string)) string++;
+    }
+    if (*string == 't') {
+        string++;
+        params->type = atoi(string);
+        while (*string && isdigit((unsigned char)*string)) string++;
+    }
+    if (*string == 'd') {
+        int i;
+        string++;
+        for (i = 0; i < DIFF_MAX; i++)
+            if (*string == diffchars[i])
+                params->diff = i;
+        if (*string) string++;
+    }
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char str[80];
+    sprintf(str, "%dx%dt%d", params->w, params->h, params->type);
+    if (full)
+        sprintf(str + strlen(str), "d%c", diffchars[params->diff]);
+    return dupstr(str);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+    char buf[80];
+
+    ret = snewn(5, config_item);
+
+    ret[0].name = "Width";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->w);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Height";
+    ret[1].type = C_STRING;
+    sprintf(buf, "%d", params->h);
+    ret[1].sval = dupstr(buf);
+    ret[1].ival = 0;
+
+    ret[2].name = "Grid type";
+    ret[2].type = C_CHOICES;
+    ret[2].sval = GRID_CONFIGS;
+    ret[2].ival = params->type;
+
+    ret[3].name = "Difficulty";
+    ret[3].type = C_CHOICES;
+    ret[3].sval = DIFFCONFIG;
+    ret[3].ival = params->diff;
+
+    ret[4].name = NULL;
+    ret[4].type = C_END;
+    ret[4].sval = NULL;
+    ret[4].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = atoi(cfg[0].sval);
+    ret->h = atoi(cfg[1].sval);
+    ret->type = cfg[2].ival;
+    ret->diff = cfg[3].ival;
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    if (params->type < 0 || params->type >= NUM_GRID_TYPES)
+        return "Illegal grid type";
+    if (params->w < grid_size_limits[params->type].amin ||
+       params->h < grid_size_limits[params->type].amin)
+        return grid_size_limits[params->type].aerr;
+    if (params->w < grid_size_limits[params->type].omin &&
+       params->h < grid_size_limits[params->type].omin)
+        return grid_size_limits[params->type].oerr;
+
+    /*
+     * This shouldn't be able to happen at all, since decode_params
+     * and custom_params will never generate anything that isn't
+     * within range.
+     */
+    assert(params->diff < DIFF_MAX);
+
+    return NULL;
+}
+
+/* Returns a newly allocated string describing the current puzzle */
+static char *state_to_text(const game_state *state)
+{
+    grid *g = state->game_grid;
+    char *retval;
+    int num_faces = g->num_faces;
+    char *description = snewn(num_faces + 1, char);
+    char *dp = description;
+    int empty_count = 0;
+    int i;
+
+    for (i = 0; i < num_faces; i++) {
+        if (state->clues[i] < 0) {
+            if (empty_count > 25) {
+                dp += sprintf(dp, "%c", (int)(empty_count + 'a' - 1));
+                empty_count = 0;
+            }
+            empty_count++;
+        } else {
+            if (empty_count) {
+                dp += sprintf(dp, "%c", (int)(empty_count + 'a' - 1));
+                empty_count = 0;
+            }
+            dp += sprintf(dp, "%c", (int)CLUE2CHAR(state->clues[i]));
+        }
+    }
+
+    if (empty_count)
+        dp += sprintf(dp, "%c", (int)(empty_count + 'a' - 1));
+
+    retval = dupstr(description);
+    sfree(description);
+
+    return retval;
+}
+
+#define GRID_DESC_SEP '_'
+
+/* Splits up a (optional) grid_desc from the game desc. Returns the
+ * grid_desc (which needs freeing) and updates the desc pointer to
+ * start of real desc, or returns NULL if no desc. */
+static char *extract_grid_desc(const char **desc)
+{
+    char *sep = strchr(*desc, GRID_DESC_SEP), *gd;
+    int gd_len;
+
+    if (!sep) return NULL;
+
+    gd_len = sep - (*desc);
+    gd = snewn(gd_len+1, char);
+    memcpy(gd, *desc, gd_len);
+    gd[gd_len] = '\0';
+
+    *desc = sep+1;
+
+    return gd;
+}
+
+/* We require that the params pass the test in validate_params and that the
+ * description fills the entire game area */
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    int count = 0;
+    grid *g;
+    char *grid_desc, *ret;
+
+    /* It's pretty inefficient to do this just for validation. All we need to
+     * know is the precise number of faces. */
+    grid_desc = extract_grid_desc(&desc);
+    ret = grid_validate_desc(grid_types[params->type], params->w, params->h, grid_desc);
+    if (ret) return ret;
+
+    g = loopy_generate_grid(params, grid_desc);
+    if (grid_desc) sfree(grid_desc);
+
+    for (; *desc; ++desc) {
+        if ((*desc >= '0' && *desc <= '9') || (*desc >= 'A' && *desc <= 'Z')) {
+            count++;
+            continue;
+        }
+        if (*desc >= 'a') {
+            count += *desc - 'a' + 1;
+            continue;
+        }
+        return "Unknown character in description";
+    }
+
+    if (count < g->num_faces)
+        return "Description too short for board size";
+    if (count > g->num_faces)
+        return "Description too long for board size";
+
+    grid_free(g);
+
+    return NULL;
+}
+
+/* Sums the lengths of the numbers in range [0,n) */
+/* See equivalent function in solo.c for justification of this. */
+static int len_0_to_n(int n)
+{
+    int len = 1; /* Counting 0 as a bit of a special case */
+    int i;
+
+    for (i = 1; i < n; i *= 10) {
+        len += max(n - i, 0);
+    }
+
+    return len;
+}
+
+static char *encode_solve_move(const game_state *state)
+{
+    int len;
+    char *ret, *p;
+    int i;
+    int num_edges = state->game_grid->num_edges;
+
+    /* This is going to return a string representing the moves needed to set
+     * every line in a grid to be the same as the ones in 'state'.  The exact
+     * length of this string is predictable. */
+
+    len = 1;  /* Count the 'S' prefix */
+    /* Numbers in all lines */
+    len += len_0_to_n(num_edges);
+    /* For each line we also have a letter */
+    len += num_edges;
+
+    ret = snewn(len + 1, char);
+    p = ret;
+
+    p += sprintf(p, "S");
+
+    for (i = 0; i < num_edges; i++) {
+        switch (state->lines[i]) {
+         case LINE_YES:
+           p += sprintf(p, "%dy", i);
+           break;
+         case LINE_NO:
+           p += sprintf(p, "%dn", i);
+           break;
+        }
+    }
+
+    /* No point in doing sums like that if they're going to be wrong */
+    assert(strlen(ret) <= (size_t)len);
+    return ret;
+}
+
+static game_ui *new_ui(const game_state *state)
+{
+    return NULL;
+}
+
+static void free_ui(game_ui *ui)
+{
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+}
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    int grid_width, grid_height, rendered_width, rendered_height;
+    int g_tilesize;
+
+    grid_compute_size(grid_types[params->type], params->w, params->h,
+                      &g_tilesize, &grid_width, &grid_height);
+
+    /* multiply first to minimise rounding error on integer division */
+    rendered_width = grid_width * tilesize / g_tilesize;
+    rendered_height = grid_height * tilesize / g_tilesize;
+    *x = rendered_width + 2 * BORDER(tilesize) + 1;
+    *y = rendered_height + 2 * BORDER(tilesize) + 1;
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+
+    frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
+
+    ret[COL_FOREGROUND * 3 + 0] = 0.0F;
+    ret[COL_FOREGROUND * 3 + 1] = 0.0F;
+    ret[COL_FOREGROUND * 3 + 2] = 0.0F;
+
+    /*
+     * We want COL_LINEUNKNOWN to be a yellow which is a bit darker
+     * than the background. (I previously set it to 0.8,0.8,0, but
+     * found that this went badly with the 0.8,0.8,0.8 favoured as a
+     * background by the Java frontend.)
+     */
+    ret[COL_LINEUNKNOWN * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 0.9F;
+    ret[COL_LINEUNKNOWN * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 0.9F;
+    ret[COL_LINEUNKNOWN * 3 + 2] = 0.0F;
+
+    ret[COL_HIGHLIGHT * 3 + 0] = 1.0F;
+    ret[COL_HIGHLIGHT * 3 + 1] = 1.0F;
+    ret[COL_HIGHLIGHT * 3 + 2] = 1.0F;
+
+    ret[COL_MISTAKE * 3 + 0] = 1.0F;
+    ret[COL_MISTAKE * 3 + 1] = 0.0F;
+    ret[COL_MISTAKE * 3 + 2] = 0.0F;
+
+    ret[COL_SATISFIED * 3 + 0] = 0.0F;
+    ret[COL_SATISFIED * 3 + 1] = 0.0F;
+    ret[COL_SATISFIED * 3 + 2] = 0.0F;
+
+    /* We want the faint lines to be a bit darker than the background.
+     * Except if the background is pretty dark already; then it ought to be a
+     * bit lighter.  Oy vey.
+     */
+    ret[COL_FAINT * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 0.9F;
+    ret[COL_FAINT * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 0.9F;
+    ret[COL_FAINT * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] * 0.9F;
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    struct game_drawstate *ds = snew(struct game_drawstate);
+    int num_faces = state->game_grid->num_faces;
+    int num_edges = state->game_grid->num_edges;
+    int i;
+
+    ds->tilesize = 0;
+    ds->started = 0;
+    ds->lines = snewn(num_edges, char);
+    ds->clue_error = snewn(num_faces, char);
+    ds->clue_satisfied = snewn(num_faces, char);
+    ds->textx = snewn(num_faces, int);
+    ds->texty = snewn(num_faces, int);
+    ds->flashing = 0;
+
+    memset(ds->lines, LINE_UNKNOWN, num_edges);
+    memset(ds->clue_error, 0, num_faces);
+    memset(ds->clue_satisfied, 0, num_faces);
+    for (i = 0; i < num_faces; i++)
+        ds->textx[i] = ds->texty[i] = -1;
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds->textx);
+    sfree(ds->texty);
+    sfree(ds->clue_error);
+    sfree(ds->clue_satisfied);
+    sfree(ds->lines);
+    sfree(ds);
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return 0.0F;
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    if (params->type != 0)
+        return FALSE;
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    int w, h, W, H;
+    int x, y, i;
+    int cell_size;
+    char *ret;
+    grid *g = state->game_grid;
+    grid_face *f;
+
+    assert(state->grid_type == 0);
+
+    /* Work out the basic size unit */
+    f = g->faces; /* first face */
+    assert(f->order == 4);
+    /* The dots are ordered clockwise, so the two opposite
+     * corners are guaranteed to span the square */
+    cell_size = abs(f->dots[0]->x - f->dots[2]->x);
+
+    w = (g->highest_x - g->lowest_x) / cell_size;
+    h = (g->highest_y - g->lowest_y) / cell_size;
+
+    /* Create a blank "canvas" to "draw" on */
+    W = 2 * w + 2;
+    H = 2 * h + 1;
+    ret = snewn(W * H + 1, char);
+    for (y = 0; y < H; y++) {
+        for (x = 0; x < W-1; x++) {
+            ret[y*W + x] = ' ';
+        }
+        ret[y*W + W-1] = '\n';
+    }
+    ret[H*W] = '\0';
+
+    /* Fill in edge info */
+    for (i = 0; i < g->num_edges; i++) {
+        grid_edge *e = g->edges + i;
+        /* Cell coordinates, from (0,0) to (w-1,h-1) */
+        int x1 = (e->dot1->x - g->lowest_x) / cell_size;
+        int x2 = (e->dot2->x - g->lowest_x) / cell_size;
+        int y1 = (e->dot1->y - g->lowest_y) / cell_size;
+        int y2 = (e->dot2->y - g->lowest_y) / cell_size;
+        /* Midpoint, in canvas coordinates (canvas coordinates are just twice
+         * cell coordinates) */
+        x = x1 + x2;
+        y = y1 + y2;
+        switch (state->lines[i]) {
+         case LINE_YES:
+           ret[y*W + x] = (y1 == y2) ? '-' : '|';
+           break;
+         case LINE_NO:
+           ret[y*W + x] = 'x';
+           break;
+         case LINE_UNKNOWN:
+           break; /* already a space */
+         default:
+           assert(!"Illegal line state");
+        }
+    }
+
+    /* Fill in clues */
+    for (i = 0; i < g->num_faces; i++) {
+       int x1, x2, y1, y2;
+
+        f = g->faces + i;
+        assert(f->order == 4);
+        /* Cell coordinates, from (0,0) to (w-1,h-1) */
+       x1 = (f->dots[0]->x - g->lowest_x) / cell_size;
+       x2 = (f->dots[2]->x - g->lowest_x) / cell_size;
+       y1 = (f->dots[0]->y - g->lowest_y) / cell_size;
+       y2 = (f->dots[2]->y - g->lowest_y) / cell_size;
+        /* Midpoint, in canvas coordinates */
+        x = x1 + x2;
+        y = y1 + y2;
+        ret[y*W + x] = CLUE2CHAR(state->clues[i]);
+    }
+    return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * Debug code
+ */
+
+#ifdef DEBUG_CACHES
+static void check_caches(const solver_state* sstate)
+{
+    int i;
+    const game_state *state = sstate->state;
+    const grid *g = state->game_grid;
+
+    for (i = 0; i < g->num_dots; i++) {
+        assert(dot_order(state, i, LINE_YES) == sstate->dot_yes_count[i]);
+        assert(dot_order(state, i, LINE_NO) == sstate->dot_no_count[i]);
+    }
+
+    for (i = 0; i < g->num_faces; i++) {
+        assert(face_order(state, i, LINE_YES) == sstate->face_yes_count[i]);
+        assert(face_order(state, i, LINE_NO) == sstate->face_no_count[i]);
+    }
+}
+
+#if 0
+#define check_caches(s) \
+    do { \
+        fprintf(stderr, "check_caches at line %d\n", __LINE__); \
+        check_caches(s); \
+    } while (0)
+#endif
+#endif /* DEBUG_CACHES */
+
+/* ----------------------------------------------------------------------
+ * Solver utility functions
+ */
+
+/* Sets the line (with index i) to the new state 'line_new', and updates
+ * the cached counts of any affected faces and dots.
+ * Returns TRUE if this actually changed the line's state. */
+static int solver_set_line(solver_state *sstate, int i,
+                           enum line_state line_new
+#ifdef SHOW_WORKING
+                          , const char *reason
+#endif
+                          )
+{
+    game_state *state = sstate->state;
+    grid *g;
+    grid_edge *e;
+
+    assert(line_new != LINE_UNKNOWN);
+
+    check_caches(sstate);
+
+    if (state->lines[i] == line_new) {
+        return FALSE; /* nothing changed */
+    }
+    state->lines[i] = line_new;
+
+#ifdef SHOW_WORKING
+    fprintf(stderr, "solver: set line [%d] to %s (%s)\n",
+            i, line_new == LINE_YES ? "YES" : "NO",
+            reason);
+#endif
+
+    g = state->game_grid;
+    e = g->edges + i;
+
+    /* Update the cache for both dots and both faces affected by this. */
+    if (line_new == LINE_YES) {
+        sstate->dot_yes_count[e->dot1 - g->dots]++;
+        sstate->dot_yes_count[e->dot2 - g->dots]++;
+        if (e->face1) {
+            sstate->face_yes_count[e->face1 - g->faces]++;
+        }
+        if (e->face2) {
+            sstate->face_yes_count[e->face2 - g->faces]++;
+        }
+    } else {
+        sstate->dot_no_count[e->dot1 - g->dots]++;
+        sstate->dot_no_count[e->dot2 - g->dots]++;
+        if (e->face1) {
+            sstate->face_no_count[e->face1 - g->faces]++;
+        }
+        if (e->face2) {
+            sstate->face_no_count[e->face2 - g->faces]++;
+        }
+    }
+
+    check_caches(sstate);
+    return TRUE;
+}
+
+#ifdef SHOW_WORKING
+#define solver_set_line(a, b, c) \
+    solver_set_line(a, b, c, __FUNCTION__)
+#endif
+
+/*
+ * Merge two dots due to the existence of an edge between them.
+ * Updates the dsf tracking equivalence classes, and keeps track of
+ * the length of path each dot is currently a part of.
+ * Returns TRUE if the dots were already linked, ie if they are part of a
+ * closed loop, and false otherwise.
+ */
+static int merge_dots(solver_state *sstate, int edge_index)
+{
+    int i, j, len;
+    grid *g = sstate->state->game_grid;
+    grid_edge *e = g->edges + edge_index;
+
+    i = e->dot1 - g->dots;
+    j = e->dot2 - g->dots;
+
+    i = dsf_canonify(sstate->dotdsf, i);
+    j = dsf_canonify(sstate->dotdsf, j);
+
+    if (i == j) {
+        return TRUE;
+    } else {
+        len = sstate->looplen[i] + sstate->looplen[j];
+        dsf_merge(sstate->dotdsf, i, j);
+        i = dsf_canonify(sstate->dotdsf, i);
+        sstate->looplen[i] = len;
+        return FALSE;
+    }
+}
+
+/* Merge two lines because the solver has deduced that they must be either
+ * identical or opposite.   Returns TRUE if this is new information, otherwise
+ * FALSE. */
+static int merge_lines(solver_state *sstate, int i, int j, int inverse
+#ifdef SHOW_WORKING
+                       , const char *reason
+#endif
+                      )
+{
+    int inv_tmp;
+
+    assert(i < sstate->state->game_grid->num_edges);
+    assert(j < sstate->state->game_grid->num_edges);
+
+    i = edsf_canonify(sstate->linedsf, i, &inv_tmp);
+    inverse ^= inv_tmp;
+    j = edsf_canonify(sstate->linedsf, j, &inv_tmp);
+    inverse ^= inv_tmp;
+
+    edsf_merge(sstate->linedsf, i, j, inverse);
+
+#ifdef SHOW_WORKING
+    if (i != j) {
+        fprintf(stderr, "%s [%d] [%d] %s(%s)\n",
+                __FUNCTION__, i, j,
+                inverse ? "inverse " : "", reason);
+    }
+#endif
+    return (i != j);
+}
+
+#ifdef SHOW_WORKING
+#define merge_lines(a, b, c, d) \
+    merge_lines(a, b, c, d, __FUNCTION__)
+#endif
+
+/* Count the number of lines of a particular type currently going into the
+ * given dot. */
+static int dot_order(const game_state* state, int dot, char line_type)
+{
+    int n = 0;
+    grid *g = state->game_grid;
+    grid_dot *d = g->dots + dot;
+    int i;
+
+    for (i = 0; i < d->order; i++) {
+        grid_edge *e = d->edges[i];
+        if (state->lines[e - g->edges] == line_type)
+            ++n;
+    }
+    return n;
+}
+
+/* Count the number of lines of a particular type currently surrounding the
+ * given face */
+static int face_order(const game_state* state, int face, char line_type)
+{
+    int n = 0;
+    grid *g = state->game_grid;
+    grid_face *f = g->faces + face;
+    int i;
+
+    for (i = 0; i < f->order; i++) {
+        grid_edge *e = f->edges[i];
+        if (state->lines[e - g->edges] == line_type)
+            ++n;
+    }
+    return n;
+}
+
+/* Set all lines bordering a dot of type old_type to type new_type
+ * Return value tells caller whether this function actually did anything */
+static int dot_setall(solver_state *sstate, int dot,
+                     char old_type, char new_type)
+{
+    int retval = FALSE, r;
+    game_state *state = sstate->state;
+    grid *g;
+    grid_dot *d;
+    int i;
+
+    if (old_type == new_type)
+        return FALSE;
+
+    g = state->game_grid;
+    d = g->dots + dot;
+
+    for (i = 0; i < d->order; i++) {
+        int line_index = d->edges[i] - g->edges;
+        if (state->lines[line_index] == old_type) {
+            r = solver_set_line(sstate, line_index, new_type);
+            assert(r == TRUE);
+            retval = TRUE;
+        }
+    }
+    return retval;
+}
+
+/* Set all lines bordering a face of type old_type to type new_type */
+static int face_setall(solver_state *sstate, int face,
+                       char old_type, char new_type)
+{
+    int retval = FALSE, r;
+    game_state *state = sstate->state;
+    grid *g;
+    grid_face *f;
+    int i;
+
+    if (old_type == new_type)
+        return FALSE;
+
+    g = state->game_grid;
+    f = g->faces + face;
+
+    for (i = 0; i < f->order; i++) {
+        int line_index = f->edges[i] - g->edges;
+        if (state->lines[line_index] == old_type) {
+            r = solver_set_line(sstate, line_index, new_type);
+            assert(r == TRUE);
+            retval = TRUE;
+        }
+    }
+    return retval;
+}
+
+/* ----------------------------------------------------------------------
+ * Loop generation and clue removal
+ */
+
+static void add_full_clues(game_state *state, random_state *rs)
+{
+    signed char *clues = state->clues;
+    grid *g = state->game_grid;
+    char *board = snewn(g->num_faces, char);
+    int i;
+
+    generate_loop(g, board, rs, NULL, NULL);
+
+    /* Fill out all the clues by initialising to 0, then iterating over
+     * all edges and incrementing each clue as we find edges that border
+     * between BLACK/WHITE faces.  While we're at it, we verify that the
+     * algorithm does work, and there aren't any GREY faces still there. */
+    memset(clues, 0, g->num_faces);
+    for (i = 0; i < g->num_edges; i++) {
+        grid_edge *e = g->edges + i;
+        grid_face *f1 = e->face1;
+        grid_face *f2 = e->face2;
+        enum face_colour c1 = FACE_COLOUR(f1);
+        enum face_colour c2 = FACE_COLOUR(f2);
+        assert(c1 != FACE_GREY);
+        assert(c2 != FACE_GREY);
+        if (c1 != c2) {
+            if (f1) clues[f1 - g->faces]++;
+            if (f2) clues[f2 - g->faces]++;
+        }
+    }
+    sfree(board);
+}
+
+
+static int game_has_unique_soln(const game_state *state, int diff)
+{
+    int ret;
+    solver_state *sstate_new;
+    solver_state *sstate = new_solver_state((game_state *)state, diff);
+
+    sstate_new = solve_game_rec(sstate);
+
+    assert(sstate_new->solver_status != SOLVER_MISTAKE);
+    ret = (sstate_new->solver_status == SOLVER_SOLVED);
+
+    free_solver_state(sstate_new);
+    free_solver_state(sstate);
+
+    return ret;
+}
+
+
+/* Remove clues one at a time at random. */
+static game_state *remove_clues(game_state *state, random_state *rs,
+                                int diff)
+{
+    int *face_list;
+    int num_faces = state->game_grid->num_faces;
+    game_state *ret = dup_game(state), *saved_ret;
+    int n;
+
+    /* We need to remove some clues.  We'll do this by forming a list of all
+     * available clues, shuffling it, then going along one at a
+     * time clearing each clue in turn for which doing so doesn't render the
+     * board unsolvable. */
+    face_list = snewn(num_faces, int);
+    for (n = 0; n < num_faces; ++n) {
+        face_list[n] = n;
+    }
+
+    shuffle(face_list, num_faces, sizeof(int), rs);
+
+    for (n = 0; n < num_faces; ++n) {
+        saved_ret = dup_game(ret);
+        ret->clues[face_list[n]] = -1;
+
+        if (game_has_unique_soln(ret, diff)) {
+            free_game(saved_ret);
+        } else {
+            free_game(ret);
+            ret = saved_ret;
+        }
+    }
+    sfree(face_list);
+
+    return ret;
+}
+
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                           char **aux, int interactive)
+{
+    /* solution and description both use run-length encoding in obvious ways */
+    char *retval, *game_desc, *grid_desc;
+    grid *g;
+    game_state *state = snew(game_state);
+    game_state *state_new;
+
+    grid_desc = grid_new_desc(grid_types[params->type], params->w, params->h, rs);
+    state->game_grid = g = loopy_generate_grid(params, grid_desc);
+
+    state->clues = snewn(g->num_faces, signed char);
+    state->lines = snewn(g->num_edges, char);
+    state->line_errors = snewn(g->num_edges, unsigned char);
+    state->exactly_one_loop = FALSE;
+
+    state->grid_type = params->type;
+
+    newboard_please:
+
+    memset(state->lines, LINE_UNKNOWN, g->num_edges);
+    memset(state->line_errors, 0, g->num_edges);
+
+    state->solved = state->cheated = FALSE;
+
+    /* Get a new random solvable board with all its clues filled in.  Yes, this
+     * can loop for ever if the params are suitably unfavourable, but
+     * preventing games smaller than 4x4 seems to stop this happening */
+    do {
+        add_full_clues(state, rs);
+    } while (!game_has_unique_soln(state, params->diff));
+
+    state_new = remove_clues(state, rs, params->diff);
+    free_game(state);
+    state = state_new;
+
+
+    if (params->diff > 0 && game_has_unique_soln(state, params->diff-1)) {
+#ifdef SHOW_WORKING
+        fprintf(stderr, "Rejecting board, it is too easy\n");
+#endif
+        goto newboard_please;
+    }
+
+    game_desc = state_to_text(state);
+
+    free_game(state);
+
+    if (grid_desc) {
+        retval = snewn(strlen(grid_desc) + 1 + strlen(game_desc) + 1, char);
+        sprintf(retval, "%s%c%s", grid_desc, (int)GRID_DESC_SEP, game_desc);
+        sfree(grid_desc);
+        sfree(game_desc);
+    } else {
+        retval = game_desc;
+    }
+
+    assert(!validate_desc(params, retval));
+
+    return retval;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    int i;
+    game_state *state = snew(game_state);
+    int empties_to_make = 0;
+    int n,n2;
+    const char *dp;
+    char *grid_desc;
+    grid *g;
+    int num_faces, num_edges;
+
+    grid_desc = extract_grid_desc(&desc);
+    state->game_grid = g = loopy_generate_grid(params, grid_desc);
+    if (grid_desc) sfree(grid_desc);
+
+    dp = desc;
+
+    num_faces = g->num_faces;
+    num_edges = g->num_edges;
+
+    state->clues = snewn(num_faces, signed char);
+    state->lines = snewn(num_edges, char);
+    state->line_errors = snewn(num_edges, unsigned char);
+    state->exactly_one_loop = FALSE;
+
+    state->solved = state->cheated = FALSE;
+
+    state->grid_type = params->type;
+
+    for (i = 0; i < num_faces; i++) {
+        if (empties_to_make) {
+            empties_to_make--;
+            state->clues[i] = -1;
+            continue;
+        }
+
+        assert(*dp);
+        n = *dp - '0';
+        n2 = *dp - 'A' + 10;
+        if (n >= 0 && n < 10) {
+            state->clues[i] = n;
+       } else if (n2 >= 10 && n2 < 36) {
+            state->clues[i] = n2;
+        } else {
+            n = *dp - 'a' + 1;
+            assert(n > 0);
+            state->clues[i] = -1;
+            empties_to_make = n - 1;
+        }
+        ++dp;
+    }
+
+    memset(state->lines, LINE_UNKNOWN, num_edges);
+    memset(state->line_errors, 0, num_edges);
+    return state;
+}
+
+/* Calculates the line_errors data, and checks if the current state is a
+ * solution */
+static int check_completion(game_state *state)
+{
+    grid *g = state->game_grid;
+    int i, ret;
+    int *dsf, *component_state;
+    int nsilly, nloop, npath, largest_comp, largest_size, total_pathsize;
+    enum { COMP_NONE, COMP_LOOP, COMP_PATH, COMP_SILLY, COMP_EMPTY };
+
+    memset(state->line_errors, 0, g->num_edges);
+
+    /*
+     * Find loops in the grid, and determine whether the puzzle is
+     * solved.
+     *
+     * Loopy is a bit more complicated than most puzzles that care
+     * about loop detection. In most of them, loops are simply
+     * _forbidden_; so the obviously right way to do
+     * error-highlighting during play is to light up a graph edge red
+     * iff it is part of a loop, which is exactly what the centralised
+     * findloop.c makes easy.
+     *
+     * But Loopy is unusual in that you're _supposed_ to be making a
+     * loop - and yet _some_ loops are not the right loop. So we need
+     * to be more discriminating, by identifying loops one by one and
+     * then thinking about which ones to highlight, and so findloop.c
+     * isn't quite the right tool for the job in this case.
+     *
+     * Worse still, consider situations in which the grid contains a
+     * loop and also some non-loop edges: there are some cases like
+     * this in which the user's intuitive expectation would be to
+     * highlight the loop (if you're only about half way through the
+     * puzzle and have accidentally made a little loop in some corner
+     * of the grid), and others in which they'd be more likely to
+     * expect you to highlight the non-loop edges (if you've just
+     * closed off a whole loop that you thought was the entire
+     * solution, but forgot some disconnected edges in a corner
+     * somewhere). So while it's easy enough to check whether the
+     * solution is _right_, highlighting the wrong parts is a tricky
+     * problem for this puzzle!
+     *
+     * I'd quite like, in some situations, to identify the largest
+     * loop among the player's YES edges, and then light up everything
+     * other than that. But finding the longest cycle in a graph is an
+     * NP-complete problem (because, in particular, it must return a
+     * Hamilton cycle if one exists).
+     *
+     * However, I think we can make the problem tractable by
+     * exercising the Puzzles principle that it isn't absolutely
+     * necessary to highlight _all_ errors: the key point is that by
+     * the time the user has filled in the whole grid, they should
+     * either have seen a completion flash, or have _some_ error
+     * highlight showing them why the solution isn't right. So in
+     * principle it would be *just about* good enough to highlight
+     * just one error in the whole grid, if there was really no better
+     * way. But we'd like to highlight as many errors as possible.
+     *
+     * In this case, I think the simple approach is to make use of the
+     * fact that no vertex may have degree > 2, and that's really
+     * simple to detect. So the plan goes like this:
+     *
+     *  - Form the dsf of connected components of the graph vertices.
+     *
+     *  - Highlight an error at any vertex with degree > 2. (It so
+     *    happens that we do this by lighting up all the edges
+     *    incident to that vertex, but that's an output detail.)
+     *
+     *  - Any component that contains such a vertex is now excluded
+     *    from further consideration, because it already has a
+     *    highlight.
+     *
+     *  - The remaining components have no vertex with degree > 2, and
+     *    hence they all consist of either a simple loop, or a simple
+     *    path with two endpoints.
+     *
+     *  - For these purposes, group together all the paths and imagine
+     *    them to be a single component (because in most normal
+     *    situations the player will gradually build up the solution
+     *    _not_ all in one connected segment, but as lots of separate
+     *    little path pieces that gradually connect to each other).
+     *
+     *  - After doing that, if there is exactly one (sensible)
+     *    component - be it a collection of paths or a loop - then
+     *    highlight no further edge errors. (The former case is normal
+     *    during play, and the latter is a potentially solved puzzle.)
+     *
+     *  - Otherwise, find the largest of the sensible components,
+     *    leave that one unhighlighted, and light the rest up in red.
+     */
+
+    dsf = snew_dsf(g->num_dots);
+
+    /* Build the dsf. */
+    for (i = 0; i < g->num_edges; i++) {
+        if (state->lines[i] == LINE_YES) {
+            grid_edge *e = g->edges + i;
+            int d1 = e->dot1 - g->dots, d2 = e->dot2 - g->dots;
+            dsf_merge(dsf, d1, d2);
+        }
+    }
+
+    /* Initialise a state variable for each connected component. */
+    component_state = snewn(g->num_dots, int);
+    for (i = 0; i < g->num_dots; i++) {
+        if (dsf_canonify(dsf, i) == i)
+            component_state[i] = COMP_LOOP;
+        else
+            component_state[i] = COMP_NONE;
+    }
+
+    /* Check for dots with degree > 3. Here we also spot dots of
+     * degree 1 in which the user has marked all the non-edges as
+     * LINE_NO, because those are also clear vertex-level errors, so
+     * we give them the same treatment of excluding their connected
+     * component from the subsequent loop analysis. */
+    for (i = 0; i < g->num_dots; i++) {
+        int comp = dsf_canonify(dsf, i);
+        int yes = dot_order(state, i, LINE_YES);
+        int unknown = dot_order(state, i, LINE_UNKNOWN);
+        if ((yes == 1 && unknown == 0) || (yes >= 3)) {
+            /* violation, so mark all YES edges as errors */
+            grid_dot *d = g->dots + i;
+            int j;
+            for (j = 0; j < d->order; j++) {
+                int e = d->edges[j] - g->edges;
+                if (state->lines[e] == LINE_YES)
+                    state->line_errors[e] = TRUE;
+            }
+            /* And mark this component as not worthy of further
+             * consideration. */
+            component_state[comp] = COMP_SILLY;
+
+        } else if (yes == 0) {
+            /* A completely isolated dot must also be excluded it from
+             * the subsequent loop highlighting pass, but we tag it
+             * with a different enum value to avoid it counting
+             * towards the components that inhibit returning a win
+             * status. */
+            component_state[comp] = COMP_EMPTY;
+        } else if (yes == 1) {
+            /* A dot with degree 1 that didn't fall into the 'clearly
+             * erroneous' case above indicates that this connected
+             * component will be a path rather than a loop - unless
+             * something worse elsewhere in the component has
+             * classified it as silly. */
+            if (component_state[comp] != COMP_SILLY)
+                component_state[comp] = COMP_PATH;
+        }
+    }
+
+    /* Count up the components. Also, find the largest sensible
+     * component. (Tie-breaking condition is derived from the order of
+     * vertices in the grid data structure, which is fairly arbitrary
+     * but at least stays stable throughout the game.) */
+    nsilly = nloop = npath = 0;
+    total_pathsize = 0;
+    largest_comp = largest_size = -1;
+    for (i = 0; i < g->num_dots; i++) {
+        if (component_state[i] == COMP_SILLY) {
+            nsilly++;
+        } else if (component_state[i] == COMP_PATH) {
+            total_pathsize += dsf_size(dsf, i);
+            npath = 1;
+        } else if (component_state[i] == COMP_LOOP) {
+            int this_size;
+
+            nloop++;
+
+            if ((this_size = dsf_size(dsf, i)) > largest_size) {
+                largest_comp = i;
+                largest_size = this_size;
+            }
+        }
+    }
+    if (largest_size < total_pathsize) {
+        largest_comp = -1;             /* means the paths */
+        largest_size = total_pathsize;
+    }
+
+    if (nloop > 0 && nloop + npath > 1) {
+        /*
+         * If there are at least two sensible components including at
+         * least one loop, highlight all edges in every sensible
+         * component that is not the largest one.
+         */
+        for (i = 0; i < g->num_edges; i++) {
+            if (state->lines[i] == LINE_YES) {
+                grid_edge *e = g->edges + i;
+                int d1 = e->dot1 - g->dots; /* either endpoint is good enough */
+                int comp = dsf_canonify(dsf, d1);
+                if ((component_state[comp] == COMP_PATH &&
+                     -1 != largest_comp) ||
+                    (component_state[comp] == COMP_LOOP &&
+                     comp != largest_comp))
+                    state->line_errors[i] = TRUE;
+            }
+        }
+    }
+
+    if (nloop == 1 && npath == 0 && nsilly == 0) {
+        /*
+         * If there is exactly one component and it is a loop, then
+         * the puzzle is potentially complete, so check the clues.
+         */
+        ret = TRUE;
+
+        for (i = 0; i < g->num_faces; i++) {
+            int c = state->clues[i];
+            if (c >= 0 && face_order(state, i, LINE_YES) != c) {
+                ret = FALSE;
+                break;
+            }
+        }
+
+        /*
+         * Also, whether or not the puzzle is actually complete, set
+         * the flag that says this game_state has exactly one loop and
+         * nothing else, which will be used to vary the semantics of
+         * clue highlighting at display time.
+         */
+        state->exactly_one_loop = TRUE;
+    } else {
+        ret = FALSE;
+        state->exactly_one_loop = FALSE;
+    }
+
+    sfree(component_state);
+    sfree(dsf);
+
+    return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * Solver logic
+ *
+ * Our solver modes operate as follows.  Each mode also uses the modes above it.
+ *
+ *   Easy Mode
+ *   Just implement the rules of the game.
+ *
+ *   Normal and Tricky Modes
+ *   For each (adjacent) pair of lines through each dot we store a bit for
+ *   whether at least one of them is on and whether at most one is on.  (If we
+ *   know both or neither is on that's already stored more directly.)
+ *
+ *   Advanced Mode
+ *   Use edsf data structure to make equivalence classes of lines that are
+ *   known identical to or opposite to one another.
+ */
+
+
+/* DLines:
+ * For general grids, we consider "dlines" to be pairs of lines joined
+ * at a dot.  The lines must be adjacent around the dot, so we can think of
+ * a dline as being a dot+face combination.  Or, a dot+edge combination where
+ * the second edge is taken to be the next clockwise edge from the dot.
+ * Original loopy code didn't have this extra restriction of the lines being
+ * adjacent.  From my tests with square grids, this extra restriction seems to
+ * take little, if anything, away from the quality of the puzzles.
+ * A dline can be uniquely identified by an edge/dot combination, given that
+ * a dline-pair always goes clockwise around its common dot.  The edge/dot
+ * combination can be represented by an edge/bool combination - if bool is
+ * TRUE, use edge->dot1 else use edge->dot2.  So the total number of dlines is
+ * exactly twice the number of edges in the grid - although the dlines
+ * spanning the infinite face are not all that useful to the solver.
+ * Note that, by convention, a dline goes clockwise around its common dot,
+ * which means the dline goes anti-clockwise around its common face.
+ */
+
+/* Helper functions for obtaining an index into an array of dlines, given
+ * various information.  We assume the grid layout conventions about how
+ * the various lists are interleaved - see grid_make_consistent() for
+ * details. */
+
+/* i points to the first edge of the dline pair, reading clockwise around
+ * the dot. */
+static int dline_index_from_dot(grid *g, grid_dot *d, int i)
+{
+    grid_edge *e = d->edges[i];
+    int ret;
+#ifdef DEBUG_DLINES
+    grid_edge *e2;
+    int i2 = i+1;
+    if (i2 == d->order) i2 = 0;
+    e2 = d->edges[i2];
+#endif
+    ret = 2 * (e - g->edges) + ((e->dot1 == d) ? 1 : 0);
+#ifdef DEBUG_DLINES
+    printf("dline_index_from_dot: d=%d,i=%d, edges [%d,%d] - %d\n",
+           (int)(d - g->dots), i, (int)(e - g->edges),
+           (int)(e2 - g->edges), ret);
+#endif
+    return ret;
+}
+/* i points to the second edge of the dline pair, reading clockwise around
+ * the face.  That is, the edges of the dline, starting at edge{i}, read
+ * anti-clockwise around the face.  By layout conventions, the common dot
+ * of the dline will be f->dots[i] */
+static int dline_index_from_face(grid *g, grid_face *f, int i)
+{
+    grid_edge *e = f->edges[i];
+    grid_dot *d = f->dots[i];
+    int ret;
+#ifdef DEBUG_DLINES
+    grid_edge *e2;
+    int i2 = i - 1;
+    if (i2 < 0) i2 += f->order;
+    e2 = f->edges[i2];
+#endif
+    ret = 2 * (e - g->edges) + ((e->dot1 == d) ? 1 : 0);
+#ifdef DEBUG_DLINES
+    printf("dline_index_from_face: f=%d,i=%d, edges [%d,%d] - %d\n",
+           (int)(f - g->faces), i, (int)(e - g->edges),
+           (int)(e2 - g->edges), ret);
+#endif
+    return ret;
+}
+static int is_atleastone(const char *dline_array, int index)
+{
+    return BIT_SET(dline_array[index], 0);
+}
+static int set_atleastone(char *dline_array, int index)
+{
+    return SET_BIT(dline_array[index], 0);
+}
+static int is_atmostone(const char *dline_array, int index)
+{
+    return BIT_SET(dline_array[index], 1);
+}
+static int set_atmostone(char *dline_array, int index)
+{
+    return SET_BIT(dline_array[index], 1);
+}
+
+static void array_setall(char *array, char from, char to, int len)
+{
+    char *p = array, *p_old = p;
+    int len_remaining = len;
+
+    while ((p = memchr(p, from, len_remaining))) {
+        *p = to;
+        len_remaining -= p - p_old;
+        p_old = p;
+    }
+}
+
+/* Helper, called when doing dline dot deductions, in the case where we
+ * have 4 UNKNOWNs, and two of them (adjacent) have *exactly* one YES between
+ * them (because of dline atmostone/atleastone).
+ * On entry, edge points to the first of these two UNKNOWNs.  This function
+ * will find the opposite UNKNOWNS (if they are adjacent to one another)
+ * and set their corresponding dline to atleastone.  (Setting atmostone
+ * already happens in earlier dline deductions) */
+static int dline_set_opp_atleastone(solver_state *sstate,
+                                    grid_dot *d, int edge)
+{
+    game_state *state = sstate->state;
+    grid *g = state->game_grid;
+    int N = d->order;
+    int opp, opp2;
+    for (opp = 0; opp < N; opp++) {
+        int opp_dline_index;
+        if (opp == edge || opp == edge+1 || opp == edge-1)
+            continue;
+        if (opp == 0 && edge == N-1)
+            continue;
+        if (opp == N-1 && edge == 0)
+            continue;
+        opp2 = opp + 1;
+        if (opp2 == N) opp2 = 0;
+        /* Check if opp, opp2 point to LINE_UNKNOWNs */
+        if (state->lines[d->edges[opp] - g->edges] != LINE_UNKNOWN)
+            continue;
+        if (state->lines[d->edges[opp2] - g->edges] != LINE_UNKNOWN)
+            continue;
+        /* Found opposite UNKNOWNS and they're next to each other */
+        opp_dline_index = dline_index_from_dot(g, d, opp);
+        return set_atleastone(sstate->dlines, opp_dline_index);
+    }
+    return FALSE;
+}
+
+
+/* Set pairs of lines around this face which are known to be identical, to
+ * the given line_state */
+static int face_setall_identical(solver_state *sstate, int face_index,
+                                 enum line_state line_new)
+{
+    /* can[dir] contains the canonical line associated with the line in
+     * direction dir from the square in question.  Similarly inv[dir] is
+     * whether or not the line in question is inverse to its canonical
+     * element. */
+    int retval = FALSE;
+    game_state *state = sstate->state;
+    grid *g = state->game_grid;
+    grid_face *f = g->faces + face_index;
+    int N = f->order;
+    int i, j;
+    int can1, can2, inv1, inv2;
+
+    for (i = 0; i < N; i++) {
+        int line1_index = f->edges[i] - g->edges;
+        if (state->lines[line1_index] != LINE_UNKNOWN)
+            continue;
+        for (j = i + 1; j < N; j++) {
+            int line2_index = f->edges[j] - g->edges;
+            if (state->lines[line2_index] != LINE_UNKNOWN)
+                continue;
+
+            /* Found two UNKNOWNS */
+            can1 = edsf_canonify(sstate->linedsf, line1_index, &inv1);
+            can2 = edsf_canonify(sstate->linedsf, line2_index, &inv2);
+            if (can1 == can2 && inv1 == inv2) {
+                solver_set_line(sstate, line1_index, line_new);
+                solver_set_line(sstate, line2_index, line_new);
+            }
+        }
+    }
+    return retval;
+}
+
+/* Given a dot or face, and a count of LINE_UNKNOWNs, find them and
+ * return the edge indices into e. */
+static void find_unknowns(game_state *state,
+    grid_edge **edge_list, /* Edge list to search (from a face or a dot) */
+    int expected_count, /* Number of UNKNOWNs (comes from solver's cache) */
+    int *e /* Returned edge indices */)
+{
+    int c = 0;
+    grid *g = state->game_grid;
+    while (c < expected_count) {
+        int line_index = *edge_list - g->edges;
+        if (state->lines[line_index] == LINE_UNKNOWN) {
+            e[c] = line_index;
+            c++;
+        }
+        ++edge_list;
+    }
+}
+
+/* If we have a list of edges, and we know whether the number of YESs should
+ * be odd or even, and there are only a few UNKNOWNs, we can do some simple
+ * linedsf deductions.  This can be used for both face and dot deductions.
+ * Returns the difficulty level of the next solver that should be used,
+ * or DIFF_MAX if no progress was made. */
+static int parity_deductions(solver_state *sstate,
+    grid_edge **edge_list, /* Edge list (from a face or a dot) */
+    int total_parity, /* Expected number of YESs modulo 2 (either 0 or 1) */
+    int unknown_count)
+{
+    game_state *state = sstate->state;
+    int diff = DIFF_MAX;
+    int *linedsf = sstate->linedsf;
+
+    if (unknown_count == 2) {
+        /* Lines are known alike/opposite, depending on inv. */
+        int e[2];
+        find_unknowns(state, edge_list, 2, e);
+        if (merge_lines(sstate, e[0], e[1], total_parity))
+            diff = min(diff, DIFF_HARD);
+    } else if (unknown_count == 3) {
+        int e[3];
+        int can[3]; /* canonical edges */
+        int inv[3]; /* whether can[x] is inverse to e[x] */
+        find_unknowns(state, edge_list, 3, e);
+        can[0] = edsf_canonify(linedsf, e[0], inv);
+        can[1] = edsf_canonify(linedsf, e[1], inv+1);
+        can[2] = edsf_canonify(linedsf, e[2], inv+2);
+        if (can[0] == can[1]) {
+            if (solver_set_line(sstate, e[2], (total_parity^inv[0]^inv[1]) ?
+                               LINE_YES : LINE_NO))
+                diff = min(diff, DIFF_EASY);
+        }
+        if (can[0] == can[2]) {
+            if (solver_set_line(sstate, e[1], (total_parity^inv[0]^inv[2]) ?
+                               LINE_YES : LINE_NO))
+                diff = min(diff, DIFF_EASY);
+        }
+        if (can[1] == can[2]) {
+            if (solver_set_line(sstate, e[0], (total_parity^inv[1]^inv[2]) ?
+                               LINE_YES : LINE_NO))
+                diff = min(diff, DIFF_EASY);
+        }
+    } else if (unknown_count == 4) {
+        int e[4];
+        int can[4]; /* canonical edges */
+        int inv[4]; /* whether can[x] is inverse to e[x] */
+        find_unknowns(state, edge_list, 4, e);
+        can[0] = edsf_canonify(linedsf, e[0], inv);
+        can[1] = edsf_canonify(linedsf, e[1], inv+1);
+        can[2] = edsf_canonify(linedsf, e[2], inv+2);
+        can[3] = edsf_canonify(linedsf, e[3], inv+3);
+        if (can[0] == can[1]) {
+            if (merge_lines(sstate, e[2], e[3], total_parity^inv[0]^inv[1]))
+                diff = min(diff, DIFF_HARD);
+        } else if (can[0] == can[2]) {
+            if (merge_lines(sstate, e[1], e[3], total_parity^inv[0]^inv[2]))
+                diff = min(diff, DIFF_HARD);
+        } else if (can[0] == can[3]) {
+            if (merge_lines(sstate, e[1], e[2], total_parity^inv[0]^inv[3]))
+                diff = min(diff, DIFF_HARD);
+        } else if (can[1] == can[2]) {
+            if (merge_lines(sstate, e[0], e[3], total_parity^inv[1]^inv[2]))
+                diff = min(diff, DIFF_HARD);
+        } else if (can[1] == can[3]) {
+            if (merge_lines(sstate, e[0], e[2], total_parity^inv[1]^inv[3]))
+                diff = min(diff, DIFF_HARD);
+        } else if (can[2] == can[3]) {
+            if (merge_lines(sstate, e[0], e[1], total_parity^inv[2]^inv[3]))
+                diff = min(diff, DIFF_HARD);
+        }
+    }
+    return diff;
+}
+
+
+/*
+ * These are the main solver functions.
+ *
+ * Their return values are diff values corresponding to the lowest mode solver
+ * that would notice the work that they have done.  For example if the normal
+ * mode solver adds actual lines or crosses, it will return DIFF_EASY as the
+ * easy mode solver might be able to make progress using that.  It doesn't make
+ * sense for one of them to return a diff value higher than that of the
+ * function itself.
+ *
+ * Each function returns the lowest value it can, as early as possible, in
+ * order to try and pass as much work as possible back to the lower level
+ * solvers which progress more quickly.
+ */
+
+/* PROPOSED NEW DESIGN:
+ * We have a work queue consisting of 'events' notifying us that something has
+ * happened that a particular solver mode might be interested in.  For example
+ * the hard mode solver might do something that helps the normal mode solver at
+ * dot [x,y] in which case it will enqueue an event recording this fact.  Then
+ * we pull events off the work queue, and hand each in turn to the solver that
+ * is interested in them.  If a solver reports that it failed we pass the same
+ * event on to progressively more advanced solvers and the loop detector.  Once
+ * we've exhausted an event, or it has helped us progress, we drop it and
+ * continue to the next one.  The events are sorted first in order of solver
+ * complexity (easy first) then order of insertion (oldest first).
+ * Once we run out of events we loop over each permitted solver in turn
+ * (easiest first) until either a deduction is made (and an event therefore
+ * emerges) or no further deductions can be made (in which case we've failed).
+ *
+ * QUESTIONS:
+ *    * How do we 'loop over' a solver when both dots and squares are concerned.
+ *      Answer: first all squares then all dots.
+ */
+
+static int trivial_deductions(solver_state *sstate)
+{
+    int i, current_yes, current_no;
+    game_state *state = sstate->state;
+    grid *g = state->game_grid;
+    int diff = DIFF_MAX;
+
+    /* Per-face deductions */
+    for (i = 0; i < g->num_faces; i++) {
+        grid_face *f = g->faces + i;
+
+        if (sstate->face_solved[i])
+            continue;
+
+        current_yes = sstate->face_yes_count[i];
+        current_no  = sstate->face_no_count[i];
+
+        if (current_yes + current_no == f->order)  {
+            sstate->face_solved[i] = TRUE;
+            continue;
+        }
+
+        if (state->clues[i] < 0)
+            continue;
+
+        /*
+         * This code checks whether the numeric clue on a face is so
+         * large as to permit all its remaining LINE_UNKNOWNs to be
+         * filled in as LINE_YES, or alternatively so small as to
+         * permit them all to be filled in as LINE_NO.
+         */
+
+        if (state->clues[i] < current_yes) {
+            sstate->solver_status = SOLVER_MISTAKE;
+            return DIFF_EASY;
+        }
+        if (state->clues[i] == current_yes) {
+            if (face_setall(sstate, i, LINE_UNKNOWN, LINE_NO))
+                diff = min(diff, DIFF_EASY);
+            sstate->face_solved[i] = TRUE;
+            continue;
+        }
+
+        if (f->order - state->clues[i] < current_no) {
+            sstate->solver_status = SOLVER_MISTAKE;
+            return DIFF_EASY;
+        }
+        if (f->order - state->clues[i] == current_no) {
+            if (face_setall(sstate, i, LINE_UNKNOWN, LINE_YES))
+                diff = min(diff, DIFF_EASY);
+            sstate->face_solved[i] = TRUE;
+            continue;
+        }
+
+        if (f->order - state->clues[i] == current_no + 1 &&
+            f->order - current_yes - current_no > 2) {
+            /*
+             * One small refinement to the above: we also look for any
+             * adjacent pair of LINE_UNKNOWNs around the face with
+             * some LINE_YES incident on it from elsewhere. If we find
+             * one, then we know that pair of LINE_UNKNOWNs can't
+             * _both_ be LINE_YES, and hence that pushes us one line
+             * closer to being able to determine all the rest.
+             */
+            int j, k, e1, e2, e, d;
+
+            for (j = 0; j < f->order; j++) {
+                e1 = f->edges[j] - g->edges;
+                e2 = f->edges[j+1 < f->order ? j+1 : 0] - g->edges;
+
+                if (g->edges[e1].dot1 == g->edges[e2].dot1 ||
+                    g->edges[e1].dot1 == g->edges[e2].dot2) {
+                    d = g->edges[e1].dot1 - g->dots;
+                } else {
+                    assert(g->edges[e1].dot2 == g->edges[e2].dot1 ||
+                           g->edges[e1].dot2 == g->edges[e2].dot2);
+                    d = g->edges[e1].dot2 - g->dots;
+                }
+
+                if (state->lines[e1] == LINE_UNKNOWN &&
+                    state->lines[e2] == LINE_UNKNOWN) {
+                    for (k = 0; k < g->dots[d].order; k++) {
+                        int e = g->dots[d].edges[k] - g->edges;
+                        if (state->lines[e] == LINE_YES)
+                            goto found;    /* multi-level break */
+                    }
+                }
+            }
+            continue;
+
+          found:
+            /*
+             * If we get here, we've found such a pair of edges, and
+             * they're e1 and e2.
+             */
+            for (j = 0; j < f->order; j++) {
+                e = f->edges[j] - g->edges;
+                if (state->lines[e] == LINE_UNKNOWN && e != e1 && e != e2) {
+                    int r = solver_set_line(sstate, e, LINE_YES);
+                    assert(r);
+                    diff = min(diff, DIFF_EASY);
+                }
+            }
+        }
+    }
+
+    check_caches(sstate);
+
+    /* Per-dot deductions */
+    for (i = 0; i < g->num_dots; i++) {
+        grid_dot *d = g->dots + i;
+        int yes, no, unknown;
+
+        if (sstate->dot_solved[i])
+            continue;
+
+        yes = sstate->dot_yes_count[i];
+        no = sstate->dot_no_count[i];
+        unknown = d->order - yes - no;
+
+        if (yes == 0) {
+            if (unknown == 0) {
+                sstate->dot_solved[i] = TRUE;
+            } else if (unknown == 1) {
+                dot_setall(sstate, i, LINE_UNKNOWN, LINE_NO);
+                diff = min(diff, DIFF_EASY);
+                sstate->dot_solved[i] = TRUE;
+            }
+        } else if (yes == 1) {
+            if (unknown == 0) {
+                sstate->solver_status = SOLVER_MISTAKE;
+                return DIFF_EASY;
+            } else if (unknown == 1) {
+                dot_setall(sstate, i, LINE_UNKNOWN, LINE_YES);
+                diff = min(diff, DIFF_EASY);
+            }
+        } else if (yes == 2) {
+            if (unknown > 0) {
+                dot_setall(sstate, i, LINE_UNKNOWN, LINE_NO);
+                diff = min(diff, DIFF_EASY);
+            }
+            sstate->dot_solved[i] = TRUE;
+        } else {
+            sstate->solver_status = SOLVER_MISTAKE;
+            return DIFF_EASY;
+        }
+    }
+
+    check_caches(sstate);
+
+    return diff;
+}
+
+static int dline_deductions(solver_state *sstate)
+{
+    game_state *state = sstate->state;
+    grid *g = state->game_grid;
+    char *dlines = sstate->dlines;
+    int i;
+    int diff = DIFF_MAX;
+
+    /* ------ Face deductions ------ */
+
+    /* Given a set of dline atmostone/atleastone constraints, need to figure
+     * out if we can deduce any further info.  For more general faces than
+     * squares, this turns out to be a tricky problem.
+     * The approach taken here is to define (per face) NxN matrices:
+     * "maxs" and "mins".
+     * The entries maxs(j,k) and mins(j,k) define the upper and lower limits
+     * for the possible number of edges that are YES between positions j and k
+     * going clockwise around the face.  Can think of j and k as marking dots
+     * around the face (recall the labelling scheme: edge0 joins dot0 to dot1,
+     * edge1 joins dot1 to dot2 etc).
+     * Trivially, mins(j,j) = maxs(j,j) = 0, and we don't even bother storing
+     * these.  mins(j,j+1) and maxs(j,j+1) are determined by whether edge{j}
+     * is YES, NO or UNKNOWN.  mins(j,j+2) and maxs(j,j+2) are related to
+     * the dline atmostone/atleastone status for edges j and j+1.
+     *
+     * Then we calculate the remaining entries recursively.  We definitely
+     * know that
+     * mins(j,k) >= { mins(j,u) + mins(u,k) } for any u between j and k.
+     * This is because any valid placement of YESs between j and k must give
+     * a valid placement between j and u, and also between u and k.
+     * I believe it's sufficient to use just the two values of u:
+     * j+1 and j+2.  Seems to work well in practice - the bounds we compute
+     * are rigorous, even if they might not be best-possible.
+     *
+     * Once we have maxs and mins calculated, we can make inferences about
+     * each dline{j,j+1} by looking at the possible complementary edge-counts
+     * mins(j+2,j) and maxs(j+2,j) and comparing these with the face clue.
+     * As well as dlines, we can make similar inferences about single edges.
+     * For example, consider a pentagon with clue 3, and we know at most one
+     * of (edge0, edge1) is YES, and at most one of (edge2, edge3) is YES.
+     * We could then deduce edge4 is YES, because maxs(0,4) would be 2, so
+     * that final edge would have to be YES to make the count up to 3.
+     */
+
+    /* Much quicker to allocate arrays on the stack than the heap, so
+     * define the largest possible face size, and base our array allocations
+     * on that.  We check this with an assertion, in case someone decides to
+     * make a grid which has larger faces than this.  Note, this algorithm
+     * could get quite expensive if there are many large faces. */
+#define MAX_FACE_SIZE 12
+
+    for (i = 0; i < g->num_faces; i++) {
+        int maxs[MAX_FACE_SIZE][MAX_FACE_SIZE];
+        int mins[MAX_FACE_SIZE][MAX_FACE_SIZE];
+        grid_face *f = g->faces + i;
+        int N = f->order;
+        int j,m;
+        int clue = state->clues[i];
+        assert(N <= MAX_FACE_SIZE);
+        if (sstate->face_solved[i])
+            continue;
+        if (clue < 0) continue;
+
+        /* Calculate the (j,j+1) entries */
+        for (j = 0; j < N; j++) {
+            int edge_index = f->edges[j] - g->edges;
+            int dline_index;
+            enum line_state line1 = state->lines[edge_index];
+            enum line_state line2;
+            int tmp;
+            int k = j + 1;
+            if (k >= N) k = 0;
+            maxs[j][k] = (line1 == LINE_NO) ? 0 : 1;
+            mins[j][k] = (line1 == LINE_YES) ? 1 : 0;
+            /* Calculate the (j,j+2) entries */
+            dline_index = dline_index_from_face(g, f, k);
+            edge_index = f->edges[k] - g->edges;
+            line2 = state->lines[edge_index];
+            k++;
+            if (k >= N) k = 0;
+
+            /* max */
+            tmp = 2;
+            if (line1 == LINE_NO) tmp--;
+            if (line2 == LINE_NO) tmp--;
+            if (tmp == 2 && is_atmostone(dlines, dline_index))
+                tmp = 1;
+            maxs[j][k] = tmp;
+
+            /* min */
+            tmp = 0;
+            if (line1 == LINE_YES) tmp++;
+            if (line2 == LINE_YES) tmp++;
+            if (tmp == 0 && is_atleastone(dlines, dline_index))
+                tmp = 1;
+            mins[j][k] = tmp;
+        }
+
+        /* Calculate the (j,j+m) entries for m between 3 and N-1 */
+        for (m = 3; m < N; m++) {
+            for (j = 0; j < N; j++) {
+                int k = j + m;
+                int u = j + 1;
+                int v = j + 2;
+                int tmp;
+                if (k >= N) k -= N;
+                if (u >= N) u -= N;
+                if (v >= N) v -= N;
+                maxs[j][k] = maxs[j][u] + maxs[u][k];
+                mins[j][k] = mins[j][u] + mins[u][k];
+                tmp = maxs[j][v] + maxs[v][k];
+                maxs[j][k] = min(maxs[j][k], tmp);
+                tmp = mins[j][v] + mins[v][k];
+                mins[j][k] = max(mins[j][k], tmp);
+            }
+        }
+
+        /* See if we can make any deductions */
+        for (j = 0; j < N; j++) {
+            int k;
+            grid_edge *e = f->edges[j];
+            int line_index = e - g->edges;
+            int dline_index;
+
+            if (state->lines[line_index] != LINE_UNKNOWN)
+                continue;
+            k = j + 1;
+            if (k >= N) k = 0;
+
+            /* minimum YESs in the complement of this edge */
+            if (mins[k][j] > clue) {
+                sstate->solver_status = SOLVER_MISTAKE;
+                return DIFF_EASY;
+            }
+            if (mins[k][j] == clue) {
+                /* setting this edge to YES would make at least
+                 * (clue+1) edges - contradiction */
+                solver_set_line(sstate, line_index, LINE_NO);
+                diff = min(diff, DIFF_EASY);
+            }
+            if (maxs[k][j] < clue - 1) {
+                sstate->solver_status = SOLVER_MISTAKE;
+                return DIFF_EASY;
+            }
+            if (maxs[k][j] == clue - 1) {
+                /* Only way to satisfy the clue is to set edge{j} as YES */
+                solver_set_line(sstate, line_index, LINE_YES);
+                diff = min(diff, DIFF_EASY);
+            }
+
+            /* More advanced deduction that allows propagation along diagonal
+             * chains of faces connected by dots, for example, 3-2-...-2-3
+             * in square grids. */
+            if (sstate->diff >= DIFF_TRICKY) {
+                /* Now see if we can make dline deduction for edges{j,j+1} */
+                e = f->edges[k];
+                if (state->lines[e - g->edges] != LINE_UNKNOWN)
+                    /* Only worth doing this for an UNKNOWN,UNKNOWN pair.
+                     * Dlines where one of the edges is known, are handled in the
+                     * dot-deductions */
+                    continue;
+    
+                dline_index = dline_index_from_face(g, f, k);
+                k++;
+                if (k >= N) k = 0;
+    
+                /* minimum YESs in the complement of this dline */
+                if (mins[k][j] > clue - 2) {
+                    /* Adding 2 YESs would break the clue */
+                    if (set_atmostone(dlines, dline_index))
+                        diff = min(diff, DIFF_NORMAL);
+                }
+                /* maximum YESs in the complement of this dline */
+                if (maxs[k][j] < clue) {
+                    /* Adding 2 NOs would mean not enough YESs */
+                    if (set_atleastone(dlines, dline_index))
+                        diff = min(diff, DIFF_NORMAL);
+                }
+            }
+        }
+    }
+
+    if (diff < DIFF_NORMAL)
+        return diff;
+
+    /* ------ Dot deductions ------ */
+
+    for (i = 0; i < g->num_dots; i++) {
+        grid_dot *d = g->dots + i;
+        int N = d->order;
+        int yes, no, unknown;
+        int j;
+        if (sstate->dot_solved[i])
+            continue;
+        yes = sstate->dot_yes_count[i];
+        no = sstate->dot_no_count[i];
+        unknown = N - yes - no;
+
+        for (j = 0; j < N; j++) {
+            int k;
+            int dline_index;
+            int line1_index, line2_index;
+            enum line_state line1, line2;
+            k = j + 1;
+            if (k >= N) k = 0;
+            dline_index = dline_index_from_dot(g, d, j);
+            line1_index = d->edges[j] - g->edges;
+            line2_index = d->edges[k] - g->edges;
+            line1 = state->lines[line1_index];
+            line2 = state->lines[line2_index];
+
+            /* Infer dline state from line state */
+            if (line1 == LINE_NO || line2 == LINE_NO) {
+                if (set_atmostone(dlines, dline_index))
+                    diff = min(diff, DIFF_NORMAL);
+            }
+            if (line1 == LINE_YES || line2 == LINE_YES) {
+                if (set_atleastone(dlines, dline_index))
+                    diff = min(diff, DIFF_NORMAL);
+            }
+            /* Infer line state from dline state */
+            if (is_atmostone(dlines, dline_index)) {
+                if (line1 == LINE_YES && line2 == LINE_UNKNOWN) {
+                    solver_set_line(sstate, line2_index, LINE_NO);
+                    diff = min(diff, DIFF_EASY);
+                }
+                if (line2 == LINE_YES && line1 == LINE_UNKNOWN) {
+                    solver_set_line(sstate, line1_index, LINE_NO);
+                    diff = min(diff, DIFF_EASY);
+                }
+            }
+            if (is_atleastone(dlines, dline_index)) {
+                if (line1 == LINE_NO && line2 == LINE_UNKNOWN) {
+                    solver_set_line(sstate, line2_index, LINE_YES);
+                    diff = min(diff, DIFF_EASY);
+                }
+                if (line2 == LINE_NO && line1 == LINE_UNKNOWN) {
+                    solver_set_line(sstate, line1_index, LINE_YES);
+                    diff = min(diff, DIFF_EASY);
+                }
+            }
+            /* Deductions that depend on the numbers of lines.
+             * Only bother if both lines are UNKNOWN, otherwise the
+             * easy-mode solver (or deductions above) would have taken
+             * care of it. */
+            if (line1 != LINE_UNKNOWN || line2 != LINE_UNKNOWN)
+                continue;
+
+            if (yes == 0 && unknown == 2) {
+                /* Both these unknowns must be identical.  If we know
+                 * atmostone or atleastone, we can make progress. */
+                if (is_atmostone(dlines, dline_index)) {
+                    solver_set_line(sstate, line1_index, LINE_NO);
+                    solver_set_line(sstate, line2_index, LINE_NO);
+                    diff = min(diff, DIFF_EASY);
+                }
+                if (is_atleastone(dlines, dline_index)) {
+                    solver_set_line(sstate, line1_index, LINE_YES);
+                    solver_set_line(sstate, line2_index, LINE_YES);
+                    diff = min(diff, DIFF_EASY);
+                }
+            }
+            if (yes == 1) {
+                if (set_atmostone(dlines, dline_index))
+                    diff = min(diff, DIFF_NORMAL);
+                if (unknown == 2) {
+                    if (set_atleastone(dlines, dline_index))
+                        diff = min(diff, DIFF_NORMAL);
+                }
+            }
+
+            /* More advanced deduction that allows propagation along diagonal
+             * chains of faces connected by dots, for example: 3-2-...-2-3
+             * in square grids. */
+            if (sstate->diff >= DIFF_TRICKY) {
+                /* If we have atleastone set for this dline, infer
+                 * atmostone for each "opposite" dline (that is, each
+                 * dline without edges in common with this one).
+                 * Again, this test is only worth doing if both these
+                 * lines are UNKNOWN.  For if one of these lines were YES,
+                 * the (yes == 1) test above would kick in instead. */
+                if (is_atleastone(dlines, dline_index)) {
+                    int opp;
+                    for (opp = 0; opp < N; opp++) {
+                        int opp_dline_index;
+                        if (opp == j || opp == j+1 || opp == j-1)
+                            continue;
+                        if (j == 0 && opp == N-1)
+                            continue;
+                        if (j == N-1 && opp == 0)
+                            continue;
+                        opp_dline_index = dline_index_from_dot(g, d, opp);
+                        if (set_atmostone(dlines, opp_dline_index))
+                            diff = min(diff, DIFF_NORMAL);
+                    }
+                    if (yes == 0 && is_atmostone(dlines, dline_index)) {
+                        /* This dline has *exactly* one YES and there are no
+                         * other YESs.  This allows more deductions. */
+                        if (unknown == 3) {
+                            /* Third unknown must be YES */
+                            for (opp = 0; opp < N; opp++) {
+                                int opp_index;
+                                if (opp == j || opp == k)
+                                    continue;
+                                opp_index = d->edges[opp] - g->edges;
+                                if (state->lines[opp_index] == LINE_UNKNOWN) {
+                                    solver_set_line(sstate, opp_index,
+                                                    LINE_YES);
+                                    diff = min(diff, DIFF_EASY);
+                                }
+                            }
+                        } else if (unknown == 4) {
+                            /* Exactly one of opposite UNKNOWNS is YES.  We've
+                             * already set atmostone, so set atleastone as
+                             * well.
+                             */
+                            if (dline_set_opp_atleastone(sstate, d, j))
+                                diff = min(diff, DIFF_NORMAL);
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return diff;
+}
+
+static int linedsf_deductions(solver_state *sstate)
+{
+    game_state *state = sstate->state;
+    grid *g = state->game_grid;
+    char *dlines = sstate->dlines;
+    int i;
+    int diff = DIFF_MAX;
+    int diff_tmp;
+
+    /* ------ Face deductions ------ */
+
+    /* A fully-general linedsf deduction seems overly complicated
+     * (I suspect the problem is NP-complete, though in practice it might just
+     * be doable because faces are limited in size).
+     * For simplicity, we only consider *pairs* of LINE_UNKNOWNS that are
+     * known to be identical.  If setting them both to YES (or NO) would break
+     * the clue, set them to NO (or YES). */
+
+    for (i = 0; i < g->num_faces; i++) {
+        int N, yes, no, unknown;
+        int clue;
+
+        if (sstate->face_solved[i])
+            continue;
+        clue = state->clues[i];
+        if (clue < 0)
+            continue;
+
+        N = g->faces[i].order;
+        yes = sstate->face_yes_count[i];
+        if (yes + 1 == clue) {
+            if (face_setall_identical(sstate, i, LINE_NO))
+                diff = min(diff, DIFF_EASY);
+        }
+        no = sstate->face_no_count[i];
+        if (no + 1 == N - clue) {
+            if (face_setall_identical(sstate, i, LINE_YES))
+                diff = min(diff, DIFF_EASY);
+        }
+
+        /* Reload YES count, it might have changed */
+        yes = sstate->face_yes_count[i];
+        unknown = N - no - yes;
+
+        /* Deductions with small number of LINE_UNKNOWNs, based on overall
+         * parity of lines. */
+        diff_tmp = parity_deductions(sstate, g->faces[i].edges,
+                                     (clue - yes) % 2, unknown);
+        diff = min(diff, diff_tmp);
+    }
+
+    /* ------ Dot deductions ------ */
+    for (i = 0; i < g->num_dots; i++) {
+        grid_dot *d = g->dots + i;
+        int N = d->order;
+        int j;
+        int yes, no, unknown;
+        /* Go through dlines, and do any dline<->linedsf deductions wherever
+         * we find two UNKNOWNS. */
+        for (j = 0; j < N; j++) {
+            int dline_index = dline_index_from_dot(g, d, j);
+            int line1_index;
+            int line2_index;
+            int can1, can2, inv1, inv2;
+            int j2;
+            line1_index = d->edges[j] - g->edges;
+            if (state->lines[line1_index] != LINE_UNKNOWN)
+                continue;
+            j2 = j + 1;
+            if (j2 == N) j2 = 0;
+            line2_index = d->edges[j2] - g->edges;
+            if (state->lines[line2_index] != LINE_UNKNOWN)
+                continue;
+            /* Infer dline flags from linedsf */
+            can1 = edsf_canonify(sstate->linedsf, line1_index, &inv1);
+            can2 = edsf_canonify(sstate->linedsf, line2_index, &inv2);
+            if (can1 == can2 && inv1 != inv2) {
+                /* These are opposites, so set dline atmostone/atleastone */
+                if (set_atmostone(dlines, dline_index))
+                    diff = min(diff, DIFF_NORMAL);
+                if (set_atleastone(dlines, dline_index))
+                    diff = min(diff, DIFF_NORMAL);
+                continue;
+            }
+            /* Infer linedsf from dline flags */
+            if (is_atmostone(dlines, dline_index)
+               && is_atleastone(dlines, dline_index)) {
+                if (merge_lines(sstate, line1_index, line2_index, 1))
+                    diff = min(diff, DIFF_HARD);
+            }
+        }
+
+        /* Deductions with small number of LINE_UNKNOWNs, based on overall
+         * parity of lines. */
+        yes = sstate->dot_yes_count[i];
+        no = sstate->dot_no_count[i];
+        unknown = N - yes - no;
+        diff_tmp = parity_deductions(sstate, d->edges,
+                                     yes % 2, unknown);
+        diff = min(diff, diff_tmp);
+    }
+
+    /* ------ Edge dsf deductions ------ */
+
+    /* If the state of a line is known, deduce the state of its canonical line
+     * too, and vice versa. */
+    for (i = 0; i < g->num_edges; i++) {
+        int can, inv;
+        enum line_state s;
+        can = edsf_canonify(sstate->linedsf, i, &inv);
+        if (can == i)
+            continue;
+        s = sstate->state->lines[can];
+        if (s != LINE_UNKNOWN) {
+            if (solver_set_line(sstate, i, inv ? OPP(s) : s))
+                diff = min(diff, DIFF_EASY);
+        } else {
+            s = sstate->state->lines[i];
+            if (s != LINE_UNKNOWN) {
+                if (solver_set_line(sstate, can, inv ? OPP(s) : s))
+                    diff = min(diff, DIFF_EASY);
+            }
+        }
+    }
+
+    return diff;
+}
+
+static int loop_deductions(solver_state *sstate)
+{
+    int edgecount = 0, clues = 0, satclues = 0, sm1clues = 0;
+    game_state *state = sstate->state;
+    grid *g = state->game_grid;
+    int shortest_chainlen = g->num_dots;
+    int loop_found = FALSE;
+    int dots_connected;
+    int progress = FALSE;
+    int i;
+
+    /*
+     * Go through the grid and update for all the new edges.
+     * Since merge_dots() is idempotent, the simplest way to
+     * do this is just to update for _all_ the edges.
+     * Also, while we're here, we count the edges.
+     */
+    for (i = 0; i < g->num_edges; i++) {
+        if (state->lines[i] == LINE_YES) {
+            loop_found |= merge_dots(sstate, i);
+            edgecount++;
+        }
+    }
+
+    /*
+     * Count the clues, count the satisfied clues, and count the
+     * satisfied-minus-one clues.
+     */
+    for (i = 0; i < g->num_faces; i++) {
+        int c = state->clues[i];
+        if (c >= 0) {
+            int o = sstate->face_yes_count[i];
+            if (o == c)
+                satclues++;
+            else if (o == c-1)
+                sm1clues++;
+            clues++;
+        }
+    }
+
+    for (i = 0; i < g->num_dots; ++i) {
+        dots_connected =
+            sstate->looplen[dsf_canonify(sstate->dotdsf, i)];
+        if (dots_connected > 1)
+            shortest_chainlen = min(shortest_chainlen, dots_connected);
+    }
+
+    assert(sstate->solver_status == SOLVER_INCOMPLETE);
+
+    if (satclues == clues && shortest_chainlen == edgecount) {
+        sstate->solver_status = SOLVER_SOLVED;
+        /* This discovery clearly counts as progress, even if we haven't
+         * just added any lines or anything */
+        progress = TRUE;
+        goto finished_loop_deductionsing;
+    }
+
+    /*
+     * Now go through looking for LINE_UNKNOWN edges which
+     * connect two dots that are already in the same
+     * equivalence class. If we find one, test to see if the
+     * loop it would create is a solution.
+     */
+    for (i = 0; i < g->num_edges; i++) {
+        grid_edge *e = g->edges + i;
+        int d1 = e->dot1 - g->dots;
+        int d2 = e->dot2 - g->dots;
+        int eqclass, val;
+        if (state->lines[i] != LINE_UNKNOWN)
+            continue;
+
+        eqclass = dsf_canonify(sstate->dotdsf, d1);
+        if (eqclass != dsf_canonify(sstate->dotdsf, d2))
+            continue;
+
+        val = LINE_NO;  /* loop is bad until proven otherwise */
+
+        /*
+         * This edge would form a loop. Next
+         * question: how long would the loop be?
+         * Would it equal the total number of edges
+         * (plus the one we'd be adding if we added
+         * it)?
+         */
+        if (sstate->looplen[eqclass] == edgecount + 1) {
+            int sm1_nearby;
+
+            /*
+             * This edge would form a loop which
+             * took in all the edges in the entire
+             * grid. So now we need to work out
+             * whether it would be a valid solution
+             * to the puzzle, which means we have to
+             * check if it satisfies all the clues.
+             * This means that every clue must be
+             * either satisfied or satisfied-minus-
+             * 1, and also that the number of
+             * satisfied-minus-1 clues must be at
+             * most two and they must lie on either
+             * side of this edge.
+             */
+            sm1_nearby = 0;
+            if (e->face1) {
+                int f = e->face1 - g->faces;
+                int c = state->clues[f];
+                if (c >= 0 && sstate->face_yes_count[f] == c - 1)
+                    sm1_nearby++;
+            }
+            if (e->face2) {
+                int f = e->face2 - g->faces;
+                int c = state->clues[f];
+                if (c >= 0 && sstate->face_yes_count[f] == c - 1)
+                    sm1_nearby++;
+            }
+            if (sm1clues == sm1_nearby &&
+               sm1clues + satclues == clues) {
+                val = LINE_YES;  /* loop is good! */
+            }
+        }
+
+        /*
+         * Right. Now we know that adding this edge
+         * would form a loop, and we know whether
+         * that loop would be a viable solution or
+         * not.
+         *
+         * If adding this edge produces a solution,
+         * then we know we've found _a_ solution but
+         * we don't know that it's _the_ solution -
+         * if it were provably the solution then
+         * we'd have deduced this edge some time ago
+         * without the need to do loop detection. So
+         * in this state we return SOLVER_AMBIGUOUS,
+         * which has the effect that hitting Solve
+         * on a user-provided puzzle will fill in a
+         * solution but using the solver to
+         * construct new puzzles won't consider this
+         * a reasonable deduction for the user to
+         * make.
+         */
+        progress = solver_set_line(sstate, i, val);
+        assert(progress == TRUE);
+        if (val == LINE_YES) {
+            sstate->solver_status = SOLVER_AMBIGUOUS;
+            goto finished_loop_deductionsing;
+        }
+    }
+
+    finished_loop_deductionsing:
+    return progress ? DIFF_EASY : DIFF_MAX;
+}
+
+/* This will return a dynamically allocated solver_state containing the (more)
+ * solved grid */
+static solver_state *solve_game_rec(const solver_state *sstate_start)
+{
+    solver_state *sstate;
+
+    /* Index of the solver we should call next. */
+    int i = 0;
+    
+    /* As a speed-optimisation, we avoid re-running solvers that we know
+     * won't make any progress.  This happens when a high-difficulty
+     * solver makes a deduction that can only help other high-difficulty
+     * solvers.
+     * For example: if a new 'dline' flag is set by dline_deductions, the
+     * trivial_deductions solver cannot do anything with this information.
+     * If we've already run the trivial_deductions solver (because it's
+     * earlier in the list), there's no point running it again.
+     *
+     * Therefore: if a solver is earlier in the list than "threshold_index",
+     * we don't bother running it if it's difficulty level is less than
+     * "threshold_diff".
+     */
+    int threshold_diff = 0;
+    int threshold_index = 0;
+    
+    sstate = dup_solver_state(sstate_start);
+
+    check_caches(sstate);
+
+    while (i < NUM_SOLVERS) {
+        if (sstate->solver_status == SOLVER_MISTAKE)
+            return sstate;
+        if (sstate->solver_status == SOLVER_SOLVED ||
+            sstate->solver_status == SOLVER_AMBIGUOUS) {
+            /* solver finished */
+            break;
+        }
+
+        if ((solver_diffs[i] >= threshold_diff || i >= threshold_index)
+            && solver_diffs[i] <= sstate->diff) {
+            /* current_solver is eligible, so use it */
+            int next_diff = solver_fns[i](sstate);
+            if (next_diff != DIFF_MAX) {
+                /* solver made progress, so use new thresholds and
+                * start again at top of list. */
+                threshold_diff = next_diff;
+                threshold_index = i;
+                i = 0;
+                continue;
+            }
+        }
+        /* current_solver is ineligible, or failed to make progress, so
+         * go to the next solver in the list */
+        i++;
+    }
+
+    if (sstate->solver_status == SOLVER_SOLVED ||
+        sstate->solver_status == SOLVER_AMBIGUOUS) {
+        /* s/LINE_UNKNOWN/LINE_NO/g */
+        array_setall(sstate->state->lines, LINE_UNKNOWN, LINE_NO,
+                     sstate->state->game_grid->num_edges);
+        return sstate;
+    }
+
+    return sstate;
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    char *soln = NULL;
+    solver_state *sstate, *new_sstate;
+
+    sstate = new_solver_state(state, DIFF_MAX);
+    new_sstate = solve_game_rec(sstate);
+
+    if (new_sstate->solver_status == SOLVER_SOLVED) {
+        soln = encode_solve_move(new_sstate->state);
+    } else if (new_sstate->solver_status == SOLVER_AMBIGUOUS) {
+        soln = encode_solve_move(new_sstate->state);
+        /**error = "Solver found ambiguous solutions"; */
+    } else {
+        soln = encode_solve_move(new_sstate->state);
+        /**error = "Solver failed"; */
+    }
+
+    free_solver_state(new_sstate);
+    free_solver_state(sstate);
+
+    return soln;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing and mouse-handling
+ */
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    grid *g = state->game_grid;
+    grid_edge *e;
+    int i;
+    char *ret, buf[80];
+    char button_char = ' ';
+    enum line_state old_state;
+
+    button &= ~MOD_MASK;
+
+    /* Convert mouse-click (x,y) to grid coordinates */
+    x -= BORDER(ds->tilesize);
+    y -= BORDER(ds->tilesize);
+    x = x * g->tilesize / ds->tilesize;
+    y = y * g->tilesize / ds->tilesize;
+    x += g->lowest_x;
+    y += g->lowest_y;
+
+    e = grid_nearest_edge(g, x, y);
+    if (e == NULL)
+        return NULL;
+
+    i = e - g->edges;
+
+    /* I think it's only possible to play this game with mouse clicks, sorry */
+    /* Maybe will add mouse drag support some time */
+    old_state = state->lines[i];
+
+    switch (button) {
+      case LEFT_BUTTON:
+       switch (old_state) {
+         case LINE_UNKNOWN:
+           button_char = 'y';
+           break;
+         case LINE_YES:
+#ifdef STYLUS_BASED
+           button_char = 'n';
+           break;
+#endif
+         case LINE_NO:
+           button_char = 'u';
+           break;
+       }
+       break;
+      case MIDDLE_BUTTON:
+       button_char = 'u';
+       break;
+      case RIGHT_BUTTON:
+       switch (old_state) {
+         case LINE_UNKNOWN:
+           button_char = 'n';
+           break;
+         case LINE_NO:
+#ifdef STYLUS_BASED
+           button_char = 'y';
+           break;
+#endif
+         case LINE_YES:
+           button_char = 'u';
+           break;
+       }
+       break;
+      default:
+       return NULL;
+    }
+
+
+    sprintf(buf, "%d%c", i, (int)button_char);
+    ret = dupstr(buf);
+
+    return ret;
+}
+
+static game_state *execute_move(const game_state *state, const char *move)
+{
+    int i;
+    game_state *newstate = dup_game(state);
+
+    if (move[0] == 'S') {
+        move++;
+        newstate->cheated = TRUE;
+    }
+
+    while (*move) {
+        i = atoi(move);
+        if (i < 0 || i >= newstate->game_grid->num_edges)
+            goto fail;
+        move += strspn(move, "1234567890");
+        switch (*(move++)) {
+         case 'y':
+           newstate->lines[i] = LINE_YES;
+           break;
+         case 'n':
+           newstate->lines[i] = LINE_NO;
+           break;
+         case 'u':
+           newstate->lines[i] = LINE_UNKNOWN;
+           break;
+         default:
+           goto fail;
+        }
+    }
+
+    /*
+     * Check for completion.
+     */
+    if (check_completion(newstate))
+        newstate->solved = TRUE;
+
+    return newstate;
+
+    fail:
+    free_game(newstate);
+    return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+/* Convert from grid coordinates to screen coordinates */
+static void grid_to_screen(const game_drawstate *ds, const grid *g,
+                           int grid_x, int grid_y, int *x, int *y)
+{
+    *x = grid_x - g->lowest_x;
+    *y = grid_y - g->lowest_y;
+    *x = *x * ds->tilesize / g->tilesize;
+    *y = *y * ds->tilesize / g->tilesize;
+    *x += BORDER(ds->tilesize);
+    *y += BORDER(ds->tilesize);
+}
+
+/* Returns (into x,y) position of centre of face for rendering the text clue.
+ */
+static void face_text_pos(const game_drawstate *ds, const grid *g,
+                          grid_face *f, int *xret, int *yret)
+{
+    int faceindex = f - g->faces;
+
+    /*
+     * Return the cached position for this face, if we've already
+     * worked it out.
+     */
+    if (ds->textx[faceindex] >= 0) {
+        *xret = ds->textx[faceindex];
+        *yret = ds->texty[faceindex];
+        return;
+    }
+
+    /*
+     * Otherwise, use the incentre computed by grid.c and convert it
+     * to screen coordinates.
+     */
+    grid_find_incentre(f);
+    grid_to_screen(ds, g, f->ix, f->iy,
+                   &ds->textx[faceindex], &ds->texty[faceindex]);
+
+    *xret = ds->textx[faceindex];
+    *yret = ds->texty[faceindex];
+}
+
+static void face_text_bbox(game_drawstate *ds, grid *g, grid_face *f,
+                           int *x, int *y, int *w, int *h)
+{
+    int xx, yy;
+    face_text_pos(ds, g, f, &xx, &yy);
+
+    /* There seems to be a certain amount of trial-and-error involved
+     * in working out the correct bounding-box for the text. */
+
+    *x = xx - ds->tilesize/4 - 1;
+    *y = yy - ds->tilesize/4 - 3;
+    *w = ds->tilesize/2 + 2;
+    *h = ds->tilesize/2 + 5;
+}
+
+static void game_redraw_clue(drawing *dr, game_drawstate *ds,
+                            const game_state *state, int i)
+{
+    grid *g = state->game_grid;
+    grid_face *f = g->faces + i;
+    int x, y;
+    char c[20];
+
+    sprintf(c, "%d", state->clues[i]);
+
+    face_text_pos(ds, g, f, &x, &y);
+    draw_text(dr, x, y,
+             FONT_VARIABLE, ds->tilesize/2,
+             ALIGN_VCENTRE | ALIGN_HCENTRE,
+             ds->clue_error[i] ? COL_MISTAKE :
+             ds->clue_satisfied[i] ? COL_SATISFIED : COL_FOREGROUND, c);
+}
+
+static void edge_bbox(game_drawstate *ds, grid *g, grid_edge *e,
+                      int *x, int *y, int *w, int *h)
+{
+    int x1 = e->dot1->x;
+    int y1 = e->dot1->y;
+    int x2 = e->dot2->x;
+    int y2 = e->dot2->y;
+    int xmin, xmax, ymin, ymax;
+
+    grid_to_screen(ds, g, x1, y1, &x1, &y1);
+    grid_to_screen(ds, g, x2, y2, &x2, &y2);
+    /* Allow extra margin for dots, and thickness of lines */
+    xmin = min(x1, x2) - 2;
+    xmax = max(x1, x2) + 2;
+    ymin = min(y1, y2) - 2;
+    ymax = max(y1, y2) + 2;
+
+    *x = xmin;
+    *y = ymin;
+    *w = xmax - xmin + 1;
+    *h = ymax - ymin + 1;
+}
+
+static void dot_bbox(game_drawstate *ds, grid *g, grid_dot *d,
+                     int *x, int *y, int *w, int *h)
+{
+    int x1, y1;
+
+    grid_to_screen(ds, g, d->x, d->y, &x1, &y1);
+
+    *x = x1 - 2;
+    *y = y1 - 2;
+    *w = 5;
+    *h = 5;
+}
+
+static const int loopy_line_redraw_phases[] = {
+    COL_FAINT, COL_LINEUNKNOWN, COL_FOREGROUND, COL_HIGHLIGHT, COL_MISTAKE
+};
+#define NPHASES lenof(loopy_line_redraw_phases)
+
+static void game_redraw_line(drawing *dr, game_drawstate *ds,
+                            const game_state *state, int i, int phase)
+{
+    grid *g = state->game_grid;
+    grid_edge *e = g->edges + i;
+    int x1, x2, y1, y2;
+    int line_colour;
+
+    if (state->line_errors[i])
+       line_colour = COL_MISTAKE;
+    else if (state->lines[i] == LINE_UNKNOWN)
+       line_colour = COL_LINEUNKNOWN;
+    else if (state->lines[i] == LINE_NO)
+       line_colour = COL_FAINT;
+    else if (ds->flashing)
+       line_colour = COL_HIGHLIGHT;
+    else
+       line_colour = COL_FOREGROUND;
+    if (line_colour != loopy_line_redraw_phases[phase])
+        return;
+
+    /* Convert from grid to screen coordinates */
+    grid_to_screen(ds, g, e->dot1->x, e->dot1->y, &x1, &y1);
+    grid_to_screen(ds, g, e->dot2->x, e->dot2->y, &x2, &y2);
+
+    if (line_colour == COL_FAINT) {
+       static int draw_faint_lines = -1;
+       if (draw_faint_lines < 0) {
+           char *env = getenv("LOOPY_FAINT_LINES");
+           draw_faint_lines = (!env || (env[0] == 'y' ||
+                                        env[0] == 'Y'));
+       }
+       if (draw_faint_lines)
+           draw_line(dr, x1, y1, x2, y2, line_colour);
+    } else {
+       draw_thick_line(dr, 3.0,
+                       x1 + 0.5, y1 + 0.5,
+                       x2 + 0.5, y2 + 0.5,
+                       line_colour);
+    }
+}
+
+static void game_redraw_dot(drawing *dr, game_drawstate *ds,
+                           const game_state *state, int i)
+{
+    grid *g = state->game_grid;
+    grid_dot *d = g->dots + i;
+    int x, y;
+
+    grid_to_screen(ds, g, d->x, d->y, &x, &y);
+    draw_circle(dr, x, y, 2, COL_FOREGROUND, COL_FOREGROUND);
+}
+
+static int boxes_intersect(int x0, int y0, int w0, int h0,
+                           int x1, int y1, int w1, int h1)
+{
+    /*
+     * Two intervals intersect iff neither is wholly on one side of
+     * the other. Two boxes intersect iff their horizontal and
+     * vertical intervals both intersect.
+     */
+    return (x0 < x1+w1 && x1 < x0+w0 && y0 < y1+h1 && y1 < y0+h0);
+}
+
+static void game_redraw_in_rect(drawing *dr, game_drawstate *ds,
+                                const game_state *state,
+                                int x, int y, int w, int h)
+{
+    grid *g = state->game_grid;
+    int i, phase;
+    int bx, by, bw, bh;
+
+    clip(dr, x, y, w, h);
+    draw_rect(dr, x, y, w, h, COL_BACKGROUND);
+
+    for (i = 0; i < g->num_faces; i++) {
+        if (state->clues[i] >= 0) {
+            face_text_bbox(ds, g, &g->faces[i], &bx, &by, &bw, &bh);
+            if (boxes_intersect(x, y, w, h, bx, by, bw, bh))
+                game_redraw_clue(dr, ds, state, i);
+        }
+    }
+    for (phase = 0; phase < NPHASES; phase++) {
+        for (i = 0; i < g->num_edges; i++) {
+            edge_bbox(ds, g, &g->edges[i], &bx, &by, &bw, &bh);
+            if (boxes_intersect(x, y, w, h, bx, by, bw, bh))
+                game_redraw_line(dr, ds, state, i, phase);
+        }
+    }
+    for (i = 0; i < g->num_dots; i++) {
+        dot_bbox(ds, g, &g->dots[i], &bx, &by, &bw, &bh);
+        if (boxes_intersect(x, y, w, h, bx, by, bw, bh))
+            game_redraw_dot(dr, ds, state, i);
+    }
+
+    unclip(dr);
+    draw_update(dr, x, y, w, h);
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+#define REDRAW_OBJECTS_LIMIT 16                /* Somewhat arbitrary tradeoff */
+
+    grid *g = state->game_grid;
+    int border = BORDER(ds->tilesize);
+    int i;
+    int flash_changed;
+    int redraw_everything = FALSE;
+
+    int edges[REDRAW_OBJECTS_LIMIT], nedges = 0;
+    int faces[REDRAW_OBJECTS_LIMIT], nfaces = 0;
+
+    /* Redrawing is somewhat involved.
+     *
+     * An update can theoretically affect an arbitrary number of edges
+     * (consider, for example, completing or breaking a cycle which doesn't
+     * satisfy all the clues -- we'll switch many edges between error and
+     * normal states).  On the other hand, redrawing the whole grid takes a
+     * while, making the game feel sluggish, and many updates are actually
+     * quite well localized.
+     *
+     * This redraw algorithm attempts to cope with both situations gracefully
+     * and correctly.  For localized changes, we set a clip rectangle, fill
+     * it with background, and then redraw (a plausible but conservative
+     * guess at) the objects which intersect the rectangle; if several
+     * objects need redrawing, we'll do them individually.  However, if lots
+     * of objects are affected, we'll just redraw everything.
+     *
+     * The reason for all of this is that it's just not safe to do the redraw
+     * piecemeal.  If you try to draw an antialiased diagonal line over
+     * itself, you get a slightly thicker antialiased diagonal line, which
+     * looks rather ugly after a while.
+     *
+     * So, we take two passes over the grid.  The first attempts to work out
+     * what needs doing, and the second actually does it.
+     */
+
+    if (!ds->started) {
+       redraw_everything = TRUE;
+        /*
+         * But we must still go through the upcoming loops, so that we
+         * set up stuff in ds correctly for the initial redraw.
+         */
+    }
+
+    /* First, trundle through the faces. */
+    for (i = 0; i < g->num_faces; i++) {
+        grid_face *f = g->faces + i;
+        int sides = f->order;
+        int yes_order, no_order;
+        int clue_mistake;
+        int clue_satisfied;
+        int n = state->clues[i];
+        if (n < 0)
+            continue;
+
+        yes_order = face_order(state, i, LINE_YES);
+        if (state->exactly_one_loop) {
+            /*
+             * Special case: if the set of LINE_YES edges in the grid
+             * consists of exactly one loop and nothing else, then we
+             * switch to treating LINE_UNKNOWN the same as LINE_NO for
+             * purposes of clue checking.
+             *
+             * This is because some people like to play Loopy without
+             * using the right-click, i.e. never setting anything to
+             * LINE_NO. Without this special case, if a person playing
+             * in that style fills in what they think is a correct
+             * solution loop but in fact it has an underfilled clue,
+             * then we will display no victory flash and also no error
+             * highlight explaining why not. With this special case,
+             * we light up underfilled clues at the instant the loop
+             * is closed. (Of course, *overfilled* clues are fine
+             * either way.)
+             *
+             * (It might still be considered unfortunate that we can't
+             * warn this style of player any earlier, if they make a
+             * mistake very near the beginning which doesn't show up
+             * until they close the last edge of the loop. One other
+             * thing we _could_ do here is to treat any LINE_UNKNOWN
+             * as LINE_NO if either of its endpoints has yes-degree 2,
+             * reflecting the fact that setting that line to YES would
+             * be an obvious error. But I don't think even that could
+             * catch _all_ clue errors in a timely manner; I think
+             * there are some that won't be displayed until the loop
+             * is filled in, even so, and there's no way to avoid that
+             * with complete reliability except to switch to being a
+             * player who sets things to LINE_NO.)
+             */
+            no_order = sides - yes_order;
+        } else {
+            no_order = face_order(state, i, LINE_NO);
+        }
+
+        clue_mistake = (yes_order > n || no_order > (sides-n));
+        clue_satisfied = (yes_order == n && no_order == (sides-n));
+
+        if (clue_mistake != ds->clue_error[i] ||
+            clue_satisfied != ds->clue_satisfied[i]) {
+            ds->clue_error[i] = clue_mistake;
+            ds->clue_satisfied[i] = clue_satisfied;
+            if (nfaces == REDRAW_OBJECTS_LIMIT)
+                redraw_everything = TRUE;
+            else
+                faces[nfaces++] = i;
+        }
+    }
+
+    /* Work out what the flash state needs to be. */
+    if (flashtime > 0 &&
+        (flashtime <= FLASH_TIME/3 ||
+         flashtime >= FLASH_TIME*2/3)) {
+        flash_changed = !ds->flashing;
+        ds->flashing = TRUE;
+    } else {
+        flash_changed = ds->flashing;
+        ds->flashing = FALSE;
+    }
+
+    /* Now, trundle through the edges. */
+    for (i = 0; i < g->num_edges; i++) {
+        char new_ds =
+            state->line_errors[i] ? DS_LINE_ERROR : state->lines[i];
+        if (new_ds != ds->lines[i] ||
+            (flash_changed && state->lines[i] == LINE_YES)) {
+            ds->lines[i] = new_ds;
+            if (nedges == REDRAW_OBJECTS_LIMIT)
+                redraw_everything = TRUE;
+            else
+                edges[nedges++] = i;
+        }
+    }
+
+    /* Pass one is now done.  Now we do the actual drawing. */
+    if (redraw_everything) {
+        int grid_width = g->highest_x - g->lowest_x;
+        int grid_height = g->highest_y - g->lowest_y;
+        int w = grid_width * ds->tilesize / g->tilesize;
+        int h = grid_height * ds->tilesize / g->tilesize;
+
+        game_redraw_in_rect(dr, ds, state,
+                            0, 0, w + 2*border + 1, h + 2*border + 1);
+    } else {
+
+       /* Right.  Now we roll up our sleeves. */
+
+       for (i = 0; i < nfaces; i++) {
+           grid_face *f = g->faces + faces[i];
+           int x, y, w, h;
+
+            face_text_bbox(ds, g, f, &x, &y, &w, &h);
+            game_redraw_in_rect(dr, ds, state, x, y, w, h);
+       }
+
+       for (i = 0; i < nedges; i++) {
+           grid_edge *e = g->edges + edges[i];
+            int x, y, w, h;
+
+            edge_bbox(ds, g, e, &x, &y, &w, &h);
+            game_redraw_in_rect(dr, ds, state, x, y, w, h);
+       }
+    }
+
+    ds->started = TRUE;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    if (!oldstate->solved  &&  newstate->solved &&
+        !oldstate->cheated && !newstate->cheated) {
+        return FLASH_TIME;
+    }
+
+    return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    return state->solved ? +1 : 0;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+    int pw, ph;
+
+    /*
+     * I'll use 7mm "squares" by default.
+     */
+    game_compute_size(params, 700, &pw, &ph);
+    *x = pw / 100.0F;
+    *y = ph / 100.0F;
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+    int ink = print_mono_colour(dr, 0);
+    int i;
+    game_drawstate ads, *ds = &ads;
+    grid *g = state->game_grid;
+
+    ds->tilesize = tilesize;
+    ds->textx = snewn(g->num_faces, int);
+    ds->texty = snewn(g->num_faces, int);
+    for (i = 0; i < g->num_faces; i++)
+        ds->textx[i] = ds->texty[i] = -1;
+
+    for (i = 0; i < g->num_dots; i++) {
+        int x, y;
+        grid_to_screen(ds, g, g->dots[i].x, g->dots[i].y, &x, &y);
+        draw_circle(dr, x, y, ds->tilesize / 15, ink, ink);
+    }
+
+    /*
+     * Clues.
+     */
+    for (i = 0; i < g->num_faces; i++) {
+        grid_face *f = g->faces + i;
+        int clue = state->clues[i];
+        if (clue >= 0) {
+            char c[20];
+            int x, y;
+            sprintf(c, "%d", state->clues[i]);
+            face_text_pos(ds, g, f, &x, &y);
+            draw_text(dr, x, y,
+                      FONT_VARIABLE, ds->tilesize / 2,
+                      ALIGN_VCENTRE | ALIGN_HCENTRE, ink, c);
+        }
+    }
+
+    /*
+     * Lines.
+     */
+    for (i = 0; i < g->num_edges; i++) {
+        int thickness = (state->lines[i] == LINE_YES) ? 30 : 150;
+        grid_edge *e = g->edges + i;
+        int x1, y1, x2, y2;
+        grid_to_screen(ds, g, e->dot1->x, e->dot1->y, &x1, &y1);
+        grid_to_screen(ds, g, e->dot2->x, e->dot2->y, &x2, &y2);
+        if (state->lines[i] == LINE_YES)
+        {
+            /* (dx, dy) points from (x1, y1) to (x2, y2).
+             * The line is then "fattened" in a perpendicular
+             * direction to create a thin rectangle. */
+            double d = sqrt(SQ((double)x1 - x2) + SQ((double)y1 - y2));
+            double dx = (x2 - x1) / d;
+            double dy = (y2 - y1) / d;
+           int points[8];
+
+            dx = (dx * ds->tilesize) / thickness;
+            dy = (dy * ds->tilesize) / thickness;
+           points[0] = x1 + (int)dy;
+           points[1] = y1 - (int)dx;
+           points[2] = x1 - (int)dy;
+           points[3] = y1 + (int)dx;
+           points[4] = x2 - (int)dy;
+           points[5] = y2 + (int)dx;
+           points[6] = x2 + (int)dy;
+           points[7] = y2 - (int)dx;
+            draw_polygon(dr, points, 4, ink, ink);
+        }
+        else
+        {
+            /* Draw a dotted line */
+            int divisions = 6;
+            int j;
+            for (j = 1; j < divisions; j++) {
+                /* Weighted average */
+                int x = (x1 * (divisions -j) + x2 * j) / divisions;
+                int y = (y1 * (divisions -j) + y2 * j) / divisions;
+                draw_circle(dr, x, y, ds->tilesize / thickness, ink, ink);
+            }
+        }
+    }
+
+    sfree(ds->textx);
+    sfree(ds->texty);
+}
+
+#ifdef COMBINED
+#define thegame loopy
+#endif
+
+const struct game thegame = {
+    "Loopy", "games.loopy", "loopy",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    1, solve_game,
+    TRUE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    TRUE, FALSE, game_print_size, game_print,
+    FALSE /* wants_statusbar */,
+    FALSE, game_timing_state,
+    0,                                       /* mouse_priorities */
+};
+
+#ifdef STANDALONE_SOLVER
+
+/*
+ * Half-hearted standalone solver. It can't output the solution to
+ * anything but a square puzzle, and it can't log the deductions
+ * it makes either. But it can solve square puzzles, and more
+ * importantly it can use its solver to grade the difficulty of
+ * any puzzle you give it.
+ */
+
+#include <stdarg.h>
+
+int main(int argc, char **argv)
+{
+    game_params *p;
+    game_state *s;
+    char *id = NULL, *desc, *err;
+    int grade = FALSE;
+    int ret, diff;
+#if 0 /* verbose solver not supported here (yet) */
+    int really_verbose = FALSE;
+#endif
+
+    while (--argc > 0) {
+        char *p = *++argv;
+#if 0 /* verbose solver not supported here (yet) */
+        if (!strcmp(p, "-v")) {
+            really_verbose = TRUE;
+        } else
+#endif
+       if (!strcmp(p, "-g")) {
+            grade = TRUE;
+        } else if (*p == '-') {
+            fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p);
+            return 1;
+        } else {
+            id = p;
+        }
+    }
+
+    if (!id) {
+        fprintf(stderr, "usage: %s [-g | -v] <game_id>\n", argv[0]);
+        return 1;
+    }
+
+    desc = strchr(id, ':');
+    if (!desc) {
+        fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]);
+        return 1;
+    }
+    *desc++ = '\0';
+
+    p = default_params();
+    decode_params(p, id);
+    err = validate_desc(p, desc);
+    if (err) {
+        fprintf(stderr, "%s: %s\n", argv[0], err);
+        return 1;
+    }
+    s = new_game(NULL, p, desc);
+
+    /*
+     * When solving an Easy puzzle, we don't want to bother the
+     * user with Hard-level deductions. For this reason, we grade
+     * the puzzle internally before doing anything else.
+     */
+    ret = -1;                         /* placate optimiser */
+    for (diff = 0; diff < DIFF_MAX; diff++) {
+       solver_state *sstate_new;
+       solver_state *sstate = new_solver_state((game_state *)s, diff);
+
+       sstate_new = solve_game_rec(sstate);
+
+       if (sstate_new->solver_status == SOLVER_MISTAKE)
+           ret = 0;
+       else if (sstate_new->solver_status == SOLVER_SOLVED)
+           ret = 1;
+       else
+           ret = 2;
+
+       free_solver_state(sstate_new);
+       free_solver_state(sstate);
+
+       if (ret < 2)
+           break;
+    }
+
+    if (diff == DIFF_MAX) {
+       if (grade)
+           printf("Difficulty rating: harder than Hard, or ambiguous\n");
+       else
+           printf("Unable to find a unique solution\n");
+    } else {
+       if (grade) {
+           if (ret == 0)
+               printf("Difficulty rating: impossible (no solution exists)\n");
+           else if (ret == 1)
+               printf("Difficulty rating: %s\n", diffnames[diff]);
+       } else {
+           solver_state *sstate_new;
+           solver_state *sstate = new_solver_state((game_state *)s, diff);
+
+           /* If we supported a verbose solver, we'd set verbosity here */
+
+           sstate_new = solve_game_rec(sstate);
+
+           if (sstate_new->solver_status == SOLVER_MISTAKE)
+               printf("Puzzle is inconsistent\n");
+           else {
+               assert(sstate_new->solver_status == SOLVER_SOLVED);
+               if (s->grid_type == 0) {
+                   fputs(game_text_format(sstate_new->state), stdout);
+               } else {
+                   printf("Unable to output non-square grids\n");
+               }
+           }
+
+           free_solver_state(sstate_new);
+           free_solver_state(sstate);
+       }
+    }
+
+    return 0;
+}
+
+#endif
+
+/* vim: set shiftwidth=4 tabstop=8: */
diff --git a/magnets.R b/magnets.R
new file mode 100644 (file)
index 0000000..e55e474
--- /dev/null
+++ b/magnets.R
@@ -0,0 +1,24 @@
+# -*- makefile -*-
+
+MAGNETS_EXTRA = laydomino
+
+magnets      : [X] GTK COMMON magnets MAGNETS_EXTRA magnets-icon|no-icon
+
+magnets      : [G] WINDOWS COMMON magnets MAGNETS_EXTRA magnets.res|noicon.res
+
+magnetssolver :     [U] magnets[STANDALONE_SOLVER] MAGNETS_EXTRA STANDALONE m.lib
+magnetssolver :     [C] magnets[STANDALONE_SOLVER] MAGNETS_EXTRA STANDALONE
+
+ALL += magnets[COMBINED] MAGNETS_EXTRA
+
+!begin am gtk
+GAMES += magnets
+!end
+
+!begin >list.c
+    A(magnets) \
+!end
+
+!begin >gamedesc.txt
+magnets:magnets.exe:Magnets:Magnet-placing puzzle:Place magnets to satisfy the clues and avoid like poles touching.
+!end
diff --git a/magnets.c b/magnets.c
new file mode 100644 (file)
index 0000000..4fec131
--- /dev/null
+++ b/magnets.c
@@ -0,0 +1,2641 @@
+/*
+ * magnets.c: implementation of janko.at 'magnets puzzle' game.
+ *
+ * http://64.233.179.104/translate_c?hl=en&u=http://www.janko.at/Raetsel/Magnete/Beispiel.htm
+ *
+ * Puzzle definition is just the size, and then the list of + (across then
+ * down) and - (across then down) present, then domino edges.
+ *
+ * An example:
+ *
+ *  + 2 0 1
+ *   +-----+
+ *  1|+ -| |1
+ *   |-+-+ |
+ *  0|-|#| |1
+ *   | +-+-|
+ *  2|+|- +|1
+ *   +-----+
+ *    1 2 0 -
+ *
+ * 3x3:201,102,120,111,LRTT*BBLR
+ *
+ * 'Zotmeister' examples:
+ * 5x5:.2..1,3..1.,.2..2,2..2.,LRLRTTLRTBBT*BTTBLRBBLRLR
+ * 9x9:3.51...33,.2..23.13,..33.33.2,12...5.3.,**TLRTLR*,*TBLRBTLR,TBLRLRBTT,BLRTLRTBB,LRTB*TBLR,LRBLRBLRT,TTTLRLRTB,BBBTLRTB*,*LRBLRB**
+ *
+ * Janko 6x6 with solution:
+ * 6x6:322223,323132,232223,232223,LRTLRTTTBLRBBBTTLRLRBBLRTTLRTTBBLRBB
+ *
+ * janko 8x8:
+ * 8x8:34131323,23131334,43122323,21332243,LRTLRLRT,LRBTTTTB,LRTBBBBT,TTBTLRTB,BBTBTTBT,TTBTBBTB,BBTBLRBT,LRBLRLRB
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+
+#ifdef STANDALONE_SOLVER
+int verbose = 0;
+#endif
+
+enum {
+    COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT,
+    COL_TEXT, COL_ERROR, COL_CURSOR, COL_DONE,
+    COL_NEUTRAL, COL_NEGATIVE, COL_POSITIVE, COL_NOT,
+    NCOLOURS
+};
+
+/* Cell states. */
+enum { EMPTY = 0, NEUTRAL = EMPTY, POSITIVE = 1, NEGATIVE = 2 };
+
+#if defined DEBUGGING || defined STANDALONE_SOLVER
+static const char *cellnames[3] = { "neutral", "positive", "negative" };
+#define NAME(w) ( ((w) < 0 || (w) > 2) ? "(out of range)" : cellnames[(w)] )
+#endif
+
+#define GRID2CHAR(g) ( ((g) >= 0 && (g) <= 2) ? ".+-"[(g)] : '?' )
+#define CHAR2GRID(c) ( (c) == '+' ? POSITIVE : (c) == '-' ? NEGATIVE : NEUTRAL )
+
+#define INGRID(s,x,y) ((x) >= 0 && (x) < (s)->w && (y) >= 0 && (y) < (s)->h)
+
+#define OPPOSITE(x) ( ((x)*2) % 3 ) /* 0 --> 0,
+                                       1 --> 2,
+                                       2 --> 4 --> 1 */
+
+#define FLASH_TIME 0.7F
+
+/* Macro ickery copied from slant.c */
+#define DIFFLIST(A) \
+    A(EASY,Easy,e) \
+    A(TRICKY,Tricky,t)
+#define ENUM(upper,title,lower) DIFF_ ## upper,
+#define TITLE(upper,title,lower) #title,
+#define ENCODE(upper,title,lower) #lower
+#define CONFIG(upper,title,lower) ":" #title
+enum { DIFFLIST(ENUM) DIFFCOUNT };
+static char const *const magnets_diffnames[] = { DIFFLIST(TITLE) "(count)" };
+static char const magnets_diffchars[] = DIFFLIST(ENCODE);
+#define DIFFCONFIG DIFFLIST(CONFIG)
+
+
+/* --------------------------------------------------------------- */
+/* Game parameter functions. */
+
+struct game_params {
+    int w, h, diff, stripclues;
+};
+
+#define DEFAULT_PRESET 2
+
+static const struct game_params magnets_presets[] = {
+    {6, 5, DIFF_EASY, 0},
+    {6, 5, DIFF_TRICKY, 0},
+    {6, 5, DIFF_TRICKY, 1},
+    {8, 7, DIFF_EASY, 0},
+    {8, 7, DIFF_TRICKY, 0},
+    {8, 7, DIFF_TRICKY, 1},
+    {10, 9, DIFF_TRICKY, 0},
+    {10, 9, DIFF_TRICKY, 1}
+};
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+
+    *ret = magnets_presets[DEFAULT_PRESET];
+
+    return ret;
+}
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params *ret;
+    char buf[64];
+
+    if (i < 0 || i >= lenof(magnets_presets)) return FALSE;
+
+    ret = default_params();
+    *ret = magnets_presets[i]; /* struct copy */
+    *params = ret;
+
+    sprintf(buf, "%dx%d %s%s",
+            magnets_presets[i].w, magnets_presets[i].h,
+            magnets_diffnames[magnets_presets[i].diff],
+            magnets_presets[i].stripclues ? ", strip clues" : "");
+    *name = dupstr(buf);
+
+    return TRUE;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;       /* structure copy */
+    return ret;
+}
+
+static void decode_params(game_params *ret, char const *string)
+{
+    ret->w = ret->h = atoi(string);
+    while (*string && isdigit((unsigned char) *string)) ++string;
+    if (*string == 'x') {
+        string++;
+        ret->h = atoi(string);
+        while (*string && isdigit((unsigned char)*string)) string++;
+    }
+
+    ret->diff = DIFF_EASY;
+    if (*string == 'd') {
+       int i;
+       string++;
+       for (i = 0; i < DIFFCOUNT; i++)
+           if (*string == magnets_diffchars[i])
+               ret->diff = i;
+       if (*string) string++;
+    }
+
+    ret->stripclues = 0;
+    if (*string == 'S') {
+        string++;
+        ret->stripclues = 1;
+    }
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char buf[256];
+    sprintf(buf, "%dx%d", params->w, params->h);
+    if (full)
+        sprintf(buf + strlen(buf), "d%c%s",
+                magnets_diffchars[params->diff],
+                params->stripclues ? "S" : "");
+    return dupstr(buf);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+    char buf[64];
+
+    ret = snewn(5, config_item);
+
+    ret[0].name = "Width";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->w);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Height";
+    ret[1].type = C_STRING;
+    sprintf(buf, "%d", params->h);
+    ret[1].sval = dupstr(buf);
+    ret[1].ival = 0;
+
+    ret[2].name = "Difficulty";
+    ret[2].type = C_CHOICES;
+    ret[2].sval = DIFFCONFIG;
+    ret[2].ival = params->diff;
+
+    ret[3].name = "Strip clues";
+    ret[3].type = C_BOOLEAN;
+    ret[3].sval = NULL;
+    ret[3].ival = params->stripclues;
+
+    ret[4].name = NULL;
+    ret[4].type = C_END;
+    ret[4].sval = NULL;
+    ret[4].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = atoi(cfg[0].sval);
+    ret->h = atoi(cfg[1].sval);
+    ret->diff = cfg[2].ival;
+    ret->stripclues = cfg[3].ival;
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    if (params->w < 2) return "Width must be at least one";
+    if (params->h < 2) return "Height must be at least one";
+    if (params->diff < 0 || params->diff >= DIFFCOUNT)
+        return "Unknown difficulty level";
+
+    return NULL;
+}
+
+/* --------------------------------------------------------------- */
+/* Game state allocation, deallocation. */
+
+struct game_common {
+    int *dominoes;      /* size w*h, dominoes[i] points to other end of domino. */
+    int *rowcount;      /* size 3*h, array of [plus, minus, neutral] counts */
+    int *colcount;      /* size 3*w, ditto */
+    int refcount;
+};
+
+#define GS_ERROR        1
+#define GS_SET          2
+#define GS_NOTPOSITIVE  4
+#define GS_NOTNEGATIVE  8
+#define GS_NOTNEUTRAL  16
+#define GS_MARK        32
+
+#define GS_NOTMASK (GS_NOTPOSITIVE|GS_NOTNEGATIVE|GS_NOTNEUTRAL)
+
+#define NOTFLAG(w) ( (w) == NEUTRAL ? GS_NOTNEUTRAL : \
+                     (w) == POSITIVE ? GS_NOTPOSITIVE : \
+                     (w) == NEGATIVE ? GS_NOTNEGATIVE : \
+                     0 )
+
+#define POSSIBLE(f,w) (!(state->flags[(f)] & NOTFLAG(w)))
+
+struct game_state {
+    int w, h, wh;
+    int *grid;                  /* size w*h, for cell state (pos/neg) */
+    unsigned int *flags;        /* size w*h */
+    int solved, completed, numbered;
+    unsigned char *counts_done;
+
+    struct game_common *common; /* domino layout never changes. */
+};
+
+static void clear_state(game_state *ret)
+{
+    int i;
+
+    ret->solved = ret->completed = ret->numbered = 0;
+
+    memset(ret->common->rowcount, 0, ret->h*3*sizeof(int));
+    memset(ret->common->colcount, 0, ret->w*3*sizeof(int));
+    memset(ret->counts_done, 0, (ret->h + ret->w) * 2 * sizeof(unsigned char));
+
+    for (i = 0; i < ret->wh; i++) {
+        ret->grid[i] = EMPTY;
+        ret->flags[i] = 0;
+        ret->common->dominoes[i] = i;
+    }
+}
+
+static game_state *new_state(int w, int h)
+{
+    game_state *ret = snew(game_state);
+
+    memset(ret, 0, sizeof(game_state));
+    ret->w = w;
+    ret->h = h;
+    ret->wh = w*h;
+
+    ret->grid = snewn(ret->wh, int);
+    ret->flags = snewn(ret->wh, unsigned int);
+    ret->counts_done = snewn((ret->h + ret->w) * 2, unsigned char);
+
+    ret->common = snew(struct game_common);
+    ret->common->refcount = 1;
+
+    ret->common->dominoes = snewn(ret->wh, int);
+    ret->common->rowcount = snewn(ret->h*3, int);
+    ret->common->colcount = snewn(ret->w*3, int);
+
+    clear_state(ret);
+
+    return ret;
+}
+
+static game_state *dup_game(const game_state *src)
+{
+    game_state *dest = snew(game_state);
+
+    dest->w = src->w;
+    dest->h = src->h;
+    dest->wh = src->wh;
+
+    dest->solved = src->solved;
+    dest->completed = src->completed;
+    dest->numbered = src->numbered;
+
+    dest->common = src->common;
+    dest->common->refcount++;
+
+    dest->grid = snewn(dest->wh, int);
+    memcpy(dest->grid, src->grid, dest->wh*sizeof(int));
+
+    dest->counts_done = snewn((dest->h + dest->w) * 2, unsigned char);
+    memcpy(dest->counts_done, src->counts_done,
+           (dest->h + dest->w) * 2 * sizeof(unsigned char));
+
+    dest->flags = snewn(dest->wh, unsigned int);
+    memcpy(dest->flags, src->flags, dest->wh*sizeof(unsigned int));
+
+    return dest;
+}
+
+static void free_game(game_state *state)
+{
+    state->common->refcount--;
+    if (state->common->refcount == 0) {
+        sfree(state->common->dominoes);
+        sfree(state->common->rowcount);
+        sfree(state->common->colcount);
+        sfree(state->common);
+    }
+    sfree(state->counts_done);
+    sfree(state->flags);
+    sfree(state->grid);
+    sfree(state);
+}
+
+/* --------------------------------------------------------------- */
+/* Game generation and reading. */
+
+/* For a game of size w*h the game description is:
+ * w-sized string of column + numbers (L-R), or '.' for none
+ * semicolon
+ * h-sized string of row + numbers (T-B), or '.'
+ * semicolon
+ * w-sized string of column - numbers (L-R), or '.'
+ * semicolon
+ * h-sized string of row - numbers (T-B), or '.'
+ * semicolon
+ * w*h-sized string of 'L', 'R', 'U', 'D' for domino associations,
+ *   or '*' for a black singleton square.
+ *
+ * for a total length of 2w + 2h + wh + 4.
+ */
+
+static char n2c(int num) { /* XXX cloned from singles.c */
+    if (num == -1)
+        return '.';
+    if (num < 10)
+        return '0' + num;
+    else if (num < 10+26)
+        return 'a' + num - 10;
+    else
+        return 'A' + num - 10 - 26;
+    return '?';
+}
+
+static int c2n(char c) { /* XXX cloned from singles.c */
+    if (isdigit((unsigned char)c))
+        return (int)(c - '0');
+    else if (c >= 'a' && c <= 'z')
+        return (int)(c - 'a' + 10);
+    else if (c >= 'A' && c <= 'Z')
+        return (int)(c - 'A' + 10 + 26);
+    return -1;
+}
+
+static const char *readrow(const char *desc, int n, int *array, int off,
+                           const char **prob)
+{
+    int i, num;
+    char c;
+
+    for (i = 0; i < n; i++) {
+        c = *desc++;
+        if (c == 0) goto badchar;
+        if (c == '.')
+            num = -1;
+        else {
+            num = c2n(c);
+            if (num < 0) goto badchar;
+        }
+        array[i*3+off] = num;
+    }
+    c = *desc++;
+    if (c != ',') goto badchar;
+    return desc;
+
+badchar:
+    *prob = (c == 0) ?
+                "Game description too short" :
+                "Game description contained unexpected characters";
+    return NULL;
+}
+
+static game_state *new_game_int(const game_params *params, const char *desc,
+                                const char **prob)
+{
+    game_state *state = new_state(params->w, params->h);
+    int x, y, idx, *count;
+    char c;
+
+    *prob = NULL;
+
+    /* top row, left-to-right */
+    desc = readrow(desc, state->w, state->common->colcount, POSITIVE, prob);
+    if (*prob) goto done;
+
+    /* left column, top-to-bottom */
+    desc = readrow(desc, state->h, state->common->rowcount, POSITIVE, prob);
+    if (*prob) goto done;
+
+    /* bottom row, left-to-right */
+    desc = readrow(desc, state->w, state->common->colcount, NEGATIVE, prob);
+    if (*prob) goto done;
+
+    /* right column, top-to-bottom */
+    desc = readrow(desc, state->h, state->common->rowcount, NEGATIVE, prob);
+    if (*prob) goto done;
+
+    /* Add neutral counts (== size - pos - neg) to columns and rows.
+     * Any singleton cells will just be treated as permanently neutral. */
+    count = state->common->colcount;
+    for (x = 0; x < state->w; x++) {
+        if (count[x*3+POSITIVE] < 0 || count[x*3+NEGATIVE] < 0)
+            count[x*3+NEUTRAL] = -1;
+        else {
+            count[x*3+NEUTRAL] =
+                state->h - count[x*3+POSITIVE] - count[x*3+NEGATIVE];
+            if (count[x*3+NEUTRAL] < 0) {
+                *prob = "Column counts inconsistent";
+                goto done;
+            }
+        }
+    }
+    count = state->common->rowcount;
+    for (y = 0; y < state->h; y++) {
+        if (count[y*3+POSITIVE] < 0 || count[y*3+NEGATIVE] < 0)
+            count[y*3+NEUTRAL] = -1;
+        else {
+            count[y*3+NEUTRAL] =
+                state->w - count[y*3+POSITIVE] - count[y*3+NEGATIVE];
+            if (count[y*3+NEUTRAL] < 0) {
+                *prob = "Row counts inconsistent";
+                goto done;
+            }
+        }
+    }
+
+
+    for (y = 0; y < state->h; y++) {
+        for (x = 0; x < state->w; x++) {
+            idx = y*state->w + x;
+nextchar:
+            c = *desc++;
+
+            if (c == 'L') /* this square is LHS of a domino */
+                state->common->dominoes[idx] = idx+1;
+            else if (c == 'R') /* ... RHS of a domino */
+                state->common->dominoes[idx] = idx-1;
+            else if (c == 'T') /* ... top of a domino */
+                state->common->dominoes[idx] = idx+state->w;
+            else if (c == 'B') /* ... bottom of a domino */
+                state->common->dominoes[idx] = idx-state->w;
+            else if (c == '*') /* singleton */
+                state->common->dominoes[idx] = idx;
+            else if (c == ',') /* spacer, ignore */
+                goto nextchar;
+            else goto badchar;
+        }
+    }
+
+    /* Check dominoes as input are sensibly consistent
+     * (i.e. each end points to the other) */
+    for (idx = 0; idx < state->wh; idx++) {
+        if (state->common->dominoes[idx] < 0 ||
+            state->common->dominoes[idx] > state->wh ||
+            state->common->dominoes[state->common->dominoes[idx]] != idx) {
+            *prob = "Domino descriptions inconsistent";
+            goto done;
+        }
+        if (state->common->dominoes[idx] == idx) {
+            state->grid[idx] = NEUTRAL;
+            state->flags[idx] |= GS_SET;
+        }
+    }
+    /* Success. */
+    state->numbered = 1;
+    goto done;
+
+badchar:
+    *prob = (c == 0) ?
+                "Game description too short" :
+                "Game description contained unexpected characters";
+
+done:
+    if (*prob) {
+        free_game(state);
+        return NULL;
+    }
+    return state;
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    const char *prob;
+    game_state *st = new_game_int(params, desc, &prob);
+    if (!st) return (char*)prob;
+    free_game(st);
+    return NULL;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    const char *prob;
+    game_state *st = new_game_int(params, desc, &prob);
+    assert(st);
+    return st;
+}
+
+static char *generate_desc(game_state *new)
+{
+    int x, y, idx, other, w = new->w, h = new->h;
+    char *desc = snewn(new->wh + 2*(w + h) + 5, char), *p = desc;
+
+    for (x = 0; x < w; x++) *p++ = n2c(new->common->colcount[x*3+POSITIVE]);
+    *p++ = ',';
+    for (y = 0; y < h; y++) *p++ = n2c(new->common->rowcount[y*3+POSITIVE]);
+    *p++ = ',';
+
+    for (x = 0; x < w; x++) *p++ = n2c(new->common->colcount[x*3+NEGATIVE]);
+    *p++ = ',';
+    for (y = 0; y < h; y++) *p++ = n2c(new->common->rowcount[y*3+NEGATIVE]);
+    *p++ = ',';
+
+    for (y = 0; y < h; y++) {
+        for (x = 0; x < w; x++) {
+            idx = y*w + x;
+            other = new->common->dominoes[idx];
+
+            if (other == idx) *p++ = '*';
+            else if (other == idx+1) *p++ = 'L';
+            else if (other == idx-1) *p++ = 'R';
+            else if (other == idx+w) *p++ = 'T';
+            else if (other == idx-w) *p++ = 'B';
+            else assert(!"mad domino orientation");
+        }
+    }
+    *p = '\0';
+
+    return desc;
+}
+
+static void game_text_hborder(const game_state *state, char **p_r)
+{
+    char *p = *p_r;
+    int x;
+
+    *p++ = ' ';
+    *p++ = '+';
+    for (x = 0; x < state->w*2-1; x++) *p++ = '-';
+    *p++ = '+';
+    *p++ = '\n';
+
+    *p_r = p;
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    int len, x, y, i;
+    char *ret, *p;
+
+    len = ((state->w*2)+4) * ((state->h*2)+4) + 2;
+    p = ret = snewn(len, char);
+
+    /* top row: '+' then column totals for plus. */
+    *p++ = '+';
+    for (x = 0; x < state->w; x++) {
+        *p++ = ' ';
+        *p++ = n2c(state->common->colcount[x*3+POSITIVE]);
+    }
+    *p++ = '\n';
+
+    /* top border. */
+    game_text_hborder(state, &p);
+
+    for (y = 0; y < state->h; y++) {
+        *p++ = n2c(state->common->rowcount[y*3+POSITIVE]);
+        *p++ = '|';
+        for (x = 0; x < state->w; x++) {
+            i = y*state->w+x;
+            *p++ = state->common->dominoes[i] == i ? '#' :
+                state->grid[i] == POSITIVE ? '+' :
+                state->grid[i] == NEGATIVE ? '-' :
+                state->flags[i] & GS_SET ? '*' : ' ';
+            if (x < (state->w-1))
+                *p++ = state->common->dominoes[i] == i+1 ? ' ' : '|';
+        }
+        *p++ = '|';
+        *p++ = n2c(state->common->rowcount[y*3+NEGATIVE]);
+        *p++ = '\n';
+
+        if (y < (state->h-1)) {
+            *p++ = ' ';
+            *p++ = '|';
+            for (x = 0; x < state->w; x++) {
+                i = y*state->w+x;
+                *p++ = state->common->dominoes[i] == i+state->w ? ' ' : '-';
+                if (x < (state->w-1))
+                    *p++ = '+';
+            }
+            *p++ = '|';
+            *p++ = '\n';
+        }
+    }
+
+    /* bottom border. */
+    game_text_hborder(state, &p);
+
+    /* bottom row: column totals for minus then '-'. */
+    *p++ = ' ';
+    for (x = 0; x < state->w; x++) {
+        *p++ = ' ';
+        *p++ = n2c(state->common->colcount[x*3+NEGATIVE]);
+    }
+    *p++ = ' ';
+    *p++ = '-';
+    *p++ = '\n';
+    *p++ = '\0';
+
+    return ret;
+}
+
+static void game_debug(game_state *state, const char *desc)
+{
+    char *fmt = game_text_format(state);
+    debug(("%s:\n%s\n", desc, fmt));
+    sfree(fmt);
+}
+
+enum { ROW, COLUMN };
+
+typedef struct rowcol {
+    int i, di, n, roworcol, num;
+    int *targets;
+    const char *name;
+} rowcol;
+
+static rowcol mkrowcol(const game_state *state, int num, int roworcol)
+{
+    rowcol rc;
+
+    rc.roworcol = roworcol;
+    rc.num = num;
+
+    if (roworcol == ROW) {
+        rc.i = num * state->w;
+        rc.di = 1;
+        rc.n = state->w;
+        rc.targets = &(state->common->rowcount[num*3]);
+        rc.name = "row";
+    } else if (roworcol == COLUMN) {
+        rc.i = num;
+        rc.di = state->w;
+        rc.n = state->h;
+        rc.targets = &(state->common->colcount[num*3]);
+        rc.name = "column";
+    } else {
+        assert(!"unknown roworcol");
+    }
+    return rc;
+}
+
+static int count_rowcol(const game_state *state, int num, int roworcol,
+                        int which)
+{
+    int i, count = 0;
+    rowcol rc = mkrowcol(state, num, roworcol);
+
+    for (i = 0; i < rc.n; i++, rc.i += rc.di) {
+        if (which < 0) {
+            if (state->grid[rc.i] == EMPTY &&
+                !(state->flags[rc.i] & GS_SET))
+                count++;
+        } else if (state->grid[rc.i] == which)
+            count++;
+    }
+    return count;
+}
+
+static void check_rowcol(game_state *state, int num, int roworcol, int which,
+                        int *wrong, int *incomplete)
+{
+    int count, target = mkrowcol(state, num, roworcol).targets[which];
+
+    if (target == -1) return; /* no number to check against. */
+
+    count = count_rowcol(state, num, roworcol, which);
+    if (count < target) *incomplete = 1;
+    if (count > target) *wrong = 1;
+}
+
+static int check_completion(game_state *state)
+{
+    int i, j, x, y, idx, w = state->w, h = state->h;
+    int which = POSITIVE, wrong = 0, incomplete = 0;
+
+    /* Check row and column counts for magnets. */
+    for (which = POSITIVE, j = 0; j < 2; which = OPPOSITE(which), j++) {
+        for (i = 0; i < w; i++)
+            check_rowcol(state, i, COLUMN, which, &wrong, &incomplete);
+
+        for (i = 0; i < h; i++)
+            check_rowcol(state, i, ROW, which, &wrong, &incomplete);
+    }
+    /* Check each domino has been filled, and that we don't have
+     * touching identical terminals. */
+    for (i = 0; i < state->wh; i++) state->flags[i] &= ~GS_ERROR;
+    for (x = 0; x < w; x++) {
+        for (y = 0; y < h; y++) {
+            idx = y*w + x;
+            if (state->common->dominoes[idx] == idx)
+                continue; /* no domino here */
+
+            if (!(state->flags[idx] & GS_SET))
+                incomplete = 1;
+
+            which = state->grid[idx];
+            if (which != NEUTRAL) {
+#define CHECK(xx,yy) do { \
+    if (INGRID(state,xx,yy) && \
+        (state->grid[(yy)*w+(xx)] == which)) { \
+        wrong = 1; \
+        state->flags[(yy)*w+(xx)] |= GS_ERROR; \
+        state->flags[y*w+x] |= GS_ERROR; \
+    } \
+} while(0)
+                CHECK(x,y-1);
+                CHECK(x,y+1);
+                CHECK(x-1,y);
+                CHECK(x+1,y);
+#undef CHECK
+            }
+        }
+    }
+    return wrong ? -1 : incomplete ? 0 : 1;
+}
+
+static const int dx[4] = {-1, 1, 0, 0};
+static const int dy[4] = {0, 0, -1, 1};
+
+static void solve_clearflags(game_state *state)
+{
+    int i;
+
+    for (i = 0; i < state->wh; i++) {
+        state->flags[i] &= ~GS_NOTMASK;
+        if (state->common->dominoes[i] != i)
+            state->flags[i] &= ~GS_SET;
+    }
+}
+
+/* Knowing a given cell cannot be a certain colour also tells us
+ * something about the other cell in that domino. */
+static int solve_unflag(game_state *state, int i, int which,
+                        const char *why, rowcol *rc)
+{
+    int ii, ret = 0;
+#if defined DEBUGGING || defined STANDALONE_SOLVER
+    int w = state->w;
+#endif
+
+    assert(i >= 0 && i < state->wh);
+    ii = state->common->dominoes[i];
+    if (ii == i) return 0;
+
+    if (rc)
+        debug(("solve_unflag: (%d,%d) for %s %d", i%w, i/w, rc->name, rc->num));
+
+    if ((state->flags[i] & GS_SET) && (state->grid[i] == which)) {
+        debug(("solve_unflag: (%d,%d) already %s, cannot unflag (for %s).",
+               i%w, i/w, NAME(which), why));
+        return -1;
+    }
+    if ((state->flags[ii] & GS_SET) && (state->grid[ii] == OPPOSITE(which))) {
+        debug(("solve_unflag: (%d,%d) opposite already %s, cannot unflag (for %s).",
+               ii%w, ii/w, NAME(OPPOSITE(which)), why));
+        return -1;
+    }
+    if (POSSIBLE(i, which)) {
+        state->flags[i] |= NOTFLAG(which);
+        ret++;
+        debug(("solve_unflag: (%d,%d) CANNOT be %s (%s)",
+               i%w, i/w, NAME(which), why));
+    }
+    if (POSSIBLE(ii, OPPOSITE(which))) {
+        state->flags[ii] |= NOTFLAG(OPPOSITE(which));
+        ret++;
+        debug(("solve_unflag: (%d,%d) CANNOT be %s (%s, other half)",
+               ii%w, ii/w, NAME(OPPOSITE(which)), why));
+    }
+#ifdef STANDALONE_SOLVER
+    if (verbose && ret) {
+        printf("(%d,%d)", i%w, i/w);
+        if (rc) printf(" in %s %d", rc->name, rc->num);
+        printf(" cannot be %s (%s); opposite (%d,%d) not %s.\n",
+               NAME(which), why, ii%w, ii/w, NAME(OPPOSITE(which)));
+    }
+#endif
+    return ret;
+}
+
+static int solve_unflag_surrounds(game_state *state, int i, int which)
+{
+    int x = i%state->w, y = i/state->w, xx, yy, j, ii;
+
+    assert(INGRID(state, x, y));
+
+    for (j = 0; j < 4; j++) {
+        xx = x+dx[j]; yy = y+dy[j];
+        if (!INGRID(state, xx, yy)) continue;
+
+        ii = yy*state->w+xx;
+        if (solve_unflag(state, ii, which, "adjacent to set cell", NULL) < 0)
+            return -1;
+    }
+    return 0;
+}
+
+/* Sets a cell to a particular colour, and also perform other
+ * housekeeping around that. */
+static int solve_set(game_state *state, int i, int which,
+                     const char *why, rowcol *rc)
+{
+    int ii;
+#if defined DEBUGGING || defined STANDALONE_SOLVER
+    int w = state->w;
+#endif
+
+    ii = state->common->dominoes[i];
+
+    if (state->flags[i] & GS_SET) {
+        if (state->grid[i] == which) {
+            return 0; /* was already set and held, do nothing. */
+        } else {
+            debug(("solve_set: (%d,%d) is held and %s, cannot set to %s",
+                   i%w, i/w, NAME(state->grid[i]), NAME(which)));
+            return -1;
+        }
+    }
+    if ((state->flags[ii] & GS_SET) && state->grid[ii] != OPPOSITE(which)) {
+        debug(("solve_set: (%d,%d) opposite is held and %s, cannot set to %s",
+                ii%w, ii/w, NAME(state->grid[ii]), NAME(OPPOSITE(which))));
+        return -1;
+    }
+    if (!POSSIBLE(i, which)) {
+        debug(("solve_set: (%d,%d) NOT %s, cannot set.", i%w, i/w, NAME(which)));
+        return -1;
+    }
+    if (!POSSIBLE(ii, OPPOSITE(which))) {
+        debug(("solve_set: (%d,%d) NOT %s, cannot set (%d,%d).",
+               ii%w, ii/w, NAME(OPPOSITE(which)), i%w, i/w));
+        return -1;
+    }
+
+#ifdef STANDALONE_SOLVER
+    if (verbose) {
+        printf("(%d,%d)", i%w, i/w);
+        if (rc) printf(" in %s %d", rc->name, rc->num);
+        printf(" set to %s (%s), opposite (%d,%d) set to %s.\n",
+               NAME(which), why, ii%w, ii/w, NAME(OPPOSITE(which)));
+    }
+#endif
+    if (rc)
+        debug(("solve_set: (%d,%d) for %s %d", i%w, i/w, rc->name, rc->num));
+    debug(("solve_set: (%d,%d) setting to %s (%s), surrounds first:",
+           i%w, i/w, NAME(which), why));
+
+    if (which != NEUTRAL) {
+        if (solve_unflag_surrounds(state, i, which) < 0)
+            return -1;
+        if (solve_unflag_surrounds(state, ii, OPPOSITE(which)) < 0)
+            return -1;
+    }
+
+    state->grid[i] = which;
+    state->grid[ii] = OPPOSITE(which);
+
+    state->flags[i] |= GS_SET;
+    state->flags[ii] |= GS_SET;
+
+    debug(("solve_set: (%d,%d) set to %s (%s)", i%w, i/w, NAME(which), why));
+
+    return 1;
+}
+
+/* counts should be int[4]. */
+static void solve_counts(game_state *state, rowcol rc, int *counts, int *unset)
+{
+    int i, j, which;
+
+    assert(counts);
+    for (i = 0; i < 4; i++) {
+        counts[i] = 0;
+        if (unset) unset[i] = 0;
+    }
+
+    for (i = rc.i, j = 0; j < rc.n; i += rc.di, j++) {
+        if (state->flags[i] & GS_SET) {
+            assert(state->grid[i] < 3);
+            counts[state->grid[i]]++;
+        } else if (unset) {
+            for (which = 0; which <= 2; which++) {
+                if (POSSIBLE(i, which))
+                    unset[which]++;
+            }
+        }
+    }
+}
+
+static int solve_checkfull(game_state *state, rowcol rc, int *counts)
+{
+    int starti = rc.i, j, which, didsth = 0, target;
+    int unset[4];
+
+    assert(state->numbered); /* only useful (should only be called) if numbered. */
+
+    solve_counts(state, rc, counts, unset);
+
+    for (which = 0; which <= 2; which++) {
+        target = rc.targets[which];
+        if (target == -1) continue;
+
+        /*debug(("%s %d for %s: target %d, count %d, unset %d",
+               rc.name, rc.num, NAME(which),
+               target, counts[which], unset[which]));*/
+
+        if (target < counts[which]) {
+            debug(("%s %d has too many (%d) %s squares (target %d), impossible!",
+                   rc.name, rc.num, counts[which], NAME(which), target));
+            return -1;
+        }
+        if (target == counts[which]) {
+            /* We have the correct no. of the colour in this row/column
+             * already; unflag all the rest. */
+            for (rc.i = starti, j = 0; j < rc.n; rc.i += rc.di, j++) {
+                if (state->flags[rc.i] & GS_SET) continue;
+                if (!POSSIBLE(rc.i, which)) continue;
+
+                if (solve_unflag(state, rc.i, which, "row/col full", &rc) < 0)
+                    return -1;
+                didsth = 1;
+            }
+        } else if ((target - counts[which]) == unset[which]) {
+            /* We need all the remaining unset squares for this colour;
+             * set them all. */
+            for (rc.i = starti, j = 0; j < rc.n; rc.i += rc.di, j++) {
+                if (state->flags[rc.i] & GS_SET) continue;
+                if (!POSSIBLE(rc.i, which)) continue;
+
+                if (solve_set(state, rc.i, which, "row/col needs all unset", &rc) < 0)
+                    return -1;
+                didsth = 1;
+            }
+        }
+    }
+    return didsth;
+}
+
+static int solve_startflags(game_state *state)
+{
+    int x, y, i;
+
+    for (x = 0; x < state->w; x++) {
+        for (y = 0; y < state->h; y++) {
+            i = y*state->w+x;
+            if (state->common->dominoes[i] == i) continue;
+            if (state->grid[i] != NEUTRAL ||
+                state->flags[i] & GS_SET) {
+                if (solve_set(state, i, state->grid[i], "initial set-and-hold", NULL) < 0)
+                    return -1;
+            }
+        }
+    }
+    return 0;
+}
+
+typedef int (*rowcolfn)(game_state *state, rowcol rc, int *counts);
+
+static int solve_rowcols(game_state *state, rowcolfn fn)
+{
+    int x, y, didsth = 0, ret;
+    rowcol rc;
+    int counts[4];
+
+    for (x = 0; x < state->w; x++) {
+        rc = mkrowcol(state, x, COLUMN);
+        solve_counts(state, rc, counts, NULL);
+
+        ret = fn(state, rc, counts);
+        if (ret < 0) return ret;
+        didsth += ret;
+    }
+    for (y = 0; y < state->h; y++) {
+        rc = mkrowcol(state, y, ROW);
+        solve_counts(state, rc, counts, NULL);
+
+        ret = fn(state, rc, counts);
+        if (ret < 0) return ret;
+        didsth += ret;
+    }
+    return didsth;
+}
+
+static int solve_force(game_state *state)
+{
+    int i, which, didsth = 0;
+    unsigned long f;
+
+    for (i = 0; i < state->wh; i++) {
+        if (state->flags[i] & GS_SET) continue;
+        if (state->common->dominoes[i] == i) continue;
+
+        f = state->flags[i] & GS_NOTMASK;
+        which = -1;
+        if (f == (GS_NOTPOSITIVE|GS_NOTNEGATIVE))
+            which = NEUTRAL;
+        if (f == (GS_NOTPOSITIVE|GS_NOTNEUTRAL))
+            which = NEGATIVE;
+        if (f == (GS_NOTNEGATIVE|GS_NOTNEUTRAL))
+            which = POSITIVE;
+        if (which != -1) {
+            if (solve_set(state, i, which, "forced by flags", NULL) < 0)
+                return -1;
+            didsth = 1;
+        }
+    }
+    return didsth;
+}
+
+static int solve_neither(game_state *state)
+{
+    int i, j, didsth = 0;
+
+    for (i = 0; i < state->wh; i++) {
+        if (state->flags[i] & GS_SET) continue;
+        j = state->common->dominoes[i];
+        if (i == j) continue;
+
+        if (((state->flags[i] & GS_NOTPOSITIVE) &&
+             (state->flags[j] & GS_NOTPOSITIVE)) ||
+            ((state->flags[i] & GS_NOTNEGATIVE) &&
+             (state->flags[j] & GS_NOTNEGATIVE))) {
+            if (solve_set(state, i, NEUTRAL, "neither tile magnet", NULL) < 0)
+                return -1;
+            didsth = 1;
+        }
+    }
+    return didsth;
+}
+
+static int solve_advancedfull(game_state *state, rowcol rc, int *counts)
+{
+    int i, j, nfound = 0, clearpos = 0, clearneg = 0, ret = 0;
+
+    /* For this row/col, look for a domino entirely within the row where
+     * both ends can only be + or - (but isn't held).
+     * The +/- counts can thus be decremented by 1 each, and the 'unset'
+     * count by 2.
+     *
+     * Once that's done for all such dominoes (and they're marked), try
+     * and made usual deductions about rest of the row based on new totals. */
+
+    if (rc.targets[POSITIVE] == -1 && rc.targets[NEGATIVE] == -1)
+        return 0; /* don't have a target for either colour, nothing to do. */
+    if ((rc.targets[POSITIVE] >= 0 && counts[POSITIVE] == rc.targets[POSITIVE]) &&
+        (rc.targets[NEGATIVE] >= 0 && counts[NEGATIVE] == rc.targets[NEGATIVE]))
+        return 0; /* both colours are full up already, nothing to do. */
+
+    for (i = rc.i, j = 0; j < rc.n; i += rc.di, j++)
+        state->flags[i] &= ~GS_MARK;
+
+    for (i = rc.i, j = 0; j < rc.n; i += rc.di, j++) {
+        if (state->flags[i] & GS_SET) continue;
+
+        /* We're looking for a domino in our row/col, thus if
+         * dominoes[i] -> i+di we've found one. */
+        if (state->common->dominoes[i] != i+rc.di) continue;
+
+        /* We need both squares of this domino to be either + or -
+         * (i.e. both NOTNEUTRAL only). */
+        if (((state->flags[i] & GS_NOTMASK) != GS_NOTNEUTRAL) ||
+            ((state->flags[i+rc.di] & GS_NOTMASK) != GS_NOTNEUTRAL))
+            continue;
+
+        debug(("Domino in %s %d at (%d,%d) must be polarised.",
+               rc.name, rc.num, i%state->w, i/state->w));
+        state->flags[i] |= GS_MARK;
+        state->flags[i+rc.di] |= GS_MARK;
+        nfound++;
+    }
+    if (nfound == 0) return 0;
+
+    /* nfound is #dominoes we matched, which will all be marked. */
+    counts[POSITIVE] += nfound;
+    counts[NEGATIVE] += nfound;
+
+    if (rc.targets[POSITIVE] >= 0 && counts[POSITIVE] == rc.targets[POSITIVE]) {
+        debug(("%s %d has now filled POSITIVE:", rc.name, rc.num));
+        clearpos = 1;
+    }
+    if (rc.targets[NEGATIVE] >= 0 && counts[NEGATIVE] == rc.targets[NEGATIVE]) {
+        debug(("%s %d has now filled NEGATIVE:", rc.name, rc.num));
+        clearneg = 1;
+    }
+
+    if (!clearpos && !clearneg) return 0;
+
+    for (i = rc.i, j = 0; j < rc.n; i += rc.di, j++) {
+        if (state->flags[i] & GS_SET) continue;
+        if (state->flags[i] & GS_MARK) continue;
+
+        if (clearpos && !(state->flags[i] & GS_NOTPOSITIVE)) {
+            if (solve_unflag(state, i, POSITIVE, "row/col full (+ve) [tricky]", &rc) < 0)
+                return -1;
+            ret++;
+        }
+        if (clearneg && !(state->flags[i] & GS_NOTNEGATIVE)) {
+            if (solve_unflag(state, i, NEGATIVE, "row/col full (-ve) [tricky]", &rc) < 0)
+                return -1;
+            ret++;
+        }
+    }
+
+    return ret;
+}
+
+/* If we only have one neutral still to place on a row/column then no
+   dominoes entirely in that row/column can be neutral. */
+static int solve_nonneutral(game_state *state, rowcol rc, int *counts)
+{
+    int i, j, ret = 0;
+
+    if (rc.targets[NEUTRAL] != counts[NEUTRAL]+1)
+        return 0;
+
+    for (i = rc.i, j = 0; j < rc.n; i += rc.di, j++) {
+        if (state->flags[i] & GS_SET) continue;
+        if (state->common->dominoes[i] != i+rc.di) continue;
+
+        if (!(state->flags[i] & GS_NOTNEUTRAL)) {
+            if (solve_unflag(state, i, NEUTRAL, "single neutral in row/col [tricky]", &rc) < 0)
+                return -1;
+            ret++;
+        }
+    }
+    return ret;
+}
+
+/* If we need to fill all unfilled cells with +-, and we need 1 more of
+ * one than the other, and we have a single odd-numbered region of unfilled
+ * cells, that odd-numbered region must start and end with the extra number. */
+static int solve_oddlength(game_state *state, rowcol rc, int *counts)
+{
+    int i, j, ret = 0, extra, tpos, tneg;
+    int start = -1, length = 0, inempty = 0, startodd = -1;
+
+    /* need zero neutral cells still to find... */
+    if (rc.targets[NEUTRAL] != counts[NEUTRAL])
+        return 0;
+
+    /* ...and #positive and #negative to differ by one. */
+    tpos = rc.targets[POSITIVE] - counts[POSITIVE];
+    tneg = rc.targets[NEGATIVE] - counts[NEGATIVE];
+    if (tpos == tneg+1)
+        extra = POSITIVE;
+    else if (tneg == tpos+1)
+        extra = NEGATIVE;
+    else return 0;
+
+    for (i = rc.i, j = 0; j < rc.n; i += rc.di, j++) {
+        if (state->flags[i] & GS_SET) {
+            if (inempty) {
+                if (length % 2) {
+                    /* we've just finished an odd-length section. */
+                    if (startodd != -1) goto twoodd;
+                    startodd = start;
+                }
+                inempty = 0;
+            }
+        } else {
+            if (inempty)
+                length++;
+            else {
+                start = i;
+                length = 1;
+                inempty = 1;
+            }
+        }
+    }
+    if (inempty && (length % 2)) {
+        if (startodd != -1) goto twoodd;
+        startodd = start;
+    }
+    if (startodd != -1)
+        ret = solve_set(state, startodd, extra, "odd-length section start", &rc);
+
+    return ret;
+
+twoodd:
+    debug(("%s %d has >1 odd-length sections, starting at %d,%d and %d,%d.",
+           rc.name, rc.num,
+           startodd%state->w, startodd/state->w,
+           start%state->w, start/state->w));
+    return 0;
+}
+
+/* Count the number of remaining empty dominoes in any row/col.
+ * If that number is equal to the #remaining positive,
+ * or to the #remaining negative, no empty cells can be neutral. */
+static int solve_countdominoes_neutral(game_state *state, rowcol rc, int *counts)
+{
+    int i, j, ndom = 0, nonn = 0, ret = 0;
+
+    if ((rc.targets[POSITIVE] == -1) && (rc.targets[NEGATIVE] == -1))
+        return 0; /* need at least one target to compare. */
+
+    for (i = rc.i, j = 0; j < rc.n; i += rc.di, j++) {
+        if (state->flags[i] & GS_SET) continue;
+        assert(state->grid[i] == EMPTY);
+
+        /* Skip solo cells, or second cell in domino. */
+        if ((state->common->dominoes[i] == i) ||
+            (state->common->dominoes[i] == i-rc.di))
+            continue;
+
+        ndom++;
+    }
+
+    if ((rc.targets[POSITIVE] != -1) &&
+        (rc.targets[POSITIVE]-counts[POSITIVE] == ndom))
+        nonn = 1;
+    if ((rc.targets[NEGATIVE] != -1) &&
+        (rc.targets[NEGATIVE]-counts[NEGATIVE] == ndom))
+        nonn = 1;
+
+    if (!nonn) return 0;
+
+    for (i = rc.i, j = 0; j < rc.n; i += rc.di, j++) {
+        if (state->flags[i] & GS_SET) continue;
+
+        if (!(state->flags[i] & GS_NOTNEUTRAL)) {
+            if (solve_unflag(state, i, NEUTRAL, "all dominoes +/- [tricky]", &rc) < 0)
+                return -1;
+            ret++;
+        }
+    }
+    return ret;
+}
+
+static int solve_domino_count(game_state *state, rowcol rc, int i, int which)
+{
+    int nposs = 0;
+
+    /* Skip solo cells or 2nd in domino. */
+    if ((state->common->dominoes[i] == i) ||
+        (state->common->dominoes[i] == i-rc.di))
+        return 0;
+
+    if (state->flags[i] & GS_SET)
+        return 0;
+
+    if (POSSIBLE(i, which))
+        nposs++;
+
+    if (state->common->dominoes[i] == i+rc.di) {
+        /* second cell of domino is on our row: test that too. */
+        if (POSSIBLE(i+rc.di, which))
+            nposs++;
+    }
+    return nposs;
+}
+
+/* Count number of dominoes we could put each of + and - into. If it is equal
+ * to the #left, any domino we can only put + or - in one cell of must have it. */
+static int solve_countdominoes_nonneutral(game_state *state, rowcol rc, int *counts)
+{
+    int which, w, i, j, ndom = 0, didsth = 0, toset;
+
+    for (which = POSITIVE, w = 0; w < 2; which = OPPOSITE(which), w++) {
+        if (rc.targets[which] == -1) continue;
+
+        for (i = rc.i, j = 0; j < rc.n; i += rc.di, j++) {
+            if (solve_domino_count(state, rc, i, which) > 0)
+                ndom++;
+        }
+
+        if ((rc.targets[which] - counts[which]) != ndom)
+            continue;
+
+        for (i = rc.i, j = 0; j < rc.n; i += rc.di, j++) {
+            if (solve_domino_count(state, rc, i, which) == 1) {
+                if (POSSIBLE(i, which))
+                    toset = i;
+                else {
+                    /* paranoia, should have been checked by solve_domino_count. */
+                    assert(state->common->dominoes[i] == i+rc.di);
+                    assert(POSSIBLE(i+rc.di, which));
+                    toset = i+rc.di;
+                }
+                if (solve_set(state, toset, which, "all empty dominoes need +/- [tricky]", &rc) < 0)
+                    return -1;
+                didsth++;
+            }
+        }
+    }
+    return didsth;
+}
+
+/* danger, evil macro. can't use the do { ... } while(0) trick because
+ * the continue breaks. */
+#define SOLVE_FOR_ROWCOLS(fn) \
+    ret = solve_rowcols(state, fn); \
+    if (ret < 0) { debug(("%s said impossible, cannot solve", #fn)); return -1; } \
+    if (ret > 0) continue
+
+static int solve_state(game_state *state, int diff)
+{
+    int ret;
+
+    debug(("solve_state, difficulty %s", magnets_diffnames[diff]));
+
+    solve_clearflags(state);
+    if (solve_startflags(state) < 0) return -1;
+
+    while (1) {
+        ret = solve_force(state);
+        if (ret > 0) continue;
+        if (ret < 0) return -1;
+
+        ret = solve_neither(state);
+        if (ret > 0) continue;
+        if (ret < 0) return -1;
+
+        SOLVE_FOR_ROWCOLS(solve_checkfull);
+        SOLVE_FOR_ROWCOLS(solve_oddlength);
+
+        if (diff < DIFF_TRICKY) break;
+
+        SOLVE_FOR_ROWCOLS(solve_advancedfull);
+        SOLVE_FOR_ROWCOLS(solve_nonneutral);
+        SOLVE_FOR_ROWCOLS(solve_countdominoes_neutral);
+        SOLVE_FOR_ROWCOLS(solve_countdominoes_nonneutral);
+
+        /* more ... */
+
+        break;
+    }
+    return check_completion(state);
+}
+
+
+static char *game_state_diff(const game_state *src, const game_state *dst,
+                             int issolve)
+{
+    char *ret = NULL, buf[80], c;
+    int retlen = 0, x, y, i, k;
+
+    assert(src->w == dst->w && src->h == dst->h);
+
+    if (issolve) {
+        ret = sresize(ret, 3, char);
+        ret[0] = 'S'; ret[1] = ';'; ret[2] = '\0';
+        retlen += 2;
+    }
+    for (x = 0; x < dst->w; x++) {
+        for (y = 0; y < dst->h; y++) {
+            i = y*dst->w+x;
+
+            if (src->common->dominoes[i] == i) continue;
+
+#define APPEND do { \
+    ret = sresize(ret, retlen + k + 1, char); \
+    strcpy(ret + retlen, buf); \
+    retlen += k; \
+} while(0)
+
+            if ((src->grid[i] != dst->grid[i]) ||
+                ((src->flags[i] & GS_SET) != (dst->flags[i] & GS_SET))) {
+                if (dst->grid[i] == EMPTY && !(dst->flags[i] & GS_SET))
+                    c = ' ';
+                else
+                    c = GRID2CHAR(dst->grid[i]);
+                k = sprintf(buf, "%c%d,%d;", (int)c, x, y);
+                APPEND;
+            }
+        }
+    }
+    debug(("game_state_diff returns %s", ret));
+    return ret;
+}
+
+static void solve_from_aux(const game_state *state, const char *aux)
+{
+    int i;
+    assert(strlen(aux) == state->wh);
+    for (i = 0; i < state->wh; i++) {
+        state->grid[i] = CHAR2GRID(aux[i]);
+        state->flags[i] |= GS_SET;
+    }
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    game_state *solved = dup_game(currstate);
+    char *move = NULL;
+    int ret;
+
+    if (aux && strlen(aux) == state->wh) {
+        solve_from_aux(solved, aux);
+        goto solved;
+    }
+
+    if (solve_state(solved, DIFFCOUNT) > 0) goto solved;
+    free_game(solved);
+
+    solved = dup_game(state);
+    ret = solve_state(solved, DIFFCOUNT);
+    if (ret > 0) goto solved;
+    free_game(solved);
+
+    *error = (ret < 0) ? "Puzzle is impossible." : "Unable to solve puzzle.";
+    return NULL;
+
+solved:
+    move = game_state_diff(currstate, solved, 1);
+    free_game(solved);
+    return move;
+}
+
+static int solve_unnumbered(game_state *state)
+{
+    int i, ret;
+    while (1) {
+        ret = solve_force(state);
+        if (ret > 0) continue;
+        if (ret < 0) return -1;
+
+        ret = solve_neither(state);
+        if (ret > 0) continue;
+        if (ret < 0) return -1;
+
+        break;
+    }
+    for (i = 0; i < state->wh; i++) {
+        if (!(state->flags[i] & GS_SET)) return 0;
+    }
+    return 1;
+}
+
+static int lay_dominoes(game_state *state, random_state *rs, int *scratch)
+{
+    int n, i, ret = 0, nlaid = 0, n_initial_neutral;
+
+    for (i = 0; i < state->wh; i++) {
+        scratch[i] = i;
+        state->grid[i] = EMPTY;
+        state->flags[i] = (state->common->dominoes[i] == i) ? GS_SET : 0;
+    }
+    shuffle(scratch, state->wh, sizeof(int), rs);
+
+    n_initial_neutral = (state->wh > 100) ? 5 : (state->wh / 10);
+
+    for (n = 0; n < state->wh; n++) {
+        /* Find a space ... */
+
+        i = scratch[n];
+        if (state->flags[i] & GS_SET) continue; /* already laid here. */
+
+        /* ...and lay a domino if we can. */
+
+        debug(("Laying domino at i:%d, (%d,%d)\n", i, i%state->w, i/state->w));
+
+        /* The choice of which type of domino to lay here leads to subtle differences
+         * in the sorts of boards that get produced. Too much bias towards magnets
+         * leads to games that are too easy.
+         *
+         * Currently, it lays a small set of dominoes at random as neutral, and
+         * then lays the rest preferring to be magnets -- however, if the
+         * current layout is such that a magnet won't go there, then it lays
+         * another neutral.
+         *
+         * The number of initially neutral dominoes is limited as grids get bigger:
+         * too many neutral dominoes invariably ends up with insoluble puzzle at
+         * this size, and the positioning process means it'll always end up laying
+         * more than the initial 5 anyway.
+         */
+
+        /* We should always be able to lay a neutral anywhere. */
+        assert(!(state->flags[i] & GS_NOTNEUTRAL));
+
+        if (n < n_initial_neutral) {
+            debug(("  ...laying neutral\n"));
+            ret = solve_set(state, i, NEUTRAL, "layout initial neutral", NULL);
+        } else {
+            debug(("  ... preferring magnet\n"));
+            if (!(state->flags[i] & GS_NOTPOSITIVE))
+                ret = solve_set(state, i, POSITIVE, "layout", NULL);
+            else if (!(state->flags[i] & GS_NOTNEGATIVE))
+                ret = solve_set(state, i, NEGATIVE, "layout", NULL);
+            else
+                ret = solve_set(state, i, NEUTRAL, "layout", NULL);
+        }
+        if (!ret) {
+            debug(("Unable to lay anything at (%d,%d), giving up.",
+                   i%state->w, i/state->w));
+            ret = -1;
+            break;
+        }
+
+        nlaid++;
+        ret = solve_unnumbered(state);
+        if (ret == -1)
+            debug(("solve_unnumbered decided impossible.\n"));
+        if (ret != 0)
+            break;
+    }
+
+    debug(("Laid %d dominoes, total %d dominoes.\n", nlaid, state->wh/2));
+    game_debug(state, "Final layout");
+    return ret;
+}
+
+static void gen_game(game_state *new, random_state *rs)
+{
+    int ret, x, y, val;
+    int *scratch = snewn(new->wh, int);
+
+#ifdef STANDALONE_SOLVER
+    if (verbose) printf("Generating new game...\n");
+#endif
+
+    clear_state(new);
+    sfree(new->common->dominoes); /* bit grotty. */
+    new->common->dominoes = domino_layout(new->w, new->h, rs);
+
+    do {
+        ret = lay_dominoes(new, rs, scratch);
+    } while(ret == -1);
+
+    /* for each cell, update colcount/rowcount as appropriate. */
+    memset(new->common->colcount, 0, new->w*3*sizeof(int));
+    memset(new->common->rowcount, 0, new->h*3*sizeof(int));
+    for (x = 0; x < new->w; x++) {
+        for (y = 0; y < new->h; y++) {
+            val = new->grid[y*new->w+x];
+            new->common->colcount[x*3+val]++;
+            new->common->rowcount[y*3+val]++;
+        }
+    }
+    new->numbered = 1;
+
+    sfree(scratch);
+}
+
+static void generate_aux(game_state *new, char *aux)
+{
+    int i;
+    for (i = 0; i < new->wh; i++)
+        aux[i] = GRID2CHAR(new->grid[i]);
+    aux[new->wh] = '\0';
+}
+
+static int check_difficulty(const game_params *params, game_state *new,
+                            random_state *rs)
+{
+    int *scratch, *grid_correct, slen, i;
+
+    memset(new->grid, EMPTY, new->wh*sizeof(int));
+
+    if (params->diff > DIFF_EASY) {
+        /* If this is too easy, return. */
+        if (solve_state(new, params->diff-1) > 0) {
+            debug(("Puzzle is too easy."));
+            return -1;
+        }
+    }
+    if (solve_state(new, params->diff) <= 0) {
+        debug(("Puzzle is not soluble at requested difficulty."));
+        return -1;
+    }
+    if (!params->stripclues) return 0;
+
+    /* Copy the correct grid away. */
+    grid_correct = snewn(new->wh, int);
+    memcpy(grid_correct, new->grid, new->wh*sizeof(int));
+
+    /* Create shuffled array of side-clue locations. */
+    slen = new->w*2 + new->h*2;
+    scratch = snewn(slen, int);
+    for (i = 0; i < slen; i++) scratch[i] = i;
+    shuffle(scratch, slen, sizeof(int), rs);
+
+    /* For each clue, check whether removing it makes the puzzle unsoluble;
+     * put it back if so. */
+    for (i = 0; i < slen; i++) {
+        int num = scratch[i], which, roworcol, target, targetn, ret;
+        rowcol rc;
+
+        /* work out which clue we meant. */
+        if (num < new->w+new->h) { which = POSITIVE; }
+        else { which = NEGATIVE; num -= new->w+new->h; }
+
+        if (num < new->w) { roworcol = COLUMN; }
+        else { roworcol = ROW; num -= new->w; }
+
+        /* num is now the row/column index in question. */
+        rc = mkrowcol(new, num, roworcol);
+
+        /* Remove clue, storing original... */
+        target = rc.targets[which];
+        targetn = rc.targets[NEUTRAL];
+        rc.targets[which] = -1;
+        rc.targets[NEUTRAL] = -1;
+
+        /* ...and see if we can still solve it. */
+        game_debug(new, "removed clue, new board:");
+        memset(new->grid, EMPTY, new->wh * sizeof(int));
+        ret = solve_state(new, params->diff);
+        assert(ret != -1);
+
+        if (ret == 0 ||
+            memcmp(new->grid, grid_correct, new->wh*sizeof(int)) != 0) {
+            /* We made it ambiguous: put clue back. */
+            debug(("...now impossible/different, put clue back."));
+            rc.targets[which] = target;
+            rc.targets[NEUTRAL] = targetn;
+        }
+    }
+    sfree(scratch);
+    sfree(grid_correct);
+
+    return 0;
+}
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                          char **aux_r, int interactive)
+{
+    game_state *new = new_state(params->w, params->h);
+    char *desc, *aux = snewn(new->wh+1, char);
+
+    do {
+        gen_game(new, rs);
+        generate_aux(new, aux);
+    } while (check_difficulty(params, new, rs) < 0);
+
+    /* now we're complete, generate the description string
+     * and an aux_info for the completed game. */
+    desc = generate_desc(new);
+
+    free_game(new);
+
+    *aux_r = aux;
+    return desc;
+}
+
+struct game_ui {
+    int cur_x, cur_y, cur_visible;
+};
+
+static game_ui *new_ui(const game_state *state)
+{
+    game_ui *ui = snew(game_ui);
+    ui->cur_x = ui->cur_y = 0;
+    ui->cur_visible = 0;
+    return ui;
+}
+
+static void free_ui(game_ui *ui)
+{
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+    if (!oldstate->completed && newstate->completed)
+        ui->cur_visible = 0;
+}
+
+struct game_drawstate {
+    int tilesize, started, solved;
+    int w, h;
+    unsigned long *what;                /* size w*h */
+    unsigned long *colwhat, *rowwhat;   /* size 3*w, 3*h */
+};
+
+#define DS_WHICH_MASK 0xf
+
+#define DS_ERROR    0x10
+#define DS_CURSOR   0x20
+#define DS_SET      0x40
+#define DS_NOTPOS   0x80
+#define DS_NOTNEG   0x100
+#define DS_NOTNEU   0x200
+#define DS_FLASH    0x400
+
+#define PREFERRED_TILE_SIZE 32
+#define TILE_SIZE (ds->tilesize)
+#define BORDER    (TILE_SIZE / 8)
+
+#define COORD(x) ( (x+1) * TILE_SIZE + BORDER )
+#define FROMCOORD(x) ( (x - BORDER) / TILE_SIZE - 1 )
+
+static int is_clue(const game_state *state, int x, int y)
+{
+    int h = state->h, w = state->w;
+
+    if (((x == -1 || x == w) && y >= 0 && y < h) ||
+        ((y == -1 || y == h) && x >= 0 && x < w))
+        return TRUE;
+
+    return FALSE;
+}
+
+static int clue_index(const game_state *state, int x, int y)
+{
+    int h = state->h, w = state->w;
+
+    if (y == -1)
+        return x;
+    else if (x == w)
+        return w + y;
+    else if (y == h)
+        return 2 * w + h - x - 1;
+    else if (x == -1)
+        return 2 * (w + h) - y - 1;
+
+    return -1;
+}
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    int gx = FROMCOORD(x), gy = FROMCOORD(y), idx, curr;
+    char *nullret = NULL, buf[80], movech;
+    enum { CYCLE_MAGNET, CYCLE_NEUTRAL } action;
+
+    if (IS_CURSOR_MOVE(button)) {
+        move_cursor(button, &ui->cur_x, &ui->cur_y, state->w, state->h, 0);
+        ui->cur_visible = 1;
+        return "";
+    } else if (IS_CURSOR_SELECT(button)) {
+        if (!ui->cur_visible) {
+            ui->cur_visible = 1;
+            return "";
+        }
+        action = (button == CURSOR_SELECT) ? CYCLE_MAGNET : CYCLE_NEUTRAL;
+        gx = ui->cur_x;
+        gy = ui->cur_y;
+    } else if (INGRID(state, gx, gy) &&
+               (button == LEFT_BUTTON || button == RIGHT_BUTTON)) {
+        if (ui->cur_visible) {
+            ui->cur_visible = 0;
+            nullret = "";
+        }
+        action = (button == LEFT_BUTTON) ? CYCLE_MAGNET : CYCLE_NEUTRAL;
+    } else if (button == LEFT_BUTTON && is_clue(state, gx, gy)) {
+        sprintf(buf, "D%d,%d", gx, gy);
+        return dupstr(buf);
+    } else
+        return NULL;
+
+    idx = gy * state->w + gx;
+    if (state->common->dominoes[idx] == idx) return nullret;
+    curr = state->grid[idx];
+
+    if (action == CYCLE_MAGNET) {
+        /* ... empty --> positive --> negative --> empty ... */
+
+        if (state->grid[idx] == NEUTRAL && state->flags[idx] & GS_SET)
+            return nullret; /* can't cycle a magnet from a neutral. */
+        movech = (curr == EMPTY) ? '+' : (curr == POSITIVE) ? '-' : ' ';
+    } else if (action == CYCLE_NEUTRAL) {
+        /* ... empty -> neutral -> !neutral --> empty ... */
+
+        if (state->grid[idx] != NEUTRAL)
+            return nullret; /* can't cycle through neutral from a magnet. */
+
+        /* All of these are grid == EMPTY == NEUTRAL; it twiddles
+         * combinations of flags. */
+        if (state->flags[idx] & GS_SET) /* neutral */
+            movech = '?';
+        else if (state->flags[idx] & GS_NOTNEUTRAL) /* !neutral */
+            movech = ' ';
+        else
+            movech = '.';
+    } else {
+        assert(!"unknown action");
+       movech = 0;                    /* placate optimiser */
+    }
+
+    sprintf(buf, "%c%d,%d", movech, gx, gy);
+
+    return dupstr(buf);
+}
+
+static game_state *execute_move(const game_state *state, const char *move)
+{
+    game_state *ret = dup_game(state);
+    int x, y, n, idx, idx2;
+    char c;
+
+    if (!*move) goto badmove;
+    while (*move) {
+        c = *move++;
+        if (c == 'S') {
+            ret->solved = TRUE;
+            n = 0;
+        } else if (c == '+' || c == '-' ||
+                   c == '.' || c == ' ' || c == '?') {
+            if ((sscanf(move, "%d,%d%n", &x, &y, &n) != 2) ||
+                !INGRID(state, x, y)) goto badmove;
+
+            idx = y*state->w + x;
+            idx2 = state->common->dominoes[idx];
+            if (idx == idx2) goto badmove;
+
+            ret->flags[idx] &= ~GS_NOTMASK;
+            ret->flags[idx2] &= ~GS_NOTMASK;
+
+            if (c == ' ' || c == '?') {
+                ret->grid[idx] = EMPTY;
+                ret->grid[idx2] = EMPTY;
+                ret->flags[idx] &= ~GS_SET;
+                ret->flags[idx2] &= ~GS_SET;
+                if (c == '?') {
+                    ret->flags[idx] |= GS_NOTNEUTRAL;
+                    ret->flags[idx2] |= GS_NOTNEUTRAL;
+                }
+            } else {
+                ret->grid[idx] = CHAR2GRID(c);
+                ret->grid[idx2] = OPPOSITE(CHAR2GRID(c));
+                ret->flags[idx] |= GS_SET;
+                ret->flags[idx2] |= GS_SET;
+            }
+        } else if (c == 'D' && sscanf(move, "%d,%d%n", &x, &y, &n) == 2 &&
+                   is_clue(ret, x, y)) {
+            ret->counts_done[clue_index(ret, x, y)] ^= 1;
+        } else
+            goto badmove;
+
+        move += n;
+        if (*move == ';') move++;
+        else if (*move) goto badmove;
+    }
+    if (check_completion(ret) == 1)
+        ret->completed = 1;
+
+    return ret;
+
+badmove:
+    free_game(ret);
+    return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    struct { int tilesize; } ads, *ds = &ads;
+    ads.tilesize = tilesize;
+
+    *x = TILE_SIZE * (params->w+2) + 2 * BORDER;
+    *y = TILE_SIZE * (params->h+2) + 2 * BORDER;
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+    int i;
+
+    game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT);
+
+    for (i = 0; i < 3; i++) {
+        ret[COL_TEXT * 3 + i] = 0.0F;
+        ret[COL_NEGATIVE * 3 + i] = 0.0F;
+        ret[COL_CURSOR * 3 + i] = 0.9F;
+        ret[COL_DONE * 3 + i] = ret[COL_BACKGROUND * 3 + i] / 1.5F;
+    }
+
+    ret[COL_POSITIVE * 3 + 0] = 0.8F;
+    ret[COL_POSITIVE * 3 + 1] = 0.0F;
+    ret[COL_POSITIVE * 3 + 2] = 0.0F;
+
+    ret[COL_NEUTRAL * 3 + 0] = 0.10F;
+    ret[COL_NEUTRAL * 3 + 1] = 0.60F;
+    ret[COL_NEUTRAL * 3 + 2] = 0.10F;
+
+    ret[COL_ERROR * 3 + 0] = 1.0F;
+    ret[COL_ERROR * 3 + 1] = 0.0F;
+    ret[COL_ERROR * 3 + 2] = 0.0F;
+
+    ret[COL_NOT * 3 + 0] = 0.2F;
+    ret[COL_NOT * 3 + 1] = 0.2F;
+    ret[COL_NOT * 3 + 2] = 1.0F;
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    struct game_drawstate *ds = snew(struct game_drawstate);
+
+    ds->tilesize = ds->started = ds->solved = 0;
+    ds->w = state->w;
+    ds->h = state->h;
+
+    ds->what = snewn(state->wh, unsigned long);
+    memset(ds->what, 0, state->wh*sizeof(unsigned long));
+
+    ds->colwhat = snewn(state->w*3, unsigned long);
+    memset(ds->colwhat, 0, state->w*3*sizeof(unsigned long));
+    ds->rowwhat = snewn(state->h*3, unsigned long);
+    memset(ds->rowwhat, 0, state->h*3*sizeof(unsigned long));
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds->colwhat);
+    sfree(ds->rowwhat);
+    sfree(ds->what);
+    sfree(ds);
+}
+
+static void draw_num(drawing *dr, game_drawstate *ds, int rowcol, int which,
+                         int idx, int colbg, int col, int num)
+{
+    char buf[32];
+    int cx, cy, tsz;
+
+    if (num < 0) return;
+
+    sprintf(buf, "%d", num);
+    tsz = (strlen(buf) == 1) ? (7*TILE_SIZE/10) : (9*TILE_SIZE/10)/strlen(buf);
+
+    if (rowcol == ROW) {
+        cx = BORDER;
+        if (which == NEGATIVE) cx += TILE_SIZE * (ds->w+1);
+        cy = BORDER + TILE_SIZE * (idx+1);
+    } else {
+        cx = BORDER + TILE_SIZE * (idx+1);
+        cy = BORDER;
+        if (which == NEGATIVE) cy += TILE_SIZE * (ds->h+1);
+    }
+
+    draw_rect(dr, cx, cy, TILE_SIZE, TILE_SIZE, colbg);
+    draw_text(dr, cx + TILE_SIZE/2, cy + TILE_SIZE/2, FONT_VARIABLE, tsz,
+              ALIGN_VCENTRE | ALIGN_HCENTRE, col, buf);
+
+    draw_update(dr, cx, cy, TILE_SIZE, TILE_SIZE);
+}
+
+static void draw_sym(drawing *dr, game_drawstate *ds, int x, int y, int which, int col)
+{
+    int cx = COORD(x), cy = COORD(y);
+    int ccx = cx + TILE_SIZE/2, ccy = cy + TILE_SIZE/2;
+    int roff = TILE_SIZE/4, rsz = 2*roff+1;
+    int soff = TILE_SIZE/16, ssz = 2*soff+1;
+
+    if (which == POSITIVE || which == NEGATIVE) {
+        draw_rect(dr, ccx - roff, ccy - soff, rsz, ssz, col);
+        if (which == POSITIVE)
+            draw_rect(dr, ccx - soff, ccy - roff, ssz, rsz, col);
+    } else if (col == COL_NOT) {
+        /* not-a-neutral is a blue question mark. */
+        char qu[2] = { '?', 0 };
+        draw_text(dr, ccx, ccy, FONT_VARIABLE, 7*TILE_SIZE/10,
+                  ALIGN_VCENTRE | ALIGN_HCENTRE, col, qu);
+    } else {
+        draw_line(dr, ccx - roff, ccy - roff, ccx + roff, ccy + roff, col);
+        draw_line(dr, ccx + roff, ccy - roff, ccx - roff, ccy + roff, col);
+    }
+}
+
+enum {
+    TYPE_L,
+    TYPE_R,
+    TYPE_T,
+    TYPE_B,
+    TYPE_BLANK
+};
+
+/* NOT responsible for redrawing background or updating. */
+static void draw_tile_col(drawing *dr, game_drawstate *ds, int *dominoes,
+                          int x, int y, int which, int bg, int fg, int perc)
+{
+    int cx = COORD(x), cy = COORD(y), i, other, type = TYPE_BLANK;
+    int gutter, radius, coffset;
+
+    /* gutter is TSZ/16 for 100%, 8*TSZ/16 (TSZ/2) for 0% */
+    gutter = (TILE_SIZE / 16) + ((100 - perc) * (7*TILE_SIZE / 16))/100;
+    radius = (perc * (TILE_SIZE / 8)) / 100;
+    coffset = gutter + radius;
+
+    i = y*ds->w + x;
+    other = dominoes[i];
+
+    if (other == i) return;
+    else if (other == i+1) type = TYPE_L;
+    else if (other == i-1) type = TYPE_R;
+    else if (other == i+ds->w) type = TYPE_T;
+    else if (other == i-ds->w) type = TYPE_B;
+    else assert(!"mad domino orientation");
+
+    /* domino drawing shamelessly stolen from dominosa.c. */
+    if (type == TYPE_L || type == TYPE_T)
+        draw_circle(dr, cx+coffset, cy+coffset,
+                    radius, bg, bg);
+    if (type == TYPE_R || type == TYPE_T)
+        draw_circle(dr, cx+TILE_SIZE-1-coffset, cy+coffset,
+                    radius, bg, bg);
+    if (type == TYPE_L || type == TYPE_B)
+        draw_circle(dr, cx+coffset, cy+TILE_SIZE-1-coffset,
+                    radius, bg, bg);
+    if (type == TYPE_R || type == TYPE_B)
+        draw_circle(dr, cx+TILE_SIZE-1-coffset,
+                    cy+TILE_SIZE-1-coffset,
+                    radius, bg, bg);
+
+    for (i = 0; i < 2; i++) {
+        int x1, y1, x2, y2;
+
+        x1 = cx + (i ? gutter : coffset);
+        y1 = cy + (i ? coffset : gutter);
+        x2 = cx + TILE_SIZE-1 - (i ? gutter : coffset);
+        y2 = cy + TILE_SIZE-1 - (i ? coffset : gutter);
+        if (type == TYPE_L)
+            x2 = cx + TILE_SIZE;
+        else if (type == TYPE_R)
+            x1 = cx;
+        else if (type == TYPE_T)
+            y2 = cy + TILE_SIZE ;
+        else if (type == TYPE_B)
+            y1 = cy;
+
+        draw_rect(dr, x1, y1, x2-x1+1, y2-y1+1, bg);
+    }
+
+    if (fg != -1) draw_sym(dr, ds, x, y, which, fg);
+}
+
+static void draw_tile(drawing *dr, game_drawstate *ds, int *dominoes,
+                      int x, int y, unsigned long flags)
+{
+    int cx = COORD(x), cy = COORD(y), bg, fg, perc = 100;
+    int which = flags & DS_WHICH_MASK;
+
+    flags &= ~DS_WHICH_MASK;
+
+    draw_rect(dr, cx, cy, TILE_SIZE, TILE_SIZE, COL_BACKGROUND);
+
+    if (flags & DS_CURSOR)
+        bg = COL_CURSOR;        /* off-white white for cursor */
+    else if (which == POSITIVE)
+        bg = COL_POSITIVE;
+    else if (which == NEGATIVE)
+        bg = COL_NEGATIVE;
+    else if (flags & DS_SET)
+        bg = COL_NEUTRAL;       /* green inner for neutral cells */
+    else
+        bg = COL_LOWLIGHT;      /* light grey for empty cells. */
+
+    if (which == EMPTY && !(flags & DS_SET)) {
+        int notwhich = -1;
+        fg = -1; /* don't draw cross unless actually set as neutral. */
+
+        if (flags & DS_NOTPOS) notwhich = POSITIVE;
+        if (flags & DS_NOTNEG) notwhich = NEGATIVE;
+        if (flags & DS_NOTNEU) notwhich = NEUTRAL;
+        if (notwhich != -1) {
+            which = notwhich;
+            fg = COL_NOT;
+        }
+    } else
+        fg = (flags & DS_ERROR) ? COL_ERROR :
+             (flags & DS_CURSOR) ? COL_TEXT : COL_BACKGROUND;
+
+    draw_rect(dr, cx, cy, TILE_SIZE, TILE_SIZE, COL_BACKGROUND);
+
+    if (flags & DS_FLASH) {
+        int bordercol = COL_HIGHLIGHT;
+        draw_tile_col(dr, ds, dominoes, x, y, which, bordercol, -1, perc);
+        perc = 3*perc/4;
+    }
+    draw_tile_col(dr, ds, dominoes, x, y, which, bg, fg, perc);
+
+    draw_update(dr, cx, cy, TILE_SIZE, TILE_SIZE);
+}
+
+static int get_count_color(const game_state *state, int rowcol, int which,
+                           int index, int target)
+{
+    int idx;
+    int count = count_rowcol(state, index, rowcol, which);
+
+    if ((count > target) ||
+        (count < target && !count_rowcol(state, index, rowcol, -1))) {
+        return COL_ERROR;
+    } else if (rowcol == COLUMN) {
+        idx = clue_index(state, index, which == POSITIVE ? -1 : state->h);
+    } else {
+        idx = clue_index(state, which == POSITIVE ? -1 : state->w, index);
+    }
+
+    if (state->counts_done[idx]) {
+        return COL_DONE;
+    }
+
+    return COL_TEXT;
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int x, y, w = state->w, h = state->h, which, i, j, flash;
+
+    flash = (int)(flashtime * 5 / FLASH_TIME) % 2;
+
+    if (!ds->started) {
+        /* draw background, corner +-. */
+        draw_rect(dr, 0, 0,
+                  TILE_SIZE * (w+2) + 2 * BORDER,
+                  TILE_SIZE * (h+2) + 2 * BORDER,
+                  COL_BACKGROUND);
+
+        draw_sym(dr, ds, -1, -1, POSITIVE, COL_TEXT);
+        draw_sym(dr, ds, state->w, state->h, NEGATIVE, COL_TEXT);
+
+        draw_update(dr, 0, 0,
+                    TILE_SIZE * (ds->w+2) + 2 * BORDER,
+                    TILE_SIZE * (ds->h+2) + 2 * BORDER);
+    }
+
+    /* Draw grid */
+    for (y = 0; y < h; y++) {
+        for (x = 0; x < w; x++) {
+            int idx = y*w+x;
+            unsigned long c = state->grid[idx];
+
+            if (state->flags[idx] & GS_ERROR)
+                c |= DS_ERROR;
+            if (state->flags[idx] & GS_SET)
+                c |= DS_SET;
+
+            if (x == ui->cur_x && y == ui->cur_y && ui->cur_visible)
+                c |= DS_CURSOR;
+
+            if (flash)
+                c |= DS_FLASH;
+
+            if (state->flags[idx] & GS_NOTPOSITIVE)
+                c |= DS_NOTPOS;
+            if (state->flags[idx] & GS_NOTNEGATIVE)
+                c |= DS_NOTNEG;
+            if (state->flags[idx] & GS_NOTNEUTRAL)
+                c |= DS_NOTNEU;
+
+            if (ds->what[idx] != c || !ds->started) {
+                draw_tile(dr, ds, state->common->dominoes, x, y, c);
+                ds->what[idx] = c;
+            }
+        }
+    }
+    /* Draw counts around side */
+    for (which = POSITIVE, j = 0; j < 2; which = OPPOSITE(which), j++) {
+        for (i = 0; i < w; i++) {
+            int index = i * 3 + which;
+            int target = state->common->colcount[index];
+            int color = get_count_color(state, COLUMN, which, i, target);
+
+            if (color != ds->colwhat[index] || !ds->started) {
+                draw_num(dr, ds, COLUMN, which, i, COL_BACKGROUND, color, target);
+                ds->colwhat[index] = color;
+            }
+        }
+        for (i = 0; i < h; i++) {
+            int index = i * 3 + which;
+            int target = state->common->rowcount[index];
+            int color = get_count_color(state, ROW, which, i, target);
+
+            if (color != ds->rowwhat[index] || !ds->started) {
+                draw_num(dr, ds, ROW, which, i, COL_BACKGROUND, color, target);
+                ds->rowwhat[index] = color;
+            }
+        }
+    }
+
+    ds->started = 1;
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return 0.0F;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    if (!oldstate->completed && newstate->completed &&
+        !oldstate->solved && !newstate->solved)
+        return FLASH_TIME;
+    return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    return state->completed ? +1 : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+    int pw, ph;
+
+    /*
+     * I'll use 6mm squares by default.
+     */
+    game_compute_size(params, 600, &pw, &ph);
+    *x = pw / 100.0F;
+    *y = ph / 100.0F;
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+    int w = state->w, h = state->h;
+    int ink = print_mono_colour(dr, 0);
+    int paper = print_mono_colour(dr, 1);
+    int x, y, which, i, j;
+
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    game_drawstate ads, *ds = &ads;
+    game_set_size(dr, ds, NULL, tilesize);
+    ds->w = w; ds->h = h;
+
+    /* Border. */
+    print_line_width(dr, TILE_SIZE/12);
+
+    /* Numbers and +/- for corners. */
+    draw_sym(dr, ds, -1, -1, POSITIVE, ink);
+    draw_sym(dr, ds, state->w, state->h, NEGATIVE, ink);
+    for (which = POSITIVE, j = 0; j < 2; which = OPPOSITE(which), j++) {
+        for (i = 0; i < w; i++) {
+            draw_num(dr, ds, COLUMN, which, i, paper, ink,
+                         state->common->colcount[i*3+which]);
+        }
+        for (i = 0; i < h; i++) {
+            draw_num(dr, ds, ROW, which, i, paper, ink,
+                         state->common->rowcount[i*3+which]);
+        }
+    }
+
+    /* Dominoes. */
+    for (x = 0; x < w; x++) {
+        for (y = 0; y < h; y++) {
+            i = y*state->w + x;
+           if (state->common->dominoes[i] == i+1 ||
+               state->common->dominoes[i] == i+w) {
+               int dx = state->common->dominoes[i] == i+1 ? 2 : 1;
+               int dy = 3 - dx;
+               int xx, yy;
+               int cx = COORD(x), cy = COORD(y);
+
+               print_line_width(dr, 0);
+
+               /* Ink the domino */
+               for (yy = 0; yy < 2; yy++)
+                   for (xx = 0; xx < 2; xx++)
+                       draw_circle(dr,
+                                   cx+xx*dx*TILE_SIZE+(1-2*xx)*3*TILE_SIZE/16,
+                                   cy+yy*dy*TILE_SIZE+(1-2*yy)*3*TILE_SIZE/16,
+                                   TILE_SIZE/8, ink, ink);
+               draw_rect(dr, cx + TILE_SIZE/16, cy + 3*TILE_SIZE/16,
+                         dx*TILE_SIZE - 2*(TILE_SIZE/16),
+                         dy*TILE_SIZE - 6*(TILE_SIZE/16), ink);
+               draw_rect(dr, cx + 3*TILE_SIZE/16, cy + TILE_SIZE/16,
+                         dx*TILE_SIZE - 6*(TILE_SIZE/16),
+                         dy*TILE_SIZE - 2*(TILE_SIZE/16), ink);
+
+               /* Un-ink the domino interior */
+               for (yy = 0; yy < 2; yy++)
+                   for (xx = 0; xx < 2; xx++)
+                       draw_circle(dr,
+                                   cx+xx*dx*TILE_SIZE+(1-2*xx)*3*TILE_SIZE/16,
+                                   cy+yy*dy*TILE_SIZE+(1-2*yy)*3*TILE_SIZE/16,
+                                   3*TILE_SIZE/32, paper, paper);
+               draw_rect(dr, cx + 3*TILE_SIZE/32, cy + 3*TILE_SIZE/16,
+                         dx*TILE_SIZE - 2*(3*TILE_SIZE/32),
+                         dy*TILE_SIZE - 6*(TILE_SIZE/16), paper);
+               draw_rect(dr, cx + 3*TILE_SIZE/16, cy + 3*TILE_SIZE/32,
+                         dx*TILE_SIZE - 6*(TILE_SIZE/16),
+                         dy*TILE_SIZE - 2*(3*TILE_SIZE/32), paper);
+           }
+        }
+    }
+
+    /* Grid symbols (solution). */
+    for (x = 0; x < w; x++) {
+        for (y = 0; y < h; y++) {
+            i = y*state->w + x;
+           if ((state->grid[i] != NEUTRAL) || (state->flags[i] & GS_SET))
+               draw_sym(dr, ds, x, y, state->grid[i], ink);
+       }
+    }
+}
+
+#ifdef COMBINED
+#define thegame magnets
+#endif
+
+const struct game thegame = {
+    "Magnets", "games.magnets", "magnets",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    TRUE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    TRUE, FALSE, game_print_size, game_print,
+    FALSE,                            /* wants_statusbar */
+    FALSE, game_timing_state,
+    REQUIRE_RBUTTON,                  /* flags */
+};
+
+#ifdef STANDALONE_SOLVER
+
+#include <time.h>
+#include <stdarg.h>
+
+const char *quis = NULL;
+int csv = 0;
+
+void usage(FILE *out) {
+    fprintf(out, "usage: %s [-v] [--print] <params>|<game id>\n", quis);
+}
+
+void doprint(game_state *state)
+{
+    char *fmt = game_text_format(state);
+    printf("%s", fmt);
+    sfree(fmt);
+}
+
+static void pnum(int n, int ntot, const char *desc)
+{
+    printf("%2.1f%% (%d) %s", (double)n*100.0 / (double)ntot, n, desc);
+}
+
+static void start_soak(game_params *p, random_state *rs)
+{
+    time_t tt_start, tt_now, tt_last;
+    char *aux;
+    game_state *s, *s2;
+    int n = 0, nsolved = 0, nimpossible = 0, ntricky = 0, ret, i;
+    long nn, nn_total = 0, nn_solved = 0, nn_tricky = 0;
+
+    tt_start = tt_now = time(NULL);
+
+    if (csv)
+        printf("time, w, h,  #generated, #solved, #tricky, #impossible,  "
+               "#neutral, #neutral/solved, #neutral/tricky\n");
+    else
+        printf("Soak-testing a %dx%d grid.\n", p->w, p->h);
+
+    s = new_state(p->w, p->h);
+    aux = snewn(s->wh+1, char);
+
+    while (1) {
+        gen_game(s, rs);
+
+        nn = 0;
+        for (i = 0; i < s->wh; i++) {
+            if (s->grid[i] == NEUTRAL) nn++;
+        }
+
+        generate_aux(s, aux);
+        memset(s->grid, EMPTY, s->wh * sizeof(int));
+        s2 = dup_game(s);
+
+        ret = solve_state(s, DIFFCOUNT);
+
+        n++;
+        nn_total += nn;
+        if (ret > 0) {
+            nsolved++;
+            nn_solved += nn;
+            if (solve_state(s2, DIFF_EASY) <= 0) {
+                ntricky++;
+                nn_tricky += nn;
+            }
+        } else if (ret < 0) {
+            char *desc = generate_desc(s);
+            solve_from_aux(s, aux);
+            printf("Game considered impossible:\n  %dx%d:%s\n",
+                    p->w, p->h, desc);
+            sfree(desc);
+            doprint(s);
+            nimpossible++;
+        }
+
+        free_game(s2);
+
+        tt_last = time(NULL);
+        if (tt_last > tt_now) {
+            tt_now = tt_last;
+            if (csv) {
+                printf("%d,%d,%d, %d,%d,%d,%d, %ld,%ld,%ld\n",
+                       (int)(tt_now - tt_start), p->w, p->h,
+                       n, nsolved, ntricky, nimpossible,
+                       nn_total, nn_solved, nn_tricky);
+            } else {
+                printf("%d total, %3.1f/s, ",
+                       n, (double)n / ((double)tt_now - tt_start));
+                pnum(nsolved, n, "solved"); printf(", ");
+                pnum(ntricky, n, "tricky");
+                if (nimpossible > 0)
+                    pnum(nimpossible, n, "impossible");
+                printf("\n");
+
+                printf("  overall %3.1f%% neutral (%3.1f%% for solved, %3.1f%% for tricky)\n",
+                       (double)(nn_total * 100) / (double)(p->w * p->h * n),
+                       (double)(nn_solved * 100) / (double)(p->w * p->h * nsolved),
+                       (double)(nn_tricky * 100) / (double)(p->w * p->h * ntricky));
+            }
+        }
+    }
+    free_game(s);
+    sfree(aux);
+}
+
+int main(int argc, const char *argv[])
+{
+    int print = 0, soak = 0, solved = 0, ret;
+    char *id = NULL, *desc, *desc_gen = NULL, *err, *aux = NULL;
+    game_state *s = NULL;
+    game_params *p = NULL;
+    random_state *rs = NULL;
+    time_t seed = time(NULL);
+
+    setvbuf(stdout, NULL, _IONBF, 0);
+
+    quis = argv[0];
+    while (--argc > 0) {
+        char *p = (char*)(*++argv);
+        if (!strcmp(p, "-v") || !strcmp(p, "--verbose")) {
+            verbose = 1;
+        } else if (!strcmp(p, "--csv")) {
+            csv = 1;
+        } else if (!strcmp(p, "-e") || !strcmp(p, "--seed")) {
+            seed = atoi(*++argv);
+            argc--;
+        } else if (!strcmp(p, "-p") || !strcmp(p, "--print")) {
+            print = 1;
+        } else if (!strcmp(p, "-s") || !strcmp(p, "--soak")) {
+            soak = 1;
+        } else if (*p == '-') {
+            fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p);
+            usage(stderr);
+            exit(1);
+        } else {
+            id = p;
+        }
+    }
+
+    rs = random_new((void*)&seed, sizeof(time_t));
+
+    if (!id) {
+        fprintf(stderr, "usage: %s [-v] [--soak] <params> | <game_id>\n", argv[0]);
+        goto done;
+    }
+    desc = strchr(id, ':');
+    if (desc) *desc++ = '\0';
+
+    p = default_params();
+    decode_params(p, id);
+    err = validate_params(p, 1);
+    if (err) {
+        fprintf(stderr, "%s: %s", argv[0], err);
+        goto done;
+    }
+
+    if (soak) {
+        if (desc) {
+            fprintf(stderr, "%s: --soak needs parameters, not description.\n", quis);
+            goto done;
+        }
+        start_soak(p, rs);
+        goto done;
+    }
+
+    if (!desc)
+        desc = desc_gen = new_game_desc(p, rs, &aux, 0);
+
+    err = validate_desc(p, desc);
+    if (err) {
+        fprintf(stderr, "%s: %s\nDescription: %s\n", quis, err, desc);
+        goto done;
+    }
+    s = new_game(NULL, p, desc);
+    printf("%s:%s (seed %ld)\n", id, desc, (long)seed);
+    if (aux) {
+        /* We just generated this ourself. */
+        if (verbose || print) {
+            doprint(s);
+            solve_from_aux(s, aux);
+            solved = 1;
+        }
+    } else {
+        doprint(s);
+        verbose = 1;
+        ret = solve_state(s, DIFFCOUNT);
+        if (ret < 0) printf("Puzzle is impossible.\n");
+        else if (ret == 0) printf("Puzzle is ambiguous.\n");
+        else printf("Puzzle was solved.\n");
+        verbose = 0;
+        solved = 1;
+    }
+    if (solved) doprint(s);
+
+done:
+    if (desc_gen) sfree(desc_gen);
+    if (p) free_params(p);
+    if (s) free_game(s);
+    if (rs) random_free(rs);
+    if (aux) sfree(aux);
+
+    return 0;
+}
+
+#endif
+
+/* vim: set shiftwidth=4 tabstop=8: */
diff --git a/malloc.c b/malloc.c
new file mode 100644 (file)
index 0000000..a7fa7c5
--- /dev/null
+++ b/malloc.c
@@ -0,0 +1,53 @@
+/*
+ * malloc.c: safe wrappers around malloc, realloc, free, strdup
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include "puzzles.h"
+
+/*
+ * smalloc should guarantee to return a useful pointer - Halibut
+ * can do nothing except die when it's out of memory anyway.
+ */
+void *smalloc(size_t size) {
+    void *p;
+    p = malloc(size);
+    if (!p)
+       fatal("out of memory");
+    return p;
+}
+
+/*
+ * sfree should guaranteeably deal gracefully with freeing NULL
+ */
+void sfree(void *p) {
+    if (p) {
+       free(p);
+    }
+}
+
+/*
+ * srealloc should guaranteeably be able to realloc NULL
+ */
+void *srealloc(void *p, size_t size) {
+    void *q;
+    if (p) {
+       q = realloc(p, size);
+    } else {
+       q = malloc(size);
+    }
+    if (!q)
+       fatal("out of memory");
+    return q;
+}
+
+/*
+ * dupstr is like strdup, but with the never-return-NULL property
+ * of smalloc (and also reliably defined in all environments :-)
+ */
+char *dupstr(const char *s) {
+    char *r = smalloc(1+strlen(s));
+    strcpy(r,s);
+    return r;
+}
diff --git a/map.R b/map.R
new file mode 100644 (file)
index 0000000..1e702aa
--- /dev/null
+++ b/map.R
@@ -0,0 +1,24 @@
+# -*- makefile -*-
+
+MAP_EXTRA = dsf
+
+map      : [X] GTK COMMON map MAP_EXTRA map-icon|no-icon
+
+map      : [G] WINDOWS COMMON map MAP_EXTRA map.res|noicon.res
+
+mapsolver :     [U] map[STANDALONE_SOLVER] MAP_EXTRA STANDALONE m.lib
+mapsolver :     [C] map[STANDALONE_SOLVER] MAP_EXTRA STANDALONE
+
+ALL += map[COMBINED] MAP_EXTRA
+
+!begin am gtk
+GAMES += map
+!end
+
+!begin >list.c
+    A(map) \
+!end
+
+!begin >gamedesc.txt
+map:map.exe:Map:Map-colouring puzzle:Colour the map so that adjacent regions are never the same colour.
+!end
diff --git a/map.c b/map.c
new file mode 100644 (file)
index 0000000..f3c4430
--- /dev/null
+++ b/map.c
@@ -0,0 +1,3340 @@
+/*
+ * map.c: Game involving four-colouring a map.
+ */
+
+/*
+ * TODO:
+ * 
+ *  - clue marking
+ *  - better four-colouring algorithm?
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+
+/*
+ * In standalone solver mode, `verbose' is a variable which can be
+ * set by command-line option; in debugging mode it's simply always
+ * true.
+ */
+#if defined STANDALONE_SOLVER
+#define SOLVER_DIAGNOSTICS
+int verbose = FALSE;
+#elif defined SOLVER_DIAGNOSTICS
+#define verbose TRUE
+#endif
+
+/*
+ * I don't seriously anticipate wanting to change the number of
+ * colours used in this game, but it doesn't cost much to use a
+ * #define just in case :-)
+ */
+#define FOUR 4
+#define THREE (FOUR-1)
+#define FIVE (FOUR+1)
+#define SIX (FOUR+2)
+
+/*
+ * Ghastly run-time configuration option, just for Gareth (again).
+ */
+static int flash_type = -1;
+static float flash_length;
+
+/*
+ * Difficulty levels. I do some macro ickery here to ensure that my
+ * enum and the various forms of my name list always match up.
+ */
+#define DIFFLIST(A) \
+    A(EASY,Easy,e) \
+    A(NORMAL,Normal,n) \
+    A(HARD,Hard,h) \
+    A(RECURSE,Unreasonable,u)
+#define ENUM(upper,title,lower) DIFF_ ## upper,
+#define TITLE(upper,title,lower) #title,
+#define ENCODE(upper,title,lower) #lower
+#define CONFIG(upper,title,lower) ":" #title
+enum { DIFFLIST(ENUM) DIFFCOUNT };
+static char const *const map_diffnames[] = { DIFFLIST(TITLE) };
+static char const map_diffchars[] = DIFFLIST(ENCODE);
+#define DIFFCONFIG DIFFLIST(CONFIG)
+
+enum { TE, BE, LE, RE };               /* top/bottom/left/right edges */
+
+enum {
+    COL_BACKGROUND,
+    COL_GRID,
+    COL_0, COL_1, COL_2, COL_3,
+    COL_ERROR, COL_ERRTEXT,
+    NCOLOURS
+};
+
+struct game_params {
+    int w, h, n, diff;
+};
+
+struct map {
+    int refcount;
+    int *map;
+    int *graph;
+    int n;
+    int ngraph;
+    int *immutable;
+    int *edgex, *edgey;                       /* position of a point on each edge */
+    int *regionx, *regiony;            /* position of a point in each region */
+};
+
+struct game_state {
+    game_params p;
+    struct map *map;
+    int *colouring, *pencil;
+    int completed, cheated;
+};
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+
+#ifdef PORTRAIT_SCREEN
+    ret->w = 16;
+    ret->h = 18;
+#else
+    ret->w = 20;
+    ret->h = 15;
+#endif
+    ret->n = 30;
+    ret->diff = DIFF_NORMAL;
+
+    return ret;
+}
+
+static const struct game_params map_presets[] = {
+#ifdef PORTRAIT_SCREEN
+    {16, 18, 30, DIFF_EASY},
+    {16, 18, 30, DIFF_NORMAL},
+    {16, 18, 30, DIFF_HARD},
+    {16, 18, 30, DIFF_RECURSE},
+    {25, 30, 75, DIFF_NORMAL},
+    {25, 30, 75, DIFF_HARD},
+#else
+    {20, 15, 30, DIFF_EASY},
+    {20, 15, 30, DIFF_NORMAL},
+    {20, 15, 30, DIFF_HARD},
+    {20, 15, 30, DIFF_RECURSE},
+    {30, 25, 75, DIFF_NORMAL},
+    {30, 25, 75, DIFF_HARD},
+#endif
+};
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params *ret;
+    char str[80];
+
+    if (i < 0 || i >= lenof(map_presets))
+        return FALSE;
+
+    ret = snew(game_params);
+    *ret = map_presets[i];
+
+    sprintf(str, "%dx%d, %d regions, %s", ret->w, ret->h, ret->n,
+           map_diffnames[ret->diff]);
+
+    *name = dupstr(str);
+    *params = ret;
+    return TRUE;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+static void decode_params(game_params *params, char const *string)
+{
+    char const *p = string;
+
+    params->w = atoi(p);
+    while (*p && isdigit((unsigned char)*p)) p++;
+    if (*p == 'x') {
+        p++;
+        params->h = atoi(p);
+        while (*p && isdigit((unsigned char)*p)) p++;
+    } else {
+        params->h = params->w;
+    }
+    if (*p == 'n') {
+       p++;
+       params->n = atoi(p);
+       while (*p && (*p == '.' || isdigit((unsigned char)*p))) p++;
+    } else {
+       params->n = params->w * params->h / 8;
+    }
+    if (*p == 'd') {
+       int i;
+       p++;
+       for (i = 0; i < DIFFCOUNT; i++)
+           if (*p == map_diffchars[i])
+               params->diff = i;
+       if (*p) p++;
+    }
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char ret[400];
+
+    sprintf(ret, "%dx%dn%d", params->w, params->h, params->n);
+    if (full)
+       sprintf(ret + strlen(ret), "d%c", map_diffchars[params->diff]);
+
+    return dupstr(ret);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+    char buf[80];
+
+    ret = snewn(5, config_item);
+
+    ret[0].name = "Width";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->w);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Height";
+    ret[1].type = C_STRING;
+    sprintf(buf, "%d", params->h);
+    ret[1].sval = dupstr(buf);
+    ret[1].ival = 0;
+
+    ret[2].name = "Regions";
+    ret[2].type = C_STRING;
+    sprintf(buf, "%d", params->n);
+    ret[2].sval = dupstr(buf);
+    ret[2].ival = 0;
+
+    ret[3].name = "Difficulty";
+    ret[3].type = C_CHOICES;
+    ret[3].sval = DIFFCONFIG;
+    ret[3].ival = params->diff;
+
+    ret[4].name = NULL;
+    ret[4].type = C_END;
+    ret[4].sval = NULL;
+    ret[4].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = atoi(cfg[0].sval);
+    ret->h = atoi(cfg[1].sval);
+    ret->n = atoi(cfg[2].sval);
+    ret->diff = cfg[3].ival;
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    if (params->w < 2 || params->h < 2)
+       return "Width and height must be at least two";
+    if (params->n < 5)
+       return "Must have at least five regions";
+    if (params->n > params->w * params->h)
+       return "Too many regions to fit in grid";
+    return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Cumulative frequency table functions.
+ */
+
+/*
+ * Initialise a cumulative frequency table. (Hardly worth writing
+ * this function; all it does is to initialise everything in the
+ * array to zero.)
+ */
+static void cf_init(int *table, int n)
+{
+    int i;
+
+    for (i = 0; i < n; i++)
+       table[i] = 0;
+}
+
+/*
+ * Increment the count of symbol `sym' by `count'.
+ */
+static void cf_add(int *table, int n, int sym, int count)
+{
+    int bit;
+
+    bit = 1;
+    while (sym != 0) {
+       if (sym & bit) {
+           table[sym] += count;
+           sym &= ~bit;
+       }
+       bit <<= 1;
+    }
+
+    table[0] += count;
+}
+
+/*
+ * Cumulative frequency lookup: return the total count of symbols
+ * with value less than `sym'.
+ */
+static int cf_clookup(int *table, int n, int sym)
+{
+    int bit, index, limit, count;
+
+    if (sym == 0)
+       return 0;
+
+    assert(0 < sym && sym <= n);
+
+    count = table[0];                 /* start with the whole table size */
+
+    bit = 1;
+    while (bit < n)
+       bit <<= 1;
+
+    limit = n;
+
+    while (bit > 0) {
+       /*
+        * Find the least number with its lowest set bit in this
+        * position which is greater than or equal to sym.
+        */
+       index = ((sym + bit - 1) &~ (bit * 2 - 1)) + bit;
+
+       if (index < limit) {
+           count -= table[index];
+           limit = index;
+       }
+
+       bit >>= 1;
+    }
+
+    return count;
+}
+
+/*
+ * Single frequency lookup: return the count of symbol `sym'.
+ */
+static int cf_slookup(int *table, int n, int sym)
+{
+    int count, bit;
+
+    assert(0 <= sym && sym < n);
+
+    count = table[sym];
+
+    for (bit = 1; sym+bit < n && !(sym & bit); bit <<= 1)
+       count -= table[sym+bit];
+
+    return count;
+}
+
+/*
+ * Return the largest symbol index such that the cumulative
+ * frequency up to that symbol is less than _or equal to_ count.
+ */
+static int cf_whichsym(int *table, int n, int count) {
+    int bit, sym, top;
+
+    assert(count >= 0 && count < table[0]);
+
+    bit = 1;
+    while (bit < n)
+       bit <<= 1;
+
+    sym = 0;
+    top = table[0];
+
+    while (bit > 0) {
+       if (sym+bit < n) {
+           if (count >= top - table[sym+bit])
+               sym += bit;
+           else
+               top -= table[sym+bit];
+       }
+
+       bit >>= 1;
+    }
+
+    return sym;
+}
+
+/* ----------------------------------------------------------------------
+ * Map generation.
+ * 
+ * FIXME: this isn't entirely optimal at present, because it
+ * inherently prioritises growing the largest region since there
+ * are more squares adjacent to it. This acts as a destabilising
+ * influence leading to a few large regions and mostly small ones.
+ * It might be better to do it some other way.
+ */
+
+#define WEIGHT_INCREASED 2             /* for increased perimeter */
+#define WEIGHT_DECREASED 4             /* for decreased perimeter */
+#define WEIGHT_UNCHANGED 3             /* for unchanged perimeter */
+
+/*
+ * Look at a square and decide which colours can be extended into
+ * it.
+ * 
+ * If called with index < 0, it adds together one of
+ * WEIGHT_INCREASED, WEIGHT_DECREASED or WEIGHT_UNCHANGED for each
+ * colour that has a valid extension (according to the effect that
+ * it would have on the perimeter of the region being extended) and
+ * returns the overall total.
+ * 
+ * If called with index >= 0, it returns one of the possible
+ * colours depending on the value of index, in such a way that the
+ * number of possible inputs which would give rise to a given
+ * return value correspond to the weight of that value.
+ */
+static int extend_options(int w, int h, int n, int *map,
+                          int x, int y, int index)
+{
+    int c, i, dx, dy;
+    int col[8];
+    int total = 0;
+
+    if (map[y*w+x] >= 0) {
+        assert(index < 0);
+        return 0;                      /* can't do this square at all */
+    }
+
+    /*
+     * Fetch the eight neighbours of this square, in order around
+     * the square.
+     */
+    for (dy = -1; dy <= +1; dy++)
+        for (dx = -1; dx <= +1; dx++) {
+            int index = (dy < 0 ? 6-dx : dy > 0 ? 2+dx : 2*(1+dx));
+            if (x+dx >= 0 && x+dx < w && y+dy >= 0 && y+dy < h)
+                col[index] = map[(y+dy)*w+(x+dx)];
+            else
+                col[index] = -1;
+        }
+
+    /*
+     * Iterate over each colour that might be feasible.
+     * 
+     * FIXME: this routine currently has O(n) running time. We
+     * could turn it into O(FOUR) by only bothering to iterate over
+     * the colours mentioned in the four neighbouring squares.
+     */
+
+    for (c = 0; c < n; c++) {
+        int count, neighbours, runs;
+
+        /*
+         * One of the even indices of col (representing the
+         * orthogonal neighbours of this square) must be equal to
+         * c, or else this square is not adjacent to region c and
+         * obviously cannot become an extension of it at this time.
+         */
+        neighbours = 0;
+        for (i = 0; i < 8; i += 2)
+            if (col[i] == c)
+                neighbours++;
+        if (!neighbours)
+            continue;
+
+        /*
+         * Now we know this square is adjacent to region c. The
+         * next question is, would extending it cause the region to
+         * become non-simply-connected? If so, we mustn't do it.
+         * 
+         * We determine this by looking around col to see if we can
+         * find more than one separate run of colour c.
+         */
+        runs = 0;
+        for (i = 0; i < 8; i++)
+            if (col[i] == c && col[(i+1) & 7] != c)
+                runs++;
+        if (runs > 1)
+            continue;
+
+        assert(runs == 1);
+
+        /*
+         * This square is a possibility. Determine its effect on
+         * the region's perimeter (computed from the number of
+         * orthogonal neighbours - 1 means a perimeter increase, 3
+         * a decrease, 2 no change; 4 is impossible because the
+         * region would already not be simply connected) and we're
+         * done.
+         */
+        assert(neighbours > 0 && neighbours < 4);
+        count = (neighbours == 1 ? WEIGHT_INCREASED :
+                 neighbours == 2 ? WEIGHT_UNCHANGED : WEIGHT_DECREASED);
+
+        total += count;
+        if (index >= 0 && index < count)
+            return c;
+        else
+            index -= count;
+    }
+
+    assert(index < 0);
+
+    return total;
+}
+
+static void genmap(int w, int h, int n, int *map, random_state *rs)
+{
+    int wh = w*h;
+    int x, y, i, k;
+    int *tmp;
+
+    assert(n <= wh);
+    tmp = snewn(wh, int);
+
+    /*
+     * Clear the map, and set up `tmp' as a list of grid indices.
+     */
+    for (i = 0; i < wh; i++) {
+        map[i] = -1;
+        tmp[i] = i;
+    }
+
+    /*
+     * Place the region seeds by selecting n members from `tmp'.
+     */
+    k = wh;
+    for (i = 0; i < n; i++) {
+        int j = random_upto(rs, k);
+        map[tmp[j]] = i;
+        tmp[j] = tmp[--k];
+    }
+
+    /*
+     * Re-initialise `tmp' as a cumulative frequency table. This
+     * will store the number of possible region colours we can
+     * extend into each square.
+     */
+    cf_init(tmp, wh);
+
+    /*
+     * Go through the grid and set up the initial cumulative
+     * frequencies.
+     */
+    for (y = 0; y < h; y++)
+        for (x = 0; x < w; x++)
+            cf_add(tmp, wh, y*w+x,
+                   extend_options(w, h, n, map, x, y, -1));
+
+    /*
+     * Now repeatedly choose a square we can extend a region into,
+     * and do so.
+     */
+    while (tmp[0] > 0) {
+        int k = random_upto(rs, tmp[0]);
+        int sq;
+        int colour;
+        int xx, yy;
+
+        sq = cf_whichsym(tmp, wh, k);
+        k -= cf_clookup(tmp, wh, sq);
+        x = sq % w;
+        y = sq / w;
+        colour = extend_options(w, h, n, map, x, y, k);
+
+        map[sq] = colour;
+
+        /*
+         * Re-scan the nine cells around the one we've just
+         * modified.
+         */
+        for (yy = max(y-1, 0); yy < min(y+2, h); yy++)
+            for (xx = max(x-1, 0); xx < min(x+2, w); xx++) {
+                cf_add(tmp, wh, yy*w+xx,
+                       -cf_slookup(tmp, wh, yy*w+xx) +
+                       extend_options(w, h, n, map, xx, yy, -1));
+            }
+    }
+
+    /*
+     * Finally, go through and normalise the region labels into
+     * order, meaning that indistinguishable maps are actually
+     * identical.
+     */
+    for (i = 0; i < n; i++)
+        tmp[i] = -1;
+    k = 0;
+    for (i = 0; i < wh; i++) {
+        assert(map[i] >= 0);
+        if (tmp[map[i]] < 0)
+            tmp[map[i]] = k++;
+        map[i] = tmp[map[i]];
+    }
+
+    sfree(tmp);
+}
+
+/* ----------------------------------------------------------------------
+ * Functions to handle graphs.
+ */
+
+/*
+ * Having got a map in a square grid, convert it into a graph
+ * representation.
+ */
+static int gengraph(int w, int h, int n, int *map, int *graph)
+{
+    int i, j, x, y;
+
+    /*
+     * Start by setting the graph up as an adjacency matrix. We'll
+     * turn it into a list later.
+     */
+    for (i = 0; i < n*n; i++)
+       graph[i] = 0;
+
+    /*
+     * Iterate over the map looking for all adjacencies.
+     */
+    for (y = 0; y < h; y++)
+        for (x = 0; x < w; x++) {
+           int v, vx, vy;
+           v = map[y*w+x];
+           if (x+1 < w && (vx = map[y*w+(x+1)]) != v)
+               graph[v*n+vx] = graph[vx*n+v] = 1;
+           if (y+1 < h && (vy = map[(y+1)*w+x]) != v)
+               graph[v*n+vy] = graph[vy*n+v] = 1;
+       }
+
+    /*
+     * Turn the matrix into a list.
+     */
+    for (i = j = 0; i < n*n; i++)
+       if (graph[i])
+           graph[j++] = i;
+
+    return j;
+}
+
+static int graph_edge_index(int *graph, int n, int ngraph, int i, int j)
+{
+    int v = i*n+j;
+    int top, bot, mid;
+
+    bot = -1;
+    top = ngraph;
+    while (top - bot > 1) {
+       mid = (top + bot) / 2;
+       if (graph[mid] == v)
+           return mid;
+       else if (graph[mid] < v)
+           bot = mid;
+       else
+           top = mid;
+    }
+    return -1;
+}
+
+#define graph_adjacent(graph, n, ngraph, i, j) \
+    (graph_edge_index((graph), (n), (ngraph), (i), (j)) >= 0)
+
+static int graph_vertex_start(int *graph, int n, int ngraph, int i)
+{
+    int v = i*n;
+    int top, bot, mid;
+
+    bot = -1;
+    top = ngraph;
+    while (top - bot > 1) {
+       mid = (top + bot) / 2;
+       if (graph[mid] < v)
+           bot = mid;
+       else
+           top = mid;
+    }
+    return top;
+}
+
+/* ----------------------------------------------------------------------
+ * Generate a four-colouring of a graph.
+ *
+ * FIXME: it would be nice if we could convert this recursion into
+ * pseudo-recursion using some sort of explicit stack array, for
+ * the sake of the Palm port and its limited stack.
+ */
+
+static int fourcolour_recurse(int *graph, int n, int ngraph,
+                             int *colouring, int *scratch, random_state *rs)
+{
+    int nfree, nvert, start, i, j, k, c, ci;
+    int cs[FOUR];
+
+    /*
+     * Find the smallest number of free colours in any uncoloured
+     * vertex, and count the number of such vertices.
+     */
+
+    nfree = FIVE;                     /* start off bigger than FOUR! */
+    nvert = 0;
+    for (i = 0; i < n; i++)
+       if (colouring[i] < 0 && scratch[i*FIVE+FOUR] <= nfree) {
+           if (nfree > scratch[i*FIVE+FOUR]) {
+               nfree = scratch[i*FIVE+FOUR];
+               nvert = 0;
+           }
+           nvert++;
+       }
+
+    /*
+     * If there aren't any uncoloured vertices at all, we're done.
+     */
+    if (nvert == 0)
+       return TRUE;                   /* we've got a colouring! */
+
+    /*
+     * Pick a random vertex in that set.
+     */
+    j = random_upto(rs, nvert);
+    for (i = 0; i < n; i++)
+       if (colouring[i] < 0 && scratch[i*FIVE+FOUR] == nfree)
+           if (j-- == 0)
+               break;
+    assert(i < n);
+    start = graph_vertex_start(graph, n, ngraph, i);
+
+    /*
+     * Loop over the possible colours for i, and recurse for each
+     * one.
+     */
+    ci = 0;
+    for (c = 0; c < FOUR; c++)
+       if (scratch[i*FIVE+c] == 0)
+           cs[ci++] = c;
+    shuffle(cs, ci, sizeof(*cs), rs);
+
+    while (ci-- > 0) {
+       c = cs[ci];
+
+       /*
+        * Fill in this colour.
+        */
+       colouring[i] = c;
+
+       /*
+        * Update the scratch space to reflect a new neighbour
+        * of this colour for each neighbour of vertex i.
+        */
+       for (j = start; j < ngraph && graph[j] < n*(i+1); j++) {
+           k = graph[j] - i*n;
+           if (scratch[k*FIVE+c] == 0)
+               scratch[k*FIVE+FOUR]--;
+           scratch[k*FIVE+c]++;
+       }
+
+       /*
+        * Recurse.
+        */
+       if (fourcolour_recurse(graph, n, ngraph, colouring, scratch, rs))
+           return TRUE;               /* got one! */
+
+       /*
+        * If that didn't work, clean up and try again with a
+        * different colour.
+        */
+       for (j = start; j < ngraph && graph[j] < n*(i+1); j++) {
+           k = graph[j] - i*n;
+           scratch[k*FIVE+c]--;
+           if (scratch[k*FIVE+c] == 0)
+               scratch[k*FIVE+FOUR]++;
+       }
+       colouring[i] = -1;
+    }
+
+    /*
+     * If we reach here, we were unable to find a colouring at all.
+     * (This doesn't necessarily mean the Four Colour Theorem is
+     * violated; it might just mean we've gone down a dead end and
+     * need to back up and look somewhere else. It's only an FCT
+     * violation if we get all the way back up to the top level and
+     * still fail.)
+     */
+    return FALSE;
+}
+
+static void fourcolour(int *graph, int n, int ngraph, int *colouring,
+                      random_state *rs)
+{
+    int *scratch;
+    int i;
+
+    /*
+     * For each vertex and each colour, we store the number of
+     * neighbours that have that colour. Also, we store the number
+     * of free colours for the vertex.
+     */
+    scratch = snewn(n * FIVE, int);
+    for (i = 0; i < n * FIVE; i++)
+       scratch[i] = (i % FIVE == FOUR ? FOUR : 0);
+
+    /*
+     * Clear the colouring to start with.
+     */
+    for (i = 0; i < n; i++)
+       colouring[i] = -1;
+
+    i = fourcolour_recurse(graph, n, ngraph, colouring, scratch, rs);
+    assert(i);                        /* by the Four Colour Theorem :-) */
+
+    sfree(scratch);
+}
+
+/* ----------------------------------------------------------------------
+ * Non-recursive solver.
+ */
+
+struct solver_scratch {
+    unsigned char *possible;          /* bitmap of colours for each region */
+
+    int *graph;
+    int n;
+    int ngraph;
+
+    int *bfsqueue;
+    int *bfscolour;
+#ifdef SOLVER_DIAGNOSTICS
+    int *bfsprev;
+#endif
+
+    int depth;
+};
+
+static struct solver_scratch *new_scratch(int *graph, int n, int ngraph)
+{
+    struct solver_scratch *sc;
+
+    sc = snew(struct solver_scratch);
+    sc->graph = graph;
+    sc->n = n;
+    sc->ngraph = ngraph;
+    sc->possible = snewn(n, unsigned char);
+    sc->depth = 0;
+    sc->bfsqueue = snewn(n, int);
+    sc->bfscolour = snewn(n, int);
+#ifdef SOLVER_DIAGNOSTICS
+    sc->bfsprev = snewn(n, int);
+#endif
+
+    return sc;
+}
+
+static void free_scratch(struct solver_scratch *sc)
+{
+    sfree(sc->possible);
+    sfree(sc->bfsqueue);
+    sfree(sc->bfscolour);
+#ifdef SOLVER_DIAGNOSTICS
+    sfree(sc->bfsprev);
+#endif
+    sfree(sc);
+}
+
+/*
+ * Count the bits in a word. Only needs to cope with FOUR bits.
+ */
+static int bitcount(int word)
+{
+    assert(FOUR <= 4);                 /* or this needs changing */
+    word = ((word & 0xA) >> 1) + (word & 0x5);
+    word = ((word & 0xC) >> 2) + (word & 0x3);
+    return word;
+}
+
+#ifdef SOLVER_DIAGNOSTICS
+static const char colnames[FOUR] = { 'R', 'Y', 'G', 'B' };
+#endif
+
+static int place_colour(struct solver_scratch *sc,
+                       int *colouring, int index, int colour
+#ifdef SOLVER_DIAGNOSTICS
+                        , char *verb
+#endif
+                        )
+{
+    int *graph = sc->graph, n = sc->n, ngraph = sc->ngraph;
+    int j, k;
+
+    if (!(sc->possible[index] & (1 << colour))) {
+#ifdef SOLVER_DIAGNOSTICS
+        if (verbose)
+            printf("%*scannot place %c in region %d\n", 2*sc->depth, "",
+                   colnames[colour], index);
+#endif
+       return FALSE;                  /* can't do it */
+    }
+
+    sc->possible[index] = 1 << colour;
+    colouring[index] = colour;
+
+#ifdef SOLVER_DIAGNOSTICS
+    if (verbose)
+       printf("%*s%s %c in region %d\n", 2*sc->depth, "",
+               verb, colnames[colour], index);
+#endif
+
+    /*
+     * Rule out this colour from all the region's neighbours.
+     */
+    for (j = graph_vertex_start(graph, n, ngraph, index);
+        j < ngraph && graph[j] < n*(index+1); j++) {
+       k = graph[j] - index*n;
+#ifdef SOLVER_DIAGNOSTICS
+        if (verbose && (sc->possible[k] & (1 << colour)))
+            printf("%*s  ruling out %c in region %d\n", 2*sc->depth, "",
+                   colnames[colour], k);
+#endif
+       sc->possible[k] &= ~(1 << colour);
+    }
+
+    return TRUE;
+}
+
+#ifdef SOLVER_DIAGNOSTICS
+static char *colourset(char *buf, int set)
+{
+    int i;
+    char *p = buf;
+    char *sep = "";
+
+    for (i = 0; i < FOUR; i++)
+        if (set & (1 << i)) {
+            p += sprintf(p, "%s%c", sep, colnames[i]);
+            sep = ",";
+        }
+
+    return buf;
+}
+#endif
+
+/*
+ * Returns 0 for impossible, 1 for success, 2 for failure to
+ * converge (i.e. puzzle is either ambiguous or just too
+ * difficult).
+ */
+static int map_solver(struct solver_scratch *sc,
+                     int *graph, int n, int ngraph, int *colouring,
+                      int difficulty)
+{
+    int i;
+
+    if (sc->depth == 0) {
+        /*
+         * Initialise scratch space.
+         */
+        for (i = 0; i < n; i++)
+            sc->possible[i] = (1 << FOUR) - 1;
+
+        /*
+         * Place clues.
+         */
+        for (i = 0; i < n; i++)
+            if (colouring[i] >= 0) {
+                if (!place_colour(sc, colouring, i, colouring[i]
+#ifdef SOLVER_DIAGNOSTICS
+                                  , "initial clue:"
+#endif
+                                  )) {
+#ifdef SOLVER_DIAGNOSTICS
+                    if (verbose)
+                        printf("%*sinitial clue set is inconsistent\n",
+                               2*sc->depth, "");
+#endif
+                    return 0;         /* the clues aren't even consistent! */
+                }
+            }
+    }
+
+    /*
+     * Now repeatedly loop until we find nothing further to do.
+     */
+    while (1) {
+       int done_something = FALSE;
+
+        if (difficulty < DIFF_EASY)
+            break;                     /* can't do anything at all! */
+
+       /*
+        * Simplest possible deduction: find a region with only one
+        * possible colour.
+        */
+       for (i = 0; i < n; i++) if (colouring[i] < 0) {
+           int p = sc->possible[i];
+
+           if (p == 0) {
+#ifdef SOLVER_DIAGNOSTICS
+                if (verbose)
+                    printf("%*sregion %d has no possible colours left\n",
+                           2*sc->depth, "", i);
+#endif
+               return 0;              /* puzzle is inconsistent */
+            }
+
+           if ((p & (p-1)) == 0) {    /* p is a power of two */
+               int c, ret;
+               for (c = 0; c < FOUR; c++)
+                   if (p == (1 << c))
+                       break;
+               assert(c < FOUR);
+               ret = place_colour(sc, colouring, i, c
+#ifdef SOLVER_DIAGNOSTICS
+                                   , "placing"
+#endif
+                                   );
+                /*
+                 * place_colour() can only fail if colour c was not
+                 * even a _possibility_ for region i, and we're
+                 * pretty sure it was because we checked before
+                 * calling place_colour(). So we can safely assert
+                 * here rather than having to return a nice
+                 * friendly error code.
+                 */
+                assert(ret);
+               done_something = TRUE;
+           }
+       }
+
+        if (done_something)
+            continue;
+
+        if (difficulty < DIFF_NORMAL)
+            break;                     /* can't do anything harder */
+
+        /*
+         * Failing that, go up one level. Look for pairs of regions
+         * which (a) both have the same pair of possible colours,
+         * (b) are adjacent to one another, (c) are adjacent to the
+         * same region, and (d) that region still thinks it has one
+         * or both of those possible colours.
+         * 
+         * Simplest way to do this is by going through the graph
+         * edge by edge, so that we start with property (b) and
+         * then look for (a) and finally (c) and (d).
+         */
+        for (i = 0; i < ngraph; i++) {
+            int j1 = graph[i] / n, j2 = graph[i] % n;
+            int j, k, v, v2;
+#ifdef SOLVER_DIAGNOSTICS
+            int started = FALSE;
+#endif
+
+            if (j1 > j2)
+                continue;              /* done it already, other way round */
+
+            if (colouring[j1] >= 0 || colouring[j2] >= 0)
+                continue;              /* they're not undecided */
+
+            if (sc->possible[j1] != sc->possible[j2])
+                continue;              /* they don't have the same possibles */
+
+            v = sc->possible[j1];
+            /*
+             * See if v contains exactly two set bits.
+             */
+            v2 = v & -v;           /* find lowest set bit */
+            v2 = v & ~v2;          /* clear it */
+            if (v2 == 0 || (v2 & (v2-1)) != 0)   /* not power of 2 */
+                continue;
+
+            /*
+             * We've found regions j1 and j2 satisfying properties
+             * (a) and (b): they have two possible colours between
+             * them, and since they're adjacent to one another they
+             * must use _both_ those colours between them.
+             * Therefore, if they are both adjacent to any other
+             * region then that region cannot be either colour.
+             * 
+             * Go through the neighbours of j1 and see if any are
+             * shared with j2.
+             */
+            for (j = graph_vertex_start(graph, n, ngraph, j1);
+                 j < ngraph && graph[j] < n*(j1+1); j++) {
+                k = graph[j] - j1*n;
+                if (graph_adjacent(graph, n, ngraph, k, j2) &&
+                    (sc->possible[k] & v)) {
+#ifdef SOLVER_DIAGNOSTICS
+                    if (verbose) {
+                        char buf[80];
+                        if (!started)
+                            printf("%*sadjacent regions %d,%d share colours"
+                                   " %s\n", 2*sc->depth, "", j1, j2,
+                                   colourset(buf, v));
+                        started = TRUE;
+                        printf("%*s  ruling out %s in region %d\n",2*sc->depth,
+                               "", colourset(buf, sc->possible[k] & v), k);
+                    }
+#endif
+                    sc->possible[k] &= ~v;
+                    done_something = TRUE;
+                }
+            }
+        }
+
+        if (done_something)
+            continue;
+
+        if (difficulty < DIFF_HARD)
+            break;                     /* can't do anything harder */
+
+        /*
+         * Right; now we get creative. Now we're going to look for
+         * `forcing chains'. A forcing chain is a path through the
+         * graph with the following properties:
+         * 
+         *  (a) Each vertex on the path has precisely two possible
+         *      colours.
+         * 
+         *  (b) Each pair of vertices which are adjacent on the
+         *      path share at least one possible colour in common.
+         * 
+         *  (c) Each vertex in the middle of the path shares _both_
+         *      of its colours with at least one of its neighbours
+         *      (not the same one with both neighbours).
+         * 
+         * These together imply that at least one of the possible
+         * colour choices at one end of the path forces _all_ the
+         * rest of the colours along the path. In order to make
+         * real use of this, we need further properties:
+         * 
+         *  (c) Ruling out some colour C from the vertex at one end
+         *      of the path forces the vertex at the other end to
+         *      take colour C.
+         * 
+         *  (d) The two end vertices are mutually adjacent to some
+         *      third vertex.
+         * 
+         *  (e) That third vertex currently has C as a possibility.
+         * 
+         * If we can find all of that lot, we can deduce that at
+         * least one of the two ends of the forcing chain has
+         * colour C, and that therefore the mutually adjacent third
+         * vertex does not.
+         * 
+         * To find forcing chains, we're going to start a bfs at
+         * each suitable vertex of the graph, once for each of its
+         * two possible colours.
+         */
+        for (i = 0; i < n; i++) {
+            int c;
+
+            if (colouring[i] >= 0 || bitcount(sc->possible[i]) != 2)
+                continue;
+
+            for (c = 0; c < FOUR; c++)
+                if (sc->possible[i] & (1 << c)) {
+                    int j, k, gi, origc, currc, head, tail;
+                    /*
+                     * Try a bfs from this vertex, ruling out
+                     * colour c.
+                     * 
+                     * Within this loop, we work in colour bitmaps
+                     * rather than actual colours, because
+                     * converting back and forth is a needless
+                     * computational expense.
+                     */
+
+                    origc = 1 << c;
+
+                    for (j = 0; j < n; j++) {
+                        sc->bfscolour[j] = -1;
+#ifdef SOLVER_DIAGNOSTICS
+                        sc->bfsprev[j] = -1;
+#endif
+                    }
+                    head = tail = 0;
+                    sc->bfsqueue[tail++] = i;
+                    sc->bfscolour[i] = sc->possible[i] &~ origc;
+
+                    while (head < tail) {
+                        j = sc->bfsqueue[head++];
+                        currc = sc->bfscolour[j];
+
+                        /*
+                         * Try neighbours of j.
+                         */
+                        for (gi = graph_vertex_start(graph, n, ngraph, j);
+                             gi < ngraph && graph[gi] < n*(j+1); gi++) {
+                            k = graph[gi] - j*n;
+
+                            /*
+                             * To continue with the bfs in vertex
+                             * k, we need k to be
+                             *  (a) not already visited
+                             *  (b) have two possible colours
+                             *  (c) those colours include currc.
+                             */
+
+                            if (sc->bfscolour[k] < 0 &&
+                                colouring[k] < 0 &&
+                                bitcount(sc->possible[k]) == 2 &&
+                                (sc->possible[k] & currc)) {
+                                sc->bfsqueue[tail++] = k;
+                                sc->bfscolour[k] =
+                                    sc->possible[k] &~ currc;
+#ifdef SOLVER_DIAGNOSTICS
+                                sc->bfsprev[k] = j;
+#endif
+                            }
+
+                            /*
+                             * One other possibility is that k
+                             * might be the region in which we can
+                             * make a real deduction: if it's
+                             * adjacent to i, contains currc as a
+                             * possibility, and currc is equal to
+                             * the original colour we ruled out.
+                             */
+                            if (currc == origc &&
+                                graph_adjacent(graph, n, ngraph, k, i) &&
+                                (sc->possible[k] & currc)) {
+#ifdef SOLVER_DIAGNOSTICS
+                                if (verbose) {
+                                    char buf[80], *sep = "";
+                                    int r;
+
+                                    printf("%*sforcing chain, colour %s, ",
+                                           2*sc->depth, "",
+                                           colourset(buf, origc));
+                                    for (r = j; r != -1; r = sc->bfsprev[r]) {
+                                        printf("%s%d", sep, r);
+                                        sep = "-";
+                                    }
+                                    printf("\n%*s  ruling out %s in region"
+                                           " %d\n", 2*sc->depth, "",
+                                           colourset(buf, origc), k);
+                                }
+#endif
+                                sc->possible[k] &= ~origc;
+                                done_something = TRUE;
+                            }
+                        }
+                    }
+
+                    assert(tail <= n);
+                }
+        }
+
+       if (!done_something)
+           break;
+    }
+
+    /*
+     * See if we've got a complete solution, and return if so.
+     */
+    for (i = 0; i < n; i++)
+       if (colouring[i] < 0)
+            break;
+    if (i == n) {
+#ifdef SOLVER_DIAGNOSTICS
+        if (verbose)
+            printf("%*sone solution found\n", 2*sc->depth, "");
+#endif
+        return 1;                      /* success! */
+    }
+
+    /*
+     * If recursion is not permissible, we now give up.
+     */
+    if (difficulty < DIFF_RECURSE) {
+#ifdef SOLVER_DIAGNOSTICS
+        if (verbose)
+            printf("%*sunable to proceed further without recursion\n",
+                   2*sc->depth, "");
+#endif
+        return 2;                      /* unable to complete */
+    }
+
+    /*
+     * Now we've got to do something recursive. So first hunt for a
+     * currently-most-constrained region.
+     */
+    {
+        int best, bestc;
+        struct solver_scratch *rsc;
+        int *subcolouring, *origcolouring;
+        int ret, subret;
+        int we_already_got_one;
+
+        best = -1;
+        bestc = FIVE;
+
+        for (i = 0; i < n; i++) if (colouring[i] < 0) {
+            int p = sc->possible[i];
+            enum { compile_time_assertion = 1 / (FOUR <= 4) };
+            int c;
+
+            /* Count the set bits. */
+            c = (p & 5) + ((p >> 1) & 5);
+            c = (c & 3) + ((c >> 2) & 3);
+            assert(c > 1);             /* or colouring[i] would be >= 0 */
+
+            if (c < bestc) {
+                best = i;
+                bestc = c;
+            }
+        }
+
+        assert(best >= 0);             /* or we'd be solved already */
+
+#ifdef SOLVER_DIAGNOSTICS
+        if (verbose)
+            printf("%*srecursing on region %d\n", 2*sc->depth, "", best);
+#endif
+
+        /*
+         * Now iterate over the possible colours for this region.
+         */
+        rsc = new_scratch(graph, n, ngraph);
+        rsc->depth = sc->depth + 1;
+        origcolouring = snewn(n, int);
+        memcpy(origcolouring, colouring, n * sizeof(int));
+        subcolouring = snewn(n, int);
+        we_already_got_one = FALSE;
+        ret = 0;
+
+        for (i = 0; i < FOUR; i++) {
+            if (!(sc->possible[best] & (1 << i)))
+                continue;
+
+            memcpy(rsc->possible, sc->possible, n);
+            memcpy(subcolouring, origcolouring, n * sizeof(int));
+
+            place_colour(rsc, subcolouring, best, i
+#ifdef SOLVER_DIAGNOSTICS
+                         , "trying"
+#endif
+                         );
+
+            subret = map_solver(rsc, graph, n, ngraph,
+                                subcolouring, difficulty);
+
+#ifdef SOLVER_DIAGNOSTICS
+            if (verbose) {
+                printf("%*sretracting %c in region %d; found %s\n",
+                       2*sc->depth, "", colnames[i], best,
+                       subret == 0 ? "no solutions" :
+                       subret == 1 ? "one solution" : "multiple solutions");
+            }
+#endif
+
+            /*
+             * If this possibility turned up more than one valid
+             * solution, or if it turned up one and we already had
+             * one, we're definitely ambiguous.
+             */
+            if (subret == 2 || (subret == 1 && we_already_got_one)) {
+                ret = 2;
+                break;
+            }
+
+            /*
+             * If this possibility turned up one valid solution and
+             * it's the first we've seen, copy it into the output.
+             */
+            if (subret == 1) {
+                memcpy(colouring, subcolouring, n * sizeof(int));
+                we_already_got_one = TRUE;
+                ret = 1;
+            }
+
+            /*
+             * Otherwise, this guess led to a contradiction, so we
+             * do nothing.
+             */
+        }
+
+        sfree(origcolouring);
+        sfree(subcolouring);
+        free_scratch(rsc);
+
+#ifdef SOLVER_DIAGNOSTICS
+        if (verbose && sc->depth == 0) {
+            printf("%*s%s found\n",
+                   2*sc->depth, "",
+                   ret == 0 ? "no solutions" :
+                   ret == 1 ? "one solution" : "multiple solutions");
+        }
+#endif
+        return ret;
+    }
+}
+
+/* ----------------------------------------------------------------------
+ * Game generation main function.
+ */
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                          char **aux, int interactive)
+{
+    struct solver_scratch *sc = NULL;
+    int *map, *graph, ngraph, *colouring, *colouring2, *regions;
+    int i, j, w, h, n, solveret, cfreq[FOUR];
+    int wh;
+    int mindiff, tries;
+#ifdef GENERATION_DIAGNOSTICS
+    int x, y;
+#endif
+    char *ret, buf[80];
+    int retlen, retsize;
+
+    w = params->w;
+    h = params->h;
+    n = params->n;
+    wh = w*h;
+
+    *aux = NULL;
+
+    map = snewn(wh, int);
+    graph = snewn(n*n, int);
+    colouring = snewn(n, int);
+    colouring2 = snewn(n, int);
+    regions = snewn(n, int);
+
+    /*
+     * This is the minimum difficulty below which we'll completely
+     * reject a map design. Normally we set this to one below the
+     * requested difficulty, ensuring that we have the right
+     * result. However, for particularly dense maps or maps with
+     * particularly few regions it might not be possible to get the
+     * desired difficulty, so we will eventually drop this down to
+     * -1 to indicate that any old map will do.
+     */
+    mindiff = params->diff;
+    tries = 50;
+
+    while (1) {
+
+        /*
+         * Create the map.
+         */
+        genmap(w, h, n, map, rs);
+
+#ifdef GENERATION_DIAGNOSTICS
+        for (y = 0; y < h; y++) {
+            for (x = 0; x < w; x++) {
+                int v = map[y*w+x];
+                if (v >= 62)
+                    putchar('!');
+                else if (v >= 36)
+                    putchar('a' + v-36);
+                else if (v >= 10)
+                    putchar('A' + v-10);
+                else
+                    putchar('0' + v);
+            }
+            putchar('\n');
+        }
+#endif
+
+        /*
+         * Convert the map into a graph.
+         */
+        ngraph = gengraph(w, h, n, map, graph);
+
+#ifdef GENERATION_DIAGNOSTICS
+        for (i = 0; i < ngraph; i++)
+            printf("%d-%d\n", graph[i]/n, graph[i]%n);
+#endif
+
+        /*
+         * Colour the map.
+         */
+        fourcolour(graph, n, ngraph, colouring, rs);
+
+#ifdef GENERATION_DIAGNOSTICS
+        for (i = 0; i < n; i++)
+            printf("%d: %d\n", i, colouring[i]);
+
+        for (y = 0; y < h; y++) {
+            for (x = 0; x < w; x++) {
+                int v = colouring[map[y*w+x]];
+                if (v >= 36)
+                    putchar('a' + v-36);
+                else if (v >= 10)
+                    putchar('A' + v-10);
+                else
+                    putchar('0' + v);
+            }
+            putchar('\n');
+        }
+#endif
+
+        /*
+         * Encode the solution as an aux string.
+         */
+        if (*aux)                      /* in case we've come round again */
+            sfree(*aux);
+        retlen = retsize = 0;
+        ret = NULL;
+        for (i = 0; i < n; i++) {
+            int len;
+
+            if (colouring[i] < 0)
+                continue;
+
+            len = sprintf(buf, "%s%d:%d", i ? ";" : "S;", colouring[i], i);
+            if (retlen + len >= retsize) {
+                retsize = retlen + len + 256;
+                ret = sresize(ret, retsize, char);
+            }
+            strcpy(ret + retlen, buf);
+            retlen += len;
+        }
+        *aux = ret;
+
+        /*
+         * Remove the region colours one by one, keeping
+         * solubility. Also ensure that there always remains at
+         * least one region of every colour, so that the user can
+         * drag from somewhere.
+         */
+        for (i = 0; i < FOUR; i++)
+            cfreq[i] = 0;
+        for (i = 0; i < n; i++) {
+            regions[i] = i;
+            cfreq[colouring[i]]++;
+        }
+        for (i = 0; i < FOUR; i++)
+            if (cfreq[i] == 0)
+                continue;
+
+        shuffle(regions, n, sizeof(*regions), rs);
+
+        if (sc) free_scratch(sc);
+        sc = new_scratch(graph, n, ngraph);
+
+        for (i = 0; i < n; i++) {
+            j = regions[i];
+
+            if (cfreq[colouring[j]] == 1)
+                continue;              /* can't remove last region of colour */
+
+            memcpy(colouring2, colouring, n*sizeof(int));
+            colouring2[j] = -1;
+            solveret = map_solver(sc, graph, n, ngraph, colouring2,
+                                 params->diff);
+            assert(solveret >= 0);            /* mustn't be impossible! */
+            if (solveret == 1) {
+                cfreq[colouring[j]]--;
+                colouring[j] = -1;
+            }
+        }
+
+#ifdef GENERATION_DIAGNOSTICS
+        for (i = 0; i < n; i++)
+            if (colouring[i] >= 0) {
+                if (i >= 62)
+                    putchar('!');
+                else if (i >= 36)
+                    putchar('a' + i-36);
+                else if (i >= 10)
+                    putchar('A' + i-10);
+                else
+                    putchar('0' + i);
+                printf(": %d\n", colouring[i]);
+            }
+#endif
+
+        /*
+         * Finally, check that the puzzle is _at least_ as hard as
+         * required, and indeed that it isn't already solved.
+         * (Calling map_solver with negative difficulty ensures the
+         * latter - if a solver which _does nothing_ can solve it,
+         * it's too easy!)
+         */
+        memcpy(colouring2, colouring, n*sizeof(int));
+        if (map_solver(sc, graph, n, ngraph, colouring2,
+                       mindiff - 1) == 1) {
+           /*
+            * Drop minimum difficulty if necessary.
+            */
+           if (mindiff > 0 && (n < 9 || n > 2*wh/3)) {
+               if (tries-- <= 0)
+                   mindiff = 0;       /* give up and go for Easy */
+           }
+            continue;
+       }
+
+        break;
+    }
+
+    /*
+     * Encode as a game ID. We do this by:
+     * 
+     *         - first going along the horizontal edges row by row, and
+     *           then the vertical edges column by column
+     *         - encoding the lengths of runs of edges and runs of
+     *           non-edges
+     *         - the decoder will reconstitute the region boundaries from
+     *           this and automatically number them the same way we did
+     *         - then we encode the initial region colours in a Slant-like
+     *           fashion (digits 0-3 interspersed with letters giving
+     *           lengths of runs of empty spaces).
+     */
+    retlen = retsize = 0;
+    ret = NULL;
+
+    {
+       int run, pv;
+
+       /*
+        * Start with a notional non-edge, so that there'll be an
+        * explicit `a' to distinguish the case where we start with
+        * an edge.
+        */
+       run = 1;
+       pv = 0;
+
+       for (i = 0; i < w*(h-1) + (w-1)*h; i++) {
+           int x, y, dx, dy, v;
+
+           if (i < w*(h-1)) {
+               /* Horizontal edge. */
+               y = i / w;
+               x = i % w;
+               dx = 0;
+               dy = 1;
+           } else {
+               /* Vertical edge. */
+               x = (i - w*(h-1)) / h;
+               y = (i - w*(h-1)) % h;
+               dx = 1;
+               dy = 0;
+           }
+
+           if (retlen + 10 >= retsize) {
+               retsize = retlen + 256;
+               ret = sresize(ret, retsize, char);
+           }
+
+           v = (map[y*w+x] != map[(y+dy)*w+(x+dx)]);
+
+           if (pv != v) {
+               ret[retlen++] = 'a'-1 + run;
+               run = 1;
+               pv = v;
+           } else {
+               /*
+                * 'z' is a special case in this encoding. Rather
+                * than meaning a run of 26 and a state switch, it
+                * means a run of 25 and _no_ state switch, because
+                * otherwise there'd be no way to encode runs of
+                * more than 26.
+                */
+               if (run == 25) {
+                   ret[retlen++] = 'z';
+                   run = 0;
+               }
+               run++;
+           }
+       }
+
+       ret[retlen++] = 'a'-1 + run;
+       ret[retlen++] = ',';
+
+       run = 0;
+       for (i = 0; i < n; i++) {
+           if (retlen + 10 >= retsize) {
+               retsize = retlen + 256;
+               ret = sresize(ret, retsize, char);
+           }
+
+           if (colouring[i] < 0) {
+               /*
+                * In _this_ encoding, 'z' is a run of 26, since
+                * there's no implicit state switch after each run.
+                * Confusingly different, but more compact.
+                */
+               if (run == 26) {
+                   ret[retlen++] = 'z';
+                   run = 0;
+               }
+               run++;
+           } else {
+               if (run > 0)
+                   ret[retlen++] = 'a'-1 + run;
+               ret[retlen++] = '0' + colouring[i];
+               run = 0;
+           }
+       }
+       if (run > 0)
+           ret[retlen++] = 'a'-1 + run;
+       ret[retlen] = '\0';
+
+       assert(retlen < retsize);
+    }
+
+    free_scratch(sc);
+    sfree(regions);
+    sfree(colouring2);
+    sfree(colouring);
+    sfree(graph);
+    sfree(map);
+
+    return ret;
+}
+
+static char *parse_edge_list(const game_params *params, const char **desc,
+                             int *map)
+{
+    int w = params->w, h = params->h, wh = w*h, n = params->n;
+    int i, k, pos, state;
+    const char *p = *desc;
+
+    dsf_init(map+wh, wh);
+
+    pos = -1;
+    state = 0;
+
+    /*
+     * Parse the game description to get the list of edges, and
+     * build up a disjoint set forest as we go (by identifying
+     * pairs of squares whenever the edge list shows a non-edge).
+     */
+    while (*p && *p != ',') {
+       if (*p < 'a' || *p > 'z')
+           return "Unexpected character in edge list";
+       if (*p == 'z')
+           k = 25;
+       else
+           k = *p - 'a' + 1;
+       while (k-- > 0) {
+           int x, y, dx, dy;
+
+           if (pos < 0) {
+               pos++;
+               continue;
+           } else if (pos < w*(h-1)) {
+               /* Horizontal edge. */
+               y = pos / w;
+               x = pos % w;
+               dx = 0;
+               dy = 1;
+           } else if (pos < 2*wh-w-h) {
+               /* Vertical edge. */
+               x = (pos - w*(h-1)) / h;
+               y = (pos - w*(h-1)) % h;
+               dx = 1;
+               dy = 0;
+           } else
+               return "Too much data in edge list";
+           if (!state)
+               dsf_merge(map+wh, y*w+x, (y+dy)*w+(x+dx));
+
+           pos++;
+       }
+       if (*p != 'z')
+           state = !state;
+       p++;
+    }
+    assert(pos <= 2*wh-w-h);
+    if (pos < 2*wh-w-h)
+       return "Too little data in edge list";
+
+    /*
+     * Now go through again and allocate region numbers.
+     */
+    pos = 0;
+    for (i = 0; i < wh; i++)
+       map[i] = -1;
+    for (i = 0; i < wh; i++) {
+       k = dsf_canonify(map+wh, i);
+       if (map[k] < 0)
+           map[k] = pos++;
+       map[i] = map[k];
+    }
+    if (pos != n)
+       return "Edge list defines the wrong number of regions";
+
+    *desc = p;
+
+    return NULL;
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    int w = params->w, h = params->h, wh = w*h, n = params->n;
+    int area;
+    int *map;
+    char *ret;
+
+    map = snewn(2*wh, int);
+    ret = parse_edge_list(params, &desc, map);
+    sfree(map);
+    if (ret)
+       return ret;
+
+    if (*desc != ',')
+       return "Expected comma before clue list";
+    desc++;                           /* eat comma */
+
+    area = 0;
+    while (*desc) {
+       if (*desc >= '0' && *desc < '0'+FOUR)
+           area++;
+       else if (*desc >= 'a' && *desc <= 'z')
+           area += *desc - 'a' + 1;
+       else
+           return "Unexpected character in clue list";
+       desc++;
+    }
+    if (area < n)
+       return "Too little data in clue list";
+    else if (area > n)
+       return "Too much data in clue list";
+
+    return NULL;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    int w = params->w, h = params->h, wh = w*h, n = params->n;
+    int i, pos;
+    const char *p;
+    game_state *state = snew(game_state);
+
+    state->p = *params;
+    state->colouring = snewn(n, int);
+    for (i = 0; i < n; i++)
+       state->colouring[i] = -1;
+    state->pencil = snewn(n, int);
+    for (i = 0; i < n; i++)
+       state->pencil[i] = 0;
+
+    state->completed = state->cheated = FALSE;
+
+    state->map = snew(struct map);
+    state->map->refcount = 1;
+    state->map->map = snewn(wh*4, int);
+    state->map->graph = snewn(n*n, int);
+    state->map->n = n;
+    state->map->immutable = snewn(n, int);
+    for (i = 0; i < n; i++)
+       state->map->immutable[i] = FALSE;
+
+    p = desc;
+
+    {
+       char *ret;
+       ret = parse_edge_list(params, &p, state->map->map);
+       assert(!ret);
+    }
+
+    /*
+     * Set up the other three quadrants in `map'.
+     */
+    for (i = wh; i < 4*wh; i++)
+       state->map->map[i] = state->map->map[i % wh];
+
+    assert(*p == ',');
+    p++;
+
+    /*
+     * Now process the clue list.
+     */
+    pos = 0;
+    while (*p) {
+       if (*p >= '0' && *p < '0'+FOUR) {
+           state->colouring[pos] = *p - '0';
+           state->map->immutable[pos] = TRUE;
+           pos++;
+       } else {
+           assert(*p >= 'a' && *p <= 'z');
+           pos += *p - 'a' + 1;
+       }
+       p++;
+    }
+    assert(pos == n);
+
+    state->map->ngraph = gengraph(w, h, n, state->map->map, state->map->graph);
+
+    /*
+     * Attempt to smooth out some of the more jagged region
+     * outlines by the judicious use of diagonally divided squares.
+     */
+    {
+        random_state *rs = random_new(desc, strlen(desc));
+        int *squares = snewn(wh, int);
+        int done_something;
+
+        for (i = 0; i < wh; i++)
+            squares[i] = i;
+        shuffle(squares, wh, sizeof(*squares), rs);
+
+        do {
+            done_something = FALSE;
+            for (i = 0; i < wh; i++) {
+                int y = squares[i] / w, x = squares[i] % w;
+                int c = state->map->map[y*w+x];
+                int tc, bc, lc, rc;
+
+                if (x == 0 || x == w-1 || y == 0 || y == h-1)
+                    continue;
+
+                if (state->map->map[TE * wh + y*w+x] !=
+                    state->map->map[BE * wh + y*w+x])
+                    continue;
+
+                tc = state->map->map[BE * wh + (y-1)*w+x];
+                bc = state->map->map[TE * wh + (y+1)*w+x];
+                lc = state->map->map[RE * wh + y*w+(x-1)];
+                rc = state->map->map[LE * wh + y*w+(x+1)];
+
+                /*
+                 * If this square is adjacent on two sides to one
+                 * region and on the other two sides to the other
+                 * region, and is itself one of the two regions, we can
+                 * adjust it so that it's a diagonal.
+                 */
+                if (tc != bc && (tc == c || bc == c)) {
+                    if ((lc == tc && rc == bc) ||
+                        (lc == bc && rc == tc)) {
+                        state->map->map[TE * wh + y*w+x] = tc;
+                        state->map->map[BE * wh + y*w+x] = bc;
+                        state->map->map[LE * wh + y*w+x] = lc;
+                        state->map->map[RE * wh + y*w+x] = rc;
+                        done_something = TRUE;
+                    }
+                }
+            }
+        } while (done_something);
+        sfree(squares);
+        random_free(rs);
+    }
+
+    /*
+     * Analyse the map to find a canonical line segment
+     * corresponding to each edge, and a canonical point
+     * corresponding to each region. The former are where we'll
+     * eventually put error markers; the latter are where we'll put
+     * per-region flags such as numbers (when in diagnostic mode).
+     */
+    {
+       int *bestx, *besty, *an, pass;
+       float *ax, *ay, *best;
+
+       ax = snewn(state->map->ngraph + n, float);
+       ay = snewn(state->map->ngraph + n, float);
+       an = snewn(state->map->ngraph + n, int);
+       bestx = snewn(state->map->ngraph + n, int);
+       besty = snewn(state->map->ngraph + n, int);
+       best = snewn(state->map->ngraph + n, float);
+
+       for (i = 0; i < state->map->ngraph + n; i++) {
+           bestx[i] = besty[i] = -1;
+           best[i] = (float)(2*(w+h)+1);
+           ax[i] = ay[i] = 0.0F;
+           an[i] = 0;
+       }
+
+       /*
+        * We make two passes over the map, finding all the line
+        * segments separating regions and all the suitable points
+        * within regions. In the first pass, we compute the
+        * _average_ x and y coordinate of all the points in a
+        * given class; in the second pass, for each such average
+        * point, we find the candidate closest to it and call that
+        * canonical.
+        * 
+        * Line segments are considered to have coordinates in
+        * their centre. Thus, at least one coordinate for any line
+        * segment is always something-and-a-half; so we store our
+        * coordinates as twice their normal value.
+        */
+       for (pass = 0; pass < 2; pass++) {
+           int x, y;
+
+           for (y = 0; y < h; y++)
+               for (x = 0; x < w; x++) {
+                   int ex[4], ey[4], ea[4], eb[4], en = 0;
+
+                   /*
+                    * Look for an edge to the right of this
+                    * square, an edge below it, and an edge in the
+                    * middle of it. Also look to see if the point
+                    * at the bottom right of this square is on an
+                    * edge (and isn't a place where more than two
+                    * regions meet).
+                    */
+                   if (x+1 < w) {
+                       /* right edge */
+                       ea[en] = state->map->map[RE * wh + y*w+x];
+                       eb[en] = state->map->map[LE * wh + y*w+(x+1)];
+                        ex[en] = (x+1)*2;
+                        ey[en] = y*2+1;
+                        en++;
+                   }
+                   if (y+1 < h) {
+                       /* bottom edge */
+                       ea[en] = state->map->map[BE * wh + y*w+x];
+                       eb[en] = state->map->map[TE * wh + (y+1)*w+x];
+                        ex[en] = x*2+1;
+                        ey[en] = (y+1)*2;
+                        en++;
+                   }
+                   /* diagonal edge */
+                   ea[en] = state->map->map[TE * wh + y*w+x];
+                   eb[en] = state->map->map[BE * wh + y*w+x];
+                    ex[en] = x*2+1;
+                    ey[en] = y*2+1;
+                    en++;
+
+                   if (x+1 < w && y+1 < h) {
+                       /* bottom right corner */
+                       int oct[8], othercol, nchanges;
+                       oct[0] = state->map->map[RE * wh + y*w+x];
+                       oct[1] = state->map->map[LE * wh + y*w+(x+1)];
+                       oct[2] = state->map->map[BE * wh + y*w+(x+1)];
+                       oct[3] = state->map->map[TE * wh + (y+1)*w+(x+1)];
+                       oct[4] = state->map->map[LE * wh + (y+1)*w+(x+1)];
+                       oct[5] = state->map->map[RE * wh + (y+1)*w+x];
+                       oct[6] = state->map->map[TE * wh + (y+1)*w+x];
+                       oct[7] = state->map->map[BE * wh + y*w+x];
+
+                       othercol = -1;
+                       nchanges = 0;
+                       for (i = 0; i < 8; i++) {
+                           if (oct[i] != oct[0]) {
+                               if (othercol < 0)
+                                   othercol = oct[i];
+                               else if (othercol != oct[i])
+                                   break;   /* three colours at this point */
+                           }
+                           if (oct[i] != oct[(i+1) & 7])
+                               nchanges++;
+                       }
+
+                       /*
+                        * Now if there are exactly two regions at
+                        * this point (not one, and not three or
+                        * more), and only two changes around the
+                        * loop, then this is a valid place to put
+                        * an error marker.
+                        */
+                       if (i == 8 && othercol >= 0 && nchanges == 2) {
+                           ea[en] = oct[0];
+                           eb[en] = othercol;
+                           ex[en] = (x+1)*2;
+                           ey[en] = (y+1)*2;
+                           en++;
+                       }
+
+                        /*
+                         * If there's exactly _one_ region at this
+                         * point, on the other hand, it's a valid
+                         * place to put a region centre.
+                         */
+                        if (othercol < 0) {
+                           ea[en] = eb[en] = oct[0];
+                           ex[en] = (x+1)*2;
+                           ey[en] = (y+1)*2;
+                           en++;
+                        }
+                   }
+
+                   /*
+                    * Now process the points we've found, one by
+                    * one.
+                    */
+                   for (i = 0; i < en; i++) {
+                       int emin = min(ea[i], eb[i]);
+                       int emax = max(ea[i], eb[i]);
+                       int gindex;
+
+                        if (emin != emax) {
+                            /* Graph edge */
+                            gindex =
+                                graph_edge_index(state->map->graph, n,
+                                                 state->map->ngraph, emin,
+                                                 emax);
+                        } else {
+                            /* Region number */
+                            gindex = state->map->ngraph + emin;
+                        }
+
+                       assert(gindex >= 0);
+
+                       if (pass == 0) {
+                           /*
+                            * In pass 0, accumulate the values
+                            * we'll use to compute the average
+                            * positions.
+                            */
+                           ax[gindex] += ex[i];
+                           ay[gindex] += ey[i];
+                           an[gindex] += 1;
+                       } else {
+                           /*
+                            * In pass 1, work out whether this
+                            * point is closer to the average than
+                            * the last one we've seen.
+                            */
+                           float dx, dy, d;
+
+                           assert(an[gindex] > 0);
+                           dx = ex[i] - ax[gindex];
+                           dy = ey[i] - ay[gindex];
+                           d = (float)sqrt(dx*dx + dy*dy);
+                           if (d < best[gindex]) {
+                               best[gindex] = d;
+                               bestx[gindex] = ex[i];
+                               besty[gindex] = ey[i];
+                           }
+                       }
+                   }
+               }
+
+           if (pass == 0) {
+               for (i = 0; i < state->map->ngraph + n; i++)
+                   if (an[i] > 0) {
+                       ax[i] /= an[i];
+                       ay[i] /= an[i];
+                   }
+           }
+       }
+
+       state->map->edgex = snewn(state->map->ngraph, int);
+       state->map->edgey = snewn(state->map->ngraph, int);
+        memcpy(state->map->edgex, bestx, state->map->ngraph * sizeof(int));
+        memcpy(state->map->edgey, besty, state->map->ngraph * sizeof(int));
+
+       state->map->regionx = snewn(n, int);
+       state->map->regiony = snewn(n, int);
+        memcpy(state->map->regionx, bestx + state->map->ngraph, n*sizeof(int));
+        memcpy(state->map->regiony, besty + state->map->ngraph, n*sizeof(int));
+
+       for (i = 0; i < state->map->ngraph; i++)
+           if (state->map->edgex[i] < 0) {
+               /* Find the other representation of this edge. */
+               int e = state->map->graph[i];
+               int iprime = graph_edge_index(state->map->graph, n,
+                                             state->map->ngraph, e%n, e/n);
+               assert(state->map->edgex[iprime] >= 0);
+               state->map->edgex[i] = state->map->edgex[iprime];
+               state->map->edgey[i] = state->map->edgey[iprime];
+           }
+
+       sfree(ax);
+       sfree(ay);
+       sfree(an);
+       sfree(best);
+       sfree(bestx);
+       sfree(besty);
+    }
+
+    return state;
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    game_state *ret = snew(game_state);
+
+    ret->p = state->p;
+    ret->colouring = snewn(state->p.n, int);
+    memcpy(ret->colouring, state->colouring, state->p.n * sizeof(int));
+    ret->pencil = snewn(state->p.n, int);
+    memcpy(ret->pencil, state->pencil, state->p.n * sizeof(int));
+    ret->map = state->map;
+    ret->map->refcount++;
+    ret->completed = state->completed;
+    ret->cheated = state->cheated;
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    if (--state->map->refcount <= 0) {
+       sfree(state->map->map);
+       sfree(state->map->graph);
+       sfree(state->map->immutable);
+       sfree(state->map->edgex);
+       sfree(state->map->edgey);
+       sfree(state->map->regionx);
+       sfree(state->map->regiony);
+       sfree(state->map);
+    }
+    sfree(state->pencil);
+    sfree(state->colouring);
+    sfree(state);
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    if (!aux) {
+       /*
+        * Use the solver.
+        */
+       int *colouring;
+       struct solver_scratch *sc;
+       int sret;
+       int i;
+       char *ret, buf[80];
+       int retlen, retsize;
+
+       colouring = snewn(state->map->n, int);
+       memcpy(colouring, state->colouring, state->map->n * sizeof(int));
+
+       sc = new_scratch(state->map->graph, state->map->n, state->map->ngraph);
+       sret = map_solver(sc, state->map->graph, state->map->n,
+                        state->map->ngraph, colouring, DIFFCOUNT-1);
+       free_scratch(sc);
+
+       if (sret != 1) {
+           sfree(colouring);
+           if (sret == 0)
+               *error = "Puzzle is inconsistent";
+           else
+               *error = "Unable to find a unique solution for this puzzle";
+           return NULL;
+       }
+
+        retsize = 64;
+        ret = snewn(retsize, char);
+        strcpy(ret, "S");
+        retlen = 1;
+
+       for (i = 0; i < state->map->n; i++) {
+            int len;
+
+           assert(colouring[i] >= 0);
+            if (colouring[i] == currstate->colouring[i])
+                continue;
+           assert(!state->map->immutable[i]);
+
+            len = sprintf(buf, ";%d:%d", colouring[i], i);
+            if (retlen + len >= retsize) {
+                retsize = retlen + len + 256;
+                ret = sresize(ret, retsize, char);
+            }
+            strcpy(ret + retlen, buf);
+            retlen += len;
+        }
+
+       sfree(colouring);
+
+       return ret;
+    }
+    return dupstr(aux);
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    return NULL;
+}
+
+struct game_ui {
+    /*
+     * drag_colour:
+     * 
+     *  - -2 means no drag currently active.
+     *  - >=0 means we're dragging a solid colour.
+     *         - -1 means we're dragging a blank space, and drag_pencil
+     *           might or might not add some pencil-mark stipples to that.
+     */
+    int drag_colour;
+    int drag_pencil;
+    int dragx, dragy;
+    int show_numbers;
+
+    int cur_x, cur_y, cur_visible, cur_moved, cur_lastmove;
+};
+
+static game_ui *new_ui(const game_state *state)
+{
+    game_ui *ui = snew(game_ui);
+    ui->dragx = ui->dragy = -1;
+    ui->drag_colour = -2;
+    ui->drag_pencil = 0;
+    ui->show_numbers = FALSE;
+    ui->cur_x = ui->cur_y = ui->cur_visible = ui->cur_moved = 0;
+    ui->cur_lastmove = 0;
+    return ui;
+}
+
+static void free_ui(game_ui *ui)
+{
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+}
+
+struct game_drawstate {
+    int tilesize;
+    unsigned long *drawn, *todraw;
+    int started;
+    int dragx, dragy, drag_visible;
+    blitter *bl;
+};
+
+/* Flags in `drawn'. */
+#define ERR_BASE      0x00800000L
+#define ERR_MASK      0xFF800000L
+#define PENCIL_T_BASE 0x00080000L
+#define PENCIL_T_MASK 0x00780000L
+#define PENCIL_B_BASE 0x00008000L
+#define PENCIL_B_MASK 0x00078000L
+#define PENCIL_MASK   0x007F8000L
+#define SHOW_NUMBERS  0x00004000L
+
+#define TILESIZE (ds->tilesize)
+#define BORDER (TILESIZE)
+#define COORD(x)  ( (x) * TILESIZE + BORDER )
+#define FROMCOORD(x)  ( ((x) - BORDER + TILESIZE) / TILESIZE - 1 )
+
+ /*
+  * EPSILON_FOO are epsilons added to absolute cursor position by
+  * cursor movement, such that in pathological cases (e.g. a very
+  * small diamond-shaped area) it's relatively easy to select the
+  * region you wanted.
+  */
+
+#define EPSILON_X(button) (((button) == CURSOR_RIGHT) ? +1 : \
+                           ((button) == CURSOR_LEFT)  ? -1 : 0)
+#define EPSILON_Y(button) (((button) == CURSOR_DOWN)  ? +1 : \
+                           ((button) == CURSOR_UP)    ? -1 : 0)
+
+
+static int region_from_coords(const game_state *state,
+                              const game_drawstate *ds, int x, int y)
+{
+    int w = state->p.w, h = state->p.h, wh = w*h /*, n = state->p.n */;
+    int tx = FROMCOORD(x), ty = FROMCOORD(y);
+    int dx = x - COORD(tx), dy = y - COORD(ty);
+    int quadrant;
+
+    if (tx < 0 || tx >= w || ty < 0 || ty >= h)
+        return -1;                     /* border */
+
+    quadrant = 2 * (dx > dy) + (TILESIZE - dx > dy);
+    quadrant = (quadrant == 0 ? BE :
+                quadrant == 1 ? LE :
+                quadrant == 2 ? RE : TE);
+
+    return state->map->map[quadrant * wh + ty*w+tx];
+}
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    char *bufp, buf[256];
+    int alt_button;
+
+    /*
+     * Enable or disable numeric labels on regions.
+     */
+    if (button == 'l' || button == 'L') {
+        ui->show_numbers = !ui->show_numbers;
+        return "";
+    }
+
+    if (IS_CURSOR_MOVE(button)) {
+        move_cursor(button, &ui->cur_x, &ui->cur_y, state->p.w, state->p.h, 0);
+        ui->cur_visible = 1;
+        ui->cur_moved = 1;
+        ui->cur_lastmove = button;
+        ui->dragx = COORD(ui->cur_x) + TILESIZE/2 + EPSILON_X(button);
+        ui->dragy = COORD(ui->cur_y) + TILESIZE/2 + EPSILON_Y(button);
+        return "";
+    }
+    if (IS_CURSOR_SELECT(button)) {
+        if (!ui->cur_visible) {
+            ui->dragx = COORD(ui->cur_x) + TILESIZE/2 + EPSILON_X(ui->cur_lastmove);
+            ui->dragy = COORD(ui->cur_y) + TILESIZE/2 + EPSILON_Y(ui->cur_lastmove);
+            ui->cur_visible = 1;
+            return "";
+        }
+        if (ui->drag_colour == -2) { /* not currently cursor-dragging, start. */
+            int r = region_from_coords(state, ds, ui->dragx, ui->dragy);
+            if (r >= 0) {
+                ui->drag_colour = state->colouring[r];
+                ui->drag_pencil = (ui->drag_colour >= 0) ? 0 : state->pencil[r];
+            } else {
+                ui->drag_colour = -1;
+                ui->drag_pencil = 0;
+            }
+            ui->cur_moved = 0;
+            return "";
+        } else { /* currently cursor-dragging; drop the colour in the new region. */
+            x = COORD(ui->cur_x) + TILESIZE/2 + EPSILON_X(ui->cur_lastmove);
+            y = COORD(ui->cur_y) + TILESIZE/2 + EPSILON_Y(ui->cur_lastmove);
+            alt_button = (button == CURSOR_SELECT2) ? 1 : 0;
+            /* Double-select removes current colour. */
+            if (!ui->cur_moved) ui->drag_colour = -1;
+            goto drag_dropped;
+        }
+    }
+
+    if (button == LEFT_BUTTON || button == RIGHT_BUTTON) {
+       int r = region_from_coords(state, ds, x, y);
+
+        if (r >= 0) {
+            ui->drag_colour = state->colouring[r];
+           ui->drag_pencil = state->pencil[r];
+           if (ui->drag_colour >= 0)
+               ui->drag_pencil = 0;  /* should be already, but double-check */
+       } else {
+            ui->drag_colour = -1;
+           ui->drag_pencil = 0;
+       }
+        ui->dragx = x;
+        ui->dragy = y;
+        ui->cur_visible = 0;
+        return "";
+    }
+
+    if ((button == LEFT_DRAG || button == RIGHT_DRAG) &&
+        ui->drag_colour > -2) {
+        ui->dragx = x;
+        ui->dragy = y;
+        return "";
+    }
+
+    if ((button == LEFT_RELEASE || button == RIGHT_RELEASE) &&
+        ui->drag_colour > -2) {
+        alt_button = (button == RIGHT_RELEASE) ? 1 : 0;
+        goto drag_dropped;
+    }
+
+    return NULL;
+
+drag_dropped:
+    {
+       int r = region_from_coords(state, ds, x, y);
+        int c = ui->drag_colour;
+       int p = ui->drag_pencil;
+       int oldp;
+
+        /*
+         * Cancel the drag, whatever happens.
+         */
+        ui->drag_colour = -2;
+
+       if (r < 0)
+            return "";                 /* drag into border; do nothing else */
+
+       if (state->map->immutable[r])
+           return "";                 /* can't change this region */
+
+        if (state->colouring[r] == c && state->pencil[r] == p)
+            return "";                 /* don't _need_ to change this region */
+
+       if (alt_button) {
+           if (state->colouring[r] >= 0) {
+               /* Can't pencil on a coloured region */
+               return "";
+           } else if (c >= 0) {
+               /* Right-dragging from colour to blank toggles one pencil */
+               p = state->pencil[r] ^ (1 << c);
+               c = -1;
+           }
+           /* Otherwise, right-dragging from blank to blank is equivalent
+            * to left-dragging. */
+       }
+
+       bufp = buf;
+       oldp = state->pencil[r];
+       if (c != state->colouring[r]) {
+           bufp += sprintf(bufp, ";%c:%d", (int)(c < 0 ? 'C' : '0' + c), r);
+           if (c >= 0)
+               oldp = 0;
+       }
+       if (p != oldp) {
+           int i;
+           for (i = 0; i < FOUR; i++)
+               if ((oldp ^ p) & (1 << i))
+                   bufp += sprintf(bufp, ";p%c:%d", (int)('0' + i), r);
+       }
+
+       return dupstr(buf+1);          /* ignore first semicolon */
+    }
+}
+
+static game_state *execute_move(const game_state *state, const char *move)
+{
+    int n = state->p.n;
+    game_state *ret = dup_game(state);
+    int c, k, adv, i;
+
+    while (*move) {
+        int pencil = FALSE;
+
+       c = *move;
+        if (c == 'p') {
+            pencil = TRUE;
+            c = *++move;
+        }
+       if ((c == 'C' || (c >= '0' && c < '0'+FOUR)) &&
+           sscanf(move+1, ":%d%n", &k, &adv) == 1 &&
+           k >= 0 && k < state->p.n) {
+           move += 1 + adv;
+            if (pencil) {
+               if (ret->colouring[k] >= 0) {
+                   free_game(ret);
+                   return NULL;
+               }
+                if (c == 'C')
+                    ret->pencil[k] = 0;
+                else
+                    ret->pencil[k] ^= 1 << (c - '0');
+            } else {
+                ret->colouring[k] = (c == 'C' ? -1 : c - '0');
+                ret->pencil[k] = 0;
+            }
+       } else if (*move == 'S') {
+           move++;
+           ret->cheated = TRUE;
+       } else {
+           free_game(ret);
+           return NULL;
+       }
+
+       if (*move && *move != ';') {
+           free_game(ret);
+           return NULL;
+       }
+       if (*move)
+           move++;
+    }
+
+    /*
+     * Check for completion.
+     */
+    if (!ret->completed) {
+       int ok = TRUE;
+
+       for (i = 0; i < n; i++)
+           if (ret->colouring[i] < 0) {
+               ok = FALSE;
+               break;
+           }
+
+       if (ok) {
+           for (i = 0; i < ret->map->ngraph; i++) {
+               int j = ret->map->graph[i] / n;
+               int k = ret->map->graph[i] % n;
+               if (ret->colouring[j] == ret->colouring[k]) {
+                   ok = FALSE;
+                   break;
+               }
+           }
+       }
+
+       if (ok)
+           ret->completed = TRUE;
+    }
+
+    return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    struct { int tilesize; } ads, *ds = &ads;
+    ads.tilesize = tilesize;
+
+    *x = params->w * TILESIZE + 2 * BORDER + 1;
+    *y = params->h * TILESIZE + 2 * BORDER + 1;
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+
+    assert(!ds->bl);                   /* set_size is never called twice */
+    ds->bl = blitter_new(dr, TILESIZE+3, TILESIZE+3);
+}
+
+const float map_colours[FOUR][3] = {
+#ifdef VIVID_COLOURS
+    /* Use more vivid colours (e.g. on the Pocket PC) */
+    {0.75F, 0.25F, 0.25F},
+    {0.3F,  0.7F,  0.3F},
+    {0.3F,  0.3F,  0.7F},
+    {0.85F, 0.85F, 0.1F},
+#else
+    {0.7F, 0.5F, 0.4F},
+    {0.8F, 0.7F, 0.4F},
+    {0.5F, 0.6F, 0.4F},
+    {0.55F, 0.45F, 0.35F},
+#endif
+};
+const int map_hatching[FOUR] = {
+    HATCH_VERT, HATCH_SLASH, HATCH_HORIZ, HATCH_BACKSLASH
+};
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+
+    frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
+
+    ret[COL_GRID * 3 + 0] = 0.0F;
+    ret[COL_GRID * 3 + 1] = 0.0F;
+    ret[COL_GRID * 3 + 2] = 0.0F;
+
+    memcpy(ret + COL_0 * 3, map_colours[0], 3 * sizeof(float));
+    memcpy(ret + COL_1 * 3, map_colours[1], 3 * sizeof(float));
+    memcpy(ret + COL_2 * 3, map_colours[2], 3 * sizeof(float));
+    memcpy(ret + COL_3 * 3, map_colours[3], 3 * sizeof(float));
+
+    ret[COL_ERROR * 3 + 0] = 1.0F;
+    ret[COL_ERROR * 3 + 1] = 0.0F;
+    ret[COL_ERROR * 3 + 2] = 0.0F;
+
+    ret[COL_ERRTEXT * 3 + 0] = 1.0F;
+    ret[COL_ERRTEXT * 3 + 1] = 1.0F;
+    ret[COL_ERRTEXT * 3 + 2] = 1.0F;
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    struct game_drawstate *ds = snew(struct game_drawstate);
+    int i;
+
+    ds->tilesize = 0;
+    ds->drawn = snewn(state->p.w * state->p.h, unsigned long);
+    for (i = 0; i < state->p.w * state->p.h; i++)
+       ds->drawn[i] = 0xFFFFL;
+    ds->todraw = snewn(state->p.w * state->p.h, unsigned long);
+    ds->started = FALSE;
+    ds->bl = NULL;
+    ds->drag_visible = FALSE;
+    ds->dragx = ds->dragy = -1;
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds->drawn);
+    sfree(ds->todraw);
+    if (ds->bl)
+        blitter_free(dr, ds->bl);
+    sfree(ds);
+}
+
+static void draw_error(drawing *dr, game_drawstate *ds, int x, int y)
+{
+    int coords[8];
+    int yext, xext;
+
+    /*
+     * Draw a diamond.
+     */
+    coords[0] = x - TILESIZE*2/5;
+    coords[1] = y;
+    coords[2] = x;
+    coords[3] = y - TILESIZE*2/5;
+    coords[4] = x + TILESIZE*2/5;
+    coords[5] = y;
+    coords[6] = x;
+    coords[7] = y + TILESIZE*2/5;
+    draw_polygon(dr, coords, 4, COL_ERROR, COL_GRID);
+
+    /*
+     * Draw an exclamation mark in the diamond. This turns out to
+     * look unpleasantly off-centre if done via draw_text, so I do
+     * it by hand on the basis that exclamation marks aren't that
+     * difficult to draw...
+     */
+    xext = TILESIZE/16;
+    yext = TILESIZE*2/5 - (xext*2+2);
+    draw_rect(dr, x-xext, y-yext, xext*2+1, yext*2+1 - (xext*3),
+             COL_ERRTEXT);
+    draw_rect(dr, x-xext, y+yext-xext*2+1, xext*2+1, xext*2, COL_ERRTEXT);
+}
+
+static void draw_square(drawing *dr, game_drawstate *ds,
+                       const game_params *params, struct map *map,
+                       int x, int y, unsigned long v)
+{
+    int w = params->w, h = params->h, wh = w*h;
+    int tv, bv, xo, yo, i, j, oldj;
+    unsigned long errs, pencil, show_numbers;
+
+    errs = v & ERR_MASK;
+    v &= ~ERR_MASK;
+    pencil = v & PENCIL_MASK;
+    v &= ~PENCIL_MASK;
+    show_numbers = v & SHOW_NUMBERS;
+    v &= ~SHOW_NUMBERS;
+    tv = v / FIVE;
+    bv = v % FIVE;
+
+    clip(dr, COORD(x), COORD(y), TILESIZE, TILESIZE);
+
+    /*
+     * Draw the region colour.
+     */
+    draw_rect(dr, COORD(x), COORD(y), TILESIZE, TILESIZE,
+             (tv == FOUR ? COL_BACKGROUND : COL_0 + tv));
+    /*
+     * Draw the second region colour, if this is a diagonally
+     * divided square.
+     */
+    if (map->map[TE * wh + y*w+x] != map->map[BE * wh + y*w+x]) {
+        int coords[6];
+        coords[0] = COORD(x)-1;
+        coords[1] = COORD(y+1)+1;
+        if (map->map[LE * wh + y*w+x] == map->map[TE * wh + y*w+x])
+            coords[2] = COORD(x+1)+1;
+        else
+            coords[2] = COORD(x)-1;
+        coords[3] = COORD(y)-1;
+        coords[4] = COORD(x+1)+1;
+        coords[5] = COORD(y+1)+1;
+        draw_polygon(dr, coords, 3,
+                     (bv == FOUR ? COL_BACKGROUND : COL_0 + bv), COL_GRID);
+    }
+
+    /*
+     * Draw `pencil marks'. Currently we arrange these in a square
+     * formation, which means we may be in trouble if the value of
+     * FOUR changes later...
+     */
+    assert(FOUR == 4);
+    for (yo = 0; yo < 4; yo++)
+       for (xo = 0; xo < 4; xo++) {
+           int te = map->map[TE * wh + y*w+x];
+           int e, ee, c;
+
+           e = (yo < xo && yo < 3-xo ? TE :
+                yo > xo && yo > 3-xo ? BE :
+                xo < 2 ? LE : RE);
+           ee = map->map[e * wh + y*w+x];
+
+           if (xo != (yo * 2 + 1) % 5)
+               continue;
+           c = yo;
+
+           if (!(pencil & ((ee == te ? PENCIL_T_BASE : PENCIL_B_BASE) << c)))
+               continue;
+
+           if (yo == xo &&
+               (map->map[TE * wh + y*w+x] != map->map[LE * wh + y*w+x]))
+               continue;              /* avoid TL-BR diagonal line */
+           if (yo == 3-xo &&
+               (map->map[TE * wh + y*w+x] != map->map[RE * wh + y*w+x]))
+               continue;              /* avoid BL-TR diagonal line */
+
+           draw_circle(dr, COORD(x) + (xo+1)*TILESIZE/5,
+                       COORD(y) + (yo+1)*TILESIZE/5,
+                       TILESIZE/7, COL_0 + c, COL_0 + c);
+       }
+
+    /*
+     * Draw the grid lines, if required.
+     */
+    if (x <= 0 || map->map[RE*wh+y*w+(x-1)] != map->map[LE*wh+y*w+x])
+       draw_rect(dr, COORD(x), COORD(y), 1, TILESIZE, COL_GRID);
+    if (y <= 0 || map->map[BE*wh+(y-1)*w+x] != map->map[TE*wh+y*w+x])
+       draw_rect(dr, COORD(x), COORD(y), TILESIZE, 1, COL_GRID);
+    if (x <= 0 || y <= 0 ||
+        map->map[RE*wh+(y-1)*w+(x-1)] != map->map[TE*wh+y*w+x] ||
+        map->map[BE*wh+(y-1)*w+(x-1)] != map->map[LE*wh+y*w+x])
+       draw_rect(dr, COORD(x), COORD(y), 1, 1, COL_GRID);
+
+    /*
+     * Draw error markers.
+     */
+    for (yo = 0; yo < 3; yo++)
+       for (xo = 0; xo < 3; xo++)
+           if (errs & (ERR_BASE << (yo*3+xo)))
+               draw_error(dr, ds,
+                          (COORD(x)*2+TILESIZE*xo)/2,
+                          (COORD(y)*2+TILESIZE*yo)/2);
+
+    /*
+     * Draw region numbers, if desired.
+     */
+    if (show_numbers) {
+        oldj = -1;
+        for (i = 0; i < 2; i++) {
+            j = map->map[(i?BE:TE)*wh+y*w+x];
+            if (oldj == j)
+                continue;
+            oldj = j;
+
+            xo = map->regionx[j] - 2*x;
+            yo = map->regiony[j] - 2*y;
+            if (xo >= 0 && xo <= 2 && yo >= 0 && yo <= 2) {
+                char buf[80];
+                sprintf(buf, "%d", j);
+                draw_text(dr, (COORD(x)*2+TILESIZE*xo)/2,
+                          (COORD(y)*2+TILESIZE*yo)/2,
+                          FONT_VARIABLE, 3*TILESIZE/5,
+                          ALIGN_HCENTRE|ALIGN_VCENTRE,
+                          COL_GRID, buf);
+            }
+        }
+    }
+
+    unclip(dr);
+
+    draw_update(dr, COORD(x), COORD(y), TILESIZE, TILESIZE);
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int w = state->p.w, h = state->p.h, wh = w*h, n = state->p.n;
+    int x, y, i;
+    int flash;
+
+    if (ds->drag_visible) {
+        blitter_load(dr, ds->bl, ds->dragx, ds->dragy);
+        draw_update(dr, ds->dragx, ds->dragy, TILESIZE + 3, TILESIZE + 3);
+        ds->drag_visible = FALSE;
+    }
+
+    /*
+     * The initial contents of the window are not guaranteed and
+     * can vary with front ends. To be on the safe side, all games
+     * should start by drawing a big background-colour rectangle
+     * covering the whole window.
+     */
+    if (!ds->started) {
+       int ww, wh;
+
+       game_compute_size(&state->p, TILESIZE, &ww, &wh);
+       draw_rect(dr, 0, 0, ww, wh, COL_BACKGROUND);
+       draw_rect(dr, COORD(0), COORD(0), w*TILESIZE+1, h*TILESIZE+1,
+                 COL_GRID);
+
+       draw_update(dr, 0, 0, ww, wh);
+       ds->started = TRUE;
+    }
+
+    if (flashtime) {
+       if (flash_type == 1)
+           flash = (int)(flashtime * FOUR / flash_length);
+       else
+           flash = 1 + (int)(flashtime * THREE / flash_length);
+    } else
+       flash = -1;
+
+    /*
+     * Set up the `todraw' array.
+     */
+    for (y = 0; y < h; y++)
+       for (x = 0; x < w; x++) {
+           int tv = state->colouring[state->map->map[TE * wh + y*w+x]];
+           int bv = state->colouring[state->map->map[BE * wh + y*w+x]];
+            unsigned long v;
+
+           if (tv < 0)
+               tv = FOUR;
+           if (bv < 0)
+               bv = FOUR;
+
+           if (flash >= 0) {
+               if (flash_type == 1) {
+                   if (tv == flash)
+                       tv = FOUR;
+                   if (bv == flash)
+                       bv = FOUR;
+               } else if (flash_type == 2) {
+                   if (flash % 2)
+                       tv = bv = FOUR;
+               } else {
+                   if (tv != FOUR)
+                       tv = (tv + flash) % FOUR;
+                   if (bv != FOUR)
+                       bv = (bv + flash) % FOUR;
+               }
+           }
+
+            v = tv * FIVE + bv;
+
+            /*
+             * Add pencil marks.
+             */
+           for (i = 0; i < FOUR; i++) {
+               if (state->colouring[state->map->map[TE * wh + y*w+x]] < 0 &&
+                   (state->pencil[state->map->map[TE * wh + y*w+x]] & (1<<i)))
+                   v |= PENCIL_T_BASE << i;
+               if (state->colouring[state->map->map[BE * wh + y*w+x]] < 0 &&
+                   (state->pencil[state->map->map[BE * wh + y*w+x]] & (1<<i)))
+                   v |= PENCIL_B_BASE << i;
+           }
+
+            if (ui->show_numbers)
+                v |= SHOW_NUMBERS;
+
+           ds->todraw[y*w+x] = v;
+       }
+
+    /*
+     * Add error markers to the `todraw' array.
+     */
+    for (i = 0; i < state->map->ngraph; i++) {
+       int v1 = state->map->graph[i] / n;
+       int v2 = state->map->graph[i] % n;
+       int xo, yo;
+
+       if (state->colouring[v1] < 0 || state->colouring[v2] < 0)
+           continue;
+       if (state->colouring[v1] != state->colouring[v2])
+           continue;
+
+       x = state->map->edgex[i];
+       y = state->map->edgey[i];
+
+       xo = x % 2; x /= 2;
+       yo = y % 2; y /= 2;
+
+       ds->todraw[y*w+x] |= ERR_BASE << (yo*3+xo);
+       if (xo == 0) {
+           assert(x > 0);
+           ds->todraw[y*w+(x-1)] |= ERR_BASE << (yo*3+2);
+       }
+       if (yo == 0) {
+           assert(y > 0);
+           ds->todraw[(y-1)*w+x] |= ERR_BASE << (2*3+xo);
+       }
+       if (xo == 0 && yo == 0) {
+           assert(x > 0 && y > 0);
+           ds->todraw[(y-1)*w+(x-1)] |= ERR_BASE << (2*3+2);
+       }
+    }
+
+    /*
+     * Now actually draw everything.
+     */
+    for (y = 0; y < h; y++)
+       for (x = 0; x < w; x++) {
+           unsigned long v = ds->todraw[y*w+x];
+           if (ds->drawn[y*w+x] != v) {
+               draw_square(dr, ds, &state->p, state->map, x, y, v);
+               ds->drawn[y*w+x] = v;
+           }
+       }
+
+    /*
+     * Draw the dragged colour blob if any.
+     */
+    if ((ui->drag_colour > -2) || ui->cur_visible) {
+        int bg, iscur = 0;
+        if (ui->drag_colour >= 0)
+            bg = COL_0 + ui->drag_colour;
+        else if (ui->drag_colour == -1) {
+            bg = COL_BACKGROUND;
+        } else {
+            int r = region_from_coords(state, ds, ui->dragx, ui->dragy);
+            int c = (r < 0) ? -1 : state->colouring[r];
+            assert(ui->cur_visible);
+            /*bg = COL_GRID;*/
+            bg = (c < 0) ? COL_BACKGROUND : COL_0 + c;
+            iscur = 1;
+        }
+
+        ds->dragx = ui->dragx - TILESIZE/2 - 2;
+        ds->dragy = ui->dragy - TILESIZE/2 - 2;
+        blitter_save(dr, ds->bl, ds->dragx, ds->dragy);
+        draw_circle(dr, ui->dragx, ui->dragy,
+                    iscur ? TILESIZE/4 : TILESIZE/2, bg, COL_GRID);
+       for (i = 0; i < FOUR; i++)
+           if (ui->drag_pencil & (1 << i))
+               draw_circle(dr, ui->dragx + ((i*4+2)%10-3) * TILESIZE/10,
+                           ui->dragy + (i*2-3) * TILESIZE/10,
+                           TILESIZE/8, COL_0 + i, COL_0 + i);
+        draw_update(dr, ds->dragx, ds->dragy, TILESIZE + 3, TILESIZE + 3);
+        ds->drag_visible = TRUE;
+    }
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return 0.0F;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    if (!oldstate->completed && newstate->completed &&
+       !oldstate->cheated && !newstate->cheated) {
+       if (flash_type < 0) {
+           char *env = getenv("MAP_ALTERNATIVE_FLASH");
+           if (env)
+               flash_type = atoi(env);
+           else
+               flash_type = 0;
+           flash_length = (flash_type == 1 ? 0.50F : 0.30F);
+       }
+       return flash_length;
+    } else
+       return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    return state->completed ? +1 : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+    int pw, ph;
+
+    /*
+     * I'll use 4mm squares by default, I think. Simplest way to
+     * compute this size is to compute the pixel puzzle size at a
+     * given tile size and then scale.
+     */
+    game_compute_size(params, 400, &pw, &ph);
+    *x = pw / 100.0F;
+    *y = ph / 100.0F;
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+    int w = state->p.w, h = state->p.h, wh = w*h, n = state->p.n;
+    int ink, c[FOUR], i;
+    int x, y, r;
+    int *coords, ncoords, coordsize;
+
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    struct { int tilesize; } ads, *ds = &ads;
+    /* We can't call game_set_size() here because we don't want a blitter */
+    ads.tilesize = tilesize;
+
+    ink = print_mono_colour(dr, 0);
+    for (i = 0; i < FOUR; i++)
+       c[i] = print_rgb_hatched_colour(dr, map_colours[i][0],
+                                       map_colours[i][1], map_colours[i][2],
+                                       map_hatching[i]);
+
+    coordsize = 0;
+    coords = NULL;
+
+    print_line_width(dr, TILESIZE / 16);
+
+    /*
+     * Draw a single filled polygon around each region.
+     */
+    for (r = 0; r < n; r++) {
+       int octants[8], lastdir, d1, d2, ox, oy;
+
+       /*
+        * Start by finding a point on the region boundary. Any
+        * point will do. To do this, we'll search for a square
+        * containing the region and then decide which corner of it
+        * to use.
+        */
+       x = w;
+       for (y = 0; y < h; y++) {
+           for (x = 0; x < w; x++) {
+               if (state->map->map[wh*0+y*w+x] == r ||
+                   state->map->map[wh*1+y*w+x] == r ||
+                   state->map->map[wh*2+y*w+x] == r ||
+                   state->map->map[wh*3+y*w+x] == r)
+                   break;
+           }
+           if (x < w)
+               break;
+       }
+       assert(y < h && x < w);        /* we must have found one somewhere */
+       /*
+        * This is the first square in lexicographic order which
+        * contains part of this region. Therefore, one of the top
+        * two corners of the square must be what we're after. The
+        * only case in which it isn't the top left one is if the
+        * square is diagonally divided and the region is in the
+        * bottom right half.
+        */
+       if (state->map->map[wh*TE+y*w+x] != r &&
+           state->map->map[wh*LE+y*w+x] != r)
+           x++;                       /* could just as well have done y++ */
+
+       /*
+        * Now we have a point on the region boundary. Trace around
+        * the region until we come back to this point,
+        * accumulating coordinates for a polygon draw operation as
+        * we go.
+        */
+       lastdir = -1;
+       ox = x;
+       oy = y;
+       ncoords = 0;
+
+       do {
+           /*
+            * There are eight possible directions we could head in
+            * from here. We identify them by octant numbers, and
+            * we also use octant numbers to identify the spaces
+            * between them:
+            * 
+            *   6   7   0
+            *    \ 7|0 /
+            *     \ | /
+            *    6 \|/ 1
+            * 5-----+-----1
+            *    5 /|\ 2
+            *     / | \
+            *    / 4|3 \
+            *   4   3   2
+            */
+           octants[0] = x<w && y>0 ? state->map->map[wh*LE+(y-1)*w+x] : -1;
+           octants[1] = x<w && y>0 ? state->map->map[wh*BE+(y-1)*w+x] : -1;
+           octants[2] = x<w && y<h ? state->map->map[wh*TE+y*w+x] : -1;
+           octants[3] = x<w && y<h ? state->map->map[wh*LE+y*w+x] : -1;
+           octants[4] = x>0 && y<h ? state->map->map[wh*RE+y*w+(x-1)] : -1;
+           octants[5] = x>0 && y<h ? state->map->map[wh*TE+y*w+(x-1)] : -1;
+           octants[6] = x>0 && y>0 ? state->map->map[wh*BE+(y-1)*w+(x-1)] :-1;
+           octants[7] = x>0 && y>0 ? state->map->map[wh*RE+(y-1)*w+(x-1)] :-1;
+
+           d1 = d2 = -1;
+           for (i = 0; i < 8; i++)
+               if ((octants[i] == r) ^ (octants[(i+1)%8] == r)) {
+                   assert(d2 == -1);
+                   if (d1 == -1)
+                       d1 = i;
+                   else
+                       d2 = i;
+               }
+
+           assert(d1 != -1 && d2 != -1);
+           if (d1 == lastdir)
+               d1 = d2;
+
+           /*
+            * Now we're heading in direction d1. Save the current
+            * coordinates.
+            */
+           if (ncoords + 2 > coordsize) {
+               coordsize += 128;
+               coords = sresize(coords, coordsize, int);
+           }
+           coords[ncoords++] = COORD(x);
+           coords[ncoords++] = COORD(y);
+
+           /*
+            * Compute the new coordinates.
+            */
+           x += (d1 % 4 == 3 ? 0 : d1 < 4 ? +1 : -1);
+           y += (d1 % 4 == 1 ? 0 : d1 > 1 && d1 < 5 ? +1 : -1);
+           assert(x >= 0 && x <= w && y >= 0 && y <= h);
+
+           lastdir = d1 ^ 4;
+       } while (x != ox || y != oy);
+
+       draw_polygon(dr, coords, ncoords/2,
+                    state->colouring[r] >= 0 ?
+                    c[state->colouring[r]] : -1, ink);
+    }
+    sfree(coords);
+}
+
+#ifdef COMBINED
+#define thegame map
+#endif
+
+const struct game thegame = {
+    "Map", "games.map", "map",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    FALSE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    20, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    TRUE, TRUE, game_print_size, game_print,
+    FALSE,                            /* wants_statusbar */
+    FALSE, game_timing_state,
+    0,                                /* flags */
+};
+
+#ifdef STANDALONE_SOLVER
+
+int main(int argc, char **argv)
+{
+    game_params *p;
+    game_state *s;
+    char *id = NULL, *desc, *err;
+    int grade = FALSE;
+    int ret, diff, really_verbose = FALSE;
+    struct solver_scratch *sc;
+    int i;
+
+    while (--argc > 0) {
+        char *p = *++argv;
+        if (!strcmp(p, "-v")) {
+            really_verbose = TRUE;
+        } else if (!strcmp(p, "-g")) {
+            grade = TRUE;
+        } else if (*p == '-') {
+            fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p);
+            return 1;
+        } else {
+            id = p;
+        }
+    }
+
+    if (!id) {
+        fprintf(stderr, "usage: %s [-g | -v] <game_id>\n", argv[0]);
+        return 1;
+    }
+
+    desc = strchr(id, ':');
+    if (!desc) {
+        fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]);
+        return 1;
+    }
+    *desc++ = '\0';
+
+    p = default_params();
+    decode_params(p, id);
+    err = validate_desc(p, desc);
+    if (err) {
+        fprintf(stderr, "%s: %s\n", argv[0], err);
+        return 1;
+    }
+    s = new_game(NULL, p, desc);
+
+    sc = new_scratch(s->map->graph, s->map->n, s->map->ngraph);
+
+    /*
+     * When solving an Easy puzzle, we don't want to bother the
+     * user with Hard-level deductions. For this reason, we grade
+     * the puzzle internally before doing anything else.
+     */
+    ret = -1;                         /* placate optimiser */
+    for (diff = 0; diff < DIFFCOUNT; diff++) {
+        for (i = 0; i < s->map->n; i++)
+            if (!s->map->immutable[i])
+                s->colouring[i] = -1;
+       ret = map_solver(sc, s->map->graph, s->map->n, s->map->ngraph,
+                         s->colouring, diff);
+       if (ret < 2)
+           break;
+    }
+
+    if (diff == DIFFCOUNT) {
+       if (grade)
+           printf("Difficulty rating: harder than Hard, or ambiguous\n");
+       else
+           printf("Unable to find a unique solution\n");
+    } else {
+       if (grade) {
+           if (ret == 0)
+               printf("Difficulty rating: impossible (no solution exists)\n");
+           else if (ret == 1)
+               printf("Difficulty rating: %s\n", map_diffnames[diff]);
+       } else {
+           verbose = really_verbose;
+            for (i = 0; i < s->map->n; i++)
+                if (!s->map->immutable[i])
+                    s->colouring[i] = -1;
+            ret = map_solver(sc, s->map->graph, s->map->n, s->map->ngraph,
+                             s->colouring, diff);
+           if (ret == 0)
+               printf("Puzzle is inconsistent\n");
+           else {
+                int col = 0;
+
+                for (i = 0; i < s->map->n; i++) {
+                    printf("%5d <- %c%c", i, colnames[s->colouring[i]],
+                           (col < 6 && i+1 < s->map->n ? ' ' : '\n'));
+                    if (++col == 7)
+                        col = 0;
+                }
+            }
+       }
+    }
+
+    return 0;
+}
+
+#endif
+
+/* vim: set shiftwidth=4 tabstop=8: */
diff --git a/maxflow.c b/maxflow.c
new file mode 100644 (file)
index 0000000..028946b
--- /dev/null
+++ b/maxflow.c
@@ -0,0 +1,461 @@
+/*
+ * Edmonds-Karp algorithm for finding a maximum flow and minimum
+ * cut in a network. Almost identical to the Ford-Fulkerson
+ * algorithm, but apparently using breadth-first search to find the
+ * _shortest_ augmenting path is a good way to guarantee
+ * termination and ensure the time complexity is not dependent on
+ * the actual value of the maximum flow. I don't understand why
+ * that should be, but it's claimed on the Internet that it's been
+ * proved, and that's good enough for me. I prefer BFS to DFS
+ * anyway :-)
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "maxflow.h"
+
+#include "puzzles.h"                  /* for snewn/sfree */
+
+int maxflow_with_scratch(void *scratch, int nv, int source, int sink,
+                        int ne, const int *edges, const int *backedges,
+                        const int *capacity, int *flow, int *cut)
+{
+    int *todo = (int *)scratch;
+    int *prev = todo + nv;
+    int *firstedge = todo + 2*nv;
+    int *firstbackedge = todo + 3*nv;
+    int i, j, head, tail, from, to;
+    int totalflow;
+
+    /*
+     * Scan the edges array to find the index of the first edge
+     * from each node.
+     */
+    j = 0;
+    for (i = 0; i < ne; i++)
+       while (j <= edges[2*i])
+           firstedge[j++] = i;
+    while (j < nv)
+       firstedge[j++] = ne;
+    assert(j == nv);
+
+    /*
+     * Scan the backedges array to find the index of the first edge
+     * _to_ each node.
+     */
+    j = 0;
+    for (i = 0; i < ne; i++)
+       while (j <= edges[2*backedges[i]+1])
+           firstbackedge[j++] = i;
+    while (j < nv)
+       firstbackedge[j++] = ne;
+    assert(j == nv);
+
+    /*
+     * Start the flow off at zero on every edge.
+     */
+    for (i = 0; i < ne; i++)
+       flow[i] = 0;
+    totalflow = 0;
+
+    /*
+     * Repeatedly look for an augmenting path, and follow it.
+     */
+    while (1) {
+
+       /*
+        * Set up the prev array.
+        */
+       for (i = 0; i < nv; i++)
+           prev[i] = -1;
+
+       /*
+        * Initialise the to-do list for BFS.
+        */
+       head = tail = 0;
+       todo[tail++] = source;
+
+       /*
+        * Now do the BFS loop.
+        */
+       while (head < tail && prev[sink] <= 0) {
+           from = todo[head++];
+
+           /*
+            * Try all the forward edges out of node `from'. For a
+            * forward edge to be valid, it must have flow
+            * currently less than its capacity.
+            */
+           for (i = firstedge[from]; i < ne && edges[2*i] == from; i++) {
+               to = edges[2*i+1];
+               if (to == source || prev[to] >= 0)
+                   continue;
+               if (capacity[i] >= 0 && flow[i] >= capacity[i])
+                   continue;
+               /*
+                * This is a valid augmenting edge. Visit node `to'.
+                */
+               prev[to] = 2*i;
+               todo[tail++] = to;
+           }
+
+           /*
+            * Try all the backward edges into node `from'. For a
+            * backward edge to be valid, it must have flow
+            * currently greater than zero.
+            */
+           for (i = firstbackedge[from];
+                j = backedges[i], i < ne && edges[2*j+1]==from; i++) {
+               to = edges[2*j];
+               if (to == source || prev[to] >= 0)
+                   continue;
+               if (flow[j] <= 0)
+                   continue;
+               /*
+                * This is a valid augmenting edge. Visit node `to'.
+                */
+               prev[to] = 2*j+1;
+               todo[tail++] = to;
+           }
+       }
+
+       /*
+        * If prev[sink] is non-null, we have found an augmenting
+        * path.
+        */
+       if (prev[sink] >= 0) {
+           int max;
+
+           /*
+            * Work backwards along the path figuring out the
+            * maximum flow we can add.
+            */
+           to = sink;
+           max = -1;
+           while (to != source) {
+               int spare;
+
+               /*
+                * Find the edge we're currently moving along.
+                */
+               i = prev[to];
+               from = edges[i];
+               assert(from != to);
+
+               /*
+                * Determine the spare capacity of this edge.
+                */
+               if (i & 1)
+                   spare = flow[i / 2];   /* backward edge */
+               else if (capacity[i / 2] >= 0)
+                   spare = capacity[i / 2] - flow[i / 2];   /* forward edge */
+               else
+                   spare = -1;        /* unlimited forward edge */
+
+               assert(spare != 0);
+
+               if (max < 0 || (spare >= 0 && spare < max))
+                   max = spare;
+
+               to = from;
+           }
+           /*
+            * Fail an assertion if max is still < 0, i.e. there is
+            * an entirely unlimited path from source to sink. Also
+            * max should not _be_ zero, because by construction
+            * this _should_ be an augmenting path.
+            */
+           assert(max > 0);
+
+           /*
+            * Now work backwards along the path again, this time
+            * actually adjusting the flow.
+            */
+           to = sink;
+           while (to != source) {
+               /*
+                * Find the edge we're currently moving along.
+                */
+               i = prev[to];
+               from = edges[i];
+               assert(from != to);
+
+               /*
+                * Adjust the edge.
+                */
+               if (i & 1)
+                   flow[i / 2] -= max;  /* backward edge */
+               else
+                   flow[i / 2] += max;  /* forward edge */
+
+               to = from;
+           }
+
+           /*
+            * And adjust the overall flow counter.
+            */
+           totalflow += max;
+
+           continue;
+       }
+
+       /*
+        * If we reach here, we have failed to find an augmenting
+        * path, which means we're done. Output the `cut' array if
+        * required, and leave.
+        */
+       if (cut) {
+           for (i = 0; i < nv; i++) {
+               if (i == source || prev[i] >= 0)
+                   cut[i] = 0;
+               else
+                   cut[i] = 1;
+           }
+       }
+       return totalflow;
+    }
+}
+
+int maxflow_scratch_size(int nv)
+{
+    return (nv * 4) * sizeof(int);
+}
+
+void maxflow_setup_backedges(int ne, const int *edges, int *backedges)
+{
+    int i, n;
+
+    for (i = 0; i < ne; i++)
+       backedges[i] = i;
+
+    /*
+     * We actually can't use the C qsort() function, because we'd
+     * need to pass `edges' as a context parameter to its
+     * comparator function. So instead I'm forced to implement my
+     * own sorting algorithm internally, which is a pest. I'll use
+     * heapsort, because I like it.
+     */
+
+#define LESS(i,j) ( (edges[2*(i)+1] < edges[2*(j)+1]) || \
+                   (edges[2*(i)+1] == edges[2*(j)+1] && \
+                    edges[2*(i)] < edges[2*(j)]) )
+#define PARENT(n) ( ((n)-1)/2 )
+#define LCHILD(n) ( 2*(n)+1 )
+#define RCHILD(n) ( 2*(n)+2 )
+#define SWAP(i,j) do { int swaptmp = (i); (i) = (j); (j) = swaptmp; } while (0)
+
+    /*
+     * Phase 1: build the heap. We want the _largest_ element at
+     * the top.
+     */
+    n = 0;
+    while (n < ne) {
+       n++;
+
+       /*
+        * Swap element n with its parent repeatedly to preserve
+        * the heap property.
+        */
+       i = n-1;
+
+       while (i > 0) {
+           int p = PARENT(i);
+
+           if (LESS(backedges[p], backedges[i])) {
+               SWAP(backedges[p], backedges[i]);
+               i = p;
+           } else
+               break;
+       }
+    }
+
+    /*
+     * Phase 2: repeatedly remove the largest element and stick it
+     * at the top of the array.
+     */
+    while (n > 0) {
+       /*
+        * The largest element is at position 0. Put it at the top,
+        * and swap the arbitrary element from that position into
+        * position 0.
+        */
+       n--;
+       SWAP(backedges[0], backedges[n]);
+
+       /*
+        * Now repeatedly move that arbitrary element down the heap
+        * by swapping it with the more suitable of its children.
+        */
+       i = 0;
+       while (1) {
+           int lc, rc;
+
+           lc = LCHILD(i);
+           rc = RCHILD(i);
+
+           if (lc >= n)
+               break;                 /* we've hit bottom */
+
+           if (rc >= n) {
+               /*
+                * Special case: there is only one child to check.
+                */
+               if (LESS(backedges[i], backedges[lc]))
+                   SWAP(backedges[i], backedges[lc]);
+
+               /* _Now_ we've hit bottom. */
+               break;
+           } else {
+               /*
+                * The common case: there are two children and we
+                * must check them both.
+                */
+               if (LESS(backedges[i], backedges[lc]) ||
+                   LESS(backedges[i], backedges[rc])) {
+                   /*
+                    * Pick the more appropriate child to swap with
+                    * (i.e. the one which would want to be the
+                    * parent if one were above the other - as one
+                    * is about to be).
+                    */
+                   if (LESS(backedges[lc], backedges[rc])) {
+                       SWAP(backedges[i], backedges[rc]);
+                       i = rc;
+                   } else {
+                       SWAP(backedges[i], backedges[lc]);
+                       i = lc;
+                   }
+               } else {
+                   /* This element is in the right place; we're done. */
+                   break;
+               }
+           }
+       }
+    }
+
+#undef LESS
+#undef PARENT
+#undef LCHILD
+#undef RCHILD
+#undef SWAP
+
+}
+
+int maxflow(int nv, int source, int sink,
+           int ne, const int *edges, const int *capacity,
+           int *flow, int *cut)
+{
+    void *scratch;
+    int *backedges;
+    int size;
+    int ret;
+
+    /*
+     * Allocate the space.
+     */
+    size = ne * sizeof(int) + maxflow_scratch_size(nv);
+    backedges = smalloc(size);
+    if (!backedges)
+       return -1;
+    scratch = backedges + ne;
+
+    /*
+     * Set up the backedges array.
+     */
+    maxflow_setup_backedges(ne, edges, backedges);
+
+    /*
+     * Call the main function.
+     */
+    ret = maxflow_with_scratch(scratch, nv, source, sink, ne, edges,
+                              backedges, capacity, flow, cut);
+
+    /*
+     * Free the scratch space.
+     */
+    sfree(backedges);
+
+    /*
+     * And we're done.
+     */
+    return ret;
+}
+
+#ifdef TESTMODE
+
+#define MAXEDGES 256
+#define MAXVERTICES 128
+#define ADDEDGE(i,j) do{edges[ne*2] = (i); edges[ne*2+1] = (j); ne++;}while(0)
+
+int compare_edge(const void *av, const void *bv)
+{
+    const int *a = (const int *)av;
+    const int *b = (const int *)bv;
+
+    if (a[0] < b[0])
+       return -1;
+    else if (a[0] > b[0])
+       return +1;
+    else if (a[1] < b[1])
+       return -1;
+    else if (a[1] > b[1])
+       return +1;
+    else
+       return 0;
+}
+
+int main(void)
+{
+    int edges[MAXEDGES*2], ne, nv;
+    int capacity[MAXEDGES], flow[MAXEDGES], cut[MAXVERTICES];
+    int source, sink, p, q, i, j, ret;
+
+    /*
+     * Use this algorithm to find a maximal complete matching in a
+     * bipartite graph.
+     */
+    ne = 0;
+    nv = 0;
+    source = nv++;
+    p = nv;
+    nv += 5;
+    q = nv;
+    nv += 5;
+    sink = nv++;
+    for (i = 0; i < 5; i++) {
+       capacity[ne] = 1;
+       ADDEDGE(source, p+i);
+    }
+    for (i = 0; i < 5; i++) {
+       capacity[ne] = 1;
+       ADDEDGE(q+i, sink);
+    }
+    j = ne;
+    capacity[ne] = 1; ADDEDGE(p+0,q+0);
+    capacity[ne] = 1; ADDEDGE(p+1,q+0);
+    capacity[ne] = 1; ADDEDGE(p+1,q+1);
+    capacity[ne] = 1; ADDEDGE(p+2,q+1);
+    capacity[ne] = 1; ADDEDGE(p+2,q+2);
+    capacity[ne] = 1; ADDEDGE(p+3,q+2);
+    capacity[ne] = 1; ADDEDGE(p+3,q+3);
+    capacity[ne] = 1; ADDEDGE(p+4,q+3);
+    /* capacity[ne] = 1; ADDEDGE(p+2,q+4); */
+    qsort(edges, ne, 2*sizeof(int), compare_edge);
+
+    ret = maxflow(nv, source, sink, ne, edges, capacity, flow, cut);
+
+    printf("ret = %d\n", ret);
+
+    for (i = 0; i < ne; i++)
+       printf("flow %d: %d -> %d\n", flow[i], edges[2*i], edges[2*i+1]);
+
+    for (i = 0; i < nv; i++)
+       if (cut[i] == 0)
+           printf("difficult set includes %d\n", i);
+
+    return 0;
+}
+
+#endif
diff --git a/maxflow.h b/maxflow.h
new file mode 100644 (file)
index 0000000..d490f45
--- /dev/null
+++ b/maxflow.h
@@ -0,0 +1,95 @@
+/*
+ * Edmonds-Karp algorithm for finding a maximum flow and minimum
+ * cut in a network. Almost identical to the Ford-Fulkerson
+ * algorithm, but apparently using breadth-first search to find the
+ * _shortest_ augmenting path is a good way to guarantee
+ * termination and ensure the time complexity is not dependent on
+ * the actual value of the maximum flow. I don't understand why
+ * that should be, but it's claimed on the Internet that it's been
+ * proved, and that's good enough for me. I prefer BFS to DFS
+ * anyway :-)
+ */
+
+#ifndef MAXFLOW_MAXFLOW_H
+#define MAXFLOW_MAXFLOW_H
+
+/*
+ * The actual algorithm.
+ * 
+ * Inputs:
+ * 
+ *  - `scratch' is previously allocated scratch space of a size
+ *    previously determined by calling `maxflow_scratch_size'.
+ * 
+ *  - `nv' is the number of vertices. Vertices are assumed to be
+ *    numbered from 0 to nv-1.
+ * 
+ *  - `source' and `sink' are the distinguished source and sink
+ *    vertices.
+ * 
+ *  - `ne' is the number of edges in the graph.
+ * 
+ *  - `edges' is an array of 2*ne integers, giving a (source, dest)
+ *    pair for each network edge. Edge pairs are expected to be
+ *    sorted in lexicographic order.
+ * 
+ *  - `backedges' is an array of `ne' integers, each a distinct
+ *    index into `edges'. The edges in `edges', if permuted as
+ *    specified by this array, should end up sorted in the _other_
+ *    lexicographic order, i.e. dest taking priority over source.
+ * 
+ *  - `capacity' is an array of `ne' integers, giving a maximum
+ *    flow capacity for each edge. A negative value is taken to
+ *    indicate unlimited capacity on that edge, but note that there
+ *    may not be any unlimited-capacity _path_ from source to sink
+ *    or an assertion will be failed.
+ * 
+ * Output:
+ * 
+ *  - `flow' must be non-NULL. It is an array of `ne' integers,
+ *    each giving the final flow along each edge.
+ * 
+ *  - `cut' may be NULL. If non-NULL, it is an array of `nv'
+ *    integers, which will be set to zero or one on output, in such
+ *    a way that:
+ *     + the set of zero vertices includes the source
+ *     + the set of one vertices includes the sink
+ *     + the maximum flow capacity between the zero and one vertex
+ *      sets is achieved (i.e. all edges from a zero vertex to a
+ *      one vertex are at full capacity, while all edges from a
+ *      one vertex to a zero vertex have no flow at all).
+ * 
+ *  - the returned value from the function is the total flow
+ *    achieved.
+ */
+int maxflow_with_scratch(void *scratch, int nv, int source, int sink,
+                        int ne, const int *edges, const int *backedges,
+                        const int *capacity, int *flow, int *cut);
+
+/*
+ * The above function expects its `scratch' and `backedges'
+ * parameters to have already been set up. This allows you to set
+ * them up once and use them in multiple invocates of the
+ * algorithm. Now I provide functions to actually do the setting
+ * up.
+ */
+int maxflow_scratch_size(int nv);
+void maxflow_setup_backedges(int ne, const int *edges, int *backedges);
+
+/*
+ * Simplified version of the above function. All parameters are the
+ * same, except that `scratch' and `backedges' are constructed
+ * internally. This is the simplest way to call the algorithm as a
+ * one-off; however, if you need to call it multiple times on the
+ * same network, it is probably better to call the above version
+ * directly so that you only construct `scratch' and `backedges'
+ * once.
+ * 
+ * Additional return value is now -1, meaning that scratch space
+ * could not be allocated.
+ */
+int maxflow(int nv, int source, int sink,
+           int ne, const int *edges, const int *capacity,
+           int *flow, int *cut);
+
+#endif /* MAXFLOW_MAXFLOW_H */
diff --git a/midend.c b/midend.c
new file mode 100644 (file)
index 0000000..190840a
--- /dev/null
+++ b/midend.c
@@ -0,0 +1,2126 @@
+/*
+ * midend.c: general middle fragment sitting between the
+ * platform-specific front end and game-specific back end.
+ * Maintains a move list, takes care of Undo and Redo commands, and
+ * processes standard keystrokes for undo/redo/new/quit.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include "puzzles.h"
+
+enum { DEF_PARAMS, DEF_SEED, DEF_DESC };   /* for midend_game_id_int */
+
+enum { NEWGAME, MOVE, SOLVE, RESTART };/* for midend_state_entry.movetype */
+
+#define special(type) ( (type) != MOVE )
+
+struct midend_state_entry {
+    game_state *state;
+    char *movestr;
+    int movetype;
+};
+
+struct midend {
+    frontend *frontend;
+    random_state *random;
+    const game *ourgame;
+
+    game_params **presets;
+    char **preset_names, **preset_encodings;
+    int npresets, presetsize;
+
+    /*
+     * `desc' and `privdesc' deserve a comment.
+     * 
+     * `desc' is the game description as presented to the user when
+     * they ask for Game -> Specific. `privdesc', if non-NULL, is a
+     * different game description used to reconstruct the initial
+     * game_state when de-serialising. If privdesc is NULL, `desc'
+     * is used for both.
+     * 
+     * For almost all games, `privdesc' is NULL and never used. The
+     * exception (as usual) is Mines: the initial game state has no
+     * squares open at all, but after the first click `desc' is
+     * rewritten to describe a game state with an initial click and
+     * thus a bunch of squares open. If we used that desc to
+     * serialise and deserialise, then the initial game state after
+     * deserialisation would look unlike the initial game state
+     * beforehand, and worse still execute_move() might fail on the
+     * attempted first click. So `privdesc' is also used in this
+     * case, to provide a game description describing the same
+     * fixed mine layout _but_ no initial click. (These game IDs
+     * may also be typed directly into Mines if you like.)
+     */
+    char *desc, *privdesc, *seedstr;
+    char *aux_info;
+    enum { GOT_SEED, GOT_DESC, GOT_NOTHING } genmode;
+
+    int nstates, statesize, statepos;
+    struct midend_state_entry *states;
+
+    game_params *params, *curparams;
+    game_drawstate *drawstate;
+    game_ui *ui;
+
+    game_state *oldstate;
+    float anim_time, anim_pos;
+    float flash_time, flash_pos;
+    int dir;
+
+    int timing;
+    float elapsed;
+    char *laststatus;
+
+    drawing *drawing;
+
+    int pressed_mouse_button;
+
+    int preferred_tilesize, tilesize, winwidth, winheight;
+
+    void (*game_id_change_notify_function)(void *);
+    void *game_id_change_notify_ctx;
+};
+
+#define ensure(me) do { \
+    if ((me)->nstates >= (me)->statesize) { \
+       (me)->statesize = (me)->nstates + 128; \
+       (me)->states = sresize((me)->states, (me)->statesize, \
+                               struct midend_state_entry); \
+    } \
+} while (0)
+
+void midend_reset_tilesize(midend *me)
+{
+    me->preferred_tilesize = me->ourgame->preferred_tilesize;
+    {
+        /*
+         * Allow an environment-based override for the default tile
+         * size by defining a variable along the lines of
+         * `NET_TILESIZE=15'.
+         */
+
+       char buf[80], *e;
+       int j, k, ts;
+
+       sprintf(buf, "%s_TILESIZE", me->ourgame->name);
+       for (j = k = 0; buf[j]; j++)
+           if (!isspace((unsigned char)buf[j]))
+               buf[k++] = toupper((unsigned char)buf[j]);
+       buf[k] = '\0';
+       if ((e = getenv(buf)) != NULL && sscanf(e, "%d", &ts) == 1 && ts > 0)
+           me->preferred_tilesize = ts;
+    }
+}
+
+midend *midend_new(frontend *fe, const game *ourgame,
+                  const drawing_api *drapi, void *drhandle)
+{
+    midend *me = snew(midend);
+    void *randseed;
+    int randseedsize;
+
+    get_random_seed(&randseed, &randseedsize);
+
+    me->frontend = fe;
+    me->ourgame = ourgame;
+    me->random = random_new(randseed, randseedsize);
+    me->nstates = me->statesize = me->statepos = 0;
+    me->states = NULL;
+    me->params = ourgame->default_params();
+    me->game_id_change_notify_function = NULL;
+    me->game_id_change_notify_ctx = NULL;
+
+    /*
+     * Allow environment-based changing of the default settings by
+     * defining a variable along the lines of `NET_DEFAULT=25x25w'
+     * in which the value is an encoded parameter string.
+     */
+    {
+        char buf[80], *e;
+        int j, k;
+        sprintf(buf, "%s_DEFAULT", me->ourgame->name);
+       for (j = k = 0; buf[j]; j++)
+           if (!isspace((unsigned char)buf[j]))
+               buf[k++] = toupper((unsigned char)buf[j]);
+       buf[k] = '\0';
+        if ((e = getenv(buf)) != NULL)
+            me->ourgame->decode_params(me->params, e);
+    }
+    me->curparams = NULL;
+    me->desc = me->privdesc = NULL;
+    me->seedstr = NULL;
+    me->aux_info = NULL;
+    me->genmode = GOT_NOTHING;
+    me->drawstate = NULL;
+    me->oldstate = NULL;
+    me->presets = NULL;
+    me->preset_names = NULL;
+    me->preset_encodings = NULL;
+    me->npresets = me->presetsize = 0;
+    me->anim_time = me->anim_pos = 0.0F;
+    me->flash_time = me->flash_pos = 0.0F;
+    me->dir = 0;
+    me->ui = NULL;
+    me->pressed_mouse_button = 0;
+    me->laststatus = NULL;
+    me->timing = FALSE;
+    me->elapsed = 0.0F;
+    me->tilesize = me->winwidth = me->winheight = 0;
+    if (drapi)
+       me->drawing = drawing_new(drapi, me, drhandle);
+    else
+       me->drawing = NULL;
+
+    midend_reset_tilesize(me);
+
+    sfree(randseed);
+
+    return me;
+}
+
+const game *midend_which_game(midend *me)
+{
+    return me->ourgame;
+}
+
+static void midend_purge_states(midend *me)
+{
+    while (me->nstates > me->statepos) {
+        me->ourgame->free_game(me->states[--me->nstates].state);
+        if (me->states[me->nstates].movestr)
+            sfree(me->states[me->nstates].movestr);
+    }
+}
+
+static void midend_free_game(midend *me)
+{
+    while (me->nstates > 0) {
+        me->nstates--;
+       me->ourgame->free_game(me->states[me->nstates].state);
+       sfree(me->states[me->nstates].movestr);
+    }
+
+    if (me->drawstate)
+        me->ourgame->free_drawstate(me->drawing, me->drawstate);
+}
+
+void midend_free(midend *me)
+{
+    int i;
+
+    midend_free_game(me);
+
+    if (me->drawing)
+       drawing_free(me->drawing);
+    random_free(me->random);
+    sfree(me->states);
+    sfree(me->desc);
+    sfree(me->privdesc);
+    sfree(me->seedstr);
+    sfree(me->aux_info);
+    me->ourgame->free_params(me->params);
+    if (me->npresets) {
+       for (i = 0; i < me->npresets; i++) {
+           sfree(me->presets[i]);
+           sfree(me->preset_names[i]);
+           sfree(me->preset_encodings[i]);
+       }
+       sfree(me->presets);
+       sfree(me->preset_names);
+       sfree(me->preset_encodings);
+    }
+    if (me->ui)
+        me->ourgame->free_ui(me->ui);
+    if (me->curparams)
+        me->ourgame->free_params(me->curparams);
+    sfree(me->laststatus);
+    sfree(me);
+}
+
+static void midend_size_new_drawstate(midend *me)
+{
+    /*
+     * Don't even bother, if we haven't worked out our tile size
+     * anyway yet.
+     */
+    if (me->tilesize > 0) {
+       me->ourgame->compute_size(me->params, me->tilesize,
+                                 &me->winwidth, &me->winheight);
+       me->ourgame->set_size(me->drawing, me->drawstate,
+                             me->params, me->tilesize);
+    }
+}
+
+void midend_size(midend *me, int *x, int *y, int user_size)
+{
+    int min, max;
+    int rx, ry;
+
+    /*
+     * We can't set the size on the same drawstate twice. So if
+     * we've already sized one drawstate, we must throw it away and
+     * create a new one.
+     */
+    if (me->drawstate && me->tilesize > 0) {
+        me->ourgame->free_drawstate(me->drawing, me->drawstate);
+        me->drawstate = me->ourgame->new_drawstate(me->drawing,
+                                                   me->states[0].state);
+    }
+
+    /*
+     * Find the tile size that best fits within the given space. If
+     * `user_size' is TRUE, we must actually find the _largest_ such
+     * tile size, in order to get as close to the user's explicit
+     * request as possible; otherwise, we bound above at the game's
+     * preferred tile size, so that the game gets what it wants
+     * provided that this doesn't break the constraint from the
+     * front-end (which is likely to be a screen size or similar).
+     */
+    if (user_size) {
+       max = 1;
+       do {
+           max *= 2;
+           me->ourgame->compute_size(me->params, max, &rx, &ry);
+       } while (rx <= *x && ry <= *y);
+    } else
+       max = me->preferred_tilesize + 1;
+    min = 1;
+
+    /*
+     * Now binary-search between min and max. We're looking for a
+     * boundary rather than a value: the point at which tile sizes
+     * stop fitting within the given dimensions. Thus, we stop when
+     * max and min differ by exactly 1.
+     */
+    while (max - min > 1) {
+       int mid = (max + min) / 2;
+       me->ourgame->compute_size(me->params, mid, &rx, &ry);
+       if (rx <= *x && ry <= *y)
+           min = mid;
+       else
+           max = mid;
+    }
+
+    /*
+     * Now `min' is a valid size, and `max' isn't. So use `min'.
+     */
+
+    me->tilesize = min;
+    if (user_size)
+        /* If the user requested a change in size, make it permanent. */
+        me->preferred_tilesize = me->tilesize;
+    midend_size_new_drawstate(me);
+    *x = me->winwidth;
+    *y = me->winheight;
+}
+
+int midend_tilesize(midend *me) { return me->tilesize; }
+
+void midend_set_params(midend *me, game_params *params)
+{
+    me->ourgame->free_params(me->params);
+    me->params = me->ourgame->dup_params(params);
+}
+
+game_params *midend_get_params(midend *me)
+{
+    return me->ourgame->dup_params(me->params);
+}
+
+static void midend_set_timer(midend *me)
+{
+    me->timing = (me->ourgame->is_timed &&
+                 me->ourgame->timing_state(me->states[me->statepos-1].state,
+                                           me->ui));
+    if (me->timing || me->flash_time || me->anim_time)
+       activate_timer(me->frontend);
+    else
+       deactivate_timer(me->frontend);
+}
+
+void midend_force_redraw(midend *me)
+{
+    if (me->drawstate)
+        me->ourgame->free_drawstate(me->drawing, me->drawstate);
+    me->drawstate = me->ourgame->new_drawstate(me->drawing,
+                                              me->states[0].state);
+    midend_size_new_drawstate(me);
+    midend_redraw(me);
+}
+
+void midend_new_game(midend *me)
+{
+    midend_stop_anim(me);
+    midend_free_game(me);
+
+    assert(me->nstates == 0);
+
+    if (me->genmode == GOT_DESC) {
+       me->genmode = GOT_NOTHING;
+    } else {
+        random_state *rs;
+
+        if (me->genmode == GOT_SEED) {
+            me->genmode = GOT_NOTHING;
+        } else {
+            /*
+             * Generate a new random seed. 15 digits comes to about
+             * 48 bits, which should be more than enough.
+             * 
+             * I'll avoid putting a leading zero on the number,
+             * just in case it confuses anybody who thinks it's
+             * processed as an integer rather than a string.
+             */
+            char newseed[16];
+            int i;
+            newseed[15] = '\0';
+            newseed[0] = '1' + (char)random_upto(me->random, 9);
+            for (i = 1; i < 15; i++)
+                newseed[i] = '0' + (char)random_upto(me->random, 10);
+            sfree(me->seedstr);
+            me->seedstr = dupstr(newseed);
+
+           if (me->curparams)
+               me->ourgame->free_params(me->curparams);
+           me->curparams = me->ourgame->dup_params(me->params);
+        }
+
+       sfree(me->desc);
+       sfree(me->privdesc);
+        sfree(me->aux_info);
+       me->aux_info = NULL;
+
+        rs = random_new(me->seedstr, strlen(me->seedstr));
+       /*
+        * If this midend has been instantiated without providing a
+        * drawing API, it is non-interactive. This means that it's
+        * being used for bulk game generation, and hence we should
+        * pass the non-interactive flag to new_desc.
+        */
+        me->desc = me->ourgame->new_desc(me->curparams, rs,
+                                        &me->aux_info, (me->drawing != NULL));
+       me->privdesc = NULL;
+        random_free(rs);
+    }
+
+    ensure(me);
+
+    /*
+     * It might seem a bit odd that we're using me->params to
+     * create the initial game state, rather than me->curparams
+     * which is better tailored to this specific game and which we
+     * always know.
+     * 
+     * It's supposed to be an invariant in the midend that
+     * me->params and me->curparams differ in no aspect that is
+     * important after generation (i.e. after new_desc()). By
+     * deliberately passing the _less_ specific of these two
+     * parameter sets, we provoke play-time misbehaviour in the
+     * case where a game has failed to encode a play-time parameter
+     * in the non-full version of encode_params().
+     */
+    me->states[me->nstates].state =
+       me->ourgame->new_game(me, me->params, me->desc);
+
+    /*
+     * As part of our commitment to self-testing, test the aux
+     * string to make sure nothing ghastly went wrong.
+     */
+    if (me->ourgame->can_solve && me->aux_info) {
+       game_state *s;
+       char *msg, *movestr;
+
+       msg = NULL;
+       movestr = me->ourgame->solve(me->states[0].state,
+                                    me->states[0].state,
+                                    me->aux_info, &msg);
+       assert(movestr && !msg);
+       s = me->ourgame->execute_move(me->states[0].state, movestr);
+       assert(s);
+       me->ourgame->free_game(s);
+       sfree(movestr);
+    }
+
+    me->states[me->nstates].movestr = NULL;
+    me->states[me->nstates].movetype = NEWGAME;
+    me->nstates++;
+    me->statepos = 1;
+    me->drawstate = me->ourgame->new_drawstate(me->drawing,
+                                              me->states[0].state);
+    midend_size_new_drawstate(me);
+    me->elapsed = 0.0F;
+    me->flash_pos = me->flash_time = 0.0F;
+    me->anim_pos = me->anim_time = 0.0F;
+    if (me->ui)
+        me->ourgame->free_ui(me->ui);
+    me->ui = me->ourgame->new_ui(me->states[0].state);
+    midend_set_timer(me);
+    me->pressed_mouse_button = 0;
+
+    if (me->game_id_change_notify_function)
+        me->game_id_change_notify_function(me->game_id_change_notify_ctx);
+}
+
+int midend_can_undo(midend *me)
+{
+    return (me->statepos > 1);
+}
+
+int midend_can_redo(midend *me)
+{
+    return (me->statepos < me->nstates);
+}
+
+static int midend_undo(midend *me)
+{
+    if (me->statepos > 1) {
+        if (me->ui)
+            me->ourgame->changed_state(me->ui,
+                                       me->states[me->statepos-1].state,
+                                       me->states[me->statepos-2].state);
+       me->statepos--;
+        me->dir = -1;
+        return 1;
+    } else
+        return 0;
+}
+
+static int midend_redo(midend *me)
+{
+    if (me->statepos < me->nstates) {
+        if (me->ui)
+            me->ourgame->changed_state(me->ui,
+                                       me->states[me->statepos-1].state,
+                                       me->states[me->statepos].state);
+       me->statepos++;
+        me->dir = +1;
+        return 1;
+    } else
+        return 0;
+}
+
+static void midend_finish_move(midend *me)
+{
+    float flashtime;
+
+    /*
+     * We do not flash if the later of the two states is special.
+     * This covers both forward Solve moves and backward (undone)
+     * Restart moves.
+     */
+    if ((me->oldstate || me->statepos > 1) &&
+        ((me->dir > 0 && !special(me->states[me->statepos-1].movetype)) ||
+         (me->dir < 0 && me->statepos < me->nstates &&
+          !special(me->states[me->statepos].movetype)))) {
+       flashtime = me->ourgame->flash_length(me->oldstate ? me->oldstate :
+                                             me->states[me->statepos-2].state,
+                                             me->states[me->statepos-1].state,
+                                             me->oldstate ? me->dir : +1,
+                                             me->ui);
+       if (flashtime > 0) {
+           me->flash_pos = 0.0F;
+           me->flash_time = flashtime;
+       }
+    }
+
+    if (me->oldstate)
+       me->ourgame->free_game(me->oldstate);
+    me->oldstate = NULL;
+    me->anim_pos = me->anim_time = 0;
+    me->dir = 0;
+
+    midend_set_timer(me);
+}
+
+void midend_stop_anim(midend *me)
+{
+    if (me->oldstate || me->anim_time != 0) {
+       midend_finish_move(me);
+        midend_redraw(me);
+    }
+}
+
+void midend_restart_game(midend *me)
+{
+    game_state *s;
+
+    assert(me->statepos >= 1);
+    if (me->statepos == 1)
+        return;                        /* no point doing anything at all! */
+
+    /*
+     * During restart, we reconstruct the game from the (public)
+     * game description rather than from states[0], because that
+     * way Mines gets slightly more sensible behaviour (restart
+     * goes to _after_ the first click so you don't have to
+     * remember where you clicked).
+     */
+    s = me->ourgame->new_game(me, me->params, me->desc);
+
+    /*
+     * Now enter the restarted state as the next move.
+     */
+    midend_stop_anim(me);
+    midend_purge_states(me);
+    ensure(me);
+    me->states[me->nstates].state = s;
+    me->states[me->nstates].movestr = dupstr(me->desc);
+    me->states[me->nstates].movetype = RESTART;
+    me->statepos = ++me->nstates;
+    if (me->ui)
+        me->ourgame->changed_state(me->ui,
+                                   me->states[me->statepos-2].state,
+                                   me->states[me->statepos-1].state);
+    me->flash_pos = me->flash_time = 0.0F;
+    midend_finish_move(me);
+    midend_redraw(me);
+    midend_set_timer(me);
+}
+
+static int midend_really_process_key(midend *me, int x, int y, int button)
+{
+    game_state *oldstate =
+        me->ourgame->dup_game(me->states[me->statepos - 1].state);
+    int type = MOVE, gottype = FALSE, ret = 1;
+    float anim_time;
+    game_state *s;
+    char *movestr;
+       
+    movestr =
+       me->ourgame->interpret_move(me->states[me->statepos-1].state,
+                                   me->ui, me->drawstate, x, y, button);
+
+    if (!movestr) {
+       if (button == 'n' || button == 'N' || button == '\x0E') {
+           midend_new_game(me);
+           midend_redraw(me);
+           goto done;                 /* never animate */
+       } else if (button == 'u' || button == 'U' ||
+                  button == '\x1A' || button == '\x1F') {
+           midend_stop_anim(me);
+           type = me->states[me->statepos-1].movetype;
+           gottype = TRUE;
+           if (!midend_undo(me))
+               goto done;
+       } else if (button == 'r' || button == 'R' ||
+                  button == '\x12' || button == '\x19') {
+           midend_stop_anim(me);
+           if (!midend_redo(me))
+               goto done;
+       } else if (button == '\x13' && me->ourgame->can_solve) {
+           if (midend_solve(me))
+               goto done;
+       } else if (button == 'q' || button == 'Q' || button == '\x11') {
+           ret = 0;
+           goto done;
+       } else
+           goto done;
+    } else {
+       if (!*movestr)
+           s = me->states[me->statepos-1].state;
+       else {
+           s = me->ourgame->execute_move(me->states[me->statepos-1].state,
+                                         movestr);
+           assert(s != NULL);
+       }
+
+        if (s == me->states[me->statepos-1].state) {
+            /*
+             * make_move() is allowed to return its input state to
+             * indicate that although no move has been made, the UI
+             * state has been updated and a redraw is called for.
+             */
+            midend_redraw(me);
+            midend_set_timer(me);
+            goto done;
+        } else if (s) {
+           midend_stop_anim(me);
+            midend_purge_states(me);
+            ensure(me);
+            assert(movestr != NULL);
+            me->states[me->nstates].state = s;
+            me->states[me->nstates].movestr = movestr;
+            me->states[me->nstates].movetype = MOVE;
+            me->statepos = ++me->nstates;
+            me->dir = +1;
+           if (me->ui)
+               me->ourgame->changed_state(me->ui,
+                                          me->states[me->statepos-2].state,
+                                          me->states[me->statepos-1].state);
+        } else {
+            goto done;
+        }
+    }
+
+    if (!gottype)
+        type = me->states[me->statepos-1].movetype;
+
+    /*
+     * See if this move requires an animation.
+     */
+    if (special(type) && !(type == SOLVE &&
+                          (me->ourgame->flags & SOLVE_ANIMATES))) {
+        anim_time = 0;
+    } else {
+        anim_time = me->ourgame->anim_length(oldstate,
+                                             me->states[me->statepos-1].state,
+                                             me->dir, me->ui);
+    }
+
+    me->oldstate = oldstate; oldstate = NULL;
+    if (anim_time > 0) {
+        me->anim_time = anim_time;
+    } else {
+        me->anim_time = 0.0;
+       midend_finish_move(me);
+    }
+    me->anim_pos = 0.0;
+
+    midend_redraw(me);
+
+    midend_set_timer(me);
+
+    done:
+    if (oldstate) me->ourgame->free_game(oldstate);
+    return ret;
+}
+
+int midend_process_key(midend *me, int x, int y, int button)
+{
+    int ret = 1;
+
+    /*
+     * Harmonise mouse drag and release messages.
+     * 
+     * Some front ends might accidentally switch from sending, say,
+     * RIGHT_DRAG messages to sending LEFT_DRAG, half way through a
+     * drag. (This can happen on the Mac, for example, since
+     * RIGHT_DRAG is usually done using Command+drag, and if the
+     * user accidentally releases Command half way through the drag
+     * then there will be trouble.)
+     * 
+     * It would be an O(number of front ends) annoyance to fix this
+     * in the front ends, but an O(number of back ends) annoyance
+     * to have each game capable of dealing with it. Therefore, we
+     * fix it _here_ in the common midend code so that it only has
+     * to be done once.
+     * 
+     * The possible ways in which things can go screwy in the front
+     * end are:
+     * 
+     *  - in a system containing multiple physical buttons button
+     *    presses can inadvertently overlap. We can see ABab (caps
+     *    meaning button-down and lowercase meaning button-up) when
+     *    the user had semantically intended AaBb.
+     * 
+     *  - in a system where one button is simulated by means of a
+     *    modifier key and another button, buttons can mutate
+     *    between press and release (possibly during drag). So we
+     *    can see Ab instead of Aa.
+     * 
+     * Definite requirements are:
+     * 
+     *  - button _presses_ must never be invented or destroyed. If
+     *    the user presses two buttons in succession, the button
+     *    presses must be transferred to the backend unchanged. So
+     *    if we see AaBb , that's fine; if we see ABab (the button
+     *    presses inadvertently overlapped) we must somehow
+     *    `correct' it to AaBb.
+     * 
+     *  - every mouse action must end up looking like a press, zero
+     *    or more drags, then a release. This allows back ends to
+     *    make the _assumption_ that incoming mouse data will be
+     *    sane in this regard, and not worry about the details.
+     * 
+     * So my policy will be:
+     * 
+     *  - treat any button-up as a button-up for the currently
+     *    pressed button, or ignore it if there is no currently
+     *    pressed button.
+     * 
+     *  - treat any drag as a drag for the currently pressed
+     *    button, or ignore it if there is no currently pressed
+     *    button.
+     * 
+     *  - if we see a button-down while another button is currently
+     *    pressed, invent a button-up for the first one and then
+     *    pass the button-down through as before.
+     * 
+     * 2005-05-31: An addendum to the above. Some games might want
+     * a `priority order' among buttons, such that if one button is
+     * pressed while another is down then a fixed one of the
+     * buttons takes priority no matter what order they're pressed
+     * in. Mines, in particular, wants to treat a left+right click
+     * like a left click for the benefit of users of other
+     * implementations. So the last of the above points is modified
+     * in the presence of an (optional) button priority order.
+     *
+     * A further addition: we translate certain keyboard presses to
+     * cursor key 'select' buttons, so that a) frontends don't have
+     * to translate these themselves (like they do for CURSOR_UP etc),
+     * and b) individual games don't have to hard-code button presses
+     * of '\n' etc for keyboard-based cursors. The choice of buttons
+     * here could eventually be controlled by a runtime configuration
+     * option.
+     */
+    if (IS_MOUSE_DRAG(button) || IS_MOUSE_RELEASE(button)) {
+        if (me->pressed_mouse_button) {
+            if (IS_MOUSE_DRAG(button)) {
+                button = me->pressed_mouse_button +
+                    (LEFT_DRAG - LEFT_BUTTON);
+            } else {
+                button = me->pressed_mouse_button +
+                    (LEFT_RELEASE - LEFT_BUTTON);
+            }
+        } else
+            return ret;                /* ignore it */
+    } else if (IS_MOUSE_DOWN(button) && me->pressed_mouse_button) {
+       /*
+        * If the new button has lower priority than the old one,
+        * don't bother doing this.
+        */
+       if (me->ourgame->flags &
+           BUTTON_BEATS(me->pressed_mouse_button, button))
+           return ret;                /* just ignore it */
+
+        /*
+         * Fabricate a button-up for the previously pressed button.
+         */
+        ret = ret && midend_really_process_key
+            (me, x, y, (me->pressed_mouse_button +
+                        (LEFT_RELEASE - LEFT_BUTTON)));
+    }
+
+    /*
+     * Translate keyboard presses to cursor selection.
+     */
+    if (button == '\n' || button == '\r')
+      button = CURSOR_SELECT;
+    if (button == ' ')
+      button = CURSOR_SELECT2;
+
+    /*
+     * Normalise both backspace characters (8 and 127) to \b. Easier
+     * to do this once, here, than to require all front ends to
+     * carefully generate the same one - now each front end can
+     * generate whichever is easiest.
+     */
+    if (button == '\177')
+       button = '\b';
+
+    /*
+     * Now send on the event we originally received.
+     */
+    ret = ret && midend_really_process_key(me, x, y, button);
+
+    /*
+     * And update the currently pressed button.
+     */
+    if (IS_MOUSE_RELEASE(button))
+        me->pressed_mouse_button = 0;
+    else if (IS_MOUSE_DOWN(button))
+        me->pressed_mouse_button = button;
+
+    return ret;
+}
+
+void midend_redraw(midend *me)
+{
+    assert(me->drawing);
+
+    if (me->statepos > 0 && me->drawstate) {
+        start_draw(me->drawing);
+        if (me->oldstate && me->anim_time > 0 &&
+            me->anim_pos < me->anim_time) {
+            assert(me->dir != 0);
+            me->ourgame->redraw(me->drawing, me->drawstate, me->oldstate,
+                               me->states[me->statepos-1].state, me->dir,
+                               me->ui, me->anim_pos, me->flash_pos);
+        } else {
+            me->ourgame->redraw(me->drawing, me->drawstate, NULL,
+                               me->states[me->statepos-1].state, +1 /*shrug*/,
+                               me->ui, 0.0, me->flash_pos);
+        }
+        end_draw(me->drawing);
+    }
+}
+
+/*
+ * Nasty hacky function used to implement the --redo option in
+ * gtk.c. Only used for generating the puzzles' icons.
+ */
+void midend_freeze_timer(midend *me, float tprop)
+{
+    me->anim_pos = me->anim_time * tprop;
+    midend_redraw(me);
+    deactivate_timer(me->frontend);
+}
+
+void midend_timer(midend *me, float tplus)
+{
+    int need_redraw = (me->anim_time > 0 || me->flash_time > 0);
+
+    me->anim_pos += tplus;
+    if (me->anim_pos >= me->anim_time ||
+        me->anim_time == 0 || !me->oldstate) {
+       if (me->anim_time > 0)
+           midend_finish_move(me);
+    }
+
+    me->flash_pos += tplus;
+    if (me->flash_pos >= me->flash_time || me->flash_time == 0) {
+       me->flash_pos = me->flash_time = 0;
+    }
+
+    if (need_redraw)
+        midend_redraw(me);
+
+    if (me->timing) {
+       float oldelapsed = me->elapsed;
+       me->elapsed += tplus;
+       if ((int)oldelapsed != (int)me->elapsed)
+           status_bar(me->drawing, me->laststatus ? me->laststatus : "");
+    }
+
+    midend_set_timer(me);
+}
+
+float *midend_colours(midend *me, int *ncolours)
+{
+    float *ret;
+
+    ret = me->ourgame->colours(me->frontend, ncolours);
+
+    {
+        int i;
+
+        /*
+         * Allow environment-based overrides for the standard
+         * colours by defining variables along the lines of
+         * `NET_COLOUR_4=6000c0'.
+         */
+
+        for (i = 0; i < *ncolours; i++) {
+            char buf[80], *e;
+            unsigned int r, g, b;
+            int j, k;
+
+            sprintf(buf, "%s_COLOUR_%d", me->ourgame->name, i);
+            for (j = k = 0; buf[j]; j++)
+               if (!isspace((unsigned char)buf[j]))
+                   buf[k++] = toupper((unsigned char)buf[j]);
+           buf[k] = '\0';
+            if ((e = getenv(buf)) != NULL &&
+                sscanf(e, "%2x%2x%2x", &r, &g, &b) == 3) {
+                ret[i*3 + 0] = r / 255.0F;
+                ret[i*3 + 1] = g / 255.0F;
+                ret[i*3 + 2] = b / 255.0F;
+            }
+        }
+    }
+
+    return ret;
+}
+
+int midend_num_presets(midend *me)
+{
+    if (!me->npresets) {
+        char *name;
+        game_params *preset;
+
+        while (me->ourgame->fetch_preset(me->npresets, &name, &preset)) {
+            if (me->presetsize <= me->npresets) {
+                me->presetsize = me->npresets + 10;
+                me->presets = sresize(me->presets, me->presetsize,
+                                      game_params *);
+                me->preset_names = sresize(me->preset_names, me->presetsize,
+                                           char *);
+                me->preset_encodings = sresize(me->preset_encodings,
+                                              me->presetsize, char *);
+            }
+
+            me->presets[me->npresets] = preset;
+            me->preset_names[me->npresets] = name;
+            me->preset_encodings[me->npresets] =
+               me->ourgame->encode_params(preset, TRUE);;
+            me->npresets++;
+        }
+    }
+
+    {
+        /*
+         * Allow environment-based extensions to the preset list by
+         * defining a variable along the lines of `SOLO_PRESETS=2x3
+         * Advanced:2x3da'. Colon-separated list of items,
+         * alternating between textual titles in the menu and
+         * encoded parameter strings.
+         */
+        char buf[80], *e, *p;
+        int j, k;
+
+        sprintf(buf, "%s_PRESETS", me->ourgame->name);
+       for (j = k = 0; buf[j]; j++)
+           if (!isspace((unsigned char)buf[j]))
+               buf[k++] = toupper((unsigned char)buf[j]);
+       buf[k] = '\0';
+
+        if ((e = getenv(buf)) != NULL) {
+            p = e = dupstr(e);
+
+            while (*p) {
+                char *name, *val;
+                game_params *preset;
+
+                name = p;
+                while (*p && *p != ':') p++;
+                if (*p) *p++ = '\0';
+                val = p;
+                while (*p && *p != ':') p++;
+                if (*p) *p++ = '\0';
+
+                preset = me->ourgame->default_params();
+                me->ourgame->decode_params(preset, val);
+
+               if (me->ourgame->validate_params(preset, TRUE)) {
+                   /* Drop this one from the list. */
+                   me->ourgame->free_params(preset);
+                   continue;
+               }
+
+                if (me->presetsize <= me->npresets) {
+                    me->presetsize = me->npresets + 10;
+                    me->presets = sresize(me->presets, me->presetsize,
+                                          game_params *);
+                    me->preset_names = sresize(me->preset_names,
+                                               me->presetsize, char *);
+                    me->preset_encodings = sresize(me->preset_encodings,
+                                                  me->presetsize, char *);
+                }
+
+                me->presets[me->npresets] = preset;
+                me->preset_names[me->npresets] = dupstr(name);
+                me->preset_encodings[me->npresets] =
+                   me->ourgame->encode_params(preset, TRUE);
+                me->npresets++;
+            }
+            sfree(e);
+        }
+    }
+
+    return me->npresets;
+}
+
+void midend_fetch_preset(midend *me, int n,
+                         char **name, game_params **params)
+{
+    assert(n >= 0 && n < me->npresets);
+    *name = me->preset_names[n];
+    *params = me->presets[n];
+}
+
+int midend_which_preset(midend *me)
+{
+    char *encoding = me->ourgame->encode_params(me->params, TRUE);
+    int i, ret;
+
+    ret = -1;
+    for (i = 0; i < me->npresets; i++)
+       if (!strcmp(encoding, me->preset_encodings[i])) {
+           ret = i;
+           break;
+       }
+
+    sfree(encoding);
+    return ret;
+}
+
+int midend_wants_statusbar(midend *me)
+{
+    return me->ourgame->wants_statusbar;
+}
+
+void midend_request_id_changes(midend *me, void (*notify)(void *), void *ctx)
+{
+    me->game_id_change_notify_function = notify;
+    me->game_id_change_notify_ctx = ctx;
+}
+
+void midend_supersede_game_desc(midend *me, char *desc, char *privdesc)
+{
+    sfree(me->desc);
+    sfree(me->privdesc);
+    me->desc = dupstr(desc);
+    me->privdesc = privdesc ? dupstr(privdesc) : NULL;
+    if (me->game_id_change_notify_function)
+        me->game_id_change_notify_function(me->game_id_change_notify_ctx);
+}
+
+config_item *midend_get_config(midend *me, int which, char **wintitle)
+{
+    char *titlebuf, *parstr, *rest;
+    config_item *ret;
+    char sep;
+
+    assert(wintitle);
+    titlebuf = snewn(40 + strlen(me->ourgame->name), char);
+
+    switch (which) {
+      case CFG_SETTINGS:
+       sprintf(titlebuf, "%s configuration", me->ourgame->name);
+       *wintitle = titlebuf;
+       return me->ourgame->configure(me->params);
+      case CFG_SEED:
+      case CFG_DESC:
+        if (!me->curparams) {
+          sfree(titlebuf);
+          return NULL;
+        }
+        sprintf(titlebuf, "%s %s selection", me->ourgame->name,
+                which == CFG_SEED ? "random" : "game");
+        *wintitle = titlebuf;
+
+       ret = snewn(2, config_item);
+
+       ret[0].type = C_STRING;
+        if (which == CFG_SEED)
+            ret[0].name = "Game random seed";
+        else
+            ret[0].name = "Game ID";
+       ret[0].ival = 0;
+        /*
+         * For CFG_DESC the text going in here will be a string
+         * encoding of the restricted parameters, plus a colon,
+         * plus the game description. For CFG_SEED it will be the
+         * full parameters, plus a hash, plus the random seed data.
+         * Either of these is a valid full game ID (although only
+         * the former is likely to persist across many code
+         * changes).
+         */
+        parstr = me->ourgame->encode_params(me->curparams, which == CFG_SEED);
+        assert(parstr);
+        if (which == CFG_DESC) {
+            rest = me->desc ? me->desc : "";
+            sep = ':';
+        } else {
+            rest = me->seedstr ? me->seedstr : "";
+            sep = '#';
+        }
+        ret[0].sval = snewn(strlen(parstr) + strlen(rest) + 2, char);
+        sprintf(ret[0].sval, "%s%c%s", parstr, sep, rest);
+        sfree(parstr);
+
+       ret[1].type = C_END;
+       ret[1].name = ret[1].sval = NULL;
+       ret[1].ival = 0;
+
+       return ret;
+    }
+
+    assert(!"We shouldn't be here");
+    return NULL;
+}
+
+static char *midend_game_id_int(midend *me, char *id, int defmode)
+{
+    char *error, *par, *desc, *seed;
+    game_params *newcurparams, *newparams, *oldparams1, *oldparams2;
+    int free_params;
+
+    seed = strchr(id, '#');
+    desc = strchr(id, ':');
+
+    if (desc && (!seed || desc < seed)) {
+        /*
+         * We have a colon separating parameters from game
+         * description. So `par' now points to the parameters
+         * string, and `desc' to the description string.
+         */
+        *desc++ = '\0';
+        par = id;
+        seed = NULL;
+    } else if (seed && (!desc || seed < desc)) {
+        /*
+         * We have a hash separating parameters from random seed.
+         * So `par' now points to the parameters string, and `seed'
+         * to the seed string.
+         */
+        *seed++ = '\0';
+        par = id;
+        desc = NULL;
+    } else {
+        /*
+         * We only have one string. Depending on `defmode', we take
+         * it to be either parameters, seed or description.
+         */
+        if (defmode == DEF_SEED) {
+            seed = id;
+            par = desc = NULL;
+        } else if (defmode == DEF_DESC) {
+            desc = id;
+            par = seed = NULL;
+        } else {
+            par = id;
+            seed = desc = NULL;
+        }
+    }
+
+    /*
+     * We must be reasonably careful here not to modify anything in
+     * `me' until we have finished validating things. This function
+     * must either return an error and do nothing to the midend, or
+     * return success and do everything; nothing in between is
+     * acceptable.
+     */
+    newcurparams = newparams = oldparams1 = oldparams2 = NULL;
+
+    if (par) {
+        /*
+         * The params string may underspecify the game parameters, so
+         * we must first initialise newcurparams with a full set of
+         * params from somewhere else before we decode_params the
+         * input string over the top.
+         *
+         * But which set? It depends on what other data we have.
+         *
+         * If we've been given a _descriptive_ game id, then that may
+         * well underspecify by design, e.g. Solo game descriptions
+         * often start just '3x3:' without specifying one of Solo's
+         * difficulty settings, because it isn't necessary once a game
+         * has been generated (and you might not even know it, if
+         * you're manually transcribing a game description). In that
+         * situation, I've always felt that the best thing to set the
+         * difficulty to (for use if the user hits 'New Game' after
+         * pasting in that game id) is whatever it was previously set
+         * to. That is, we use whatever is already in me->params as
+         * the basis for our decoding of this input string.
+         *
+         * A random-seed based game id, however, should use the real,
+         * built-in default params, and not even check the
+         * <game>_DEFAULT environment setting, because when people
+         * paste each other random seeds - whether it's two users
+         * arranging to generate the same game at the same time to
+         * race solving them, or a user sending a bug report upstream
+         * - the whole point is for the random game id to always be
+         * interpreted the same way, even if it does underspecify.
+         *
+         * A parameter string typed in on its own, with no seed _or_
+         * description, gets treated the same way as a random seed,
+         * because again I think the most likely reason for doing that
+         * is to have a portable representation of a set of params.
+         */
+        if (desc) {
+            newcurparams = me->ourgame->dup_params(me->params);
+        } else {
+            newcurparams = me->ourgame->default_params();
+        }
+        me->ourgame->decode_params(newcurparams, par);
+        error = me->ourgame->validate_params(newcurparams, desc == NULL);
+        if (error) {
+            me->ourgame->free_params(newcurparams);
+            return error;
+        }
+        oldparams1 = me->curparams;
+
+        /*
+         * Now filter only the persistent parts of this state into
+         * the long-term params structure, unless we've _only_
+         * received a params string in which case the whole lot is
+         * persistent.
+         */
+        oldparams2 = me->params;
+        if (seed || desc) {
+            char *tmpstr;
+
+            newparams = me->ourgame->dup_params(me->params);
+
+            tmpstr = me->ourgame->encode_params(newcurparams, FALSE);
+            me->ourgame->decode_params(newparams, tmpstr);
+
+            sfree(tmpstr);
+        } else {
+            newparams = me->ourgame->dup_params(newcurparams);
+        }
+        free_params = TRUE;
+    } else {
+        newcurparams = me->curparams;
+        newparams = me->params;
+        free_params = FALSE;
+    }
+
+    if (desc) {
+        error = me->ourgame->validate_desc(newparams, desc);
+        if (error) {
+            if (free_params) {
+                if (newcurparams)
+                    me->ourgame->free_params(newcurparams);
+                if (newparams)
+                    me->ourgame->free_params(newparams);
+            }
+            return error;
+        }
+    }
+
+    /*
+     * Now we've got past all possible error points. Update the
+     * midend itself.
+     */
+    me->params = newparams;
+    me->curparams = newcurparams;
+    if (oldparams1)
+        me->ourgame->free_params(oldparams1);
+    if (oldparams2)
+        me->ourgame->free_params(oldparams2);
+
+    sfree(me->desc);
+    sfree(me->privdesc);
+    me->desc = me->privdesc = NULL;
+    sfree(me->seedstr);
+    me->seedstr = NULL;
+
+    if (desc) {
+        me->desc = dupstr(desc);
+        me->genmode = GOT_DESC;
+        sfree(me->aux_info);
+       me->aux_info = NULL;
+    }
+
+    if (seed) {
+        me->seedstr = dupstr(seed);
+        me->genmode = GOT_SEED;
+    }
+
+    return NULL;
+}
+
+char *midend_game_id(midend *me, char *id)
+{
+    return midend_game_id_int(me, id, DEF_PARAMS);
+}
+
+char *midend_get_game_id(midend *me)
+{
+    char *parstr, *ret;
+
+    parstr = me->ourgame->encode_params(me->curparams, FALSE);
+    assert(parstr);
+    assert(me->desc);
+    ret = snewn(strlen(parstr) + strlen(me->desc) + 2, char);
+    sprintf(ret, "%s:%s", parstr, me->desc);
+    sfree(parstr);
+    return ret;
+}
+
+char *midend_get_random_seed(midend *me)
+{
+    char *parstr, *ret;
+
+    if (!me->seedstr)
+        return NULL;
+
+    parstr = me->ourgame->encode_params(me->curparams, TRUE);
+    assert(parstr);
+    ret = snewn(strlen(parstr) + strlen(me->seedstr) + 2, char);
+    sprintf(ret, "%s#%s", parstr, me->seedstr);
+    sfree(parstr);
+    return ret;
+}
+
+char *midend_set_config(midend *me, int which, config_item *cfg)
+{
+    char *error;
+    game_params *params;
+
+    switch (which) {
+      case CFG_SETTINGS:
+       params = me->ourgame->custom_params(cfg);
+       error = me->ourgame->validate_params(params, TRUE);
+
+       if (error) {
+           me->ourgame->free_params(params);
+           return error;
+       }
+
+       me->ourgame->free_params(me->params);
+       me->params = params;
+       break;
+
+      case CFG_SEED:
+      case CFG_DESC:
+        error = midend_game_id_int(me, cfg[0].sval,
+                                   (which == CFG_SEED ? DEF_SEED : DEF_DESC));
+       if (error)
+           return error;
+       break;
+    }
+
+    return NULL;
+}
+
+int midend_can_format_as_text_now(midend *me)
+{
+    if (me->ourgame->can_format_as_text_ever)
+       return me->ourgame->can_format_as_text_now(me->params);
+    else
+       return FALSE;
+}
+
+char *midend_text_format(midend *me)
+{
+    if (me->ourgame->can_format_as_text_ever && me->statepos > 0 &&
+       me->ourgame->can_format_as_text_now(me->params))
+       return me->ourgame->text_format(me->states[me->statepos-1].state);
+    else
+       return NULL;
+}
+
+char *midend_solve(midend *me)
+{
+    game_state *s;
+    char *msg, *movestr;
+
+    if (!me->ourgame->can_solve)
+       return "This game does not support the Solve operation";
+
+    if (me->statepos < 1)
+       return "No game set up to solve";   /* _shouldn't_ happen! */
+
+    msg = NULL;
+    movestr = me->ourgame->solve(me->states[0].state,
+                                me->states[me->statepos-1].state,
+                                me->aux_info, &msg);
+    if (!movestr) {
+       if (!msg)
+           msg = "Solve operation failed";   /* _shouldn't_ happen, but can */
+       return msg;
+    }
+    s = me->ourgame->execute_move(me->states[me->statepos-1].state, movestr);
+    assert(s);
+
+    /*
+     * Now enter the solved state as the next move.
+     */
+    midend_stop_anim(me);
+    midend_purge_states(me);
+    ensure(me);
+    me->states[me->nstates].state = s;
+    me->states[me->nstates].movestr = movestr;
+    me->states[me->nstates].movetype = SOLVE;
+    me->statepos = ++me->nstates;
+    if (me->ui)
+        me->ourgame->changed_state(me->ui,
+                                   me->states[me->statepos-2].state,
+                                   me->states[me->statepos-1].state);
+    me->dir = +1;
+    if (me->ourgame->flags & SOLVE_ANIMATES) {
+       me->oldstate = me->ourgame->dup_game(me->states[me->statepos-2].state);
+        me->anim_time =
+           me->ourgame->anim_length(me->states[me->statepos-2].state,
+                                    me->states[me->statepos-1].state,
+                                    +1, me->ui);
+        me->anim_pos = 0.0;
+    } else {
+       me->anim_time = 0.0;
+       midend_finish_move(me);
+    }
+    if (me->drawing)
+        midend_redraw(me);
+    midend_set_timer(me);
+    return NULL;
+}
+
+int midend_status(midend *me)
+{
+    /*
+     * We should probably never be called when the state stack has no
+     * states on it at all - ideally, midends should never be left in
+     * that state for long enough to get put down and forgotten about.
+     * But if we are, I think we return _true_ - pedantically speaking
+     * a midend in that state is 'vacuously solved', and more
+     * practically, a user whose midend has been left in that state
+     * probably _does_ want the 'new game' option to be prominent.
+     */
+    if (me->statepos == 0)
+        return +1;
+
+    return me->ourgame->status(me->states[me->statepos-1].state);
+}
+
+char *midend_rewrite_statusbar(midend *me, char *text)
+{
+    /*
+     * An important special case is that we are occasionally called
+     * with our own laststatus, to update the timer.
+     */
+    if (me->laststatus != text) {
+       sfree(me->laststatus);
+       me->laststatus = dupstr(text);
+    }
+
+    if (me->ourgame->is_timed) {
+       char timebuf[100], *ret;
+       int min, sec;
+
+       sec = (int)me->elapsed;
+       min = sec / 60;
+       sec %= 60;
+       sprintf(timebuf, "[%d:%02d] ", min, sec);
+
+       ret = snewn(strlen(timebuf) + strlen(text) + 1, char);
+       strcpy(ret, timebuf);
+       strcat(ret, text);
+       return ret;
+
+    } else {
+       return dupstr(text);
+    }
+}
+
+#define SERIALISE_MAGIC "Simon Tatham's Portable Puzzle Collection"
+#define SERIALISE_VERSION "1"
+
+void midend_serialise(midend *me,
+                      void (*write)(void *ctx, void *buf, int len),
+                      void *wctx)
+{
+    int i;
+
+    /*
+     * Each line of the save file contains three components. First
+     * exactly 8 characters of header word indicating what type of
+     * data is contained on the line; then a colon followed by a
+     * decimal integer giving the length of the main string on the
+     * line; then a colon followed by the string itself (exactly as
+     * many bytes as previously specified, no matter what they
+     * contain). Then a newline (of reasonably flexible form).
+     */
+#define wr(h,s) do { \
+    char hbuf[80]; \
+    char *str = (s); \
+    sprintf(hbuf, "%-8.8s:%d:", (h), (int)strlen(str)); \
+    write(wctx, hbuf, strlen(hbuf)); \
+    write(wctx, str, strlen(str)); \
+    write(wctx, "\n", 1); \
+} while (0)
+
+    /*
+     * Magic string identifying the file, and version number of the
+     * file format.
+     */
+    wr("SAVEFILE", SERIALISE_MAGIC);
+    wr("VERSION", SERIALISE_VERSION);
+
+    /*
+     * The game name. (Copied locally to avoid const annoyance.)
+     */
+    {
+        char *s = dupstr(me->ourgame->name);
+        wr("GAME", s);
+        sfree(s);
+    }
+
+    /*
+     * The current long-term parameters structure, in full.
+     */
+    if (me->params) {
+        char *s = me->ourgame->encode_params(me->params, TRUE);
+        wr("PARAMS", s);
+        sfree(s);
+    }
+
+    /*
+     * The current short-term parameters structure, in full.
+     */
+    if (me->curparams) {
+        char *s = me->ourgame->encode_params(me->curparams, TRUE);
+        wr("CPARAMS", s);
+        sfree(s);
+    }
+
+    /*
+     * The current game description, the privdesc, and the random seed.
+     */
+    if (me->seedstr)
+        wr("SEED", me->seedstr);
+    if (me->desc)
+        wr("DESC", me->desc);
+    if (me->privdesc)
+        wr("PRIVDESC", me->privdesc);
+
+    /*
+     * The game's aux_info. We obfuscate this to prevent spoilers
+     * (people are likely to run `head' or similar on a saved game
+     * file simply to find out what it is, and don't necessarily
+     * want to be told the answer to the puzzle!)
+     */
+    if (me->aux_info) {
+        unsigned char *s1;
+        char *s2;
+        int len;
+
+        len = strlen(me->aux_info);
+        s1 = snewn(len, unsigned char);
+        memcpy(s1, me->aux_info, len);
+        obfuscate_bitmap(s1, len*8, FALSE);
+        s2 = bin2hex(s1, len);
+
+        wr("AUXINFO", s2);
+
+        sfree(s2);
+        sfree(s1);
+    }
+
+    /*
+     * Any required serialisation of the game_ui.
+     */
+    if (me->ui) {
+        char *s = me->ourgame->encode_ui(me->ui);
+        if (s) {
+            wr("UI", s);
+            sfree(s);
+        }
+    }
+
+    /*
+     * The game time, if it's a timed game.
+     */
+    if (me->ourgame->is_timed) {
+        char buf[80];
+        sprintf(buf, "%g", me->elapsed);
+        wr("TIME", buf);
+    }
+
+    /*
+     * The length of, and position in, the states list.
+     */
+    {
+        char buf[80];
+        sprintf(buf, "%d", me->nstates);
+        wr("NSTATES", buf);
+        sprintf(buf, "%d", me->statepos);
+        wr("STATEPOS", buf);
+    }
+
+    /*
+     * For each state after the initial one (which we know is
+     * constructed from either privdesc or desc), enough
+     * information for execute_move() to reconstruct it from the
+     * previous one.
+     */
+    for (i = 1; i < me->nstates; i++) {
+        assert(me->states[i].movetype != NEWGAME);   /* only state 0 */
+        switch (me->states[i].movetype) {
+          case MOVE:
+            wr("MOVE", me->states[i].movestr);
+            break;
+          case SOLVE:
+            wr("SOLVE", me->states[i].movestr);
+            break;
+          case RESTART:
+            wr("RESTART", me->states[i].movestr);
+            break;
+        }
+    }
+
+#undef wr
+}
+
+/*
+ * This function returns NULL on success, or an error message.
+ */
+char *midend_deserialise(midend *me,
+                         int (*read)(void *ctx, void *buf, int len),
+                         void *rctx)
+{
+    int nstates = 0, statepos = -1, gotstates = 0;
+    int started = FALSE;
+    int i;
+
+    char *val = NULL;
+    /* Initially all errors give the same report */
+    char *ret = "Data does not appear to be a saved game file";
+
+    /*
+     * We construct all the new state in local variables while we
+     * check its sanity. Only once we have finished reading the
+     * serialised data and detected no errors at all do we start
+     * modifying stuff in the midend passed in.
+     */
+    char *seed = NULL, *parstr = NULL, *desc = NULL, *privdesc = NULL;
+    char *auxinfo = NULL, *uistr = NULL, *cparstr = NULL;
+    float elapsed = 0.0F;
+    game_params *params = NULL, *cparams = NULL;
+    game_ui *ui = NULL;
+    struct midend_state_entry *states = NULL;
+
+    /*
+     * Loop round and round reading one key/value pair at a time
+     * from the serialised stream, until we have enough game states
+     * to finish.
+     */
+    while (nstates <= 0 || statepos < 0 || gotstates < nstates-1) {
+        char key[9], c;
+        int len;
+
+        do {
+            if (!read(rctx, key, 1)) {
+                /* unexpected EOF */
+                goto cleanup;
+            }
+        } while (key[0] == '\r' || key[0] == '\n');
+
+        if (!read(rctx, key+1, 8)) {
+            /* unexpected EOF */
+            goto cleanup;
+        }
+
+        if (key[8] != ':') {
+            if (started)
+                ret = "Data was incorrectly formatted for a saved game file";
+           goto cleanup;
+        }
+        len = strcspn(key, ": ");
+        assert(len <= 8);
+        key[len] = '\0';
+
+        len = 0;
+        while (1) {
+            if (!read(rctx, &c, 1)) {
+                /* unexpected EOF */
+                goto cleanup;
+            }
+
+            if (c == ':') {
+                break;
+            } else if (c >= '0' && c <= '9') {
+                len = (len * 10) + (c - '0');
+            } else {
+                if (started)
+                    ret = "Data was incorrectly formatted for a"
+                    " saved game file";
+                goto cleanup;
+            }
+        }
+
+        val = snewn(len+1, char);
+        if (!read(rctx, val, len)) {
+            if (started)
+            goto cleanup;
+        }
+        val[len] = '\0';
+
+        if (!started) {
+            if (strcmp(key, "SAVEFILE") || strcmp(val, SERIALISE_MAGIC)) {
+                /* ret already has the right message in it */
+                goto cleanup;
+            }
+            /* Now most errors are this one, unless otherwise specified */
+            ret = "Saved data ended unexpectedly";
+            started = TRUE;
+        } else {
+            if (!strcmp(key, "VERSION")) {
+                if (strcmp(val, SERIALISE_VERSION)) {
+                    ret = "Cannot handle this version of the saved game"
+                        " file format";
+                    goto cleanup;
+                }
+            } else if (!strcmp(key, "GAME")) {
+                if (strcmp(val, me->ourgame->name)) {
+                    ret = "Save file is from a different game";
+                    goto cleanup;
+                }
+            } else if (!strcmp(key, "PARAMS")) {
+                sfree(parstr);
+                parstr = val;
+                val = NULL;
+            } else if (!strcmp(key, "CPARAMS")) {
+                sfree(cparstr);
+                cparstr = val;
+                val = NULL;
+            } else if (!strcmp(key, "SEED")) {
+                sfree(seed);
+                seed = val;
+                val = NULL;
+            } else if (!strcmp(key, "DESC")) {
+                sfree(desc);
+                desc = val;
+                val = NULL;
+            } else if (!strcmp(key, "PRIVDESC")) {
+                sfree(privdesc);
+                privdesc = val;
+                val = NULL;
+            } else if (!strcmp(key, "AUXINFO")) {
+                unsigned char *tmp;
+                int len = strlen(val) / 2;   /* length in bytes */
+                tmp = hex2bin(val, len);
+                obfuscate_bitmap(tmp, len*8, TRUE);
+
+                sfree(auxinfo);
+                auxinfo = snewn(len + 1, char);
+                memcpy(auxinfo, tmp, len);
+                auxinfo[len] = '\0';
+                sfree(tmp);
+            } else if (!strcmp(key, "UI")) {
+                sfree(uistr);
+                uistr = val;
+                val = NULL;
+            } else if (!strcmp(key, "TIME")) {
+                elapsed = (float)atof(val);
+            } else if (!strcmp(key, "NSTATES")) {
+                nstates = atoi(val);
+                if (nstates <= 0) {
+                    ret = "Number of states in save file was negative";
+                    goto cleanup;
+                }
+                if (states) {
+                    ret = "Two state counts provided in save file";
+                    goto cleanup;
+                }
+                states = snewn(nstates, struct midend_state_entry);
+                for (i = 0; i < nstates; i++) {
+                    states[i].state = NULL;
+                    states[i].movestr = NULL;
+                    states[i].movetype = NEWGAME;
+                }
+            } else if (!strcmp(key, "STATEPOS")) {
+                statepos = atoi(val);
+            } else if (!strcmp(key, "MOVE")) {
+                gotstates++;
+                states[gotstates].movetype = MOVE;
+                states[gotstates].movestr = val;
+                val = NULL;
+            } else if (!strcmp(key, "SOLVE")) {
+                gotstates++;
+                states[gotstates].movetype = SOLVE;
+                states[gotstates].movestr = val;
+                val = NULL;
+            } else if (!strcmp(key, "RESTART")) {
+                gotstates++;
+                states[gotstates].movetype = RESTART;
+                states[gotstates].movestr = val;
+                val = NULL;
+            }
+        }
+
+        sfree(val);
+        val = NULL;
+    }
+
+    params = me->ourgame->default_params();
+    me->ourgame->decode_params(params, parstr);
+    if (me->ourgame->validate_params(params, TRUE)) {
+        ret = "Long-term parameters in save file are invalid";
+        goto cleanup;
+    }
+    cparams = me->ourgame->default_params();
+    me->ourgame->decode_params(cparams, cparstr);
+    if (me->ourgame->validate_params(cparams, FALSE)) {
+        ret = "Short-term parameters in save file are invalid";
+        goto cleanup;
+    }
+    if (seed && me->ourgame->validate_params(cparams, TRUE)) {
+        /*
+         * The seed's no use with this version, but we can perfectly
+         * well use the rest of the data.
+         */
+        sfree(seed);
+        seed = NULL;
+    }
+    if (!desc) {
+        ret = "Game description in save file is missing";
+        goto cleanup;
+    } else if (me->ourgame->validate_desc(params, desc)) {
+        ret = "Game description in save file is invalid";
+        goto cleanup;
+    }
+    if (privdesc && me->ourgame->validate_desc(params, privdesc)) {
+        ret = "Game private description in save file is invalid";
+        goto cleanup;
+    }
+    if (statepos < 0 || statepos >= nstates) {
+        ret = "Game position in save file is out of range";
+    }
+
+    states[0].state = me->ourgame->new_game(me, params,
+                                            privdesc ? privdesc : desc);
+    for (i = 1; i < nstates; i++) {
+        assert(states[i].movetype != NEWGAME);
+        switch (states[i].movetype) {
+          case MOVE:
+          case SOLVE:
+            states[i].state = me->ourgame->execute_move(states[i-1].state,
+                                                        states[i].movestr);
+            if (states[i].state == NULL) {
+                ret = "Save file contained an invalid move";
+                goto cleanup;
+            }
+            break;
+          case RESTART:
+            if (me->ourgame->validate_desc(params, states[i].movestr)) {
+                ret = "Save file contained an invalid restart move";
+                goto cleanup;
+            }
+            states[i].state = me->ourgame->new_game(me, params,
+                                                    states[i].movestr);
+            break;
+        }
+    }
+
+    ui = me->ourgame->new_ui(states[0].state);
+    me->ourgame->decode_ui(ui, uistr);
+
+    /*
+     * Now we've run out of possible error conditions, so we're
+     * ready to start overwriting the real data in the current
+     * midend. We'll do this by swapping things with the local
+     * variables, so that the same cleanup code will free the old
+     * stuff.
+     */
+    {
+        char *tmp;
+
+        tmp = me->desc;
+        me->desc = desc;
+        desc = tmp;
+
+        tmp = me->privdesc;
+        me->privdesc = privdesc;
+        privdesc = tmp;
+
+        tmp = me->seedstr;
+        me->seedstr = seed;
+        seed = tmp;
+
+        tmp = me->aux_info;
+        me->aux_info = auxinfo;
+        auxinfo = tmp;
+    }
+
+    me->genmode = GOT_NOTHING;
+
+    me->statesize = nstates;
+    nstates = me->nstates;
+    me->nstates = me->statesize;
+    {
+        struct midend_state_entry *tmp;
+        tmp = me->states;
+        me->states = states;
+        states = tmp;
+    }
+    me->statepos = statepos;
+
+    {
+        game_params *tmp;
+
+        tmp = me->params;
+        me->params = params;
+        params = tmp;
+
+        tmp = me->curparams;
+        me->curparams = cparams;
+        cparams = tmp;
+    }
+
+    me->oldstate = NULL;
+    me->anim_time = me->anim_pos = me->flash_time = me->flash_pos = 0.0F;
+    me->dir = 0;
+
+    {
+        game_ui *tmp;
+
+        tmp = me->ui;
+        me->ui = ui;
+        ui = tmp;
+    }
+
+    me->elapsed = elapsed;
+    me->pressed_mouse_button = 0;
+
+    midend_set_timer(me);
+
+    if (me->drawstate)
+        me->ourgame->free_drawstate(me->drawing, me->drawstate);
+    me->drawstate =
+        me->ourgame->new_drawstate(me->drawing,
+                                  me->states[me->statepos-1].state);
+    midend_size_new_drawstate(me);
+
+    ret = NULL;                        /* success! */
+
+    cleanup:
+    sfree(val);
+    sfree(seed);
+    sfree(parstr);
+    sfree(cparstr);
+    sfree(desc);
+    sfree(privdesc);
+    sfree(auxinfo);
+    sfree(uistr);
+    if (params)
+        me->ourgame->free_params(params);
+    if (cparams)
+        me->ourgame->free_params(cparams);
+    if (ui)
+        me->ourgame->free_ui(ui);
+    if (states) {
+        int i;
+
+        for (i = 0; i < nstates; i++) {
+            if (states[i].state)
+                me->ourgame->free_game(states[i].state);
+            sfree(states[i].movestr);
+        }
+        sfree(states);
+    }
+
+    return ret;
+}
+
+/*
+ * This function examines a saved game file just far enough to
+ * determine which game type it contains. It returns NULL on success
+ * and the game name string in 'name' (which will be dynamically
+ * allocated and should be caller-freed), or an error message on
+ * failure.
+ */
+char *identify_game(char **name, int (*read)(void *ctx, void *buf, int len),
+                    void *rctx)
+{
+    int nstates = 0, statepos = -1, gotstates = 0;
+    int started = FALSE;
+
+    char *val = NULL;
+    /* Initially all errors give the same report */
+    char *ret = "Data does not appear to be a saved game file";
+
+    *name = NULL;
+
+    /*
+     * Loop round and round reading one key/value pair at a time from
+     * the serialised stream, until we've found the game name.
+     */
+    while (nstates <= 0 || statepos < 0 || gotstates < nstates-1) {
+        char key[9], c;
+        int len;
+
+        do {
+            if (!read(rctx, key, 1)) {
+                /* unexpected EOF */
+                goto cleanup;
+            }
+        } while (key[0] == '\r' || key[0] == '\n');
+
+        if (!read(rctx, key+1, 8)) {
+            /* unexpected EOF */
+            goto cleanup;
+        }
+
+        if (key[8] != ':') {
+            if (started)
+                ret = "Data was incorrectly formatted for a saved game file";
+           goto cleanup;
+        }
+        len = strcspn(key, ": ");
+        assert(len <= 8);
+        key[len] = '\0';
+
+        len = 0;
+        while (1) {
+            if (!read(rctx, &c, 1)) {
+                /* unexpected EOF */
+                goto cleanup;
+            }
+
+            if (c == ':') {
+                break;
+            } else if (c >= '0' && c <= '9') {
+                len = (len * 10) + (c - '0');
+            } else {
+                if (started)
+                    ret = "Data was incorrectly formatted for a"
+                    " saved game file";
+                goto cleanup;
+            }
+        }
+
+        val = snewn(len+1, char);
+        if (!read(rctx, val, len)) {
+            if (started)
+            goto cleanup;
+        }
+        val[len] = '\0';
+
+        if (!started) {
+            if (strcmp(key, "SAVEFILE") || strcmp(val, SERIALISE_MAGIC)) {
+                /* ret already has the right message in it */
+                goto cleanup;
+            }
+            /* Now most errors are this one, unless otherwise specified */
+            ret = "Saved data ended unexpectedly";
+            started = TRUE;
+        } else {
+            if (!strcmp(key, "VERSION")) {
+                if (strcmp(val, SERIALISE_VERSION)) {
+                    ret = "Cannot handle this version of the saved game"
+                        " file format";
+                    goto cleanup;
+                }
+            } else if (!strcmp(key, "GAME")) {
+                *name = dupstr(val);
+                ret = NULL;
+                goto cleanup;
+            }
+        }
+
+        sfree(val);
+        val = NULL;
+    }
+
+    cleanup:
+    sfree(val);
+    return ret;
+}
+
+char *midend_print_puzzle(midend *me, document *doc, int with_soln)
+{
+    game_state *soln = NULL;
+
+    if (me->statepos < 1)
+       return "No game set up to print";/* _shouldn't_ happen! */
+
+    if (with_soln) {
+       char *msg, *movestr;
+
+       if (!me->ourgame->can_solve)
+           return "This game does not support the Solve operation";
+
+       msg = "Solve operation failed";/* game _should_ overwrite on error */
+       movestr = me->ourgame->solve(me->states[0].state,
+                                    me->states[me->statepos-1].state,
+                                    me->aux_info, &msg);
+       if (!movestr)
+           return msg;
+       soln = me->ourgame->execute_move(me->states[me->statepos-1].state,
+                                        movestr);
+       assert(soln);
+
+       sfree(movestr);
+    } else
+       soln = NULL;
+
+    /*
+     * This call passes over ownership of the two game_states and
+     * the game_params. Hence we duplicate the ones we want to
+     * keep, and we don't have to bother freeing soln if it was
+     * non-NULL.
+     */
+    document_add_puzzle(doc, me->ourgame,
+                       me->ourgame->dup_params(me->curparams),
+                       me->ourgame->dup_game(me->states[0].state), soln);
+
+    return NULL;
+}
diff --git a/mines.R b/mines.R
new file mode 100644 (file)
index 0000000..275c76c
--- /dev/null
+++ b/mines.R
@@ -0,0 +1,24 @@
+# -*- makefile -*-
+
+MINES_EXTRA = tree234
+
+mines    : [X] GTK COMMON mines MINES_EXTRA mines-icon|no-icon
+
+mines    : [G] WINDOWS COMMON mines MINES_EXTRA mines.res|noicon.res
+
+mineobfusc :    [U] mines[STANDALONE_OBFUSCATOR] MINES_EXTRA STANDALONE
+mineobfusc :    [C] mines[STANDALONE_OBFUSCATOR] MINES_EXTRA STANDALONE
+
+ALL += mines[COMBINED] MINES_EXTRA
+
+!begin am gtk
+GAMES += mines
+!end
+
+!begin >list.c
+    A(mines) \
+!end
+
+!begin >gamedesc.txt
+mines:mines.exe:Mines:Mine-finding puzzle:Find all the mines without treading on any of them.
+!end
diff --git a/mines.c b/mines.c
new file mode 100644 (file)
index 0000000..da4428c
--- /dev/null
+++ b/mines.c
@@ -0,0 +1,3250 @@
+/*
+ * mines.c: Minesweeper clone with sophisticated grid generation.
+ * 
+ * Still TODO:
+ *
+ *  - think about configurably supporting question marks. Once,
+ *    that is, we've thought about configurability in general!
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "tree234.h"
+#include "puzzles.h"
+
+enum {
+    COL_BACKGROUND, COL_BACKGROUND2,
+    COL_1, COL_2, COL_3, COL_4, COL_5, COL_6, COL_7, COL_8,
+    COL_MINE, COL_BANG, COL_CROSS, COL_FLAG, COL_FLAGBASE, COL_QUERY,
+    COL_HIGHLIGHT, COL_LOWLIGHT,
+    COL_WRONGNUMBER,
+    COL_CURSOR,
+    NCOLOURS
+};
+
+#define PREFERRED_TILE_SIZE 20
+#define TILE_SIZE (ds->tilesize)
+#ifdef SMALL_SCREEN
+#define BORDER 8
+#else
+#define BORDER (TILE_SIZE * 3 / 2)
+#endif
+#define HIGHLIGHT_WIDTH (TILE_SIZE / 10)
+#define OUTER_HIGHLIGHT_WIDTH (BORDER / 10)
+#define COORD(x)  ( (x) * TILE_SIZE + BORDER )
+#define FROMCOORD(x)  ( ((x) - BORDER + TILE_SIZE) / TILE_SIZE - 1 )
+
+#define FLASH_FRAME 0.13F
+
+struct game_params {
+    int w, h, n;
+    int unique;
+};
+
+struct mine_layout {
+    /*
+     * This structure is shared between all the game_states for a
+     * given instance of the puzzle, so we reference-count it.
+     */
+    int refcount;
+    char *mines;
+    /*
+     * If we haven't yet actually generated the mine layout, here's
+     * all the data we will need to do so.
+     */
+    int n, unique;
+    random_state *rs;
+    midend *me;                       /* to give back the new game desc */
+};
+
+struct game_state {
+    int w, h, n, dead, won;
+    int used_solve;
+    struct mine_layout *layout;               /* real mine positions */
+    signed char *grid;                        /* player knowledge */
+    /*
+     * Each item in the `grid' array is one of the following values:
+     * 
+     *         - 0 to 8 mean the square is open and has a surrounding mine
+     *           count.
+     * 
+     *  - -1 means the square is marked as a mine.
+     * 
+     *  - -2 means the square is unknown.
+     * 
+     *         - -3 means the square is marked with a question mark
+     *           (FIXME: do we even want to bother with this?).
+     * 
+     *         - 64 means the square has had a mine revealed when the game
+     *           was lost.
+     * 
+     *         - 65 means the square had a mine revealed and this was the
+     *           one the player hits.
+     * 
+     *         - 66 means the square has a crossed-out mine because the
+     *           player had incorrectly marked it.
+     */
+};
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = ret->h = 9;
+    ret->n = 10;
+    ret->unique = TRUE;
+
+    return ret;
+}
+
+static const struct game_params mines_presets[] = {
+  {9, 9, 10, TRUE},
+  {9, 9, 35, TRUE},
+  {16, 16, 40, TRUE},
+  {16, 16, 99, TRUE},
+#ifndef SMALL_SCREEN
+  {30, 16, 99, TRUE},
+  {30, 16, 170, TRUE},
+#endif
+};
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params *ret;
+    char str[80];
+
+    if (i < 0 || i >= lenof(mines_presets))
+        return FALSE;
+
+    ret = snew(game_params);
+    *ret = mines_presets[i];
+
+    sprintf(str, "%dx%d, %d mines", ret->w, ret->h, ret->n);
+
+    *name = dupstr(str);
+    *params = ret;
+    return TRUE;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+static void decode_params(game_params *params, char const *string)
+{
+    char const *p = string;
+
+    params->w = atoi(p);
+    while (*p && isdigit((unsigned char)*p)) p++;
+    if (*p == 'x') {
+        p++;
+        params->h = atoi(p);
+        while (*p && isdigit((unsigned char)*p)) p++;
+    } else {
+        params->h = params->w;
+    }
+    if (*p == 'n') {
+       p++;
+       params->n = atoi(p);
+       while (*p && (*p == '.' || isdigit((unsigned char)*p))) p++;
+    } else {
+       params->n = params->w * params->h / 10;
+    }
+
+    while (*p) {
+       if (*p == 'a') {
+            p++;
+           params->unique = FALSE;
+       } else
+           p++;                       /* skip any other gunk */
+    }
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char ret[400];
+    int len;
+
+    len = sprintf(ret, "%dx%d", params->w, params->h);
+    /*
+     * Mine count is a generation-time parameter, since it can be
+     * deduced from the mine bitmap!
+     */
+    if (full)
+       len += sprintf(ret+len, "n%d", params->n);
+    if (full && !params->unique)
+        ret[len++] = 'a';
+    assert(len < lenof(ret));
+    ret[len] = '\0';
+
+    return dupstr(ret);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+    char buf[80];
+
+    ret = snewn(5, config_item);
+
+    ret[0].name = "Width";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->w);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Height";
+    ret[1].type = C_STRING;
+    sprintf(buf, "%d", params->h);
+    ret[1].sval = dupstr(buf);
+    ret[1].ival = 0;
+
+    ret[2].name = "Mines";
+    ret[2].type = C_STRING;
+    sprintf(buf, "%d", params->n);
+    ret[2].sval = dupstr(buf);
+    ret[2].ival = 0;
+
+    ret[3].name = "Ensure solubility";
+    ret[3].type = C_BOOLEAN;
+    ret[3].sval = NULL;
+    ret[3].ival = params->unique;
+
+    ret[4].name = NULL;
+    ret[4].type = C_END;
+    ret[4].sval = NULL;
+    ret[4].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = atoi(cfg[0].sval);
+    ret->h = atoi(cfg[1].sval);
+    ret->n = atoi(cfg[2].sval);
+    if (strchr(cfg[2].sval, '%'))
+       ret->n = ret->n * (ret->w * ret->h) / 100;
+    ret->unique = cfg[3].ival;
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    /*
+     * Lower limit on grid size: each dimension must be at least 3.
+     * 1 is theoretically workable if rather boring, but 2 is a
+     * real problem: there is often _no_ way to generate a uniquely
+     * solvable 2xn Mines grid. You either run into two mines
+     * blocking the way and no idea what's behind them, or one mine
+     * and no way to know which of the two rows it's in. If the
+     * mine count is even you can create a soluble grid by packing
+     * all the mines at one end (so what when you hit a two-mine
+     * wall there are only as many covered squares left as there
+     * are mines); but if it's odd, you are doomed, because you
+     * _have_ to have a gap somewhere which you can't determine the
+     * position of.
+     */
+    if (full && params->unique && (params->w <= 2 || params->h <= 2))
+       return "Width and height must both be greater than two";
+    if (params->n > params->w * params->h - 9)
+       return "Too many mines for grid size";
+
+    /*
+     * FIXME: Need more constraints here. Not sure what the
+     * sensible limits for Minesweeper actually are. The limits
+     * probably ought to change, however, depending on uniqueness.
+     */
+
+    return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Minesweeper solver, used to ensure the generated grids are
+ * solvable without having to take risks.
+ */
+
+/*
+ * Count the bits in a word. Only needs to cope with 16 bits.
+ */
+static int bitcount16(int inword)
+{
+    unsigned int word = inword;
+
+    word = ((word & 0xAAAA) >> 1) + (word & 0x5555);
+    word = ((word & 0xCCCC) >> 2) + (word & 0x3333);
+    word = ((word & 0xF0F0) >> 4) + (word & 0x0F0F);
+    word = ((word & 0xFF00) >> 8) + (word & 0x00FF);
+
+    return (int)word;
+}
+
+/*
+ * We use a tree234 to store a large number of small localised
+ * sets, each with a mine count. We also keep some of those sets
+ * linked together into a to-do list.
+ */
+struct set {
+    short x, y, mask, mines;
+    int todo;
+    struct set *prev, *next;
+};
+
+static int setcmp(void *av, void *bv)
+{
+    struct set *a = (struct set *)av;
+    struct set *b = (struct set *)bv;
+
+    if (a->y < b->y)
+       return -1;
+    else if (a->y > b->y)
+       return +1;
+    else if (a->x < b->x)
+       return -1;
+    else if (a->x > b->x)
+       return +1;
+    else if (a->mask < b->mask)
+       return -1;
+    else if (a->mask > b->mask)
+       return +1;
+    else
+       return 0;
+}
+
+struct setstore {
+    tree234 *sets;
+    struct set *todo_head, *todo_tail;
+};
+
+static struct setstore *ss_new(void)
+{
+    struct setstore *ss = snew(struct setstore);
+    ss->sets = newtree234(setcmp);
+    ss->todo_head = ss->todo_tail = NULL;
+    return ss;
+}
+
+/*
+ * Take two input sets, in the form (x,y,mask). Munge the first by
+ * taking either its intersection with the second or its difference
+ * with the second. Return the new mask part of the first set.
+ */
+static int setmunge(int x1, int y1, int mask1, int x2, int y2, int mask2,
+                   int diff)
+{
+    /*
+     * Adjust the second set so that it has the same x,y
+     * coordinates as the first.
+     */
+    if (abs(x2-x1) >= 3 || abs(y2-y1) >= 3) {
+       mask2 = 0;
+    } else {
+       while (x2 > x1) {
+           mask2 &= ~(4|32|256);
+           mask2 <<= 1;
+           x2--;
+       }
+       while (x2 < x1) {
+           mask2 &= ~(1|8|64);
+           mask2 >>= 1;
+           x2++;
+       }
+       while (y2 > y1) {
+           mask2 &= ~(64|128|256);
+           mask2 <<= 3;
+           y2--;
+       }
+       while (y2 < y1) {
+           mask2 &= ~(1|2|4);
+           mask2 >>= 3;
+           y2++;
+       }
+    }
+
+    /*
+     * Invert the second set if `diff' is set (we're after A &~ B
+     * rather than A & B).
+     */
+    if (diff)
+       mask2 ^= 511;
+
+    /*
+     * Now all that's left is a logical AND.
+     */
+    return mask1 & mask2;
+}
+
+static void ss_add_todo(struct setstore *ss, struct set *s)
+{
+    if (s->todo)
+       return;                        /* already on it */
+
+#ifdef SOLVER_DIAGNOSTICS
+    printf("adding set on todo list: %d,%d %03x %d\n",
+          s->x, s->y, s->mask, s->mines);
+#endif
+
+    s->prev = ss->todo_tail;
+    if (s->prev)
+       s->prev->next = s;
+    else
+       ss->todo_head = s;
+    ss->todo_tail = s;
+    s->next = NULL;
+    s->todo = TRUE;
+}
+
+static void ss_add(struct setstore *ss, int x, int y, int mask, int mines)
+{
+    struct set *s;
+
+    assert(mask != 0);
+
+    /*
+     * Normalise so that x and y are genuinely the bounding
+     * rectangle.
+     */
+    while (!(mask & (1|8|64)))
+       mask >>= 1, x++;
+    while (!(mask & (1|2|4)))
+       mask >>= 3, y++;
+
+    /*
+     * Create a set structure and add it to the tree.
+     */
+    s = snew(struct set);
+    s->x = x;
+    s->y = y;
+    s->mask = mask;
+    s->mines = mines;
+    s->todo = FALSE;
+    if (add234(ss->sets, s) != s) {
+       /*
+        * This set already existed! Free it and return.
+        */
+       sfree(s);
+       return;
+    }
+
+    /*
+     * We've added a new set to the tree, so put it on the todo
+     * list.
+     */
+    ss_add_todo(ss, s);
+}
+
+static void ss_remove(struct setstore *ss, struct set *s)
+{
+    struct set *next = s->next, *prev = s->prev;
+
+#ifdef SOLVER_DIAGNOSTICS
+    printf("removing set %d,%d %03x\n", s->x, s->y, s->mask);
+#endif
+    /*
+     * Remove s from the todo list.
+     */
+    if (prev)
+       prev->next = next;
+    else if (s == ss->todo_head)
+       ss->todo_head = next;
+
+    if (next)
+       next->prev = prev;
+    else if (s == ss->todo_tail)
+       ss->todo_tail = prev;
+
+    s->todo = FALSE;
+
+    /*
+     * Remove s from the tree.
+     */
+    del234(ss->sets, s);
+
+    /*
+     * Destroy the actual set structure.
+     */
+    sfree(s);
+}
+
+/*
+ * Return a dynamically allocated list of all the sets which
+ * overlap a provided input set.
+ */
+static struct set **ss_overlap(struct setstore *ss, int x, int y, int mask)
+{
+    struct set **ret = NULL;
+    int nret = 0, retsize = 0;
+    int xx, yy;
+
+    for (xx = x-3; xx < x+3; xx++)
+       for (yy = y-3; yy < y+3; yy++) {
+           struct set stmp, *s;
+           int pos;
+
+           /*
+            * Find the first set with these top left coordinates.
+            */
+           stmp.x = xx;
+           stmp.y = yy;
+           stmp.mask = 0;
+
+           if (findrelpos234(ss->sets, &stmp, NULL, REL234_GE, &pos)) {
+               while ((s = index234(ss->sets, pos)) != NULL &&
+                      s->x == xx && s->y == yy) {
+                   /*
+                    * This set potentially overlaps the input one.
+                    * Compute the intersection to see if they
+                    * really overlap, and add it to the list if
+                    * so.
+                    */
+                   if (setmunge(x, y, mask, s->x, s->y, s->mask, FALSE)) {
+                       /*
+                        * There's an overlap.
+                        */
+                       if (nret >= retsize) {
+                           retsize = nret + 32;
+                           ret = sresize(ret, retsize, struct set *);
+                       }
+                       ret[nret++] = s;
+                   }
+
+                   pos++;
+               }
+           }
+       }
+
+    ret = sresize(ret, nret+1, struct set *);
+    ret[nret] = NULL;
+
+    return ret;
+}
+
+/*
+ * Get an element from the head of the set todo list.
+ */
+static struct set *ss_todo(struct setstore *ss)
+{
+    if (ss->todo_head) {
+       struct set *ret = ss->todo_head;
+       ss->todo_head = ret->next;
+       if (ss->todo_head)
+           ss->todo_head->prev = NULL;
+       else
+           ss->todo_tail = NULL;
+       ret->next = ret->prev = NULL;
+       ret->todo = FALSE;
+       return ret;
+    } else {
+       return NULL;
+    }
+}
+
+struct squaretodo {
+    int *next;
+    int head, tail;
+};
+
+static void std_add(struct squaretodo *std, int i)
+{
+    if (std->tail >= 0)
+       std->next[std->tail] = i;
+    else
+       std->head = i;
+    std->tail = i;
+    std->next[i] = -1;
+}
+
+typedef int (*open_cb)(void *, int, int);
+
+static void known_squares(int w, int h, struct squaretodo *std,
+                          signed char *grid,
+                         open_cb open, void *openctx,
+                         int x, int y, int mask, int mine)
+{
+    int xx, yy, bit;
+
+    bit = 1;
+
+    for (yy = 0; yy < 3; yy++)
+       for (xx = 0; xx < 3; xx++) {
+           if (mask & bit) {
+               int i = (y + yy) * w + (x + xx);
+
+               /*
+                * It's possible that this square is _already_
+                * known, in which case we don't try to add it to
+                * the list twice.
+                */
+               if (grid[i] == -2) {
+
+                   if (mine) {
+                       grid[i] = -1;   /* and don't open it! */
+                   } else {
+                       grid[i] = open(openctx, x + xx, y + yy);
+                       assert(grid[i] != -1);   /* *bang* */
+                   }
+                   std_add(std, i);
+
+               }
+           }
+           bit <<= 1;
+       }
+}
+
+/*
+ * This is data returned from the `perturb' function. It details
+ * which squares have become mines and which have become clear. The
+ * solver is (of course) expected to honourably not use that
+ * knowledge directly, but to efficently adjust its internal data
+ * structures and proceed based on only the information it
+ * legitimately has.
+ */
+struct perturbation {
+    int x, y;
+    int delta;                        /* +1 == become a mine; -1 == cleared */
+};
+struct perturbations {
+    int n;
+    struct perturbation *changes;
+};
+
+/*
+ * Main solver entry point. You give it a grid of existing
+ * knowledge (-1 for a square known to be a mine, 0-8 for empty
+ * squares with a given number of neighbours, -2 for completely
+ * unknown), plus a function which you can call to open new squares
+ * once you're confident of them. It fills in as much more of the
+ * grid as it can.
+ * 
+ * Return value is:
+ * 
+ *  - -1 means deduction stalled and nothing could be done
+ *  - 0 means deduction succeeded fully
+ *  - >0 means deduction succeeded but some number of perturbation
+ *    steps were required; the exact return value is the number of
+ *    perturb calls.
+ */
+
+typedef struct perturbations *(*perturb_cb) (void *, signed char *, int, int, int);
+
+static int minesolve(int w, int h, int n, signed char *grid,
+                    open_cb open,
+                     perturb_cb perturb,
+                    void *ctx, random_state *rs)
+{
+    struct setstore *ss = ss_new();
+    struct set **list;
+    struct squaretodo astd, *std = &astd;
+    int x, y, i, j;
+    int nperturbs = 0;
+
+    /*
+     * Set up a linked list of squares with known contents, so that
+     * we can process them one by one.
+     */
+    std->next = snewn(w*h, int);
+    std->head = std->tail = -1;
+
+    /*
+     * Initialise that list with all known squares in the input
+     * grid.
+     */
+    for (y = 0; y < h; y++) {
+       for (x = 0; x < w; x++) {
+           i = y*w+x;
+           if (grid[i] != -2)
+               std_add(std, i);
+       }
+    }
+
+    /*
+     * Main deductive loop.
+     */
+    while (1) {
+       int done_something = FALSE;
+       struct set *s;
+
+       /*
+        * If there are any known squares on the todo list, process
+        * them and construct a set for each.
+        */
+       while (std->head != -1) {
+           i = std->head;
+#ifdef SOLVER_DIAGNOSTICS
+           printf("known square at %d,%d [%d]\n", i%w, i/w, grid[i]);
+#endif
+           std->head = std->next[i];
+           if (std->head == -1)
+               std->tail = -1;
+
+           x = i % w;
+           y = i / w;
+
+           if (grid[i] >= 0) {
+               int dx, dy, mines, bit, val;
+#ifdef SOLVER_DIAGNOSTICS
+               printf("creating set around this square\n");
+#endif
+               /*
+                * Empty square. Construct the set of non-known squares
+                * around this one, and determine its mine count.
+                */
+               mines = grid[i];
+               bit = 1;
+               val = 0;
+               for (dy = -1; dy <= +1; dy++) {
+                   for (dx = -1; dx <= +1; dx++) {
+#ifdef SOLVER_DIAGNOSTICS
+                       printf("grid %d,%d = %d\n", x+dx, y+dy, grid[i+dy*w+dx]);
+#endif
+                       if (x+dx < 0 || x+dx >= w || y+dy < 0 || y+dy >= h)
+                           /* ignore this one */;
+                       else if (grid[i+dy*w+dx] == -1)
+                           mines--;
+                       else if (grid[i+dy*w+dx] == -2)
+                           val |= bit;
+                       bit <<= 1;
+                   }
+               }
+               if (val)
+                   ss_add(ss, x-1, y-1, val, mines);
+           }
+
+           /*
+            * Now, whether the square is empty or full, we must
+            * find any set which contains it and replace it with
+            * one which does not.
+            */
+           {
+#ifdef SOLVER_DIAGNOSTICS
+               printf("finding sets containing known square %d,%d\n", x, y);
+#endif
+               list = ss_overlap(ss, x, y, 1);
+
+               for (j = 0; list[j]; j++) {
+                   int newmask, newmines;
+
+                   s = list[j];
+
+                   /*
+                    * Compute the mask for this set minus the
+                    * newly known square.
+                    */
+                   newmask = setmunge(s->x, s->y, s->mask, x, y, 1, TRUE);
+
+                   /*
+                    * Compute the new mine count.
+                    */
+                   newmines = s->mines - (grid[i] == -1);
+
+                   /*
+                    * Insert the new set into the collection,
+                    * unless it's been whittled right down to
+                    * nothing.
+                    */
+                   if (newmask)
+                       ss_add(ss, s->x, s->y, newmask, newmines);
+
+                   /*
+                    * Destroy the old one; it is actually obsolete.
+                    */
+                   ss_remove(ss, s);
+               }
+
+               sfree(list);
+           }
+
+           /*
+            * Marking a fresh square as known certainly counts as
+            * doing something.
+            */
+           done_something = TRUE;
+       }
+
+       /*
+        * Now pick a set off the to-do list and attempt deductions
+        * based on it.
+        */
+       if ((s = ss_todo(ss)) != NULL) {
+
+#ifdef SOLVER_DIAGNOSTICS
+           printf("set to do: %d,%d %03x %d\n", s->x, s->y, s->mask, s->mines);
+#endif
+           /*
+            * Firstly, see if this set has a mine count of zero or
+            * of its own cardinality.
+            */
+           if (s->mines == 0 || s->mines == bitcount16(s->mask)) {
+               /*
+                * If so, we can immediately mark all the squares
+                * in the set as known.
+                */
+#ifdef SOLVER_DIAGNOSTICS
+               printf("easy\n");
+#endif
+               known_squares(w, h, std, grid, open, ctx,
+                             s->x, s->y, s->mask, (s->mines != 0));
+
+               /*
+                * Having done that, we need do nothing further
+                * with this set; marking all the squares in it as
+                * known will eventually eliminate it, and will
+                * also permit further deductions about anything
+                * that overlaps it.
+                */
+               continue;
+           }
+
+           /*
+            * Failing that, we now search through all the sets
+            * which overlap this one.
+            */
+           list = ss_overlap(ss, s->x, s->y, s->mask);
+
+           for (j = 0; list[j]; j++) {
+               struct set *s2 = list[j];
+               int swing, s2wing, swc, s2wc;
+
+               /*
+                * Find the non-overlapping parts s2-s and s-s2,
+                * and their cardinalities.
+                * 
+                * I'm going to refer to these parts as `wings'
+                * surrounding the central part common to both
+                * sets. The `s wing' is s-s2; the `s2 wing' is
+                * s2-s.
+                */
+               swing = setmunge(s->x, s->y, s->mask, s2->x, s2->y, s2->mask,
+                                TRUE);
+               s2wing = setmunge(s2->x, s2->y, s2->mask, s->x, s->y, s->mask,
+                                TRUE);
+               swc = bitcount16(swing);
+               s2wc = bitcount16(s2wing);
+
+               /*
+                * If one set has more mines than the other, and
+                * the number of extra mines is equal to the
+                * cardinality of that set's wing, then we can mark
+                * every square in the wing as a known mine, and
+                * every square in the other wing as known clear.
+                */
+               if (swc == s->mines - s2->mines ||
+                   s2wc == s2->mines - s->mines) {
+                   known_squares(w, h, std, grid, open, ctx,
+                                 s->x, s->y, swing,
+                                 (swc == s->mines - s2->mines));
+                   known_squares(w, h, std, grid, open, ctx,
+                                 s2->x, s2->y, s2wing,
+                                 (s2wc == s2->mines - s->mines));
+                   continue;
+               }
+
+               /*
+                * Failing that, see if one set is a subset of the
+                * other. If so, we can divide up the mine count of
+                * the larger set between the smaller set and its
+                * complement, even if neither smaller set ends up
+                * being immediately clearable.
+                */
+               if (swc == 0 && s2wc != 0) {
+                   /* s is a subset of s2. */
+                   assert(s2->mines > s->mines);
+                   ss_add(ss, s2->x, s2->y, s2wing, s2->mines - s->mines);
+               } else if (s2wc == 0 && swc != 0) {
+                   /* s2 is a subset of s. */
+                   assert(s->mines > s2->mines);
+                   ss_add(ss, s->x, s->y, swing, s->mines - s2->mines);
+               }
+           }
+
+           sfree(list);
+
+           /*
+            * In this situation we have definitely done
+            * _something_, even if it's only reducing the size of
+            * our to-do list.
+            */
+           done_something = TRUE;
+       } else if (n >= 0) {
+           /*
+            * We have nothing left on our todo list, which means
+            * all localised deductions have failed. Our next step
+            * is to resort to global deduction based on the total
+            * mine count. This is computationally expensive
+            * compared to any of the above deductions, which is
+            * why we only ever do it when all else fails, so that
+            * hopefully it won't have to happen too often.
+            * 
+            * If you pass n<0 into this solver, that informs it
+            * that you do not know the total mine count, so it
+            * won't even attempt these deductions.
+            */
+
+           int minesleft, squaresleft;
+           int nsets, setused[10], cursor;
+
+           /*
+            * Start by scanning the current grid state to work out
+            * how many unknown squares we still have, and how many
+            * mines are to be placed in them.
+            */
+           squaresleft = 0;
+           minesleft = n;
+           for (i = 0; i < w*h; i++) {
+               if (grid[i] == -1)
+                   minesleft--;
+               else if (grid[i] == -2)
+                   squaresleft++;
+           }
+
+#ifdef SOLVER_DIAGNOSTICS
+           printf("global deduction time: squaresleft=%d minesleft=%d\n",
+                  squaresleft, minesleft);
+           for (y = 0; y < h; y++) {
+               for (x = 0; x < w; x++) {
+                   int v = grid[y*w+x];
+                   if (v == -1)
+                       putchar('*');
+                   else if (v == -2)
+                       putchar('?');
+                   else if (v == 0)
+                       putchar('-');
+                   else
+                       putchar('0' + v);
+               }
+               putchar('\n');
+           }
+#endif
+
+           /*
+            * If there _are_ no unknown squares, we have actually
+            * finished.
+            */
+           if (squaresleft == 0) {
+               assert(minesleft == 0);
+               break;
+           }
+
+           /*
+            * First really simple case: if there are no more mines
+            * left, or if there are exactly as many mines left as
+            * squares to play them in, then it's all easy.
+            */
+           if (minesleft == 0 || minesleft == squaresleft) {
+               for (i = 0; i < w*h; i++)
+                   if (grid[i] == -2)
+                       known_squares(w, h, std, grid, open, ctx,
+                                     i % w, i / w, 1, minesleft != 0);
+               continue;              /* now go back to main deductive loop */
+           }
+
+           /*
+            * Failing that, we have to do some _real_ work.
+            * Ideally what we do here is to try every single
+            * combination of the currently available sets, in an
+            * attempt to find a disjoint union (i.e. a set of
+            * squares with a known mine count between them) such
+            * that the remaining unknown squares _not_ contained
+            * in that union either contain no mines or are all
+            * mines.
+            * 
+            * Actually enumerating all 2^n possibilities will get
+            * a bit slow for large n, so I artificially cap this
+            * recursion at n=10 to avoid too much pain.
+            */
+           nsets = count234(ss->sets);
+           if (nsets <= lenof(setused)) {
+               /*
+                * Doing this with actual recursive function calls
+                * would get fiddly because a load of local
+                * variables from this function would have to be
+                * passed down through the recursion. So instead
+                * I'm going to use a virtual recursion within this
+                * function. The way this works is:
+                * 
+                *  - we have an array `setused', such that
+                *    setused[n] is 0 or 1 depending on whether set
+                *    n is currently in the union we are
+                *    considering.
+                * 
+                *  - we have a value `cursor' which indicates how
+                *    much of `setused' we have so far filled in.
+                *    It's conceptually the recursion depth.
+                * 
+                * We begin by setting `cursor' to zero. Then:
+                * 
+                *  - if cursor can advance, we advance it by one.
+                *    We set the value in `setused' that it went
+                *    past to 1 if that set is disjoint from
+                *    anything else currently in `setused', or to 0
+                *    otherwise.
+                * 
+                *  - If cursor cannot advance because it has
+                *    reached the end of the setused list, then we
+                *    have a maximal disjoint union. Check to see
+                *    whether its mine count has any useful
+                *    properties. If so, mark all the squares not
+                *    in the union as known and terminate.
+                * 
+                *  - If cursor has reached the end of setused and
+                *    the algorithm _hasn't_ terminated, back
+                *    cursor up to the nearest 1, turn it into a 0
+                *    and advance cursor just past it.
+                * 
+                *  - If we attempt to back up to the nearest 1 and
+                *    there isn't one at all, then we have gone
+                *    through all disjoint unions of sets in the
+                *    list and none of them has been helpful, so we
+                *    give up.
+                */
+               struct set *sets[lenof(setused)];
+               for (i = 0; i < nsets; i++)
+                   sets[i] = index234(ss->sets, i);
+
+               cursor = 0;
+               while (1) {
+
+                   if (cursor < nsets) {
+                       int ok = TRUE;
+
+                       /* See if any existing set overlaps this one. */
+                       for (i = 0; i < cursor; i++)
+                           if (setused[i] &&
+                               setmunge(sets[cursor]->x,
+                                        sets[cursor]->y,
+                                        sets[cursor]->mask,
+                                        sets[i]->x, sets[i]->y, sets[i]->mask,
+                                        FALSE)) {
+                               ok = FALSE;
+                               break;
+                           }
+
+                       if (ok) {
+                           /*
+                            * We're adding this set to our union,
+                            * so adjust minesleft and squaresleft
+                            * appropriately.
+                            */
+                           minesleft -= sets[cursor]->mines;
+                           squaresleft -= bitcount16(sets[cursor]->mask);
+                       }
+
+                       setused[cursor++] = ok;
+                   } else {
+#ifdef SOLVER_DIAGNOSTICS
+                       printf("trying a set combination with %d %d\n",
+                              squaresleft, minesleft);
+#endif /* SOLVER_DIAGNOSTICS */
+
+                       /*
+                        * We've reached the end. See if we've got
+                        * anything interesting.
+                        */
+                       if (squaresleft > 0 &&
+                           (minesleft == 0 || minesleft == squaresleft)) {
+                           /*
+                            * We have! There is at least one
+                            * square not contained within the set
+                            * union we've just found, and we can
+                            * deduce that either all such squares
+                            * are mines or all are not (depending
+                            * on whether minesleft==0). So now all
+                            * we have to do is actually go through
+                            * the grid, find those squares, and
+                            * mark them.
+                            */
+                           for (i = 0; i < w*h; i++)
+                               if (grid[i] == -2) {
+                                   int outside = TRUE;
+                                   y = i / w;
+                                   x = i % w;
+                                   for (j = 0; j < nsets; j++)
+                                       if (setused[j] &&
+                                           setmunge(sets[j]->x, sets[j]->y,
+                                                    sets[j]->mask, x, y, 1,
+                                                    FALSE)) {
+                                           outside = FALSE;
+                                           break;
+                                       }
+                                   if (outside)
+                                       known_squares(w, h, std, grid,
+                                                     open, ctx,
+                                                     x, y, 1, minesleft != 0);
+                               }
+
+                           done_something = TRUE;
+                           break;     /* return to main deductive loop */
+                       }
+
+                       /*
+                        * If we reach here, then this union hasn't
+                        * done us any good, so move on to the
+                        * next. Backtrack cursor to the nearest 1,
+                        * change it to a 0 and continue.
+                        */
+                       while (--cursor >= 0 && !setused[cursor]);
+                       if (cursor >= 0) {
+                           assert(setused[cursor]);
+
+                           /*
+                            * We're removing this set from our
+                            * union, so re-increment minesleft and
+                            * squaresleft.
+                            */
+                           minesleft += sets[cursor]->mines;
+                           squaresleft += bitcount16(sets[cursor]->mask);
+
+                           setused[cursor++] = 0;
+                       } else {
+                           /*
+                            * We've backtracked all the way to the
+                            * start without finding a single 1,
+                            * which means that our virtual
+                            * recursion is complete and nothing
+                            * helped.
+                            */
+                           break;
+                       }
+                   }
+
+               }
+
+           }
+       }
+
+       if (done_something)
+           continue;
+
+#ifdef SOLVER_DIAGNOSTICS
+       /*
+        * Dump the current known state of the grid.
+        */
+       printf("solver ran out of steam, ret=%d, grid:\n", nperturbs);
+       for (y = 0; y < h; y++) {
+           for (x = 0; x < w; x++) {
+               int v = grid[y*w+x];
+               if (v == -1)
+                   putchar('*');
+               else if (v == -2)
+                   putchar('?');
+               else if (v == 0)
+                   putchar('-');
+               else
+                   putchar('0' + v);
+           }
+           putchar('\n');
+       }
+
+       {
+           struct set *s;
+
+           for (i = 0; (s = index234(ss->sets, i)) != NULL; i++)
+               printf("remaining set: %d,%d %03x %d\n", s->x, s->y, s->mask, s->mines);
+       }
+#endif
+
+       /*
+        * Now we really are at our wits' end as far as solving
+        * this grid goes. Our only remaining option is to call
+        * a perturb function and ask it to modify the grid to
+        * make it easier.
+        */
+       if (perturb) {
+           struct perturbations *ret;
+           struct set *s;
+
+           nperturbs++;
+
+           /*
+            * Choose a set at random from the current selection,
+            * and ask the perturb function to either fill or empty
+            * it.
+            * 
+            * If we have no sets at all, we must give up.
+            */
+           if (count234(ss->sets) == 0) {
+#ifdef SOLVER_DIAGNOSTICS
+               printf("perturbing on entire unknown set\n");
+#endif
+               ret = perturb(ctx, grid, 0, 0, 0);
+           } else {
+               s = index234(ss->sets, random_upto(rs, count234(ss->sets)));
+#ifdef SOLVER_DIAGNOSTICS
+               printf("perturbing on set %d,%d %03x\n", s->x, s->y, s->mask);
+#endif
+               ret = perturb(ctx, grid, s->x, s->y, s->mask);
+           }
+
+           if (ret) {
+               assert(ret->n > 0);    /* otherwise should have been NULL */
+
+               /*
+                * A number of squares have been fiddled with, and
+                * the returned structure tells us which. Adjust
+                * the mine count in any set which overlaps one of
+                * those squares, and put them back on the to-do
+                * list. Also, if the square itself is marked as a
+                * known non-mine, put it back on the squares-to-do
+                * list.
+                */
+               for (i = 0; i < ret->n; i++) {
+#ifdef SOLVER_DIAGNOSTICS
+                   printf("perturbation %s mine at %d,%d\n",
+                          ret->changes[i].delta > 0 ? "added" : "removed",
+                          ret->changes[i].x, ret->changes[i].y);
+#endif
+
+                   if (ret->changes[i].delta < 0 &&
+                       grid[ret->changes[i].y*w+ret->changes[i].x] != -2) {
+                       std_add(std, ret->changes[i].y*w+ret->changes[i].x);
+                   }
+
+                   list = ss_overlap(ss,
+                                     ret->changes[i].x, ret->changes[i].y, 1);
+
+                   for (j = 0; list[j]; j++) {
+                       list[j]->mines += ret->changes[i].delta;
+                       ss_add_todo(ss, list[j]);
+                   }
+
+                   sfree(list);
+               }
+
+               /*
+                * Now free the returned data.
+                */
+               sfree(ret->changes);
+               sfree(ret);
+
+#ifdef SOLVER_DIAGNOSTICS
+               /*
+                * Dump the current known state of the grid.
+                */
+               printf("state after perturbation:\n");
+               for (y = 0; y < h; y++) {
+                   for (x = 0; x < w; x++) {
+                       int v = grid[y*w+x];
+                       if (v == -1)
+                           putchar('*');
+                       else if (v == -2)
+                           putchar('?');
+                       else if (v == 0)
+                           putchar('-');
+                       else
+                           putchar('0' + v);
+                   }
+                   putchar('\n');
+               }
+
+               {
+                   struct set *s;
+
+                   for (i = 0; (s = index234(ss->sets, i)) != NULL; i++)
+                       printf("remaining set: %d,%d %03x %d\n", s->x, s->y, s->mask, s->mines);
+               }
+#endif
+
+               /*
+                * And now we can go back round the deductive loop.
+                */
+               continue;
+           }
+       }
+
+       /*
+        * If we get here, even that didn't work (either we didn't
+        * have a perturb function or it returned failure), so we
+        * give up entirely.
+        */
+       break;
+    }
+
+    /*
+     * See if we've got any unknown squares left.
+     */
+    for (y = 0; y < h; y++)
+       for (x = 0; x < w; x++)
+           if (grid[y*w+x] == -2) {
+               nperturbs = -1;        /* failed to complete */
+               break;
+           }
+
+    /*
+     * Free the set list and square-todo list.
+     */
+    {
+       struct set *s;
+       while ((s = delpos234(ss->sets, 0)) != NULL)
+           sfree(s);
+       freetree234(ss->sets);
+       sfree(ss);
+       sfree(std->next);
+    }
+
+    return nperturbs;
+}
+
+/* ----------------------------------------------------------------------
+ * Grid generator which uses the above solver.
+ */
+
+struct minectx {
+    char *grid;
+    int w, h;
+    int sx, sy;
+    int allow_big_perturbs;
+    random_state *rs;
+};
+
+static int mineopen(void *vctx, int x, int y)
+{
+    struct minectx *ctx = (struct minectx *)vctx;
+    int i, j, n;
+
+    assert(x >= 0 && x < ctx->w && y >= 0 && y < ctx->h);
+    if (ctx->grid[y * ctx->w + x])
+       return -1;                     /* *bang* */
+
+    n = 0;
+    for (i = -1; i <= +1; i++) {
+       if (x + i < 0 || x + i >= ctx->w)
+           continue;
+       for (j = -1; j <= +1; j++) {
+           if (y + j < 0 || y + j >= ctx->h)
+               continue;
+           if (i == 0 && j == 0)
+               continue;
+           if (ctx->grid[(y+j) * ctx->w + (x+i)])
+               n++;
+       }
+    }
+
+    return n;
+}
+
+/* Structure used internally to mineperturb(). */
+struct square {
+    int x, y, type, random;
+};
+static int squarecmp(const void *av, const void *bv)
+{
+    const struct square *a = (const struct square *)av;
+    const struct square *b = (const struct square *)bv;
+    if (a->type < b->type)
+       return -1;
+    else if (a->type > b->type)
+       return +1;
+    else if (a->random < b->random)
+       return -1;
+    else if (a->random > b->random)
+       return +1;
+    else if (a->y < b->y)
+       return -1;
+    else if (a->y > b->y)
+       return +1;
+    else if (a->x < b->x)
+       return -1;
+    else if (a->x > b->x)
+       return +1;
+    return 0;
+}
+
+/*
+ * Normally this function is passed an (x,y,mask) set description.
+ * On occasions, though, there is no _localised_ set being used,
+ * and the set being perturbed is supposed to be the entirety of
+ * the unreachable area. This is signified by the special case
+ * mask==0: in this case, anything labelled -2 in the grid is part
+ * of the set.
+ * 
+ * Allowing perturbation in this special case appears to make it
+ * guaranteeably possible to generate a workable grid for any mine
+ * density, but they tend to be a bit boring, with mines packed
+ * densely into far corners of the grid and the remainder being
+ * less dense than one might like. Therefore, to improve overall
+ * grid quality I disable this feature for the first few attempts,
+ * and fall back to it after no useful grid has been generated.
+ */
+static struct perturbations *mineperturb(void *vctx, signed char *grid,
+                                        int setx, int sety, int mask)
+{
+    struct minectx *ctx = (struct minectx *)vctx;
+    struct square *sqlist;
+    int x, y, dx, dy, i, n, nfull, nempty;
+    struct square **tofill, **toempty, **todo;
+    int ntofill, ntoempty, ntodo, dtodo, dset;
+    struct perturbations *ret;
+    int *setlist;
+
+    if (!mask && !ctx->allow_big_perturbs)
+       return NULL;
+
+    /*
+     * Make a list of all the squares in the grid which we can
+     * possibly use. This list should be in preference order, which
+     * means
+     * 
+     *  - first, unknown squares on the boundary of known space
+     *  - next, unknown squares beyond that boundary
+     *         - as a very last resort, known squares, but not within one
+     *           square of the starting position.
+     * 
+     * Each of these sections needs to be shuffled independently.
+     * We do this by preparing list of all squares and then sorting
+     * it with a random secondary key.
+     */
+    sqlist = snewn(ctx->w * ctx->h, struct square);
+    n = 0;
+    for (y = 0; y < ctx->h; y++)
+       for (x = 0; x < ctx->w; x++) {
+           /*
+            * If this square is too near the starting position,
+            * don't put it on the list at all.
+            */
+           if (abs(y - ctx->sy) <= 1 && abs(x - ctx->sx) <= 1)
+               continue;
+
+           /*
+            * If this square is in the input set, also don't put
+            * it on the list!
+            */
+           if ((mask == 0 && grid[y*ctx->w+x] == -2) ||
+               (x >= setx && x < setx + 3 &&
+                y >= sety && y < sety + 3 &&
+                mask & (1 << ((y-sety)*3+(x-setx)))))
+               continue;
+
+           sqlist[n].x = x;
+           sqlist[n].y = y;
+
+           if (grid[y*ctx->w+x] != -2) {
+               sqlist[n].type = 3;    /* known square */
+           } else {
+               /*
+                * Unknown square. Examine everything around it and
+                * see if it borders on any known squares. If it
+                * does, it's class 1, otherwise it's 2.
+                */
+
+               sqlist[n].type = 2;
+
+               for (dy = -1; dy <= +1; dy++)
+                   for (dx = -1; dx <= +1; dx++)
+                       if (x+dx >= 0 && x+dx < ctx->w &&
+                           y+dy >= 0 && y+dy < ctx->h &&
+                           grid[(y+dy)*ctx->w+(x+dx)] != -2) {
+                           sqlist[n].type = 1;
+                           break;
+                       }
+           }
+
+           /*
+            * Finally, a random number to cause qsort to
+            * shuffle within each group.
+            */
+           sqlist[n].random = random_bits(ctx->rs, 31);
+
+           n++;
+       }
+
+    qsort(sqlist, n, sizeof(struct square), squarecmp);
+
+    /*
+     * Now count up the number of full and empty squares in the set
+     * we've been provided.
+     */
+    nfull = nempty = 0;
+    if (mask) {
+       for (dy = 0; dy < 3; dy++)
+           for (dx = 0; dx < 3; dx++)
+               if (mask & (1 << (dy*3+dx))) {
+                   assert(setx+dx <= ctx->w);
+                   assert(sety+dy <= ctx->h);
+                   if (ctx->grid[(sety+dy)*ctx->w+(setx+dx)])
+                       nfull++;
+                   else
+                       nempty++;
+               }
+    } else {
+       for (y = 0; y < ctx->h; y++)
+           for (x = 0; x < ctx->w; x++)
+               if (grid[y*ctx->w+x] == -2) {
+                   if (ctx->grid[y*ctx->w+x])
+                       nfull++;
+                   else
+                       nempty++;
+               }
+    }
+
+    /*
+     * Now go through our sorted list until we find either `nfull'
+     * empty squares, or `nempty' full squares; these will be
+     * swapped with the appropriate squares in the set to either
+     * fill or empty the set while keeping the same number of mines
+     * overall.
+     */
+    ntofill = ntoempty = 0;
+    if (mask) {
+       tofill = snewn(9, struct square *);
+       toempty = snewn(9, struct square *);
+    } else {
+       tofill = snewn(ctx->w * ctx->h, struct square *);
+       toempty = snewn(ctx->w * ctx->h, struct square *);
+    }
+    for (i = 0; i < n; i++) {
+       struct square *sq = &sqlist[i];
+       if (ctx->grid[sq->y * ctx->w + sq->x])
+           toempty[ntoempty++] = sq;
+       else
+           tofill[ntofill++] = sq;
+       if (ntofill == nfull || ntoempty == nempty)
+           break;
+    }
+
+    /*
+     * If we haven't found enough empty squares outside the set to
+     * empty it into _or_ enough full squares outside it to fill it
+     * up with, we'll have to settle for doing only a partial job.
+     * In this case we choose to always _fill_ the set (because
+     * this case will tend to crop up when we're working with very
+     * high mine densities and the only way to get a solvable grid
+     * is going to be to pack most of the mines solidly around the
+     * edges). So now our job is to make a list of the empty
+     * squares in the set, and shuffle that list so that we fill a
+     * random selection of them.
+     */
+    if (ntofill != nfull && ntoempty != nempty) {
+       int k;
+
+       assert(ntoempty != 0);
+
+       setlist = snewn(ctx->w * ctx->h, int);
+       i = 0;
+       if (mask) {
+           for (dy = 0; dy < 3; dy++)
+               for (dx = 0; dx < 3; dx++)
+                   if (mask & (1 << (dy*3+dx))) {
+                       assert(setx+dx <= ctx->w);
+                       assert(sety+dy <= ctx->h);
+                       if (!ctx->grid[(sety+dy)*ctx->w+(setx+dx)])
+                           setlist[i++] = (sety+dy)*ctx->w+(setx+dx);
+                   }
+       } else {
+           for (y = 0; y < ctx->h; y++)
+               for (x = 0; x < ctx->w; x++)
+                   if (grid[y*ctx->w+x] == -2) {
+                       if (!ctx->grid[y*ctx->w+x])
+                           setlist[i++] = y*ctx->w+x;
+                   }
+       }
+       assert(i > ntoempty);
+       /*
+        * Now pick `ntoempty' items at random from the list.
+        */
+       for (k = 0; k < ntoempty; k++) {
+           int index = k + random_upto(ctx->rs, i - k);
+           int tmp;
+
+           tmp = setlist[k];
+           setlist[k] = setlist[index];
+           setlist[index] = tmp;
+       }
+    } else
+       setlist = NULL;
+
+    /*
+     * Now we're pretty much there. We need to either
+     *         (a) put a mine in each of the empty squares in the set, and
+     *             take one out of each square in `toempty'
+     *         (b) take a mine out of each of the full squares in the set,
+     *             and put one in each square in `tofill'
+     * depending on which one we've found enough squares to do.
+     * 
+     * So we start by constructing our list of changes to return to
+     * the solver, so that it can update its data structures
+     * efficiently rather than having to rescan the whole grid.
+     */
+    ret = snew(struct perturbations);
+    if (ntofill == nfull) {
+       todo = tofill;
+       ntodo = ntofill;
+       dtodo = +1;
+       dset = -1;
+       sfree(toempty);
+    } else {
+       /*
+        * (We also fall into this case if we've constructed a
+        * setlist.)
+        */
+       todo = toempty;
+       ntodo = ntoempty;
+       dtodo = -1;
+       dset = +1;
+       sfree(tofill);
+    }
+    ret->n = 2 * ntodo;
+    ret->changes = snewn(ret->n, struct perturbation);
+    for (i = 0; i < ntodo; i++) {
+       ret->changes[i].x = todo[i]->x;
+       ret->changes[i].y = todo[i]->y;
+       ret->changes[i].delta = dtodo;
+    }
+    /* now i == ntodo */
+    if (setlist) {
+       int j;
+       assert(todo == toempty);
+       for (j = 0; j < ntoempty; j++) {
+           ret->changes[i].x = setlist[j] % ctx->w;
+           ret->changes[i].y = setlist[j] / ctx->w;
+           ret->changes[i].delta = dset;
+           i++;
+       }
+       sfree(setlist);
+    } else if (mask) {
+       for (dy = 0; dy < 3; dy++)
+           for (dx = 0; dx < 3; dx++)
+               if (mask & (1 << (dy*3+dx))) {
+                   int currval = (ctx->grid[(sety+dy)*ctx->w+(setx+dx)] ? +1 : -1);
+                   if (dset == -currval) {
+                       ret->changes[i].x = setx + dx;
+                       ret->changes[i].y = sety + dy;
+                       ret->changes[i].delta = dset;
+                       i++;
+                   }
+               }
+    } else {
+       for (y = 0; y < ctx->h; y++)
+           for (x = 0; x < ctx->w; x++)
+               if (grid[y*ctx->w+x] == -2) {
+                   int currval = (ctx->grid[y*ctx->w+x] ? +1 : -1);
+                   if (dset == -currval) {
+                       ret->changes[i].x = x;
+                       ret->changes[i].y = y;
+                       ret->changes[i].delta = dset;
+                       i++;
+                   }
+               }
+    }
+    assert(i == ret->n);
+
+    sfree(sqlist);
+    sfree(todo);
+
+    /*
+     * Having set up the precise list of changes we're going to
+     * make, we now simply make them and return.
+     */
+    for (i = 0; i < ret->n; i++) {
+       int delta;
+
+       x = ret->changes[i].x;
+       y = ret->changes[i].y;
+       delta = ret->changes[i].delta;
+
+       /*
+        * Check we're not trying to add an existing mine or remove
+        * an absent one.
+        */
+       assert((delta < 0) ^ (ctx->grid[y*ctx->w+x] == 0));
+
+       /*
+        * Actually make the change.
+        */
+       ctx->grid[y*ctx->w+x] = (delta > 0);
+
+       /*
+        * Update any numbers already present in the grid.
+        */
+       for (dy = -1; dy <= +1; dy++)
+           for (dx = -1; dx <= +1; dx++)
+               if (x+dx >= 0 && x+dx < ctx->w &&
+                   y+dy >= 0 && y+dy < ctx->h &&
+                   grid[(y+dy)*ctx->w+(x+dx)] != -2) {
+                   if (dx == 0 && dy == 0) {
+                       /*
+                        * The square itself is marked as known in
+                        * the grid. Mark it as a mine if it's a
+                        * mine, or else work out its number.
+                        */
+                       if (delta > 0) {
+                           grid[y*ctx->w+x] = -1;
+                       } else {
+                           int dx2, dy2, minecount = 0;
+                           for (dy2 = -1; dy2 <= +1; dy2++)
+                               for (dx2 = -1; dx2 <= +1; dx2++)
+                                   if (x+dx2 >= 0 && x+dx2 < ctx->w &&
+                                       y+dy2 >= 0 && y+dy2 < ctx->h &&
+                                       ctx->grid[(y+dy2)*ctx->w+(x+dx2)])
+                                       minecount++;
+                           grid[y*ctx->w+x] = minecount;
+                       }
+                   } else {
+                       if (grid[(y+dy)*ctx->w+(x+dx)] >= 0)
+                           grid[(y+dy)*ctx->w+(x+dx)] += delta;
+                   }
+               }
+    }
+
+#ifdef GENERATION_DIAGNOSTICS
+    {
+       int yy, xx;
+       printf("grid after perturbing:\n");
+       for (yy = 0; yy < ctx->h; yy++) {
+           for (xx = 0; xx < ctx->w; xx++) {
+               int v = ctx->grid[yy*ctx->w+xx];
+               if (yy == ctx->sy && xx == ctx->sx) {
+                   assert(!v);
+                   putchar('S');
+               } else if (v) {
+                   putchar('*');
+               } else {
+                   putchar('-');
+               }
+           }
+           putchar('\n');
+       }
+       printf("\n");
+    }
+#endif
+
+    return ret;
+}
+
+static char *minegen(int w, int h, int n, int x, int y, int unique,
+                    random_state *rs)
+{
+    char *ret = snewn(w*h, char);
+    int success;
+    int ntries = 0;
+
+    do {
+       success = FALSE;
+       ntries++;
+
+       memset(ret, 0, w*h);
+
+       /*
+        * Start by placing n mines, none of which is at x,y or within
+        * one square of it.
+        */
+       {
+           int *tmp = snewn(w*h, int);
+           int i, j, k, nn;
+
+           /*
+            * Write down the list of possible mine locations.
+            */
+           k = 0;
+           for (i = 0; i < h; i++)
+               for (j = 0; j < w; j++)
+                   if (abs(i - y) > 1 || abs(j - x) > 1)
+                       tmp[k++] = i*w+j;
+
+           /*
+            * Now pick n off the list at random.
+            */
+           nn = n;
+           while (nn-- > 0) {
+               i = random_upto(rs, k);
+               ret[tmp[i]] = 1;
+               tmp[i] = tmp[--k];
+           }
+
+           sfree(tmp);
+       }
+
+#ifdef GENERATION_DIAGNOSTICS
+       {
+           int yy, xx;
+           printf("grid after initial generation:\n");
+           for (yy = 0; yy < h; yy++) {
+               for (xx = 0; xx < w; xx++) {
+                   int v = ret[yy*w+xx];
+                   if (yy == y && xx == x) {
+                       assert(!v);
+                       putchar('S');
+                   } else if (v) {
+                       putchar('*');
+                   } else {
+                       putchar('-');
+                   }
+               }
+               putchar('\n');
+           }
+           printf("\n");
+       }
+#endif
+
+       /*
+        * Now set up a results grid to run the solver in, and a
+        * context for the solver to open squares. Then run the solver
+        * repeatedly; if the number of perturb steps ever goes up or
+        * it ever returns -1, give up completely.
+        *
+        * We bypass this bit if we're not after a unique grid.
+         */
+       if (unique) {
+           signed char *solvegrid = snewn(w*h, signed char);
+           struct minectx actx, *ctx = &actx;
+           int solveret, prevret = -2;
+
+           ctx->grid = ret;
+           ctx->w = w;
+           ctx->h = h;
+           ctx->sx = x;
+           ctx->sy = y;
+           ctx->rs = rs;
+           ctx->allow_big_perturbs = (ntries > 100);
+
+           while (1) {
+               memset(solvegrid, -2, w*h);
+               solvegrid[y*w+x] = mineopen(ctx, x, y);
+               assert(solvegrid[y*w+x] == 0); /* by deliberate arrangement */
+
+               solveret =
+                   minesolve(w, h, n, solvegrid, mineopen, mineperturb, ctx, rs);
+               if (solveret < 0 || (prevret >= 0 && solveret >= prevret)) {
+                   success = FALSE;
+                   break;
+               } else if (solveret == 0) {
+                   success = TRUE;
+                   break;
+               }
+           }
+
+           sfree(solvegrid);
+       } else {
+           success = TRUE;
+       }
+
+    } while (!success);
+
+    return ret;
+}
+
+static char *describe_layout(char *grid, int area, int x, int y,
+                             int obfuscate)
+{
+    char *ret, *p;
+    unsigned char *bmp;
+    int i;
+
+    /*
+     * Set up the mine bitmap and obfuscate it.
+     */
+    bmp = snewn((area + 7) / 8, unsigned char);
+    memset(bmp, 0, (area + 7) / 8);
+    for (i = 0; i < area; i++) {
+        if (grid[i])
+            bmp[i / 8] |= 0x80 >> (i % 8);
+    }
+    if (obfuscate)
+        obfuscate_bitmap(bmp, area, FALSE);
+
+    /*
+     * Now encode the resulting bitmap in hex. We can work to
+     * nibble rather than byte granularity, since the obfuscation
+     * function guarantees to return a bit string of the same
+     * length as its input.
+     */
+    ret = snewn((area+3)/4 + 100, char);
+    p = ret + sprintf(ret, "%d,%d,%s", x, y,
+                      obfuscate ? "m" : "u");   /* 'm' == masked */
+    for (i = 0; i < (area+3)/4; i++) {
+        int v = bmp[i/2];
+        if (i % 2 == 0)
+            v >>= 4;
+        *p++ = "0123456789abcdef"[v & 0xF];
+    }
+    *p = '\0';
+
+    sfree(bmp);
+
+    return ret;
+}
+
+static char *new_mine_layout(int w, int h, int n, int x, int y, int unique,
+                            random_state *rs, char **game_desc)
+{
+    char *grid;
+
+#ifdef TEST_OBFUSCATION
+    static int tested_obfuscation = FALSE;
+    if (!tested_obfuscation) {
+       /*
+        * A few simple test vectors for the obfuscator.
+        * 
+        * First test: the 28-bit stream 1234567. This divides up
+        * into 1234 and 567[0]. The SHA of 56 70 30 (appending
+        * "0") is 15ce8ab946640340bbb99f3f48fd2c45d1a31d30. Thus,
+        * we XOR the 16-bit string 15CE into the input 1234 to get
+        * 07FA. Next, we SHA that with "0": the SHA of 07 FA 30 is
+        * 3370135c5e3da4fed937adc004a79533962b6391. So we XOR the
+        * 12-bit string 337 into the input 567 to get 650. Thus
+        * our output is 07FA650.
+        */
+       {
+           unsigned char bmp1[] = "\x12\x34\x56\x70";
+           obfuscate_bitmap(bmp1, 28, FALSE);
+           printf("test 1 encode: %s\n",
+                  memcmp(bmp1, "\x07\xfa\x65\x00", 4) ? "failed" : "passed");
+           obfuscate_bitmap(bmp1, 28, TRUE);
+           printf("test 1 decode: %s\n",
+                  memcmp(bmp1, "\x12\x34\x56\x70", 4) ? "failed" : "passed");
+       }
+       /*
+        * Second test: a long string to make sure we switch from
+        * one SHA to the next correctly. My input string this time
+        * is simply fifty bytes of zeroes.
+        */
+       {
+           unsigned char bmp2[50];
+           unsigned char bmp2a[50];
+           memset(bmp2, 0, 50);
+           memset(bmp2a, 0, 50);
+           obfuscate_bitmap(bmp2, 50 * 8, FALSE);
+           /*
+            * SHA of twenty-five zero bytes plus "0" is
+            * b202c07b990c01f6ff2d544707f60e506019b671. SHA of
+            * twenty-five zero bytes plus "1" is
+            * fcb1d8b5a2f6b592fe6780b36aa9d65dd7aa6db9. Thus our
+            * first half becomes
+            * b202c07b990c01f6ff2d544707f60e506019b671fcb1d8b5a2.
+            * 
+            * SHA of that lot plus "0" is
+            * 10b0af913db85d37ca27f52a9f78bba3a80030db. SHA of the
+            * same string plus "1" is
+            * 3d01d8df78e76d382b8106f480135a1bc751d725. So the
+            * second half becomes
+            * 10b0af913db85d37ca27f52a9f78bba3a80030db3d01d8df78.
+            */
+           printf("test 2 encode: %s\n",
+                  memcmp(bmp2, "\xb2\x02\xc0\x7b\x99\x0c\x01\xf6\xff\x2d\x54"
+                         "\x47\x07\xf6\x0e\x50\x60\x19\xb6\x71\xfc\xb1\xd8"
+                         "\xb5\xa2\x10\xb0\xaf\x91\x3d\xb8\x5d\x37\xca\x27"
+                         "\xf5\x2a\x9f\x78\xbb\xa3\xa8\x00\x30\xdb\x3d\x01"
+                         "\xd8\xdf\x78", 50) ? "failed" : "passed");
+           obfuscate_bitmap(bmp2, 50 * 8, TRUE);
+           printf("test 2 decode: %s\n",
+                  memcmp(bmp2, bmp2a, 50) ? "failed" : "passed");
+       }
+    }
+#endif
+
+    grid = minegen(w, h, n, x, y, unique, rs);
+
+    if (game_desc)
+        *game_desc = describe_layout(grid, w * h, x, y, TRUE);
+
+    return grid;
+}
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                          char **aux, int interactive)
+{
+    /*
+     * We generate the coordinates of an initial click even if they
+     * aren't actually used. This has the effect of harmonising the
+     * random number usage between interactive and batch use: if
+     * you use `mines --generate' with an explicit random seed, you
+     * should get exactly the same results as if you type the same
+     * random seed into the interactive game and click in the same
+     * initial location. (Of course you won't get the same grid if
+     * you click in a _different_ initial location, but there's
+     * nothing to be done about that.)
+     */
+    int x = random_upto(rs, params->w);
+    int y = random_upto(rs, params->h);
+
+    if (!interactive) {
+       /*
+        * For batch-generated grids, pre-open one square.
+        */
+       char *grid;
+       char *desc;
+
+       grid = new_mine_layout(params->w, params->h, params->n,
+                              x, y, params->unique, rs, &desc);
+       sfree(grid);
+       return desc;
+    } else {
+       char *rsdesc, *desc;
+
+       rsdesc = random_state_encode(rs);
+       desc = snewn(strlen(rsdesc) + 100, char);
+       sprintf(desc, "r%d,%c,%s", params->n, (char)(params->unique ? 'u' : 'a'), rsdesc);
+       sfree(rsdesc);
+       return desc;
+    }
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    int wh = params->w * params->h;
+    int x, y;
+
+    if (*desc == 'r') {
+        desc++;
+       if (!*desc || !isdigit((unsigned char)*desc))
+           return "No initial mine count in game description";
+       while (*desc && isdigit((unsigned char)*desc))
+           desc++;                    /* skip over mine count */
+       if (*desc != ',')
+           return "No ',' after initial x-coordinate in game description";
+       desc++;
+       if (*desc != 'u' && *desc != 'a')
+           return "No uniqueness specifier in game description";
+       desc++;
+       if (*desc != ',')
+           return "No ',' after uniqueness specifier in game description";
+       /* now ignore the rest */
+    } else {
+       if (*desc && isdigit((unsigned char)*desc)) {
+           x = atoi(desc);
+           if (x < 0 || x >= params->w)
+               return "Initial x-coordinate was out of range";
+           while (*desc && isdigit((unsigned char)*desc))
+               desc++;                /* skip over x coordinate */
+           if (*desc != ',')
+               return "No ',' after initial x-coordinate in game description";
+           desc++;                    /* eat comma */
+           if (!*desc || !isdigit((unsigned char)*desc))
+               return "No initial y-coordinate in game description";
+           y = atoi(desc);
+           if (y < 0 || y >= params->h)
+               return "Initial y-coordinate was out of range";
+           while (*desc && isdigit((unsigned char)*desc))
+               desc++;                /* skip over y coordinate */
+           if (*desc != ',')
+               return "No ',' after initial y-coordinate in game description";
+           desc++;                    /* eat comma */
+       }
+       /* eat `m' for `masked' or `u' for `unmasked', if present */
+       if (*desc == 'm' || *desc == 'u')
+           desc++;
+       /* now just check length of remainder */
+       if (strlen(desc) != (wh+3)/4)
+           return "Game description is wrong length";
+    }
+
+    return NULL;
+}
+
+static int open_square(game_state *state, int x, int y)
+{
+    int w = state->w, h = state->h;
+    int xx, yy, nmines, ncovered;
+
+    if (!state->layout->mines) {
+       /*
+        * We have a preliminary game in which the mine layout
+        * hasn't been generated yet. Generate it based on the
+        * initial click location.
+        */
+       char *desc, *privdesc;
+       state->layout->mines = new_mine_layout(w, h, state->layout->n,
+                                              x, y, state->layout->unique,
+                                              state->layout->rs,
+                                              &desc);
+       /*
+        * Find the trailing substring of the game description
+        * corresponding to just the mine layout; we will use this
+        * as our second `private' game ID for serialisation.
+        */
+       privdesc = desc;
+       while (*privdesc && isdigit((unsigned char)*privdesc)) privdesc++;
+       if (*privdesc == ',') privdesc++;
+       while (*privdesc && isdigit((unsigned char)*privdesc)) privdesc++;
+       if (*privdesc == ',') privdesc++;
+       assert(*privdesc == 'm');
+       midend_supersede_game_desc(state->layout->me, desc, privdesc);
+       sfree(desc);
+       random_free(state->layout->rs);
+       state->layout->rs = NULL;
+    }
+
+    if (state->layout->mines[y*w+x]) {
+       /*
+        * The player has landed on a mine. Bad luck. Expose the
+        * mine that killed them, but not the rest (in case they
+        * want to Undo and carry on playing).
+        */
+       state->dead = TRUE;
+       state->grid[y*w+x] = 65;
+       return -1;
+    }
+
+    /*
+     * Otherwise, the player has opened a safe square. Mark it to-do.
+     */
+    state->grid[y*w+x] = -10;         /* `todo' value internal to this func */
+
+    /*
+     * Now go through the grid finding all `todo' values and
+     * opening them. Every time one of them turns out to have no
+     * neighbouring mines, we add all its unopened neighbours to
+     * the list as well.
+     * 
+     * FIXME: We really ought to be able to do this better than
+     * using repeated N^2 scans of the grid.
+     */
+    while (1) {
+       int done_something = FALSE;
+
+       for (yy = 0; yy < h; yy++)
+           for (xx = 0; xx < w; xx++)
+               if (state->grid[yy*w+xx] == -10) {
+                   int dx, dy, v;
+
+                   assert(!state->layout->mines[yy*w+xx]);
+
+                   v = 0;
+
+                   for (dx = -1; dx <= +1; dx++)
+                       for (dy = -1; dy <= +1; dy++)
+                           if (xx+dx >= 0 && xx+dx < state->w &&
+                               yy+dy >= 0 && yy+dy < state->h &&
+                               state->layout->mines[(yy+dy)*w+(xx+dx)])
+                               v++;
+
+                   state->grid[yy*w+xx] = v;
+
+                   if (v == 0) {
+                       for (dx = -1; dx <= +1; dx++)
+                           for (dy = -1; dy <= +1; dy++)
+                               if (xx+dx >= 0 && xx+dx < state->w &&
+                                   yy+dy >= 0 && yy+dy < state->h &&
+                                   state->grid[(yy+dy)*w+(xx+dx)] == -2)
+                                   state->grid[(yy+dy)*w+(xx+dx)] = -10;
+                   }
+
+                   done_something = TRUE;
+               }
+
+       if (!done_something)
+           break;
+    }
+
+    /*
+     * Finally, scan the grid and see if exactly as many squares
+     * are still covered as there are mines. If so, set the `won'
+     * flag and fill in mine markers on all covered squares.
+     */
+    nmines = ncovered = 0;
+    for (yy = 0; yy < h; yy++)
+       for (xx = 0; xx < w; xx++) {
+           if (state->grid[yy*w+xx] < 0)
+               ncovered++;
+           if (state->layout->mines[yy*w+xx])
+               nmines++;
+       }
+    assert(ncovered >= nmines);
+    if (ncovered == nmines) {
+       for (yy = 0; yy < h; yy++)
+           for (xx = 0; xx < w; xx++) {
+               if (state->grid[yy*w+xx] < 0)
+                   state->grid[yy*w+xx] = -1;
+       }
+       state->won = TRUE;
+    }
+
+    return 0;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    game_state *state = snew(game_state);
+    int i, wh, x, y, masked;
+    unsigned char *bmp;
+
+    state->w = params->w;
+    state->h = params->h;
+    state->n = params->n;
+    state->dead = state->won = FALSE;
+    state->used_solve = FALSE;
+
+    wh = state->w * state->h;
+
+    state->layout = snew(struct mine_layout);
+    memset(state->layout, 0, sizeof(struct mine_layout));
+    state->layout->refcount = 1;
+
+    state->grid = snewn(wh, signed char);
+    memset(state->grid, -2, wh);
+
+    if (*desc == 'r') {
+       desc++;
+       state->layout->n = atoi(desc);
+       while (*desc && isdigit((unsigned char)*desc))
+           desc++;                    /* skip over mine count */
+       if (*desc) desc++;             /* eat comma */
+       if (*desc == 'a')
+           state->layout->unique = FALSE;
+       else
+           state->layout->unique = TRUE;
+       desc++;
+       if (*desc) desc++;             /* eat comma */
+
+       state->layout->mines = NULL;
+       state->layout->rs = random_state_decode(desc);
+       state->layout->me = me;
+
+    } else {
+       state->layout->rs = NULL;
+       state->layout->me = NULL;
+       state->layout->mines = snewn(wh, char);
+
+       if (*desc && isdigit((unsigned char)*desc)) {
+           x = atoi(desc);
+           while (*desc && isdigit((unsigned char)*desc))
+               desc++;                /* skip over x coordinate */
+           if (*desc) desc++;         /* eat comma */
+           y = atoi(desc);
+           while (*desc && isdigit((unsigned char)*desc))
+               desc++;                /* skip over y coordinate */
+           if (*desc) desc++;         /* eat comma */
+       } else {
+           x = y = -1;
+       }
+
+       if (*desc == 'm') {
+           masked = TRUE;
+           desc++;
+       } else {
+           if (*desc == 'u')
+               desc++;
+           /*
+            * We permit game IDs to be entered by hand without the
+            * masking transformation.
+            */
+           masked = FALSE;
+       }
+
+       bmp = snewn((wh + 7) / 8, unsigned char);
+       memset(bmp, 0, (wh + 7) / 8);
+       for (i = 0; i < (wh+3)/4; i++) {
+           int c = desc[i];
+           int v;
+
+           assert(c != 0);            /* validate_desc should have caught */
+           if (c >= '0' && c <= '9')
+               v = c - '0';
+           else if (c >= 'a' && c <= 'f')
+               v = c - 'a' + 10;
+           else if (c >= 'A' && c <= 'F')
+               v = c - 'A' + 10;
+           else
+               v = 0;
+
+           bmp[i / 2] |= v << (4 * (1 - (i % 2)));
+       }
+
+       if (masked)
+           obfuscate_bitmap(bmp, wh, TRUE);
+
+       memset(state->layout->mines, 0, wh);
+       for (i = 0; i < wh; i++) {
+           if (bmp[i / 8] & (0x80 >> (i % 8)))
+               state->layout->mines[i] = 1;
+       }
+
+       if (x >= 0 && y >= 0)
+           open_square(state, x, y);
+        sfree(bmp);
+    }
+
+    return state;
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    game_state *ret = snew(game_state);
+
+    ret->w = state->w;
+    ret->h = state->h;
+    ret->n = state->n;
+    ret->dead = state->dead;
+    ret->won = state->won;
+    ret->used_solve = state->used_solve;
+    ret->layout = state->layout;
+    ret->layout->refcount++;
+    ret->grid = snewn(ret->w * ret->h, signed char);
+    memcpy(ret->grid, state->grid, ret->w * ret->h);
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    if (--state->layout->refcount <= 0) {
+       sfree(state->layout->mines);
+       if (state->layout->rs)
+           random_free(state->layout->rs);
+       sfree(state->layout);
+    }
+    sfree(state->grid);
+    sfree(state);
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    if (!state->layout->mines) {
+       *error = "Game has not been started yet";
+       return NULL;
+    }
+
+    return dupstr("S");
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    char *ret;
+    int x, y;
+
+    ret = snewn((state->w + 1) * state->h + 1, char);
+    for (y = 0; y < state->h; y++) {
+       for (x = 0; x < state->w; x++) {
+           int v = state->grid[y*state->w+x];
+           if (v == 0)
+               v = '-';
+           else if (v >= 1 && v <= 8)
+               v = '0' + v;
+           else if (v == -1)
+               v = '*';
+           else if (v == -2 || v == -3)
+               v = '?';
+           else if (v >= 64)
+               v = '!';
+           ret[y * (state->w+1) + x] = v;
+       }
+       ret[y * (state->w+1) + state->w] = '\n';
+    }
+    ret[(state->w + 1) * state->h] = '\0';
+
+    return ret;
+}
+
+struct game_ui {
+    int hx, hy, hradius;              /* for mouse-down highlights */
+    int validradius;
+    int flash_is_death;
+    int deaths, completed;
+    int cur_x, cur_y, cur_visible;
+};
+
+static game_ui *new_ui(const game_state *state)
+{
+    game_ui *ui = snew(game_ui);
+    ui->hx = ui->hy = -1;
+    ui->hradius = ui->validradius = 0;
+    ui->deaths = 0;
+    ui->completed = FALSE;
+    ui->flash_is_death = FALSE;               /* *shrug* */
+    ui->cur_x = ui->cur_y = ui->cur_visible = 0;
+    return ui;
+}
+
+static void free_ui(game_ui *ui)
+{
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    char buf[80];
+    /*
+     * The deaths counter and completion status need preserving
+     * across a serialisation.
+     */
+    sprintf(buf, "D%d", ui->deaths);
+    if (ui->completed)
+       strcat(buf, "C");
+    return dupstr(buf);
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+    int p= 0;
+    sscanf(encoding, "D%d%n", &ui->deaths, &p);
+    if (encoding[p] == 'C')
+       ui->completed = TRUE;
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+    if (newstate->won)
+       ui->completed = TRUE;
+}
+
+struct game_drawstate {
+    int w, h, started, tilesize, bg;
+    signed char *grid;
+    /*
+     * Items in this `grid' array have all the same values as in
+     * the game_state grid, and in addition:
+     * 
+     *         - -10 means the tile was drawn `specially' as a result of a
+     *           flash, so it will always need redrawing.
+     * 
+     *         - -22 and -23 mean the tile is highlighted for a possible
+     *           click.
+     */
+    int cur_x, cur_y; /* -1, -1 for no cursor displayed. */
+};
+
+static char *interpret_move(const game_state *from, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    int cx, cy;
+    char buf[256];
+
+    if (from->dead || from->won)
+       return NULL;                   /* no further moves permitted */
+
+    cx = FROMCOORD(x);
+    cy = FROMCOORD(y);
+
+    if (IS_CURSOR_MOVE(button)) {
+        move_cursor(button, &ui->cur_x, &ui->cur_y, from->w, from->h, 0);
+        ui->cur_visible = 1;
+        return "";
+    }
+    if (IS_CURSOR_SELECT(button)) {
+        int v = from->grid[ui->cur_y * from->w + ui->cur_x];
+
+        if (!ui->cur_visible) {
+            ui->cur_visible = 1;
+            return "";
+        }
+        if (button == CURSOR_SELECT2) {
+            /* As for RIGHT_BUTTON; only works on covered square. */
+            if (v != -2 && v != -1)
+                return NULL;
+            sprintf(buf, "F%d,%d", ui->cur_x, ui->cur_y);
+            return dupstr(buf);
+        }
+        /* Otherwise, treat as LEFT_BUTTON, for a single square. */
+        if (v == -2 || v == -3) {
+            if (from->layout->mines &&
+                from->layout->mines[ui->cur_y * from->w + ui->cur_x])
+                ui->deaths++;
+
+            sprintf(buf, "O%d,%d", ui->cur_x, ui->cur_y);
+            return dupstr(buf);
+        }
+        cx = ui->cur_x; cy = ui->cur_y;
+        ui->validradius = 1;
+        goto uncover;
+    }
+
+    if (button == LEFT_BUTTON || button == LEFT_DRAG ||
+       button == MIDDLE_BUTTON || button == MIDDLE_DRAG) {
+       if (cx < 0 || cx >= from->w || cy < 0 || cy >= from->h)
+           return NULL;
+
+       /*
+        * Mouse-downs and mouse-drags just cause highlighting
+        * updates.
+        */
+       ui->hx = cx;
+       ui->hy = cy;
+       ui->hradius = (from->grid[cy*from->w+cx] >= 0 ? 1 : 0);
+       if (button == LEFT_BUTTON)
+           ui->validradius = ui->hradius;
+       else if (button == MIDDLE_BUTTON)
+           ui->validradius = 1;
+        ui->cur_visible = 0;
+       return "";
+    }
+
+    if (button == RIGHT_BUTTON) {
+       if (cx < 0 || cx >= from->w || cy < 0 || cy >= from->h)
+           return NULL;
+
+       /*
+        * Right-clicking only works on a covered square, and it
+        * toggles between -1 (marked as mine) and -2 (not marked
+        * as mine).
+        *
+        * FIXME: question marks.
+        */
+       if (from->grid[cy * from->w + cx] != -2 &&
+           from->grid[cy * from->w + cx] != -1)
+           return NULL;
+
+       sprintf(buf, "F%d,%d", cx, cy);
+       return dupstr(buf);
+    }
+
+    if (button == LEFT_RELEASE || button == MIDDLE_RELEASE) {
+       ui->hx = ui->hy = -1;
+       ui->hradius = 0;
+
+       /*
+        * At this stage we must never return NULL: we have adjusted
+        * the ui, so at worst we return "".
+        */
+       if (cx < 0 || cx >= from->w || cy < 0 || cy >= from->h)
+           return "";
+
+       /*
+        * Left-clicking on a covered square opens a tile. Not
+        * permitted if the tile is marked as a mine, for safety.
+        * (Unmark it and _then_ open it.)
+        */
+       if (button == LEFT_RELEASE &&
+           (from->grid[cy * from->w + cx] == -2 ||
+            from->grid[cy * from->w + cx] == -3) &&
+           ui->validradius == 0) {
+           /* Check if you've killed yourself. */
+           if (from->layout->mines && from->layout->mines[cy * from->w + cx])
+               ui->deaths++;
+
+           sprintf(buf, "O%d,%d", cx, cy);
+           return dupstr(buf);
+       }
+        goto uncover;
+    }
+    return NULL;
+
+uncover:
+    {
+       /*
+        * Left-clicking or middle-clicking on an uncovered tile:
+        * first we check to see if the number of mine markers
+        * surrounding the tile is equal to its mine count, and if
+        * so then we open all other surrounding squares.
+        */
+       if (from->grid[cy * from->w + cx] > 0 && ui->validradius == 1) {
+           int dy, dx, n;
+
+           /* Count mine markers. */
+           n = 0;
+           for (dy = -1; dy <= +1; dy++)
+               for (dx = -1; dx <= +1; dx++)
+                   if (cx+dx >= 0 && cx+dx < from->w &&
+                       cy+dy >= 0 && cy+dy < from->h) {
+                       if (from->grid[(cy+dy)*from->w+(cx+dx)] == -1)
+                           n++;
+                   }
+
+           if (n == from->grid[cy * from->w + cx]) {
+
+               /*
+                * Now see if any of the squares we're clearing
+                * contains a mine (which will happen iff you've
+                * incorrectly marked the mines around the clicked
+                * square). If so, we open _just_ those squares, to
+                * reveal as little additional information as we
+                * can.
+                */
+               char *p = buf;
+               char *sep = "";
+
+               for (dy = -1; dy <= +1; dy++)
+                   for (dx = -1; dx <= +1; dx++)
+                       if (cx+dx >= 0 && cx+dx < from->w &&
+                           cy+dy >= 0 && cy+dy < from->h) {
+                           if (from->grid[(cy+dy)*from->w+(cx+dx)] != -1 &&
+                               from->layout->mines &&
+                               from->layout->mines[(cy+dy)*from->w+(cx+dx)]) {
+                               p += sprintf(p, "%sO%d,%d", sep, cx+dx, cy+dy);
+                               sep = ";";
+                           }
+                       }
+
+               if (p > buf) {
+                   ui->deaths++;
+               } else {
+                   sprintf(buf, "C%d,%d", cx, cy);
+               }
+
+               return dupstr(buf);
+           }
+       }
+
+       return "";
+    }
+}
+
+static game_state *execute_move(const game_state *from, const char *move)
+{
+    int cy, cx;
+    game_state *ret;
+
+    if (!strcmp(move, "S")) {
+       int yy, xx;
+
+       ret = dup_game(from);
+        if (!ret->dead) {
+            /*
+             * If the player is still alive at the moment of pressing
+             * Solve, expose the entire grid as if it were a completed
+             * solution.
+             */
+            for (yy = 0; yy < ret->h; yy++)
+                for (xx = 0; xx < ret->w; xx++) {
+
+                    if (ret->layout->mines[yy*ret->w+xx]) {
+                        ret->grid[yy*ret->w+xx] = -1;
+                    } else {
+                        int dx, dy, v;
+
+                        v = 0;
+
+                        for (dx = -1; dx <= +1; dx++)
+                            for (dy = -1; dy <= +1; dy++)
+                                if (xx+dx >= 0 && xx+dx < ret->w &&
+                                    yy+dy >= 0 && yy+dy < ret->h &&
+                                    ret->layout->mines[(yy+dy)*ret->w+(xx+dx)])
+                                    v++;
+
+                        ret->grid[yy*ret->w+xx] = v;
+                    }
+                }
+        } else {
+            /*
+             * If the player pressed Solve _after dying_, show a full
+             * corrections grid in the style of standard Minesweeper.
+             * Players who don't like Mines's behaviour on death of
+             * only showing the mine that killed you (so that in case
+             * of a typo you can undo and carry on without the rest of
+             * the grid being spoiled) can use this to get the display
+             * that ordinary Minesweeper would have given them.
+             */
+            for (yy = 0; yy < ret->h; yy++)
+                for (xx = 0; xx < ret->w; xx++) {
+                    int pos = yy*ret->w+xx;
+                    if ((ret->grid[pos] == -2 || ret->grid[pos] == -3) &&
+                        ret->layout->mines[pos]) {
+                        ret->grid[pos] = 64;
+                    } else if (ret->grid[pos] == -1 &&
+                               !ret->layout->mines[pos]) {
+                        ret->grid[pos] = 66;
+                    }
+                }
+        }
+        ret->used_solve = TRUE;
+
+       return ret;
+    } else {
+       ret = dup_game(from);
+
+       while (*move) {
+           if (move[0] == 'F' &&
+               sscanf(move+1, "%d,%d", &cx, &cy) == 2 &&
+               cx >= 0 && cx < from->w && cy >= 0 && cy < from->h) {
+               ret->grid[cy * from->w + cx] ^= (-2 ^ -1);
+           } else if (move[0] == 'O' &&
+                      sscanf(move+1, "%d,%d", &cx, &cy) == 2 &&
+                      cx >= 0 && cx < from->w && cy >= 0 && cy < from->h) {
+               open_square(ret, cx, cy);
+           } else if (move[0] == 'C' &&
+                      sscanf(move+1, "%d,%d", &cx, &cy) == 2 &&
+                      cx >= 0 && cx < from->w && cy >= 0 && cy < from->h) {
+               int dx, dy;
+
+               for (dy = -1; dy <= +1; dy++)
+                   for (dx = -1; dx <= +1; dx++)
+                       if (cx+dx >= 0 && cx+dx < ret->w &&
+                           cy+dy >= 0 && cy+dy < ret->h &&
+                           (ret->grid[(cy+dy)*ret->w+(cx+dx)] == -2 ||
+                            ret->grid[(cy+dy)*ret->w+(cx+dx)] == -3))
+                           open_square(ret, cx+dx, cy+dy);
+           } else {
+               free_game(ret);
+               return NULL;
+           }
+
+           while (*move && *move != ';') move++;
+           if (*move) move++;
+       }
+
+       return ret;
+    }
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    struct { int tilesize; } ads, *ds = &ads;
+    ads.tilesize = tilesize;
+
+    *x = BORDER * 2 + TILE_SIZE * params->w;
+    *y = BORDER * 2 + TILE_SIZE * params->h;
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+
+    frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
+
+    ret[COL_BACKGROUND2 * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 19.0F / 20.0F;
+    ret[COL_BACKGROUND2 * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 19.0F / 20.0F;
+    ret[COL_BACKGROUND2 * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] * 19.0F / 20.0F;
+
+    ret[COL_1 * 3 + 0] = 0.0F;
+    ret[COL_1 * 3 + 1] = 0.0F;
+    ret[COL_1 * 3 + 2] = 1.0F;
+
+    ret[COL_2 * 3 + 0] = 0.0F;
+    ret[COL_2 * 3 + 1] = 0.5F;
+    ret[COL_2 * 3 + 2] = 0.0F;
+
+    ret[COL_3 * 3 + 0] = 1.0F;
+    ret[COL_3 * 3 + 1] = 0.0F;
+    ret[COL_3 * 3 + 2] = 0.0F;
+
+    ret[COL_4 * 3 + 0] = 0.0F;
+    ret[COL_4 * 3 + 1] = 0.0F;
+    ret[COL_4 * 3 + 2] = 0.5F;
+
+    ret[COL_5 * 3 + 0] = 0.5F;
+    ret[COL_5 * 3 + 1] = 0.0F;
+    ret[COL_5 * 3 + 2] = 0.0F;
+
+    ret[COL_6 * 3 + 0] = 0.0F;
+    ret[COL_6 * 3 + 1] = 0.5F;
+    ret[COL_6 * 3 + 2] = 0.5F;
+
+    ret[COL_7 * 3 + 0] = 0.0F;
+    ret[COL_7 * 3 + 1] = 0.0F;
+    ret[COL_7 * 3 + 2] = 0.0F;
+
+    ret[COL_8 * 3 + 0] = 0.5F;
+    ret[COL_8 * 3 + 1] = 0.5F;
+    ret[COL_8 * 3 + 2] = 0.5F;
+
+    ret[COL_MINE * 3 + 0] = 0.0F;
+    ret[COL_MINE * 3 + 1] = 0.0F;
+    ret[COL_MINE * 3 + 2] = 0.0F;
+
+    ret[COL_BANG * 3 + 0] = 1.0F;
+    ret[COL_BANG * 3 + 1] = 0.0F;
+    ret[COL_BANG * 3 + 2] = 0.0F;
+
+    ret[COL_CROSS * 3 + 0] = 1.0F;
+    ret[COL_CROSS * 3 + 1] = 0.0F;
+    ret[COL_CROSS * 3 + 2] = 0.0F;
+
+    ret[COL_FLAG * 3 + 0] = 1.0F;
+    ret[COL_FLAG * 3 + 1] = 0.0F;
+    ret[COL_FLAG * 3 + 2] = 0.0F;
+
+    ret[COL_FLAGBASE * 3 + 0] = 0.0F;
+    ret[COL_FLAGBASE * 3 + 1] = 0.0F;
+    ret[COL_FLAGBASE * 3 + 2] = 0.0F;
+
+    ret[COL_QUERY * 3 + 0] = 0.0F;
+    ret[COL_QUERY * 3 + 1] = 0.0F;
+    ret[COL_QUERY * 3 + 2] = 0.0F;
+
+    ret[COL_HIGHLIGHT * 3 + 0] = 1.0F;
+    ret[COL_HIGHLIGHT * 3 + 1] = 1.0F;
+    ret[COL_HIGHLIGHT * 3 + 2] = 1.0F;
+
+    ret[COL_LOWLIGHT * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 2.0F / 3.0F;
+    ret[COL_LOWLIGHT * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 2.0F / 3.0F;
+    ret[COL_LOWLIGHT * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] * 2.0F / 3.0F;
+
+    ret[COL_WRONGNUMBER * 3 + 0] = 1.0F;
+    ret[COL_WRONGNUMBER * 3 + 1] = 0.6F;
+    ret[COL_WRONGNUMBER * 3 + 2] = 0.6F;
+
+    /* Red tinge to a light colour, for the cursor. */
+    ret[COL_CURSOR * 3 + 0] = ret[COL_HIGHLIGHT * 3 + 0];
+    ret[COL_CURSOR * 3 + 1] = ret[COL_HIGHLIGHT * 3 + 0] / 2.0F;
+    ret[COL_CURSOR * 3 + 2] = ret[COL_HIGHLIGHT * 3 + 0] / 2.0F;
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    struct game_drawstate *ds = snew(struct game_drawstate);
+
+    ds->w = state->w;
+    ds->h = state->h;
+    ds->started = FALSE;
+    ds->tilesize = 0;                  /* not decided yet */
+    ds->grid = snewn(ds->w * ds->h, signed char);
+    ds->bg = -1;
+    ds->cur_x = ds->cur_y = -1;
+
+    memset(ds->grid, -99, ds->w * ds->h);
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds->grid);
+    sfree(ds);
+}
+
+static void draw_tile(drawing *dr, game_drawstate *ds,
+                      int x, int y, int v, int bg)
+{
+    if (v < 0) {
+        int coords[12];
+       int hl = 0;
+
+       if (v == -22 || v == -23) {
+           v += 20;
+
+           /*
+            * Omit the highlights in this case.
+            */
+           draw_rect(dr, x, y, TILE_SIZE, TILE_SIZE,
+                      bg == COL_BACKGROUND ? COL_BACKGROUND2 : bg);
+           draw_line(dr, x, y, x + TILE_SIZE - 1, y, COL_LOWLIGHT);
+           draw_line(dr, x, y, x, y + TILE_SIZE - 1, COL_LOWLIGHT);
+       } else {
+           /*
+            * Draw highlights to indicate the square is covered.
+            */
+           coords[0] = x + TILE_SIZE - 1;
+           coords[1] = y + TILE_SIZE - 1;
+           coords[2] = x + TILE_SIZE - 1;
+           coords[3] = y;
+           coords[4] = x;
+           coords[5] = y + TILE_SIZE - 1;
+           draw_polygon(dr, coords, 3, COL_LOWLIGHT ^ hl, COL_LOWLIGHT ^ hl);
+
+           coords[0] = x;
+           coords[1] = y;
+           draw_polygon(dr, coords, 3, COL_HIGHLIGHT ^ hl,
+                        COL_HIGHLIGHT ^ hl);
+
+           draw_rect(dr, x + HIGHLIGHT_WIDTH, y + HIGHLIGHT_WIDTH,
+                     TILE_SIZE - 2*HIGHLIGHT_WIDTH, TILE_SIZE - 2*HIGHLIGHT_WIDTH,
+                     bg);
+       }
+
+       if (v == -1) {
+           /*
+            * Draw a flag.
+            */
+#define SETCOORD(n, dx, dy) do { \
+    coords[(n)*2+0] = x + (int)(TILE_SIZE * (dx)); \
+    coords[(n)*2+1] = y + (int)(TILE_SIZE * (dy)); \
+} while (0)
+           SETCOORD(0, 0.6F,  0.35F);
+           SETCOORD(1, 0.6F,  0.7F);
+           SETCOORD(2, 0.8F,  0.8F);
+           SETCOORD(3, 0.25F, 0.8F);
+           SETCOORD(4, 0.55F, 0.7F);
+           SETCOORD(5, 0.55F, 0.35F);
+           draw_polygon(dr, coords, 6, COL_FLAGBASE, COL_FLAGBASE);
+
+           SETCOORD(0, 0.6F, 0.2F);
+           SETCOORD(1, 0.6F, 0.5F);
+           SETCOORD(2, 0.2F, 0.35F);
+           draw_polygon(dr, coords, 3, COL_FLAG, COL_FLAG);
+#undef SETCOORD
+
+       } else if (v == -3) {
+           /*
+            * Draw a question mark.
+            */
+           draw_text(dr, x + TILE_SIZE / 2, y + TILE_SIZE / 2,
+                     FONT_VARIABLE, TILE_SIZE * 6 / 8,
+                     ALIGN_VCENTRE | ALIGN_HCENTRE,
+                     COL_QUERY, "?");
+       }
+    } else {
+       /*
+        * Clear the square to the background colour, and draw thin
+        * grid lines along the top and left.
+        * 
+        * Exception is that for value 65 (mine we've just trodden
+        * on), we clear the square to COL_BANG.
+        */
+        if (v & 32) {
+            bg = COL_WRONGNUMBER;
+            v &= ~32;
+        }
+        draw_rect(dr, x, y, TILE_SIZE, TILE_SIZE,
+                 (v == 65 ? COL_BANG :
+                   bg == COL_BACKGROUND ? COL_BACKGROUND2 : bg));
+       draw_line(dr, x, y, x + TILE_SIZE - 1, y, COL_LOWLIGHT);
+       draw_line(dr, x, y, x, y + TILE_SIZE - 1, COL_LOWLIGHT);
+
+       if (v > 0 && v <= 8) {
+           /*
+            * Mark a number.
+            */
+           char str[2];
+           str[0] = v + '0';
+           str[1] = '\0';
+           draw_text(dr, x + TILE_SIZE / 2, y + TILE_SIZE / 2,
+                     FONT_VARIABLE, TILE_SIZE * 7 / 8,
+                     ALIGN_VCENTRE | ALIGN_HCENTRE,
+                     (COL_1 - 1) + v, str);
+
+       } else if (v >= 64) {
+           /*
+            * Mark a mine.
+            */
+           {
+               int cx = x + TILE_SIZE / 2;
+               int cy = y + TILE_SIZE / 2;
+               int r = TILE_SIZE / 2 - 3;
+
+               draw_circle(dr, cx, cy, 5*r/6, COL_MINE, COL_MINE);
+               draw_rect(dr, cx - r/6, cy - r, 2*(r/6)+1, 2*r+1, COL_MINE);
+               draw_rect(dr, cx - r, cy - r/6, 2*r+1, 2*(r/6)+1, COL_MINE);
+               draw_rect(dr, cx-r/3, cy-r/3, r/3, r/4, COL_HIGHLIGHT);
+           }
+
+           if (v == 66) {
+               /*
+                * Cross through the mine.
+                */
+               int dx;
+               for (dx = -1; dx <= +1; dx++) {
+                   draw_line(dr, x + 3 + dx, y + 2,
+                             x + TILE_SIZE - 3 + dx,
+                             y + TILE_SIZE - 2, COL_CROSS);
+                   draw_line(dr, x + TILE_SIZE - 3 + dx, y + 2,
+                             x + 3 + dx, y + TILE_SIZE - 2,
+                             COL_CROSS);
+               }
+           }
+       }
+    }
+
+    draw_update(dr, x, y, TILE_SIZE, TILE_SIZE);
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int x, y;
+    int mines, markers, bg;
+    int cx = -1, cy = -1, cmoved;
+
+    if (flashtime) {
+       int frame = (int)(flashtime / FLASH_FRAME);
+       if (frame % 2)
+           bg = (ui->flash_is_death ? COL_BACKGROUND : COL_LOWLIGHT);
+       else
+           bg = (ui->flash_is_death ? COL_BANG : COL_HIGHLIGHT);
+    } else
+       bg = COL_BACKGROUND;
+
+    if (!ds->started) {
+        int coords[10];
+
+       draw_rect(dr, 0, 0,
+                 TILE_SIZE * state->w + 2 * BORDER,
+                 TILE_SIZE * state->h + 2 * BORDER, COL_BACKGROUND);
+       draw_update(dr, 0, 0,
+                   TILE_SIZE * state->w + 2 * BORDER,
+                   TILE_SIZE * state->h + 2 * BORDER);
+
+        /*
+         * Recessed area containing the whole puzzle.
+         */
+        coords[0] = COORD(state->w) + OUTER_HIGHLIGHT_WIDTH - 1;
+        coords[1] = COORD(state->h) + OUTER_HIGHLIGHT_WIDTH - 1;
+        coords[2] = COORD(state->w) + OUTER_HIGHLIGHT_WIDTH - 1;
+        coords[3] = COORD(0) - OUTER_HIGHLIGHT_WIDTH;
+        coords[4] = coords[2] - TILE_SIZE;
+        coords[5] = coords[3] + TILE_SIZE;
+        coords[8] = COORD(0) - OUTER_HIGHLIGHT_WIDTH;
+        coords[9] = COORD(state->h) + OUTER_HIGHLIGHT_WIDTH - 1;
+        coords[6] = coords[8] + TILE_SIZE;
+        coords[7] = coords[9] - TILE_SIZE;
+        draw_polygon(dr, coords, 5, COL_HIGHLIGHT, COL_HIGHLIGHT);
+
+        coords[1] = COORD(0) - OUTER_HIGHLIGHT_WIDTH;
+        coords[0] = COORD(0) - OUTER_HIGHLIGHT_WIDTH;
+        draw_polygon(dr, coords, 5, COL_LOWLIGHT, COL_LOWLIGHT);
+
+        ds->started = TRUE;
+    }
+
+    if (ui->cur_visible) cx = ui->cur_x;
+    if (ui->cur_visible) cy = ui->cur_y;
+    cmoved = (cx != ds->cur_x || cy != ds->cur_y);
+
+    /*
+     * Now draw the tiles. Also in this loop, count up the number
+     * of mines and mine markers.
+     */
+    mines = markers = 0;
+    for (y = 0; y < ds->h; y++)
+       for (x = 0; x < ds->w; x++) {
+           int v = state->grid[y*ds->w+x], cc = 0;
+
+           if (v == -1)
+               markers++;
+           if (state->layout->mines && state->layout->mines[y*ds->w+x])
+               mines++;
+
+            if (v >= 0 && v <= 8) {
+                /*
+                 * Count up the flags around this tile, and if
+                 * there are too _many_, highlight the tile.
+                 */
+                int dx, dy, flags = 0;
+
+                for (dy = -1; dy <= +1; dy++)
+                    for (dx = -1; dx <= +1; dx++) {
+                        int nx = x+dx, ny = y+dy;
+                        if (nx >= 0 && nx < ds->w &&
+                            ny >= 0 && ny < ds->h &&
+                            state->grid[ny*ds->w+nx] == -1)
+                            flags++;
+                    }
+
+                if (flags > v)
+                    v |= 32;
+            }
+
+           if ((v == -2 || v == -3) &&
+               (abs(x-ui->hx) <= ui->hradius && abs(y-ui->hy) <= ui->hradius))
+               v -= 20;
+
+            if (cmoved && /* if cursor has moved, force redraw of curr and prev pos */
+                ((x == cx && y == cy) || (x == ds->cur_x && y == ds->cur_y)))
+              cc = 1;
+
+           if (ds->grid[y*ds->w+x] != v || bg != ds->bg || cc) {
+               draw_tile(dr, ds, COORD(x), COORD(y), v,
+                          (x == cx && y == cy) ? COL_CURSOR : bg);
+               ds->grid[y*ds->w+x] = v;
+           }
+       }
+    ds->bg = bg;
+    ds->cur_x = cx; ds->cur_y = cy;
+
+    if (!state->layout->mines)
+       mines = state->layout->n;
+
+    /*
+     * Update the status bar.
+     */
+    {
+       char statusbar[512];
+       if (state->dead) {
+           sprintf(statusbar, "DEAD!");
+       } else if (state->won) {
+            if (state->used_solve)
+                sprintf(statusbar, "Auto-solved.");
+            else
+                sprintf(statusbar, "COMPLETED!");
+       } else {
+           sprintf(statusbar, "Marked: %d / %d", markers, mines);
+       }
+        if (ui->deaths)
+            sprintf(statusbar + strlen(statusbar),
+                    "  Deaths: %d", ui->deaths);
+       status_bar(dr, statusbar);
+    }
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return 0.0F;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    if (oldstate->used_solve || newstate->used_solve)
+        return 0.0F;
+
+    if (dir > 0 && !oldstate->dead && !oldstate->won) {
+       if (newstate->dead) {
+           ui->flash_is_death = TRUE;
+           return 3 * FLASH_FRAME;
+       }
+       if (newstate->won) {
+           ui->flash_is_death = FALSE;
+           return 2 * FLASH_FRAME;
+       }
+    }
+    return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    /*
+     * We report the game as lost only if the player has used the
+     * Solve function to reveal all the mines. Otherwise, we assume
+     * they'll undo and continue play.
+     */
+    return state->won ? (state->used_solve ? -1 : +1) : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    if (state->dead || state->won || ui->completed || !state->layout->mines)
+       return FALSE;
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+}
+
+#ifdef COMBINED
+#define thegame mines
+#endif
+
+const struct game thegame = {
+    "Mines", "games.mines", "mines",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    TRUE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    FALSE, FALSE, game_print_size, game_print,
+    TRUE,                             /* wants_statusbar */
+    TRUE, game_timing_state,
+    BUTTON_BEATS(LEFT_BUTTON, RIGHT_BUTTON) | REQUIRE_RBUTTON,
+};
+
+#ifdef STANDALONE_OBFUSCATOR
+
+/*
+ * Vaguely useful stand-alone program which translates between
+ * obfuscated and clear Mines game descriptions. Pass in a game
+ * description on the command line, and if it's clear it will be
+ * obfuscated and vice versa. The output text should also be a
+ * valid game ID describing the same game. Like this:
+ *
+ * $ ./mineobfusc 9x9:4,4,mb071b49fbd1cb6a0d5868
+ * 9x9:4,4,004000007c00010022080
+ * $ ./mineobfusc 9x9:4,4,004000007c00010022080
+ * 9x9:4,4,mb071b49fbd1cb6a0d5868
+ */
+
+int main(int argc, char **argv)
+{
+    game_params *p;
+    game_state *s;
+    char *id = NULL, *desc, *err;
+    int y, x;
+
+    while (--argc > 0) {
+        char *p = *++argv;
+       if (*p == '-') {
+            fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p);
+            return 1;
+        } else {
+            id = p;
+        }
+    }
+
+    if (!id) {
+        fprintf(stderr, "usage: %s <game_id>\n", argv[0]);
+        return 1;
+    }
+
+    desc = strchr(id, ':');
+    if (!desc) {
+        fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]);
+        return 1;
+    }
+    *desc++ = '\0';
+
+    p = default_params();
+    decode_params(p, id);
+    err = validate_desc(p, desc);
+    if (err) {
+        fprintf(stderr, "%s: %s\n", argv[0], err);
+        return 1;
+    }
+    s = new_game(NULL, p, desc);
+
+    x = atoi(desc);
+    while (*desc && *desc != ',') desc++;
+    if (*desc) desc++;
+    y = atoi(desc);
+    while (*desc && *desc != ',') desc++;
+    if (*desc) desc++;
+
+    printf("%s:%s\n", id, describe_layout(s->layout->mines,
+                                          p->w * p->h,
+                                          x, y,
+                                          (*desc != 'm')));
+
+    return 0;
+}
+
+#endif
+
+/* vim: set shiftwidth=4 tabstop=8: */
diff --git a/misc.c b/misc.c
new file mode 100644 (file)
index 0000000..fe41332
--- /dev/null
+++ b/misc.c
@@ -0,0 +1,363 @@
+/*
+ * misc.c: Miscellaneous helpful functions.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "puzzles.h"
+
+void free_cfg(config_item *cfg)
+{
+    config_item *i;
+
+    for (i = cfg; i->type != C_END; i++)
+       if (i->type == C_STRING)
+           sfree(i->sval);
+    sfree(cfg);
+}
+
+/*
+ * The Mines (among others) game descriptions contain the location of every
+ * mine, and can therefore be used to cheat.
+ *
+ * It would be pointless to attempt to _prevent_ this form of
+ * cheating by encrypting the description, since Mines is
+ * open-source so anyone can find out the encryption key. However,
+ * I think it is worth doing a bit of gentle obfuscation to prevent
+ * _accidental_ spoilers: if you happened to note that the game ID
+ * starts with an F, for example, you might be unable to put the
+ * knowledge of those mines out of your mind while playing. So,
+ * just as discussions of film endings are rot13ed to avoid
+ * spoiling it for people who don't want to be told, we apply a
+ * keyless, reversible, but visually completely obfuscatory masking
+ * function to the mine bitmap.
+ */
+void obfuscate_bitmap(unsigned char *bmp, int bits, int decode)
+{
+    int bytes, firsthalf, secondhalf;
+    struct step {
+       unsigned char *seedstart;
+       int seedlen;
+       unsigned char *targetstart;
+       int targetlen;
+    } steps[2];
+    int i, j;
+
+    /*
+     * My obfuscation algorithm is similar in concept to the OAEP
+     * encoding used in some forms of RSA. Here's a specification
+     * of it:
+     * 
+     *         + We have a `masking function' which constructs a stream of
+     *           pseudorandom bytes from a seed of some number of input
+     *           bytes.
+     * 
+     *         + We pad out our input bit stream to a whole number of
+     *           bytes by adding up to 7 zero bits on the end. (In fact
+     *           the bitmap passed as input to this function will already
+     *           have had this done in practice.)
+     * 
+     *         + We divide the _byte_ stream exactly in half, rounding the
+     *           half-way position _down_. So an 81-bit input string, for
+     *           example, rounds up to 88 bits or 11 bytes, and then
+     *           dividing by two gives 5 bytes in the first half and 6 in
+     *           the second half.
+     * 
+     *         + We generate a mask from the second half of the bytes, and
+     *           XOR it over the first half.
+     * 
+     *         + We generate a mask from the (encoded) first half of the
+     *           bytes, and XOR it over the second half. Any null bits at
+     *           the end which were added as padding are cleared back to
+     *           zero even if this operation would have made them nonzero.
+     * 
+     * To de-obfuscate, the steps are precisely the same except
+     * that the final two are reversed.
+     * 
+     * Finally, our masking function. Given an input seed string of
+     * bytes, the output mask consists of concatenating the SHA-1
+     * hashes of the seed string and successive decimal integers,
+     * starting from 0.
+     */
+
+    bytes = (bits + 7) / 8;
+    firsthalf = bytes / 2;
+    secondhalf = bytes - firsthalf;
+
+    steps[decode ? 1 : 0].seedstart = bmp + firsthalf;
+    steps[decode ? 1 : 0].seedlen = secondhalf;
+    steps[decode ? 1 : 0].targetstart = bmp;
+    steps[decode ? 1 : 0].targetlen = firsthalf;
+
+    steps[decode ? 0 : 1].seedstart = bmp;
+    steps[decode ? 0 : 1].seedlen = firsthalf;
+    steps[decode ? 0 : 1].targetstart = bmp + firsthalf;
+    steps[decode ? 0 : 1].targetlen = secondhalf;
+
+    for (i = 0; i < 2; i++) {
+       SHA_State base, final;
+       unsigned char digest[20];
+       char numberbuf[80];
+       int digestpos = 20, counter = 0;
+
+       SHA_Init(&base);
+       SHA_Bytes(&base, steps[i].seedstart, steps[i].seedlen);
+
+       for (j = 0; j < steps[i].targetlen; j++) {
+           if (digestpos >= 20) {
+               sprintf(numberbuf, "%d", counter++);
+               final = base;
+               SHA_Bytes(&final, numberbuf, strlen(numberbuf));
+               SHA_Final(&final, digest);
+               digestpos = 0;
+           }
+           steps[i].targetstart[j] ^= digest[digestpos++];
+       }
+
+       /*
+        * Mask off the pad bits in the final byte after both steps.
+        */
+       if (bits % 8)
+           bmp[bits / 8] &= 0xFF & (0xFF00 >> (bits % 8));
+    }
+}
+
+/* err, yeah, these two pretty much rely on unsigned char being 8 bits.
+ * Platforms where this is not the case probably have bigger problems
+ * than just making these two work, though... */
+char *bin2hex(const unsigned char *in, int inlen)
+{
+    char *ret = snewn(inlen*2 + 1, char), *p = ret;
+    int i;
+
+    for (i = 0; i < inlen*2; i++) {
+        int v = in[i/2];
+        if (i % 2 == 0) v >>= 4;
+        *p++ = "0123456789abcdef"[v & 0xF];
+    }
+    *p = '\0';
+    return ret;
+}
+
+unsigned char *hex2bin(const char *in, int outlen)
+{
+    unsigned char *ret = snewn(outlen, unsigned char);
+    int i;
+
+    memset(ret, 0, outlen*sizeof(unsigned char));
+    for (i = 0; i < outlen*2; i++) {
+        int c = in[i];
+        int v;
+
+        assert(c != 0);
+        if (c >= '0' && c <= '9')
+            v = c - '0';
+        else if (c >= 'a' && c <= 'f')
+            v = c - 'a' + 10;
+        else if (c >= 'A' && c <= 'F')
+            v = c - 'A' + 10;
+        else
+            v = 0;
+
+        ret[i / 2] |= v << (4 * (1 - (i % 2)));
+    }
+    return ret;
+}
+
+void game_mkhighlight_specific(frontend *fe, float *ret,
+                              int background, int highlight, int lowlight)
+{
+    float max;
+    int i;
+
+    /*
+     * Drop the background colour so that the highlight is
+     * noticeably brighter than it while still being under 1.
+     */
+    max = ret[background*3];
+    for (i = 1; i < 3; i++)
+        if (ret[background*3+i] > max)
+            max = ret[background*3+i];
+    if (max * 1.2F > 1.0F) {
+        for (i = 0; i < 3; i++)
+            ret[background*3+i] /= (max * 1.2F);
+    }
+
+    for (i = 0; i < 3; i++) {
+       if (highlight >= 0)
+           ret[highlight * 3 + i] = ret[background * 3 + i] * 1.2F;
+       if (lowlight >= 0)
+           ret[lowlight * 3 + i] = ret[background * 3 + i] * 0.8F;
+    }
+}
+
+void game_mkhighlight(frontend *fe, float *ret,
+                      int background, int highlight, int lowlight)
+{
+    frontend_default_colour(fe, &ret[background * 3]);
+    game_mkhighlight_specific(fe, ret, background, highlight, lowlight);
+}
+
+static void memswap(void *av, void *bv, int size)
+{
+    char tmpbuf[512];
+    char *a = av, *b = bv;
+
+    while (size > 0) {
+       int thislen = min(size, sizeof(tmpbuf));
+       memcpy(tmpbuf, a, thislen);
+       memcpy(a, b, thislen);
+       memcpy(b, tmpbuf, thislen);
+       a += thislen;
+       b += thislen;
+       size -= thislen;
+    }
+}
+
+void shuffle(void *array, int nelts, int eltsize, random_state *rs)
+{
+    char *carray = (char *)array;
+    int i;
+
+    for (i = nelts; i-- > 1 ;) {
+        int j = random_upto(rs, i+1);
+        if (j != i)
+            memswap(carray + eltsize * i, carray + eltsize * j, eltsize);
+    }
+}
+
+void draw_rect_outline(drawing *dr, int x, int y, int w, int h, int colour)
+{
+    int x0 = x, x1 = x+w-1, y0 = y, y1 = y+h-1;
+    int coords[8];
+
+    coords[0] = x0;
+    coords[1] = y0;
+    coords[2] = x0;
+    coords[3] = y1;
+    coords[4] = x1;
+    coords[5] = y1;
+    coords[6] = x1;
+    coords[7] = y0;
+
+    draw_polygon(dr, coords, 4, -1, colour);
+}
+
+void draw_rect_corners(drawing *dr, int cx, int cy, int r, int col)
+{
+    draw_line(dr, cx - r, cy - r, cx - r, cy - r/2, col);
+    draw_line(dr, cx - r, cy - r, cx - r/2, cy - r, col);
+    draw_line(dr, cx - r, cy + r, cx - r, cy + r/2, col);
+    draw_line(dr, cx - r, cy + r, cx - r/2, cy + r, col);
+    draw_line(dr, cx + r, cy - r, cx + r, cy - r/2, col);
+    draw_line(dr, cx + r, cy - r, cx + r/2, cy - r, col);
+    draw_line(dr, cx + r, cy + r, cx + r, cy + r/2, col);
+    draw_line(dr, cx + r, cy + r, cx + r/2, cy + r, col);
+}
+
+void move_cursor(int button, int *x, int *y, int maxw, int maxh, int wrap)
+{
+    int dx = 0, dy = 0;
+    switch (button) {
+    case CURSOR_UP:         dy = -1; break;
+    case CURSOR_DOWN:       dy = 1; break;
+    case CURSOR_RIGHT:      dx = 1; break;
+    case CURSOR_LEFT:       dx = -1; break;
+    default: return;
+    }
+    if (wrap) {
+        *x = (*x + dx + maxw) % maxw;
+        *y = (*y + dy + maxh) % maxh;
+    } else {
+        *x = min(max(*x+dx, 0), maxw - 1);
+        *y = min(max(*y+dy, 0), maxh - 1);
+    }
+}
+
+/* Used in netslide.c and sixteen.c for cursor movement around edge. */
+
+int c2pos(int w, int h, int cx, int cy)
+{
+    if (cy == -1)
+        return cx;                      /* top row, 0 .. w-1 (->) */
+    else if (cx == w)
+        return w + cy;                  /* R col, w .. w+h -1 (v) */
+    else if (cy == h)
+        return w + h + (w-cx-1);        /* bottom row, w+h .. w+h+w-1 (<-) */
+    else if (cx == -1)
+        return w + h + w + (h-cy-1);    /* L col, w+h+w .. w+h+w+h-1 (^) */
+
+    assert(!"invalid cursor pos!");
+    return -1; /* not reached */
+}
+
+int c2diff(int w, int h, int cx, int cy, int button)
+{
+    int diff = 0;
+
+    assert(IS_CURSOR_MOVE(button));
+
+    /* Obvious moves around edge. */
+    if (cy == -1)
+        diff = (button == CURSOR_RIGHT) ? +1 : (button == CURSOR_LEFT) ? -1 : diff;
+    if (cy == h)
+        diff = (button == CURSOR_RIGHT) ? -1 : (button == CURSOR_LEFT) ? +1 : diff;
+    if (cx == -1)
+        diff = (button == CURSOR_UP) ? +1 : (button == CURSOR_DOWN) ? -1 : diff;
+    if (cx == w)
+        diff = (button == CURSOR_UP) ? -1 : (button == CURSOR_DOWN) ? +1 : diff;
+
+    if (button == CURSOR_LEFT && cx == w && (cy == 0 || cy == h-1))
+        diff = (cy == 0) ? -1 : +1;
+    if (button == CURSOR_RIGHT && cx == -1 && (cy == 0 || cy == h-1))
+        diff = (cy == 0) ? +1 : -1;
+    if (button == CURSOR_DOWN && cy == -1 && (cx == 0 || cx == w-1))
+        diff = (cx == 0) ? -1 : +1;
+    if (button == CURSOR_UP && cy == h && (cx == 0 || cx == w-1))
+        diff = (cx == 0) ? +1 : -1;
+
+    debug(("cx,cy = %d,%d; w%d h%d, diff = %d", cx, cy, w, h, diff));
+    return diff;
+}
+
+void pos2c(int w, int h, int pos, int *cx, int *cy)
+{
+    int max = w+h+w+h;
+
+    pos = (pos + max) % max;
+
+    if (pos < w) {
+        *cx = pos; *cy = -1; return;
+    }
+    pos -= w;
+    if (pos < h) {
+        *cx = w; *cy = pos; return;
+    }
+    pos -= h;
+    if (pos < w) {
+        *cx = w-pos-1; *cy = h; return;
+    }
+    pos -= w;
+    if (pos < h) {
+      *cx = -1; *cy = h-pos-1; return;
+    }
+    assert(!"invalid pos, huh?"); /* limited by % above! */
+}
+
+void draw_text_outline(drawing *dr, int x, int y, int fonttype,
+                       int fontsize, int align,
+                       int text_colour, int outline_colour, char *text)
+{
+    if (outline_colour > -1) {
+        draw_text(dr, x-1, y, fonttype, fontsize, align, outline_colour, text);
+        draw_text(dr, x+1, y, fonttype, fontsize, align, outline_colour, text);
+        draw_text(dr, x, y-1, fonttype, fontsize, align, outline_colour, text);
+        draw_text(dr, x, y+1, fonttype, fontsize, align, outline_colour, text);
+    }
+    draw_text(dr, x, y, fonttype, fontsize, align, text_colour, text);
+}
+
+/* vim: set shiftwidth=4 tabstop=8: */
diff --git a/missing b/missing
new file mode 100755 (executable)
index 0000000..f62bbae
--- /dev/null
+++ b/missing
@@ -0,0 +1,215 @@
+#! /bin/sh
+# Common wrapper for a few potentially missing GNU programs.
+
+scriptversion=2013-10-28.13; # UTC
+
+# Copyright (C) 1996-2014 Free Software Foundation, Inc.
+# Originally written by Fran,cois Pinard <pinard@iro.umontreal.ca>, 1996.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+if test $# -eq 0; then
+  echo 1>&2 "Try '$0 --help' for more information"
+  exit 1
+fi
+
+case $1 in
+
+  --is-lightweight)
+    # Used by our autoconf macros to check whether the available missing
+    # script is modern enough.
+    exit 0
+    ;;
+
+  --run)
+    # Back-compat with the calling convention used by older automake.
+    shift
+    ;;
+
+  -h|--h|--he|--hel|--help)
+    echo "\
+$0 [OPTION]... PROGRAM [ARGUMENT]...
+
+Run 'PROGRAM [ARGUMENT]...', returning a proper advice when this fails due
+to PROGRAM being missing or too old.
+
+Options:
+  -h, --help      display this help and exit
+  -v, --version   output version information and exit
+
+Supported PROGRAM values:
+  aclocal   autoconf  autoheader   autom4te  automake  makeinfo
+  bison     yacc      flex         lex       help2man
+
+Version suffixes to PROGRAM as well as the prefixes 'gnu-', 'gnu', and
+'g' are ignored when checking the name.
+
+Send bug reports to <bug-automake@gnu.org>."
+    exit $?
+    ;;
+
+  -v|--v|--ve|--ver|--vers|--versi|--versio|--version)
+    echo "missing $scriptversion (GNU Automake)"
+    exit $?
+    ;;
+
+  -*)
+    echo 1>&2 "$0: unknown '$1' option"
+    echo 1>&2 "Try '$0 --help' for more information"
+    exit 1
+    ;;
+
+esac
+
+# Run the given program, remember its exit status.
+"$@"; st=$?
+
+# If it succeeded, we are done.
+test $st -eq 0 && exit 0
+
+# Also exit now if we it failed (or wasn't found), and '--version' was
+# passed; such an option is passed most likely to detect whether the
+# program is present and works.
+case $2 in --version|--help) exit $st;; esac
+
+# Exit code 63 means version mismatch.  This often happens when the user
+# tries to use an ancient version of a tool on a file that requires a
+# minimum version.
+if test $st -eq 63; then
+  msg="probably too old"
+elif test $st -eq 127; then
+  # Program was missing.
+  msg="missing on your system"
+else
+  # Program was found and executed, but failed.  Give up.
+  exit $st
+fi
+
+perl_URL=http://www.perl.org/
+flex_URL=http://flex.sourceforge.net/
+gnu_software_URL=http://www.gnu.org/software
+
+program_details ()
+{
+  case $1 in
+    aclocal|automake)
+      echo "The '$1' program is part of the GNU Automake package:"
+      echo "<$gnu_software_URL/automake>"
+      echo "It also requires GNU Autoconf, GNU m4 and Perl in order to run:"
+      echo "<$gnu_software_URL/autoconf>"
+      echo "<$gnu_software_URL/m4/>"
+      echo "<$perl_URL>"
+      ;;
+    autoconf|autom4te|autoheader)
+      echo "The '$1' program is part of the GNU Autoconf package:"
+      echo "<$gnu_software_URL/autoconf/>"
+      echo "It also requires GNU m4 and Perl in order to run:"
+      echo "<$gnu_software_URL/m4/>"
+      echo "<$perl_URL>"
+      ;;
+  esac
+}
+
+give_advice ()
+{
+  # Normalize program name to check for.
+  normalized_program=`echo "$1" | sed '
+    s/^gnu-//; t
+    s/^gnu//; t
+    s/^g//; t'`
+
+  printf '%s\n' "'$1' is $msg."
+
+  configure_deps="'configure.ac' or m4 files included by 'configure.ac'"
+  case $normalized_program in
+    autoconf*)
+      echo "You should only need it if you modified 'configure.ac',"
+      echo "or m4 files included by it."
+      program_details 'autoconf'
+      ;;
+    autoheader*)
+      echo "You should only need it if you modified 'acconfig.h' or"
+      echo "$configure_deps."
+      program_details 'autoheader'
+      ;;
+    automake*)
+      echo "You should only need it if you modified 'Makefile.am' or"
+      echo "$configure_deps."
+      program_details 'automake'
+      ;;
+    aclocal*)
+      echo "You should only need it if you modified 'acinclude.m4' or"
+      echo "$configure_deps."
+      program_details 'aclocal'
+      ;;
+   autom4te*)
+      echo "You might have modified some maintainer files that require"
+      echo "the 'autom4te' program to be rebuilt."
+      program_details 'autom4te'
+      ;;
+    bison*|yacc*)
+      echo "You should only need it if you modified a '.y' file."
+      echo "You may want to install the GNU Bison package:"
+      echo "<$gnu_software_URL/bison/>"
+      ;;
+    lex*|flex*)
+      echo "You should only need it if you modified a '.l' file."
+      echo "You may want to install the Fast Lexical Analyzer package:"
+      echo "<$flex_URL>"
+      ;;
+    help2man*)
+      echo "You should only need it if you modified a dependency" \
+           "of a man page."
+      echo "You may want to install the GNU Help2man package:"
+      echo "<$gnu_software_URL/help2man/>"
+    ;;
+    makeinfo*)
+      echo "You should only need it if you modified a '.texi' file, or"
+      echo "any other file indirectly affecting the aspect of the manual."
+      echo "You might want to install the Texinfo package:"
+      echo "<$gnu_software_URL/texinfo/>"
+      echo "The spurious makeinfo call might also be the consequence of"
+      echo "using a buggy 'make' (AIX, DU, IRIX), in which case you might"
+      echo "want to install GNU make:"
+      echo "<$gnu_software_URL/make/>"
+      ;;
+    *)
+      echo "You might have modified some files without having the proper"
+      echo "tools for further handling them.  Check the 'README' file, it"
+      echo "often tells you about the needed prerequisites for installing"
+      echo "this package.  You may also peek at any GNU archive site, in"
+      echo "case some other package contains this missing '$1' program."
+      ;;
+  esac
+}
+
+give_advice "$1" | sed -e '1s/^/WARNING: /' \
+                       -e '2,$s/^/         /' >&2
+
+# Propagate the correct exit status (expected to be 127 for a program
+# not found, 63 for a program that failed due to version mismatch).
+exit $st
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC"
+# time-stamp-end: "; # UTC"
+# End:
diff --git a/mkauto.sh b/mkauto.sh
new file mode 100755 (executable)
index 0000000..297212a
--- /dev/null
+++ b/mkauto.sh
@@ -0,0 +1,2 @@
+#! /bin/sh
+autoreconf -i && rm -rf autom4te.cache
diff --git a/mkfiles.pl b/mkfiles.pl
new file mode 100755 (executable)
index 0000000..c1623df
--- /dev/null
@@ -0,0 +1,1807 @@
+#!/usr/bin/env perl
+#
+# Cross-platform Makefile generator.
+#
+# Reads the file `Recipe' to determine the list of generated
+# executables and their component objects. Then reads the source
+# files to compute #include dependencies. Finally, writes out the
+# various target Makefiles.
+
+# PuTTY specifics which could still do with removing:
+#  - Mac makefile is not portabilised at all. Include directories
+#    are hardwired, and also the libraries are fixed. This is
+#    mainly because I was too scared to go anywhere near it.
+#  - sbcsgen.pl is still run at startup.
+
+# Other things undone:
+#  - special-define objects (foo.o[PREPROCSYMBOL]) are not
+#    supported in the mac or vcproj makefiles.
+
+use warnings;
+use IO::Handle;
+use Cwd;
+use File::Basename;
+
+while ($#ARGV >= 0) {
+    if ($ARGV[0] eq "-U") {
+        # Convenience for Unix users: -U means that after we finish what
+        # we're doing here, we also run mkauto.sh and then 'configure'. So
+        # it's a one-stop shop for regenerating the actual end-product
+        # Unix makefile.
+        #
+        # Arguments supplied after -U go to configure.
+        $do_unix = 1;
+        shift @ARGV;
+        @confargs = @ARGV;
+        @ARGV = ();
+    } else {
+        die "unrecognised command-line argument '$ARGV[0]'\n";
+    }
+}
+
+@filestack = ();
+$in = new IO::Handle;
+open $in, "Recipe" or do {
+    # We want to deal correctly with being run from one of the
+    # subdirs in the source tree. So if we can't find Recipe here,
+    # try one level up.
+    chdir "..";
+    open $in, "Recipe" or die "unable to open Recipe file\n";
+};
+push @filestack, $in;
+
+# HACK: One of the source files in `charset' is auto-generated by
+# sbcsgen.pl. We need to generate that _now_, before attempting
+# dependency analysis.
+eval 'chdir "charset"; require "sbcsgen.pl"; chdir ".."';
+
+@srcdirs = ("./");
+
+$divert = undef; # ref to array of refs of scalars in which text is
+                 # currently being put
+$help = ""; # list of newline-free lines of help text
+$project_name = "project"; # this is a good enough default
+%makefiles = (); # maps makefile types to output makefile pathnames
+%makefile_extra = (); # maps makefile types to extra Makefile text
+%programs = (); # maps prog name + type letter to listref of objects/resources
+%groups = (); # maps group name to listref of objects/resources
+
+@allobjs = (); # all object file names
+
+readinput: while (1) {
+  $in = $filestack[$#filestack];
+  while (not defined ($_ = <$in>)) {
+    close $filestack[$#filestack];
+    pop @filestack;
+    last readinput if 0 == scalar @filestack;
+    $in = $filestack[$#filestack];
+  }
+  chomp;
+  @_ = split;
+
+  # If we're gathering help text, keep doing so.
+  if (defined $divert) {
+      if ((defined $_[0]) && $_[0] eq "!end") {
+         $divert = undef;
+      } else {
+          for my $ref (@$divert) {
+              ${$ref} .= "$_\n";
+          }
+      }
+      next;
+  }
+  # Skip comments and blank lines.
+  next if /^\s*#/ or scalar @_ == 0;
+
+  if ($_[0] eq "!begin" and $_[1] eq "help") { $divert = [\$help]; next; }
+  if ($_[0] eq "!name") { $project_name = $_[1]; next; }
+  if ($_[0] eq "!srcdir") { push @srcdirs, $_[1]; next; }
+  if ($_[0] eq "!makefile" and &mfval($_[1])) { $makefiles{$_[1]}=$_[2]; next;}
+  if ($_[0] eq "!specialobj" and &mfval($_[1])) { $specialobj{$_[1]}->{$_[2]} = 1; next;}
+  if ($_[0] eq "!cflags" and &mfval($_[1])) {
+      ($rest = $_) =~ s/^\s*\S+\s+\S+\s+\S+\s*//; # find rest of input line
+      $rest = 1 if $rest eq "";
+      $cflags{$_[1]}->{$_[2]} = $rest;
+      next;
+  }
+  if ($_[0] eq "!begin") {
+      my @args = @_;
+      shift @args;
+      $divert = [];
+      for my $component (@args) {
+          if ($component =~ /^>(.*)/) {
+              push @$divert, \$auxfiles{$1};
+          } elsif ($component =~ /^([^_]*)(_.*)?$/ and &mfval($1)) {
+              push @$divert, \$makefile_extra{$component};
+          }
+      }
+      next;
+  }
+  if ($_[0] eq "!include") {
+      @newfiles = ();
+      for ($i = 1; $i <= $#_; $i++) {
+         push @newfiles, (sort glob $_[$i]);
+      }
+      for ($i = $#newfiles; $i >= 0; $i--) {
+         $file = $newfiles[$i];
+         $f = new IO::Handle;
+         open $f, "<$file" or die "unable to open include file '$file'\n";
+         push @filestack, $f;
+      }
+      next;
+  }
+
+  # Now we have an ordinary line. See if it's an = line, a : line
+  # or a + line.
+  @objs = @_;
+
+  if ($_[0] eq "+") {
+    $listref = $lastlistref;
+    $prog = undef;
+    die "$.: unexpected + line\n" if !defined $lastlistref;
+  } elsif ($_[1] eq "=") {
+    $groups{$_[0]} = [];
+    $listref = $groups{$_[0]};
+    $prog = undef;
+    shift @objs; # eat the group name
+  } elsif ($_[1] eq "+=") {
+    $groups{$_[0]} = [] if !defined $groups{$_[0]};
+    $listref = $groups{$_[0]};
+    $prog = undef;
+    shift @objs; # eat the group name
+  } elsif ($_[1] eq ":") {
+    $listref = [];
+    $prog = $_[0];
+    shift @objs; # eat the program name
+  } else {
+    die "$.: unrecognised line type: '$_'\n";
+  }
+  shift @objs; # eat the +, the = or the :
+
+  while (scalar @objs > 0) {
+    $i = shift @objs;
+    if ($groups{$i}) {
+      foreach $j (@{$groups{$i}}) { unshift @objs, $j; }
+    } elsif (($i eq "[G]" or $i eq "[C]" or $i eq "[M]" or
+              $i eq "[X]" or $i eq "[U]" or $i eq "[MX]") and defined $prog) {
+      $type = substr($i,1,(length $i)-2);
+    } else {
+      if ($i =~ /\?$/) {
+       # Object files with a trailing question mark are optional:
+       # the build can proceed fine without them, so we only use
+       # them if their primary source files are present.
+       $i =~ s/\?$//;
+       $i = undef unless defined &finddep($i);
+      } elsif ($i =~ /\|/) {
+       # Object file descriptions containing a vertical bar are
+       # lists of choices: we use the _first_ one whose primary
+       # source file is present.
+       @options = split /\|/, $i;
+       $j = undef;
+       foreach $k (@options) {
+         $j=$k, last if defined &finddep($k);
+       }
+       die "no alternative found for $i\n" unless defined $j;
+       $i = $j;
+      }
+      if (defined $i) {
+       push @$listref, $i;
+       push @allobjs, $i;
+      }
+    }
+  }
+  if ($prog and $type) {
+    die "multiple program entries for $prog [$type]\n"
+        if defined $programs{$prog . "," . $type};
+    $programs{$prog . "," . $type} = $listref;
+  }
+  $lastlistref = $listref;
+}
+
+foreach $aux (sort keys %auxfiles) {
+    open AUX, ">$aux";
+    print AUX $auxfiles{$aux};
+    close AUX;
+}
+
+# Find object file names with predefines (in square brackets after
+# the module name), and decide on actual object names for them.
+foreach $i (@allobjs) {
+  if ($i !~ /\[/) {
+    $objname{$i} = $i;
+    $srcname{$i} = $i;
+    $usedobjname{$i} = 1;
+  }
+}
+foreach $i (@allobjs) {
+  if ($i =~ /^(.*)\[([^\]]*)/) {
+    $defs{$i} = [ split ",",$2 ];
+    $srcname{$i} = $s = $1;
+    $index = 1;
+    while (1) {
+      $maxlen = length $s;
+      $maxlen = 8 if $maxlen < 8;
+      $chop = $maxlen - length $index;
+      $chop = length $s if $chop > length $s;
+      $chop = 0 if $chop < 0;
+      $name = substr($s, 0, $chop) . $index;
+      $index++, next if $usedobjname{$name};
+      $objname{$i} = $name;
+      $usedobjname{$name} = 1;
+      last;
+    }
+  }
+}
+
+# Now retrieve the complete list of objects and resource files, and
+# construct dependency data for them. While we're here, expand the
+# object list for each program, and complain if its type isn't set.
+@prognames = sort keys %programs;
+%depends = ();
+@scanlist = ();
+foreach $i (@prognames) {
+  ($prog, $type) = split ",", $i;
+  # Strip duplicate object names.
+  $prev = '';
+  @list = grep { $status = ($prev ne $_); $prev=$_; $status }
+          sort @{$programs{$i}};
+  $programs{$i} = [@list];
+  foreach $jj (@list) {
+    $j = $srcname{$jj};
+    $file = &finddep($j);
+    if (defined $file) {
+      $depends{$jj} = [$file];
+      push @scanlist, $file;
+    }
+  }
+}
+
+# Scan each file on @scanlist and find further inclusions.
+# Inclusions are given by lines of the form `#include "otherfile"'
+# (system headers are automatically ignored by this because they'll
+# be given in angle brackets). Files included by this method are
+# added back on to @scanlist to be scanned in turn (if not already
+# done).
+#
+# Resource scripts (.rc) can also include a file by means of a line
+# ending `ICON "filename"'. Files included by this method are not
+# added to @scanlist because they can never include further files.
+#
+# In this pass we write out a hash %further which maps a source
+# file name into a listref containing further source file names.
+
+%further = ();
+while (scalar @scanlist > 0) {
+  $file = shift @scanlist;
+  next if defined $further{$file}; # skip if we've already done it
+  $further{$file} = [];
+  $dirfile = &findfile($file);
+  open IN, "$dirfile" or die "unable to open source file $file\n";
+  while (<IN>) {
+    chomp;
+    /^\s*#include\s+\"([^\"]+)\"/ and do {
+      push @{$further{$file}}, $1;
+      push @scanlist, $1;
+      next;
+    };
+    /ICON\s+\"([^\"]+)\"\s*$/ and do {
+      push @{$further{$file}}, $1;
+      next;
+    }
+  }
+  close IN;
+}
+
+# Now we're ready to generate the final dependencies section. For
+# each key in %depends, we must expand the dependencies list by
+# iteratively adding entries from %further.
+foreach $i (keys %depends) {
+  %dep = ();
+  @scanlist = @{$depends{$i}};
+  foreach $i (@scanlist) { $dep{$i} = 1; }
+  while (scalar @scanlist > 0) {
+    $file = shift @scanlist;
+    foreach $j (@{$further{$file}}) {
+      if (!$dep{$j}) {
+        $dep{$j} = 1;
+        push @{$depends{$i}}, $j;
+        push @scanlist, $j;
+      }
+    }
+  }
+#  printf "%s: %s\n", $i, join ' ',@{$depends{$i}};
+}
+
+# Validation of input.
+
+sub mfval($) {
+    my ($type) = @_;
+    # Returns true if the argument is a known makefile type. Otherwise,
+    # prints a warning and returns false;
+    if (grep { $type eq $_ }
+       ("vc","vcproj","cygwin","borland","lcc","gtk","am","mpw","nestedvm","osx","wce","gnustep","emcc")) {
+           return 1;
+       }
+    warn "$.:unknown makefile type '$type'\n";
+    return 0;
+}
+
+# Utility routines while writing out the Makefiles.
+
+sub dirpfx {
+    my ($path) = shift @_;
+    my ($sep) = shift @_;
+    my $ret = "";
+    my $i;
+    while (($i = index $path, $sep) >= 0) {
+       $path = substr $path, ($i + length $sep);
+       $ret .= "..$sep";
+    }
+    return $ret;
+}
+
+sub findfile {
+  my ($name) = @_;
+  my $dir;
+  my $i;
+  my $outdir = undef;
+  unless (defined $findfilecache{$name}) {
+    $i = 0;
+    foreach $dir (@srcdirs) {
+      $outdir = $dir, $i++ if -f "$dir$name";
+    }
+    die "multiple instances of source file $name\n" if $i > 1;
+    $findfilecache{$name} = (defined $outdir ? $outdir . $name : undef);
+  }
+  return $findfilecache{$name};
+}
+
+sub finddep {
+  my $j = shift @_;
+  my $file;
+  # Find the first dependency of an object.
+
+  # Dependencies for "x" start with "x.c" or "x.m" (depending on
+  # which one exists).
+  # Dependencies for "x.res" start with "x.rc".
+  # Dependencies for "x.rsrc" start with "x.r".
+  # Both types of file are pushed on the list of files to scan.
+  # Libraries (.lib) don't have dependencies at all.
+  if ($j =~ /^(.*)\.res$/) {
+    $file = "$1.rc";
+  } elsif ($j =~ /^(.*)\.rsrc$/) {
+    $file = "$1.r";
+  } elsif ($j !~ /\./) {
+    $file = "$j.c";
+    $file = "$j.m" unless &findfile($file);
+  } else {
+    # For everything else, we assume it's its own dependency.
+    $file = $j;
+  }
+  $file = undef unless &findfile($file);
+  return $file;
+}
+
+sub objects {
+  my ($prog, $otmpl, $rtmpl, $ltmpl, $prefix, $dirsep) = @_;
+  my @ret;
+  my ($i, $x, $y);
+  ($otmpl, $rtmpl, $ltmpl) = map { defined $_ ? $_ : "" } ($otmpl, $rtmpl, $ltmpl);
+  @ret = ();
+  foreach $ii (@{$programs{$prog}}) {
+    $i = $objname{$ii};
+    $x = "";
+    if ($i =~ /^(.*)\.(res|rsrc)/) {
+      $y = $1;
+      ($x = $rtmpl) =~ s/X/$y/;
+    } elsif ($i =~ /^(.*)\.lib/) {
+      $y = $1;
+      ($x = $ltmpl) =~ s/X/$y/;
+    } elsif ($i !~ /\./) {
+      ($x = $otmpl) =~ s/X/$i/;
+    }
+    push @ret, $x if $x ne "";
+  }
+  return join " ", @ret;
+}
+
+sub special {
+  my ($prog, $suffix) = @_;
+  my @ret;
+  my ($i, $x, $y);
+  @ret = ();
+  foreach $ii (@{$programs{$prog}}) {
+    $i = $objname{$ii};
+    if (substr($i, (length $i) - (length $suffix)) eq $suffix) {
+      push @ret, $i;
+    }
+  }
+  return join " ", @ret;
+}
+
+sub splitline {
+  my ($line, $width, $splitchar) = @_;
+  my $result = "";
+  my $len;
+  $len = (defined $width ? $width : 76);
+  $splitchar = (defined $splitchar ? $splitchar : '\\');
+  while (length $line > $len) {
+    $line =~ /^(.{0,$len})\s(.*)$/ or $line =~ /^(.{$len,}?\s(.*)$/;
+    $result .= $1;
+    $result .= " ${splitchar}\n\t\t" if $2 ne '';
+    $line = $2;
+    $len = 60;
+  }
+  return $result . $line;
+}
+
+sub deps {
+  my ($otmpl, $rtmpl, $prefix, $dirsep, $depchar, $splitchar) = @_;
+  my ($i, $x, $y);
+  my @deps;
+  my @ret;
+  @ret = ();
+  $depchar ||= ':';
+  foreach $ii (sort keys %depends) {
+    $i = $objname{$ii};
+    next if $specialobj{$mftyp}->{$i};
+    if ($i =~ /^(.*)\.(res|rsrc)/) {
+      next if !defined $rtmpl;
+      $y = $1;
+      ($x = $rtmpl) =~ s/X/$y/;
+    } else {
+      ($x = $otmpl) =~ s/X/$i/;
+    }
+    @deps = @{$depends{$ii}};
+    # Skip things which are their own dependency.
+    next if grep { $_ eq $i } @deps;
+    @deps = map {
+      $_ = &findfile($_);
+      s/\//$dirsep/g;
+      $_ = $prefix . $_;
+    } @deps;
+    push @ret, {obj => $x, deps => [@deps], defs => $defs{$ii}};
+  }
+  return @ret;
+}
+
+sub prognames {
+  my ($types) = @_;
+  my ($n, $prog, $type);
+  my @ret;
+  @ret = ();
+  foreach $n (@prognames) {
+    ($prog, $type) = split ",", $n;
+    push @ret, $n if index(":$types:", ":$type:") >= 0;
+  }
+  return @ret;
+}
+
+sub progrealnames {
+  my ($types) = @_;
+  my ($n, $prog, $type);
+  my @ret;
+  @ret = ();
+  foreach $n (@prognames) {
+    ($prog, $type) = split ",", $n;
+    push @ret, $prog if index(":$types:", ":$type:") >= 0;
+  }
+  return @ret;
+}
+
+sub manpages {
+  my ($types,$suffix) = @_;
+
+  # assume that all UNIX programs have a man page
+  if($suffix eq "1" && $types =~ /:X:/) {
+    return map("$_.1", &progrealnames($types));
+  }
+  return ();
+}
+
+$orig_dir = cwd;
+
+# Now we're ready to output the actual Makefiles.
+
+if (defined $makefiles{'cygwin'}) {
+    $mftyp = 'cygwin';
+    $dirpfx = &dirpfx($makefiles{'cygwin'}, "/");
+
+    ##-- CygWin makefile
+    open OUT, ">$makefiles{'cygwin'}"; select OUT;
+    print
+    "# Makefile for $project_name under cygwin.\n".
+    "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n".
+    "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n";
+    # gcc command line option is -D not /D
+    ($_ = $help) =~ s/=\/D/=-D/gs;
+    print $_;
+    print
+    "\n".
+    "# You can define this path to point at your tools if you need to\n".
+    "# TOOLPATH = c:\\cygwin\\bin\\ # or similar, if you're running Windows\n".
+    "# TOOLPATH = /pkg/mingw32msvc/i386-mingw32msvc/bin/\n".
+    "CC = \$(TOOLPATH)gcc\n".
+    "RC = \$(TOOLPATH)windres\n".
+    "# Uncomment the following two lines to compile under Winelib\n".
+    "# CC = winegcc\n".
+    "# RC = wrc\n".
+    "# You may also need to tell windres where to find include files:\n".
+    "# RCINC = --include-dir c:\\cygwin\\include\\\n".
+    "\n".
+    &splitline("CFLAGS = -mno-cygwin -Wall -O2 -D_WINDOWS -DDEBUG -DWIN32S_COMPAT".
+      " -D_NO_OLDNAMES -DNO_MULTIMON -DNO_HTMLHELP " .
+              (join " ", map {"-I$dirpfx$_"} @srcdirs)) .
+              "\n".
+    "LDFLAGS = -mno-cygwin -s\n".
+    &splitline("RCFLAGS = \$(RCINC) --define WIN32=1 --define _WIN32=1".
+      " --define WINVER=0x0400 --define MINGW32_FIX=1 " .
+       (join " ", map {"--include $dirpfx$_"} @srcdirs) )."\n".
+    "\n";
+    print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("G:C"));
+    print "\n\n";
+    foreach $p (&prognames("G:C")) {
+      ($prog, $type) = split ",", $p;
+      $objstr = &objects($p, "X.o", "X.res.o", undef);
+      print &splitline($prog . ".exe: " . $objstr), "\n";
+      my $mw = $type eq "G" ? " -mwindows" : "";
+      $libstr = &objects($p, undef, undef, "-lX");
+      print &splitline("\t\$(CC)" . $mw . " \$(LDFLAGS) -o \$@ " .
+                       "-Wl,-Map,$prog.map " .
+                       $objstr . " $libstr", 69), "\n\n";
+    }
+    foreach $d (&deps("X.o", "X.res.o", $dirpfx, "/")) {
+      print &splitline(sprintf("%s: %s", $d->{obj}, join " ", @{$d->{deps}})),
+        "\n";
+      if ($d->{obj} =~ /\.res\.o$/) {
+       print "\t\$(RC) \$(FWHACK) \$(RCFL) \$(RCFLAGS) \$< \$\@\n";
+      } else {
+       $deflist = join "", map { " -D$_" } @{$d->{defs}};
+       print "\t\$(CC) \$(COMPAT) \$(FWHACK) \$(CFLAGS)" .
+           " \$(XFLAGS)$deflist -c \$< -o \$\@\n";
+      }
+    }
+    print "\n";
+    print $makefile_extra{'cygwin'} || "";
+    print "\nclean:\n".
+    "\trm -f *.o *.exe *.res.o *.map\n".
+    "\n";
+    select STDOUT; close OUT;
+
+}
+
+##-- Borland makefile
+if (defined $makefiles{'borland'}) {
+    $mftyp = 'borland';
+    $dirpfx = &dirpfx($makefiles{'borland'}, "\\");
+
+    %stdlibs = (  # Borland provides many Win32 API libraries intrinsically
+      "advapi32" => 1,
+      "comctl32" => 1,
+      "comdlg32" => 1,
+      "gdi32" => 1,
+      "imm32" => 1,
+      "shell32" => 1,
+      "user32" => 1,
+      "winmm" => 1,
+      "winspool" => 1,
+      "wsock32" => 1,
+    );
+    open OUT, ">$makefiles{'borland'}"; select OUT;
+    print
+    "# Makefile for $project_name under Borland C.\n".
+    "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n".
+    "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n";
+    # bcc32 command line option is -D not /D
+    ($_ = $help) =~ s/=\/D/=-D/gs;
+    print $_;
+    print
+    "\n".
+    "# If you rename this file to `Makefile', you should change this line,\n".
+    "# so that the .rsp files still depend on the correct makefile.\n".
+    "MAKEFILE = Makefile.bor\n".
+    "\n".
+    "# C compilation flags\n".
+    "CFLAGS = -D_WINDOWS -DWINVER=0x0401\n".
+    "\n".
+    "# Get include directory for resource compiler\n".
+    "!if !\$d(BCB)\n".
+    "BCB = \$(MAKEDIR)\\..\n".
+    "!endif\n".
+    "\n";
+    print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("G:C"));
+    print "\n\n";
+    foreach $p (&prognames("G:C")) {
+      ($prog, $type) = split ",", $p;
+      $objstr =  &objects($p, "X.obj", "X.res", undef);
+      print &splitline("$prog.exe: " . $objstr . " $prog.rsp"), "\n";
+      my $ap = ($type eq "G") ? "-aa" : "-ap";
+      print "\tilink32 $ap -Gn -L\$(BCB)\\lib \@$prog.rsp\n\n";
+    }
+    foreach $p (&prognames("G:C")) {
+      ($prog, $type) = split ",", $p;
+      print $prog, ".rsp: \$(MAKEFILE)\n";
+      $objstr = &objects($p, "X.obj", undef, undef);
+      @objlist = split " ", $objstr;
+      @objlines = ("");
+      foreach $i (@objlist) {
+        if (length($objlines[$#objlines] . " $i") > 50) {
+          push @objlines, "";
+        }
+        $objlines[$#objlines] .= " $i";
+      }
+      $c0w = ($type eq "G") ? "c0w32" : "c0x32";
+      print "\techo $c0w + > $prog.rsp\n";
+      for ($i=0; $i<=$#objlines; $i++) {
+        $plus = ($i < $#objlines ? " +" : "");
+        print "\techo$objlines[$i]$plus >> $prog.rsp\n";
+      }
+      print "\techo $prog.exe >> $prog.rsp\n";
+      $objstr = &objects($p, "X.obj", "X.res", undef);
+      @libs = split " ", &objects($p, undef, undef, "X");
+      @libs = grep { !$stdlibs{$_} } @libs;
+      unshift @libs, "cw32", "import32";
+      $libstr = join ' ', @libs;
+      print "\techo nul,$libstr, >> $prog.rsp\n";
+      print "\techo " . &objects($p, undef, "X.res", undef) . " >> $prog.rsp\n";
+      print "\n";
+    }
+    foreach $d (&deps("X.obj", "X.res", $dirpfx, "\\")) {
+      print &splitline(sprintf("%s: %s", $d->{obj}, join " ", @{$d->{deps}})),
+        "\n";
+      if ($d->{obj} =~ /\.res$/) {
+       print &splitline("\tbrcc32 \$(FWHACK) \$(RCFL) " .
+                        "-i \$(BCB)\\include -r -DNO_WINRESRC_H -DWIN32".
+                        " -D_WIN32 -DWINVER=0x0401 \$*.rc",69)."\n";
+      } else {
+       $deflist = join "", map { " -D$_" } @{$d->{defs}};
+       print &splitline("\tbcc32 -w-aus -w-ccc -w-par -w-pia \$(COMPAT)" .
+                        " \$(FWHACK) \$(CFLAGS) \$(XFLAGS)$deflist ".
+                        (join " ", map {"-I$dirpfx$_"} @srcdirs) .
+                        " /o$d->{obj} /c ".$d->{deps}->[0],69)."\n";
+      }
+    }
+    print "\n";
+    print $makefile_extra{'borland'} || "";
+    print "\nclean:\n".
+    "\t-del *.obj\n".
+    "\t-del *.exe\n".
+    "\t-del *.res\n".
+    "\t-del *.pch\n".
+    "\t-del *.aps\n".
+    "\t-del *.il*\n".
+    "\t-del *.pdb\n".
+    "\t-del *.rsp\n".
+    "\t-del *.tds\n".
+    "\t-del *.\$\$\$\$\$\$\n";
+    select STDOUT; close OUT;
+}
+
+if (defined $makefiles{'vc'}) {
+    $mftyp = 'vc';
+    $dirpfx = &dirpfx($makefiles{'vc'}, "\\");
+
+    ##-- Visual C++ makefile
+    open OUT, ">$makefiles{'vc'}"; select OUT;
+    print
+      "# Makefile for $project_name under Visual C.\n".
+      "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n".
+      "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n";
+    print $help;
+    print
+      "\n".
+      "# If you rename this file to `Makefile', you should change this line,\n".
+      "# so that the .rsp files still depend on the correct makefile.\n".
+      "MAKEFILE = Makefile.vc\n".
+      "\n".
+      "# C compilation flags\n".
+      "CFLAGS = /nologo /W3 /O1 /D_WINDOWS /D_WIN32_WINDOWS=0x401 /DWINVER=0x401 /I.\n".
+      "LFLAGS = /incremental:no /fixed\n".
+      "\n";
+    print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("G:C"));
+    print "\n\n";
+    foreach $p (&prognames("G:C")) {
+       ($prog, $type) = split ",", $p;
+       $objstr = &objects($p, "X.obj", "X.res", undef);
+       print &splitline("$prog.exe: " . $objstr . " $prog.rsp"), "\n";
+       print "\tlink \$(LFLAGS) -out:$prog.exe -map:$prog.map \@$prog.rsp\n\n";
+    }
+    foreach $p (&prognames("G:C")) {
+       ($prog, $type) = split ",", $p;
+       print $prog, ".rsp: \$(MAKEFILE)\n";
+       $objstr = &objects($p, "X.obj", "X.res", "X.lib");
+       @objlist = split " ", $objstr;
+       @objlines = ("");
+       foreach $i (@objlist) {
+           if (length($objlines[$#objlines] . " $i") > 50) {
+               push @objlines, "";
+           }
+           $objlines[$#objlines] .= " $i";
+       }
+       $subsys = ($type eq "G") ? "windows" : "console";
+       print "\techo /nologo /subsystem:$subsys > $prog.rsp\n";
+       for ($i=0; $i<=$#objlines; $i++) {
+           print "\techo$objlines[$i] >> $prog.rsp\n";
+       }
+       print "\n";
+    }
+    foreach $d (&deps("X.obj", "X.res", $dirpfx, "\\")) {
+       print &splitline(sprintf("%s: %s", $d->{obj}, join " ", @{$d->{deps}})),
+         "\n";
+       if ($d->{obj} =~ /\.res$/) {
+           print "\trc \$(FWHACK) \$(RCFL) -r -DWIN32 -D_WIN32 ".
+             "-DWINVER=0x0400 -fo".$d->{obj}." ".$d->{deps}->[0]."\n";
+       } else {
+           $deflist = join "", map { " /D$_" } @{$d->{defs}};
+           print "\tcl \$(COMPAT) \$(FWHACK) \$(CFLAGS) \$(XFLAGS)$deflist".
+             " /c ".$d->{deps}->[0]." /Fo$d->{obj}\n";
+       }
+    }
+    print "\n";
+    print $makefile_extra{'vc'} || "";
+    print "\nclean: tidy\n".
+      "\t-del *.exe\n\n".
+      "tidy:\n".
+      "\t-del *.obj\n".
+      "\t-del *.res\n".
+      "\t-del *.pch\n".
+      "\t-del *.aps\n".
+      "\t-del *.ilk\n".
+      "\t-del *.pdb\n".
+      "\t-del *.rsp\n".
+      "\t-del *.dsp\n".
+      "\t-del *.dsw\n".
+      "\t-del *.ncb\n".
+      "\t-del *.opt\n".
+      "\t-del *.plg\n".
+      "\t-del *.map\n".
+      "\t-del *.idb\n".
+      "\t-del debug.log\n";
+    select STDOUT; close OUT;
+}
+
+if (defined $makefiles{'wce'}) {
+    $mftyp = 'wce';
+    $dirpfx = &dirpfx($makefiles{'wce'}, "\\");
+
+    ##-- eMbedded Visual C PocketPC makefile
+    open OUT, ">$makefiles{'wce'}"; select OUT;
+    print
+      "# Makefile for $project_name on PocketPC using eMbedded Visual C.\n".
+      "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n".
+      "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n";
+    print $help;
+    print
+      "\n".
+      "# If you rename this file to `Makefile', you should change this line,\n".
+      "# so that the .rsp files still depend on the correct makefile.\n".
+      "MAKEFILE = Makefile.wce\n".
+      "\n".
+      "# This makefile expects the environment to have been set up by one\n".
+      "# of the PocketPC batch files wcearmv4.bat and wceemulator.bat. No\n".
+      "# other build targets are currently supported, because they would\n".
+      "# need a section in this if statement.\n".
+      "!if \"\$(TARGETCPU)\" == \"emulator\"\n".
+      "PLATFORM_DEFS=/D \"_i386_\" /D \"i_386_\" /D \"_X86_\" /D \"x86\"\n".
+      "CC=cl\n".
+      "BASELIBS=commctrl.lib coredll.lib corelibc.lib aygshell.lib\n".
+      "MACHINE=IX86\n".
+      "!else\n".
+      "PLATFORM_DEFS=/D \"ARM\" /D \"_ARM_\" /D \"ARMV4\"\n".
+      "CC=clarm\n".
+      "BASELIBS=commctrl.lib coredll.lib aygshell.lib\n".
+      "MACHINE=ARM\n".
+      "!endif\n".
+      "\n".
+      "# C compilation flags\n".
+      "CFLAGS = /nologo /W3 /O1 /MC /D _WIN32_WCE=420 /D \"WIN32_PLATFORM_PSPC=400\" /D UNDER_CE=420 \\\n".
+      "         \$(PLATFORM_DEFS) \\\n".
+      "         /D \"UNICODE\" /D \"_UNICODE\" /D \"NDEBUG\" /D \"NO_HTMLHELP\"\n".
+      "\n".
+      "LFLAGS = /nologo /incremental:no \\\n".
+      "         /base:0x00010000 /stack:0x10000,0x1000 /entry:WinMainCRTStartup \\\n".
+      "         /nodefaultlib:libc.lib /nodefaultlib:libcmt.lib /nodefaultlib:msvcrt.lib /nodefaultlib:OLDNAMES.lib \\\n".
+      "         /subsystem:windowsce,4.20 /align:4096 /MACHINE:\$(MACHINE)\n".
+      "\n".
+      "RCFL = /d UNDER_CE=420 /d _WIN32_WCE=420 /d \"WIN32_PLATFORM_PSPC=400\" \\\n".
+      "       \$(PLATFORM_DEFS) \\\n".
+      "       /d \"NDEBUG\" /d \"UNICODE\" /d \"_UNICODE\"\n".
+      "\n";
+    print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("G"));
+    print "\n\n";
+    foreach $p (&prognames("G")) {
+       ($prog, $type) = split ",", $p;
+       $objstr = &objects($p, "X.obj", "X.res", undef);
+       print &splitline("$prog.exe: " . $objstr . " $prog.rsp"), "\n";
+       print "\tlink \$(LFLAGS) -out:$prog.exe -map:$prog.map \@$prog.rsp\n\n";
+    }
+    foreach $p (&prognames("G")) {
+       ($prog, $type) = split ",", $p;
+       print $prog, ".rsp: \$(MAKEFILE)\n";
+       $objstr = &objects($p, "X.obj", "X.res", undef);
+       @objlist = split " ", $objstr;
+       @objlines = ("");
+       foreach $i (@objlist) {
+           if (length($objlines[$#objlines] . " $i") > 50) {
+               push @objlines, "";
+           }
+           $objlines[$#objlines] .= " $i";
+       }
+       print "\techo \$(BASELIBS) > $prog.rsp\n";
+       for ($i=0; $i<=$#objlines; $i++) {
+           print "\techo$objlines[$i] >> $prog.rsp\n";
+       }
+       print "\n";
+    }
+    foreach $d (&deps("X.obj", "X.res", $dirpfx, "\\")) {
+       print &splitline(sprintf("%s: %s", $d->{obj}, join " ", @{$d->{deps}})),
+         "\n";
+       if ($d->{obj} =~ /\.res$/) {
+           print "\trc \$(FWHACK) \$(RCFL) -r -fo".
+             $d->{obj}." ".$d->{deps}->[0]."\n";
+       } else {
+           $deflist = join "", map { " /D$_" } @{$d->{defs}};
+           print "\t\$(CC) \$(COMPAT) \$(FWHACK) \$(CFLAGS) \$(XFLAGS)$deflist".
+             " /c ".$d->{deps}->[0]." /Fo$d->{obj}\n";
+       }
+    }
+    print "\n";
+    print $makefile_extra{'wce'} || "";
+    print "\nclean: tidy\n".
+      "\t-del *.exe\n\n".
+      "tidy:\n".
+      "\t-del *.obj\n".
+      "\t-del *.res\n".
+      "\t-del *.pch\n".
+      "\t-del *.aps\n".
+      "\t-del *.ilk\n".
+      "\t-del *.pdb\n".
+      "\t-del *.rsp\n".
+      "\t-del *.dsp\n".
+      "\t-del *.dsw\n".
+      "\t-del *.ncb\n".
+      "\t-del *.opt\n".
+      "\t-del *.plg\n".
+      "\t-del *.map\n".
+      "\t-del *.idb\n".
+      "\t-del debug.log\n";
+    select STDOUT; close OUT;
+}
+
+if (defined $makefiles{'vcproj'}) {
+    $mftyp = 'vcproj';
+
+    ##-- MSVC 6 Workspace and projects
+    #
+    # Note: All files created in this section are written in binary
+    # mode, because although MSVC's command-line make can deal with
+    # LF-only line endings, MSVC project files really _need_ to be
+    # CRLF. Hence, in order for mkfiles.pl to generate usable project
+    # files even when run from Unix, I make sure all files are binary
+    # and explicitly write the CRLFs.
+    #
+    # Create directories if necessary
+    mkdir $makefiles{'vcproj'}
+        if(! -d $makefiles{'vcproj'});
+    chdir $makefiles{'vcproj'};
+    @deps = &deps("X.obj", "X.res", "", "\\");
+    %all_object_deps = map {$_->{obj} => $_->{deps}} @deps;
+    # Create the project files
+    # Get names of all Windows projects (GUI and console)
+    my @prognames = &prognames("G:C");
+    foreach $progname (@prognames) {
+       create_project(\%all_object_deps, $progname);
+    }
+    # Create the workspace file
+    open OUT, ">$project_name.dsw"; binmode OUT; select OUT;
+    print
+    "Microsoft Developer Studio Workspace File, Format Version 6.00\r\n".
+    "# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!\r\n".
+    "\r\n".
+    "###############################################################################\r\n".
+    "\r\n";
+    # List projects
+    foreach $progname (@prognames) {
+      ($windows_project, $type) = split ",", $progname;
+       print "Project: \"$windows_project\"=\".\\$windows_project\\$windows_project.dsp\" - Package Owner=<4>\r\n";
+    }
+    print
+    "\r\n".
+    "Package=<5>\r\n".
+    "{{{\r\n".
+    "}}}\r\n".
+    "\r\n".
+    "Package=<4>\r\n".
+    "{{{\r\n".
+    "}}}\r\n".
+    "\r\n".
+    "###############################################################################\r\n".
+    "\r\n".
+    "Global:\r\n".
+    "\r\n".
+    "Package=<5>\r\n".
+    "{{{\r\n".
+    "}}}\r\n".
+    "\r\n".
+    "Package=<3>\r\n".
+    "{{{\r\n".
+    "}}}\r\n".
+    "\r\n".
+    "###############################################################################\r\n".
+    "\r\n";
+    select STDOUT; close OUT;
+    chdir $orig_dir;
+
+    sub create_project {
+       my ($all_object_deps, $progname) = @_;
+       # Construct program's dependency info
+       %seen_objects = ();
+       %lib_files = ();
+       %source_files = ();
+       %header_files = ();
+       %resource_files = ();
+       @object_files = split " ", &objects($progname, "X.obj", "X.res", "X.lib");
+       foreach $object_file (@object_files) {
+           next if defined $seen_objects{$object_file};
+           $seen_objects{$object_file} = 1;
+           if($object_file =~ /\.lib$/io) {
+               $lib_files{$object_file} = 1;
+               next;
+           }
+           $object_deps = $all_object_deps{$object_file};
+           foreach $object_dep (@$object_deps) {
+               if($object_dep =~ /\.c$/io) {
+                   $source_files{$object_dep} = 1;
+                   next;
+               }
+               if($object_dep =~ /\.h$/io) {
+                   $header_files{$object_dep} = 1;
+                   next;
+               }
+               if($object_dep =~ /\.(rc|ico)$/io) {
+                   $resource_files{$object_dep} = 1;
+                   next;
+               }
+           }
+       }
+       $libs = join " ", sort keys %lib_files;
+       @source_files = sort keys %source_files;
+       @header_files = sort keys %header_files;
+       @resources = sort keys %resource_files;
+       ($windows_project, $type) = split ",", $progname;
+       mkdir $windows_project
+           if(! -d $windows_project);
+       chdir $windows_project;
+       $subsys = ($type eq "G") ? "windows" : "console";
+       open OUT, ">$windows_project.dsp"; binmode OUT; select OUT;
+       print
+       "# Microsoft Developer Studio Project File - Name=\"$windows_project\" - Package Owner=<4>\r\n".
+       "# Microsoft Developer Studio Generated Build File, Format Version 6.00\r\n".
+       "# ** DO NOT EDIT **\r\n".
+       "\r\n".
+       "# TARGTYPE \"Win32 (x86) Application\" 0x0101\r\n".
+       "\r\n".
+       "CFG=$windows_project - Win32 Debug\r\n".
+       "!MESSAGE This is not a valid makefile. To build this project using NMAKE,\r\n".
+       "!MESSAGE use the Export Makefile command and run\r\n".
+       "!MESSAGE \r\n".
+       "!MESSAGE NMAKE /f \"$windows_project.mak\".\r\n".
+       "!MESSAGE \r\n".
+       "!MESSAGE You can specify a configuration when running NMAKE\r\n".
+       "!MESSAGE by defining the macro CFG on the command line. For example:\r\n".
+       "!MESSAGE \r\n".
+       "!MESSAGE NMAKE /f \"$windows_project.mak\" CFG=\"$windows_project - Win32 Debug\"\r\n".
+       "!MESSAGE \r\n".
+       "!MESSAGE Possible choices for configuration are:\r\n".
+       "!MESSAGE \r\n".
+       "!MESSAGE \"$windows_project - Win32 Release\" (based on \"Win32 (x86) Application\")\r\n".
+       "!MESSAGE \"$windows_project - Win32 Debug\" (based on \"Win32 (x86) Application\")\r\n".
+       "!MESSAGE \r\n".
+       "\r\n".
+       "# Begin Project\r\n".
+       "# PROP AllowPerConfigDependencies 0\r\n".
+       "# PROP Scc_ProjName \"\"\r\n".
+       "# PROP Scc_LocalPath \"\"\r\n".
+       "CPP=cl.exe\r\n".
+       "MTL=midl.exe\r\n".
+       "RSC=rc.exe\r\n".
+       "\r\n".
+       "!IF  \"\$(CFG)\" == \"$windows_project - Win32 Release\"\r\n".
+       "\r\n".
+       "# PROP BASE Use_MFC 0\r\n".
+       "# PROP BASE Use_Debug_Libraries 0\r\n".
+       "# PROP BASE Output_Dir \"Release\"\r\n".
+       "# PROP BASE Intermediate_Dir \"Release\"\r\n".
+       "# PROP BASE Target_Dir \"\"\r\n".
+       "# PROP Use_MFC 0\r\n".
+       "# PROP Use_Debug_Libraries 0\r\n".
+       "# PROP Output_Dir \"Release\"\r\n".
+       "# PROP Intermediate_Dir \"Release\"\r\n".
+       "# PROP Ignore_Export_Lib 0\r\n".
+       "# PROP Target_Dir \"\"\r\n".
+       "# ADD BASE CPP /nologo /W3 /GX /O2 /D \"WIN32\" /D \"NDEBUG\" /D \"_WINDOWS\" /D \"_MBCS\" /YX /FD /c\r\n".
+       "# ADD CPP /nologo /W3 /GX /O2 /D \"WIN32\" /D \"NDEBUG\" /D \"_WINDOWS\" /D \"_MBCS\" /YX /FD /c\r\n".
+       "# ADD BASE MTL /nologo /D \"NDEBUG\" /mktyplib203 /win32\r\n".
+       "# ADD MTL /nologo /D \"NDEBUG\" /mktyplib203 /win32\r\n".
+       "# ADD BASE RSC /l 0x809 /d \"NDEBUG\"\r\n".
+       "# ADD RSC /l 0x809 /d \"NDEBUG\"\r\n".
+       "BSC32=bscmake.exe\r\n".
+       "# ADD BASE BSC32 /nologo\r\n".
+       "# ADD BSC32 /nologo\r\n".
+       "LINK32=link.exe\r\n".
+       "# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:$subsys /machine:I386\r\n".
+       "# ADD LINK32 $libs /nologo /subsystem:$subsys /machine:I386\r\n".
+       "# SUBTRACT LINK32 /pdb:none\r\n".
+       "\r\n".
+       "!ELSEIF  \"\$(CFG)\" == \"$windows_project - Win32 Debug\"\r\n".
+       "\r\n".
+       "# PROP BASE Use_MFC 0\r\n".
+       "# PROP BASE Use_Debug_Libraries 1\r\n".
+       "# PROP BASE Output_Dir \"Debug\"\r\n".
+       "# PROP BASE Intermediate_Dir \"Debug\"\r\n".
+       "# PROP BASE Target_Dir \"\"\r\n".
+       "# PROP Use_MFC 0\r\n".
+       "# PROP Use_Debug_Libraries 1\r\n".
+       "# PROP Output_Dir \"Debug\"\r\n".
+       "# PROP Intermediate_Dir \"Debug\"\r\n".
+       "# PROP Ignore_Export_Lib 0\r\n".
+       "# PROP Target_Dir \"\"\r\n".
+       "# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D \"WIN32\" /D \"_DEBUG\" /D \"_WINDOWS\" /D \"_MBCS\" /YX /FD /GZ /c\r\n".
+       "# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /D \"WIN32\" /D \"_DEBUG\" /D \"_WINDOWS\" /D \"_MBCS\" /YX /FD /GZ /c\r\n".
+       "# ADD BASE MTL /nologo /D \"_DEBUG\" /mktyplib203 /win32\r\n".
+       "# ADD MTL /nologo /D \"_DEBUG\" /mktyplib203 /win32\r\n".
+       "# ADD BASE RSC /l 0x809 /d \"_DEBUG\"\r\n".
+       "# ADD RSC /l 0x809 /d \"_DEBUG\"\r\n".
+       "BSC32=bscmake.exe\r\n".
+       "# ADD BASE BSC32 /nologo\r\n".
+       "# ADD BSC32 /nologo\r\n".
+       "LINK32=link.exe\r\n".
+       "# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:$subsys /debug /machine:I386 /pdbtype:sept\r\n".
+       "# ADD LINK32 $libs /nologo /subsystem:$subsys /debug /machine:I386 /pdbtype:sept\r\n".
+       "# SUBTRACT LINK32 /pdb:none\r\n".
+       "\r\n".
+       "!ENDIF \r\n".
+       "\r\n".
+       "# Begin Target\r\n".
+       "\r\n".
+       "# Name \"$windows_project - Win32 Release\"\r\n".
+       "# Name \"$windows_project - Win32 Debug\"\r\n".
+       "# Begin Group \"Source Files\"\r\n".
+       "\r\n".
+       "# PROP Default_Filter \"cpp;c;cxx;rc;def;r;odl;idl;hpj;bat\"\r\n";
+       foreach $source_file (@source_files) {
+           print
+             "# Begin Source File\r\n".
+             "\r\n".
+             "SOURCE=..\\..\\$source_file\r\n";
+           if($source_file =~ /ssh\.c/io) {
+               # Disable 'Edit and continue' as Visual Studio can't handle the macros
+               print
+                 "\r\n".
+                 "!IF  \"\$(CFG)\" == \"$windows_project - Win32 Release\"\r\n".
+                 "\r\n".
+                 "!ELSEIF  \"\$(CFG)\" == \"$windows_project - Win32 Debug\"\r\n".
+                 "\r\n".
+                 "# ADD CPP /Zi\r\n".
+                 "\r\n".
+                 "!ENDIF \r\n".
+                 "\r\n";
+           }
+           print "# End Source File\r\n";
+       }
+       print
+       "# End Group\r\n".
+       "# Begin Group \"Header Files\"\r\n".
+       "\r\n".
+       "# PROP Default_Filter \"h;hpp;hxx;hm;inl\"\r\n";
+       foreach $header_file (@header_files) {
+           print
+             "# Begin Source File\r\n".
+             "\r\n".
+             "SOURCE=..\\..\\$header_file\r\n".
+             "# End Source File\r\n";
+       }
+       print
+       "# End Group\r\n".
+       "# Begin Group \"Resource Files\"\r\n".
+       "\r\n".
+       "# PROP Default_Filter \"ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe\"\r\n";
+       foreach $resource_file (@resources) {
+           print
+             "# Begin Source File\r\n".
+             "\r\n".
+             "SOURCE=..\\..\\$resource_file\r\n".
+             "# End Source File\r\n";
+       }
+       print
+       "# End Group\r\n".
+       "# End Target\r\n".
+       "# End Project\r\n";
+       select STDOUT; close OUT;
+       chdir "..";
+    }
+}
+
+if (defined $makefiles{'gtk'}) {
+    $mftyp = 'gtk';
+    $dirpfx = &dirpfx($makefiles{'gtk'}, "/");
+
+    ##-- X/GTK/Unix makefile
+    open OUT, ">$makefiles{'gtk'}"; select OUT;
+    print
+    "# Makefile for $project_name under X/GTK and Unix.\n".
+    "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n".
+    "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n";
+    # gcc command line option is -D not /D
+    ($_ = $help) =~ s/=\/D/=-D/gs;
+    print $_;
+    print
+    "\n".
+    "# You can define this path to point at your tools if you need to\n".
+    "# TOOLPATH = /opt/gcc/bin\n".
+    "CC := \$(TOOLPATH)\$(CC)\n".
+    "# You can manually set this to `gtk-config' or `pkg-config gtk+-1.2'\n".
+    "# (depending on what works on your system) if you want to enforce\n".
+    "# building with GTK 1.2, or you can set it to `pkg-config gtk+-2.0'\n".
+    "# if you want to enforce 2.0. The default is to try 2.0 and fall back\n".
+    "# to 1.2 if it isn't found.\n".
+    "GTK_CONFIG = sh -c 'pkg-config gtk+-2.0 \$\$0 2>/dev/null || gtk-config \$\$0'\n".
+    "\n".
+    &splitline("CFLAGS := -O2 -Wall -Werror -ansi -pedantic -g " .
+              (join " ", map {"-I$dirpfx$_"} @srcdirs) .
+              " `\$(GTK_CONFIG) --cflags` \$(CFLAGS)")."\n".
+    "XLIBS = `\$(GTK_CONFIG) --libs` -lm\n".
+    "ULIBS = -lm#\n".
+    "INSTALL=install\n",
+    "INSTALL_PROGRAM=\$(INSTALL)\n",
+    "INSTALL_DATA=\$(INSTALL)\n",
+    "prefix=/usr/local\n",
+    "exec_prefix=\$(prefix)\n",
+    "bindir=\$(exec_prefix)/bin\n",
+    "gamesdir=\$(exec_prefix)/games\n",
+    "mandir=\$(prefix)/man\n",
+    "man1dir=\$(mandir)/man1\n",
+    "\n";
+    print &splitline("all:" . join "", map { " \$(BINPREFIX)$_" }
+                     &progrealnames("X:U"));
+    print "\n\n";
+    foreach $p (&prognames("X:U")) {
+      ($prog, $type) = split ",", $p;
+      $objstr = &objects($p, "X.o", undef, undef);
+      print &splitline("\$(BINPREFIX)" . $prog . ": " . $objstr), "\n";
+      $libstr = &objects($p, undef, undef, "-lX");
+      print &splitline("\t\$(CC) -o \$@ $objstr $libstr \$(XLFLAGS) \$(${type}LIBS)", 69),
+         "\n\n";
+    }
+    foreach $d (&deps("X.o", undef, $dirpfx, "/")) {
+      print &splitline(sprintf("%s: %s", $d->{obj}, join " ", @{$d->{deps}})),
+          "\n";
+      $deflist = join "", map { " -D$_" } @{$d->{defs}};
+      print "\t\$(CC) \$(COMPAT) \$(FWHACK) \$(CFLAGS) \$(XFLAGS)$deflist" .
+         " -c \$< -o \$\@\n";
+    }
+    print "\n";
+    print $makefile_extra{'gtk'} || "";
+    print "\nclean:\n".
+    "\trm -f *.o". (join "", map { " \$(BINPREFIX)$_" } &progrealnames("X:U")) . "\n";
+    select STDOUT; close OUT;
+}
+
+if (defined $makefiles{'am'}) {
+    $mftyp = 'am';
+    die "Makefile.am in a subdirectory is not supported\n"
+        if &dirpfx($makefiles{'am'}, "/") ne "";
+
+    ##-- Unix/autoconf Makefile.am
+    open OUT, ">$makefiles{'am'}"; select OUT;
+    print
+    "# Makefile.am for $project_name under Unix with Autoconf/Automake.\n".
+    "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n".
+    "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n\n";
+
+    print $makefile_extra{'am_begin'} || "";
+
+    # All programs go in noinstprogs by default. If you want them
+    # installed anywhere else, you have to also add them to
+    # bin_PROGRAMS using '!begin am'. (Automake doesn't seem to mind
+    # having a program name in _both_ of bin_PROGRAMS and
+    # noinst_PROGRAMS.)
+    @noinstprogs = ();
+    foreach $p (&prognames("X:U")) {
+        ($prog, $type) = split ",", $p;
+        push @noinstprogs, $prog;
+    }
+    print &splitline(join " ", "noinst_PROGRAMS", "=", @noinstprogs), "\n";
+
+    %objtosrc = ();
+    %amspeciallibs = ();
+    %amlibobjname = ();
+    %allsources = ();
+    foreach $d (&deps("X", undef, "", "/", "am")) {
+        my $obj = $d->{obj};
+        my $use_archive = 0;
+        
+        if (defined $d->{defs}) {
+            # This file needs to go in an archive, so that we can
+            # change the preprocess flags to include some -Ds
+            $use_archive = 1;
+            $archivecppflags{$obj} = [map { " -D$_" } @{$d->{defs}}];
+        }
+        if (defined $cflags{'am'} && $cflags{'am'}->{$obj}) {
+            # This file needs to go in an archive, so that we can
+            # change the compile flags as specified in Recipe
+            $use_archive = 1;
+            $archivecflags{$obj} = [$cflags{'am'}->{$obj}];
+        }
+        if ($use_archive) {
+            $amspeciallibs{$obj} = "lib${obj}.a";
+            $amlibobjname{$obj} = "lib${obj}_a-" .
+                basename($d->{deps}->[0], ".c", ".m") .
+                ".\$(OBJEXT)";
+        }
+        $objtosrc{$obj} = $d->{deps};
+        map { $allsources{$_} = 1 } @{$d->{deps}};
+    }
+
+    # 2014-02-22: as of automake-1.14 we begin to get complained at if
+    # we don't use this option
+    print "AUTOMAKE_OPTIONS = subdir-objects\n\n";
+
+    # Complete list of source and header files. Not used by the
+    # auto-generated parts of this makefile, but Recipe might like to
+    # have it available as a variable so that mandatory-rebuild things
+    # (version.o) can conveniently be made to depend on it.
+    print &splitline(join " ", "allsources", "=",
+                     sort {$a cmp $b} keys %allsources), "\n\n";
+
+    @amcppflags = map {"-I\$(srcdir)/$_"} @srcdirs;
+    print &splitline(join " ", "AM_CPPFLAGS", "=", @amcppflags, "\n");
+
+    @amcflags = ("\$(GTK_CFLAGS)", "\$(WARNINGOPTS)");
+    print &splitline(join " ", "AM_CFLAGS", "=", @amcflags), "\n";
+
+    %amlibsused = ();
+    foreach $p (&prognames("X:U")) {
+        ($prog, $type) = split ",", $p;
+        @progsources = ("${prog}_SOURCES", "=");
+        %sourcefiles = ();
+        @ldadd = ();
+        $objstr = &objects($p, "X", undef, undef);
+        foreach $obj (split / /,$objstr) {
+            if ($amspeciallibs{$obj}) {
+                $amlibsused{$obj} = 1;
+                push @ldadd, $amlibobjname{$obj};
+            } else {
+                map { $sourcefiles{$_} = 1 } @{$objtosrc{$obj}};
+            }
+        }
+        push @progsources, sort { $a cmp $b } keys %sourcefiles;
+        print &splitline(join " ", @progsources), "\n";
+        if ($type eq "X") {
+            push @ldadd, "\$(GTK_LIBS)";
+        }
+        push @ldadd, "-lm";
+        print &splitline(join " ", "${prog}_LDADD", "=", @ldadd), "\n";
+        print "\n";
+    }
+
+    foreach $obj (sort { $a cmp $b } keys %amlibsused) {
+        print &splitline(join " ", "lib${obj}_a_SOURCES", "=",
+                         @{$objtosrc{$obj}}), "\n";
+        print &splitline(join " ", "lib${obj}_a_CPPFLAGS", "=",
+                         @amcflags, @{$archivecppflags{$obj}}), "\n"
+                             if $archivecppflags{$obj};
+        print &splitline(join " ", "lib${obj}_a_CFLAGS", "=",
+                         @amcflags, @{$archivecflags{$obj}}), "\n"
+                             if $archivecflags{$obj};
+    }
+    print &splitline(join " ", "noinst_LIBRARIES", "=",
+                     sort { $a cmp $b }
+                      map { $amspeciallibs{$_} }
+                     keys %amlibsused),
+                     "\n\n";
+
+    print $makefile_extra{'am'} || "";
+    select STDOUT; close OUT;
+}
+
+if (defined $makefiles{'mpw'}) {
+    $mftyp = 'mpw';
+    ##-- MPW Makefile
+    open OUT, ">$makefiles{'mpw'}"; select OUT;
+    print
+    "# Makefile for $project_name under MPW.\n#\n".
+    "# This file was created by `mkfiles.pl' from the `Recipe' file.\n".
+    "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n";
+    # MPW command line option is -d not /D
+    ($_ = $help) =~ s/=\/D/=-d /gs;
+    print $_;
+    print "\n\n".
+    "ROptions     = `Echo \"{VER}\" | StreamEdit -e \"1,\$ replace /=(\xc5)\xa81\xb0/ 'STR=\xb6\xb6\xb6\xb6\xb6\"' \xa81 '\xb6\xb6\xb6\xb6\xb6\"'\"`".
+    "\n".
+    "C_68K = {C}\n".
+    "C_CFM68K = {C}\n".
+    "C_PPC = {PPCC}\n".
+    "C_Carbon = {PPCC}\n".
+    "\n".
+    "# -w 35 disables \"unused parameter\" warnings\n".
+    "COptions     = -i : -i :: -i ::charset -w 35 -w err -proto strict -ansi on \xb6\n".
+    "         -notOnce\n".
+    "COptions_68K = {COptions} -model far -opt time\n".
+    "# Enabling \"-opt space\" for CFM-68K gives me undefined references to\n".
+    "# _\$LDIVT and _\$LMODT.\n".
+    "COptions_CFM68K = {COptions} -model cfmSeg -opt time\n".
+    "COptions_PPC = {COptions} -opt size -traceback\n".
+    "COptions_Carbon = {COptions} -opt size -traceback -d TARGET_API_MAC_CARBON\n".
+    "\n".
+    "Link_68K = ILink\n".
+    "Link_CFM68K = ILink\n".
+    "Link_PPC = PPCLink\n".
+    "Link_Carbon = PPCLink\n".
+    "\n".
+    "LinkOptions = -c 'pTTY'\n".
+    "LinkOptions_68K = {LinkOptions} -br 68k -model far -compact\n".
+    "LinkOptions_CFM68K = {LinkOptions} -br 020 -model cfmseg -compact\n".
+    "LinkOptions_PPC = {LinkOptions}\n".
+    "LinkOptions_Carbon = -m __appstart -w {LinkOptions}\n".
+    "\n".
+    "Libs_68K =        \"{CLibraries}StdCLib.far.o\" \xb6\n".
+    "          \"{Libraries}MacRuntime.o\" \xb6\n".
+    "          \"{Libraries}MathLib.far.o\" \xb6\n".
+    "          \"{Libraries}IntEnv.far.o\" \xb6\n".
+    "          \"{Libraries}Interface.o\" \xb6\n".
+    "          \"{Libraries}Navigation.far.o\" \xb6\n".
+    "          \"{Libraries}OpenTransport.o\" \xb6\n".
+    "          \"{Libraries}OpenTransportApp.o\" \xb6\n".
+    "          \"{Libraries}OpenTptInet.o\" \xb6\n".
+    "          \"{Libraries}UnicodeConverterLib.far.o\"\n".
+    "\n".
+    "Libs_CFM =        \"{SharedLibraries}InterfaceLib\" \xb6\n".
+    "          \"{SharedLibraries}StdCLib\" \xb6\n".
+    "          \"{SharedLibraries}AppearanceLib\" \xb6\n".
+    "                  -weaklib AppearanceLib \xb6\n".
+    "          \"{SharedLibraries}NavigationLib\" \xb6\n".
+    "                  -weaklib NavigationLib \xb6\n".
+    "          \"{SharedLibraries}TextCommon\" \xb6\n".
+    "                  -weaklib TextCommon \xb6\n".
+    "          \"{SharedLibraries}UnicodeConverter\" \xb6\n".
+    "                  -weaklib UnicodeConverter\n".
+    "\n".
+    "Libs_CFM68K =     {Libs_CFM} \xb6\n".
+    "          \"{CFM68KLibraries}NuMacRuntime.o\"\n".
+    "\n".
+    "Libs_PPC =        {Libs_CFM} \xb6\n".
+    "          \"{SharedLibraries}ControlsLib\" \xb6\n".
+    "                  -weaklib ControlsLib \xb6\n".
+    "          \"{SharedLibraries}WindowsLib\" \xb6\n".
+    "                  -weaklib WindowsLib \xb6\n".
+    "          \"{SharedLibraries}OpenTransportLib\" \xb6\n".
+    "                  -weaklib OTClientLib \xb6\n".
+    "                  -weaklib OTClientUtilLib \xb6\n".
+    "          \"{SharedLibraries}OpenTptInternetLib\" \xb6\n".
+    "                  -weaklib OTInetClientLib \xb6\n".
+    "          \"{PPCLibraries}StdCRuntime.o\" \xb6\n".
+    "          \"{PPCLibraries}PPCCRuntime.o\" \xb6\n".
+    "          \"{PPCLibraries}CarbonAccessors.o\" \xb6\n".
+    "          \"{PPCLibraries}OpenTransportAppPPC.o\" \xb6\n".
+    "          \"{PPCLibraries}OpenTptInetPPC.o\"\n".
+    "\n".
+    "Libs_Carbon =     \"{PPCLibraries}CarbonStdCLib.o\" \xb6\n".
+    "          \"{PPCLibraries}StdCRuntime.o\" \xb6\n".
+    "          \"{PPCLibraries}PPCCRuntime.o\" \xb6\n".
+    "          \"{SharedLibraries}CarbonLib\" \xb6\n".
+    "          \"{SharedLibraries}StdCLib\"\n".
+    "\n";
+    print &splitline("all \xc4 " . join(" ", &progrealnames("M")), undef, "\xb6");
+    print "\n\n";
+    foreach $p (&prognames("M")) {
+      ($prog, $type) = split ",", $p;
+
+      print &splitline("$prog \xc4 $prog.68k $prog.ppc $prog.carbon",
+                  undef, "\xb6"), "\n\n";
+
+      $rsrc = &objects($p, "", "X.rsrc", undef);
+
+      foreach $arch (qw(68K CFM68K PPC Carbon)) {
+          $objstr = &objects($p, "X.\L$arch\E.o", "", undef);
+          print &splitline("$prog.\L$arch\E \xc4 $objstr $rsrc", undef, "\xb6");
+          print "\n";
+          print &splitline("\tDuplicate -y $rsrc {Targ}", 69, "\xb6"), "\n";
+          print &splitline("\t{Link_$arch} -o {Targ} -fragname $prog " .
+                      "{LinkOptions_$arch} " .
+                      $objstr . " {Libs_$arch}", 69, "\xb6"), "\n";
+          print &splitline("\tSetFile -a BMi {Targ}", 69, "\xb6"), "\n\n";
+      }
+
+    }
+    foreach $d (&deps("", "X.rsrc", "::", ":")) {
+      next unless $d->{obj};
+      print &splitline(sprintf("%s \xc4 %s", $d->{obj}, join " ", @{$d->{deps}}),
+                  undef, "\xb6"), "\n";
+      print "\tRez ", $d->{deps}->[0], " -o {Targ} {ROptions}\n\n";
+    }
+    foreach $arch (qw(68K CFM68K)) {
+        foreach $d (&deps("X.\L$arch\E.o", "", "::", ":")) {
+        next unless $d->{obj};
+       print &splitline(sprintf("%s \xc4 %s", $d->{obj},
+                                join " ", @{$d->{deps}}),
+                        undef, "\xb6"), "\n";
+        print "\t{C_$arch} ", $d->{deps}->[0],
+              " -o {Targ} {COptions_$arch}\n\n";
+         }
+    }
+    foreach $arch (qw(PPC Carbon)) {
+        foreach $d (&deps("X.\L$arch\E.o", "", "::", ":")) {
+        next unless $d->{obj};
+       print &splitline(sprintf("%s \xc4 %s", $d->{obj},
+                                join " ", @{$d->{deps}}),
+                        undef, "\xb6"), "\n";
+        # The odd stuff here seems to stop afpd getting confused.
+        print "\techo -n > {Targ}\n";
+        print "\tsetfile -t XCOF {Targ}\n";
+        print "\t{C_$arch} ", $d->{deps}->[0],
+              " -o {Targ} {COptions_$arch}\n\n";
+         }
+    }
+    select STDOUT; close OUT;
+}
+
+if (defined $makefiles{'lcc'}) {
+    $mftyp = 'lcc';
+    $dirpfx = &dirpfx($makefiles{'lcc'}, "\\");
+
+    ##-- lcc makefile
+    open OUT, ">$makefiles{'lcc'}"; select OUT;
+    print
+    "# Makefile for $project_name under lcc.\n".
+    "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n".
+    "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n";
+    # lcc command line option is -D not /D
+    ($_ = $help) =~ s/=\/D/=-D/gs;
+    print $_;
+    print
+    "\n".
+    "# If you rename this file to `Makefile', you should change this line,\n".
+    "# so that the .rsp files still depend on the correct makefile.\n".
+    "MAKEFILE = Makefile.lcc\n".
+    "\n".
+    "# C compilation flags\n".
+    "CFLAGS = -D_WINDOWS " .
+      (join " ", map {"-I$dirpfx$_"} @srcdirs) .
+      "\n".
+    "\n".
+    "# Get include directory for resource compiler\n".
+    "\n";
+    print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("G:C"));
+    print "\n\n";
+    foreach $p (&prognames("G:C")) {
+      ($prog, $type) = split ",", $p;
+      $objstr = &objects($p, "X.obj", "X.res", undef);
+      print &splitline("$prog.exe: " . $objstr ), "\n";
+      $subsystemtype = undef;
+      if ($type eq "G") { $subsystemtype = "-subsystem  windows"; }
+      my $libss = "shell32.lib wsock32.lib ws2_32.lib winspool.lib winmm.lib imm32.lib";
+      print &splitline("\tlcclnk $subsystemtype -o $prog.exe $objstr $libss");
+      print "\n\n";
+    }
+
+    foreach $d (&deps("X.obj", "X.res", $dirpfx, "\\")) {
+      print &splitline(sprintf("%s: %s", $d->{obj}, join " ", @{$d->{deps}})),
+        "\n";
+      if ($d->{obj} =~ /\.res$/) {
+       print &splitline("\tlrc \$(FWHACK) \$(RCFL) -r \$*.rc",69)."\n";
+      } else {
+       $deflist = join "", map { " -D$_" } @{$d->{defs}};
+       print &splitline("\tlcc -O -p6 \$(COMPAT) \$(FWHACK) \$(CFLAGS)".
+                        " \$(XFLAGS)$deflist ".$d->{deps}->[0]." -o \$\@",69)."\n";
+      }
+    }
+    print "\n";
+    print $makefile_extra{'lcc'} || "";
+    print "\nclean:\n".
+    "\t-del *.obj\n".
+    "\t-del *.exe\n".
+    "\t-del *.res\n";
+
+    select STDOUT; close OUT;
+}
+
+if (defined $makefiles{'nestedvm'}) {
+    $mftyp = 'nestedvm';
+    $dirpfx = &dirpfx($makefiles{'nestedvm'}, "/");
+
+    ##-- NestedVM makefile
+    open OUT, ">$makefiles{'nestedvm'}"; select OUT;
+    print
+    "# Makefile for $project_name under NestedVM.\n".
+    "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n".
+    "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n";
+    # gcc command line option is -D not /D
+    ($_ = $help) =~ s/=\/D/=-D/gs;
+    print $_;
+    print
+    "\n".
+    "# This path points at the nestedvm root directory\n".
+    "NESTEDVM = /opt/nestedvm\n".
+    "# You can define this path to point at your tools if you need to\n".
+    "TOOLPATH = \$(NESTEDVM)/upstream/install/bin\n".
+    "CC = \$(TOOLPATH)/mips-unknown-elf-gcc\n".
+    "\n".
+    &splitline("CFLAGS = -O2 -Wall -Werror -DSLOW_SYSTEM -g " .
+              (join " ", map {"-I$dirpfx$_"} @srcdirs))."\n".
+    "\n";
+    print &splitline("all:" . join "", map { " $_.jar" } &progrealnames("X"));
+    print "\n\n";
+    foreach $p (&prognames("X")) {
+      ($prog, $type) = split ",", $p;
+      $objstr = &objects($p, "X.o", undef, undef);
+      $objstr =~ s/gtk\.o/nestedvm\.o/g;
+      print &splitline($prog . ".mips: " . $objstr), "\n";
+      $libstr = &objects($p, undef, undef, "-lX");
+      print &splitline("\t\$(CC) \$(${type}LDFLAGS) -o \$@ " .
+                       $objstr . " $libstr -lm", 69), "\n\n";
+    }
+    foreach $d (&deps("X.o", undef, $dirpfx, "/")) {
+      $oobjs = $d->{obj};
+      $ddeps= join " ", @{$d->{deps}};
+      $oobjs =~ s/gtk/nestedvm/g;
+      $ddeps =~ s/gtk/nestedvm/g;
+      print &splitline(sprintf("%s: %s", $oobjs, $ddeps)),
+          "\n";
+      $deflist = join "", map { " -D$_" } @{$d->{defs}};
+      print "\t\$(CC) \$(COMPAT) \$(FWHACK) \$(CFLAGS) \$(XFLAGS)$deflist" .
+         " -c \$< -o \$\@\n";
+    }
+    print "\n";
+    print $makefile_extra{'nestedvm'} || "";
+    print "\nclean:\n".
+    "\trm -rf *.o *.mips *.class *.html *.jar org applet.manifest\n";
+    select STDOUT; close OUT;
+}
+
+if (defined $makefiles{'osx'}) {
+    $mftyp = 'osx';
+    $dirpfx = &dirpfx($makefiles{'osx'}, "/");
+    @osxarchs = ('i386');
+
+    ##-- Mac OS X makefile
+    open OUT, ">$makefiles{'osx'}"; select OUT;
+    print
+    "# Makefile for $project_name under Mac OS X.\n".
+    "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n".
+    "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n";
+    # gcc command line option is -D not /D
+    ($_ = $help) =~ s/=\/D/=-D/gs;
+    print $_;
+    print
+    "CC = \$(TOOLPATH)gcc\n".
+    "LIPO = \$(TOOLPATH)lipo\n".
+    "\n".
+    &splitline("CFLAGS = -O2 -Wall -Werror -g " .
+              (join " ", map {"-I$dirpfx$_"} @srcdirs))."\n".
+    "LDFLAGS = -framework Cocoa\n".
+    &splitline("all:" . join "", map { " $_" } &progrealnames("MX:U")) .
+    "\n";
+    print $makefile_extra{'osx'} || "";
+    print "\n".
+    ".SUFFIXES: .o .c .m\n".
+    "\n";
+    print "\n\n";
+    foreach $p (&prognames("MX")) {
+      ($prog, $type) = split ",", $p;
+      $icon = &special($p, ".icns");
+      $infoplist = &special($p, "info.plist");
+      print "${prog}.app:\n\tmkdir -p \$\@\n";
+      print "${prog}.app/Contents: ${prog}.app\n\tmkdir -p \$\@\n";
+      print "${prog}.app/Contents/MacOS: ${prog}.app/Contents\n\tmkdir -p \$\@\n";
+      $targets = "${prog}.app/Contents/MacOS/$prog";
+      if (defined $icon) {
+       print "${prog}.app/Contents/Resources: ${prog}.app/Contents\n\tmkdir -p \$\@\n";
+       print "${prog}.app/Contents/Resources/${prog}.icns: ${prog}.app/Contents/Resources $icon\n\tcp $icon \$\@\n";
+       $targets .= " ${prog}.app/Contents/Resources/${prog}.icns";
+      }
+      if (defined $infoplist) {
+       print "${prog}.app/Contents/Info.plist: ${prog}.app/Contents/Resources $infoplist\n\tcp $infoplist \$\@\n";
+       $targets .= " ${prog}.app/Contents/Info.plist";
+      }
+      $targets .= " \$(${prog}_extra)";
+      print &splitline("${prog}: $targets", 69) . "\n\n";
+      $libstr = &objects($p, undef, undef, "-lX");
+      $archbins = "";
+      foreach $arch (@osxarchs) {
+       $objstr = &objects($p, "X.${arch}.o", undef, undef);
+       print &splitline("${prog}.${arch}.bin: " . $objstr), "\n";
+       print &splitline("\t\$(CC) -arch ${arch} -mmacosx-version-min=10.4 \$(LDFLAGS) -o \$@ " .
+                       $objstr . " $libstr", 69), "\n\n";
+       $archbins .= " ${prog}.${arch}.bin";
+      }
+      print &splitline("${prog}.app/Contents/MacOS/$prog: ".
+                      "${prog}.app/Contents/MacOS" . $archbins), "\n";
+      print &splitline("\t\$(LIPO) -create $archbins -output \$@", 69), "\n\n";
+    }
+    foreach $p (&prognames("U")) {
+      ($prog, $type) = split ",", $p;
+      $libstr = &objects($p, undef, undef, "-lX");
+      $archbins = "";
+      foreach $arch (@osxarchs) {
+       $objstr = &objects($p, "X.${arch}.o", undef, undef);
+       print &splitline("${prog}.${arch}: " . $objstr), "\n";
+       print &splitline("\t\$(CC) -arch ${arch} -mmacosx-version-min=10.4 \$(ULDFLAGS) -o \$@ " .
+                       $objstr . " $libstr", 69), "\n\n";
+       $archbins .= " ${prog}.${arch}";
+      }
+      print &splitline("${prog}:" . $archbins), "\n";
+      print &splitline("\t\$(LIPO) -create $archbins -output \$@", 69), "\n\n";
+    }
+    foreach $arch (@osxarchs) {
+      foreach $d (&deps("X.${arch}.o", undef, $dirpfx, "/")) {
+        print &splitline(sprintf("%s: %s", $d->{obj}, join " ", @{$d->{deps}})),
+            "\n";
+        $deflist = join "", map { " -D$_" } @{$d->{defs}};
+        if ($d->{deps}->[0] =~ /\.m$/) {
+         print "\t\$(CC) -arch $arch -mmacosx-version-min=10.4 -x objective-c \$(COMPAT) \$(FWHACK) \$(CFLAGS)".
+             " \$(XFLAGS)$deflist -c \$< -o \$\@\n";
+        } else {
+         print "\t\$(CC) -arch $arch -mmacosx-version-min=10.4 \$(COMPAT) \$(FWHACK) \$(CFLAGS) \$(XFLAGS)$deflist" .
+             " -c \$< -o \$\@\n";
+        }
+      }
+    }
+    print "\nclean:\n".
+    "\trm -f *.o *.dmg". (join "", map { my $a=$_; (" $a", map { " ${a}.$_" } @osxarchs) } &progrealnames("U")) . "\n".
+    "\trm -rf *.app\n";
+    select STDOUT; close OUT;
+}
+
+if (defined $makefiles{'gnustep'}) {
+    $mftyp = 'gnustep';
+    $dirpfx = &dirpfx($makefiles{'gnustep'}, "/");
+
+    ##-- GNUstep makefile (use with 'gs_make -f Makefile.gnustep')
+
+    # This is a pretty evil way to do things. In an ideal world, I'd
+    # use the approved GNUstep makefile mechanism which just defines a
+    # variable or two saying what source files go into what binary and
+    # then includes application.make. Unfortunately, that has the
+    # automake-ish limitation that it doesn't let you choose different
+    # command lines for each object, so I can't arrange for all those
+    # files with -DTHIS and -DTHAT to Just Work.
+    #
+    # A simple if ugly fix would be to have mkfiles.pl construct a
+    # directory full of stub C files of the form '#define thing',
+    # '#include "real_source_file"', and then reference those in this
+    # makefile. That would also make it easy to build a proper
+    # automake makefile.
+    open OUT, ">$makefiles{'gnustep'}"; select OUT;
+    print
+    "# Makefile for $project_name under GNUstep.\n".
+    "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n".
+    "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n";
+    # gcc command line option is -D not /D
+    ($_ = $help) =~ s/=\/D/=-D/gs;
+    print $_;
+    print
+    "NEEDS_GUI=yes\n".
+    "include \$(GNUSTEP_MAKEFILES)/common.make\n".
+    "include \$(GNUSTEP_MAKEFILES)/rules.make\n".
+    "include \$(GNUSTEP_MAKEFILES)/Instance/rules.make\n".
+    "\n".
+    &splitline("all::" . join "", map { " $_" } &progrealnames("MX:U")) .
+    "\n";
+    print $makefile_extra{'gnustep'} || "";
+    print "\n".
+    ".SUFFIXES: .o .c .m\n".
+    "\n";
+    print "\n\n";
+    foreach $p (&prognames("MX")) {
+      ($prog, $type) = split ",", $p;
+      $icon = &special($p, ".icns");
+      $infoplist = &special($p, "info.plist");
+      print "${prog}.app:\n\tmkdir -p \$\@\n";
+      $targets = "${prog}.app ${prog}.app/$prog";
+      if (defined $icon) {
+       print "${prog}.app/Resources: ${prog}.app\n\tmkdir -p \$\@\n";
+       print "${prog}.app/Resources/${prog}.icns: ${prog}.app/Resources $icon\n\tcp $icon \$\@\n";
+       $targets .= " ${prog}.app/Resources/${prog}.icns";
+      }
+      if (defined $infoplist) {
+       print "${prog}.app/Info.plist: ${prog}.app $infoplist\n\tcp $infoplist \$\@\n";
+       $targets .= " ${prog}.app/Info.plist";
+      }
+      $targets .= " \$(${prog}_extra)";
+      print &splitline("${prog}: $targets", 69) . "\n\n";
+      $libstr = &objects($p, undef, undef, "-lX");
+      $objstr = &objects($p, "X.o", undef, undef);
+      print &splitline("${prog}.app/$prog: " . $objstr), "\n";
+      print &splitline("\t\$(CC) \$(ALL_LDFLAGS) -o \$@ " . $objstr . " \$(ALL_LIB_DIRS) $libstr \$(ALL_LIBS)", 69), "\n\n";
+    }
+    foreach $p (&prognames("U")) {
+      ($prog, $type) = split ",", $p;
+      $libstr = &objects($p, undef, undef, "-lX");
+      $objstr = &objects($p, "X.o", undef, undef);
+      print &splitline("${prog}: " . $objstr), "\n";
+      print &splitline("\t\$(CC) \$(ULDFLAGS) -o \$@ " . $objstr . " $libstr", 69), "\n\n";
+    }
+    foreach $d (&deps("X.o", undef, $dirpfx, "/")) {
+      print &splitline(sprintf("%s: %s", $d->{obj}, join " ", @{$d->{deps}})),
+      "\n";
+      $deflist = join "", map { " -D$_" } @{$d->{defs}};
+      if ($d->{deps}->[0] =~ /\.m$/) {
+        print "\t\$(CC) -DGNUSTEP \$(ALL_OBJCFLAGS) \$(COMPAT) \$(FWHACK) \$(OBJCFLAGS)".
+                " \$(XFLAGS)$deflist -c \$< -o \$\@\n";
+      } else {
+        print "\t\$(CC) \$(ALL_CFLAGS) \$(COMPAT) \$(FWHACK) \$(CFLAGS) \$(XFLAGS)$deflist" .
+              " -c \$< -o \$\@\n";
+      }
+    }
+    print "\nclean::\n".
+    "\trm -f *.o ". (join " ", &progrealnames("U")) . "\n".
+    "\trm -rf *.app\n";
+    select STDOUT; close OUT;
+}
+
+if (defined $makefiles{'emcc'}) {
+    $mftyp = 'emcc';
+    $dirpfx = &dirpfx($makefiles{'emcc'}, "/");
+
+    ##-- Makefile for building Javascript puzzles via Emscripten
+
+    open OUT, ">$makefiles{'emcc'}"; select OUT;
+    print
+    "# Makefile for $project_name using Emscripten. Requires GNU make.\n".
+    "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n".
+    "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n";
+    # emcc command line option is -D not /D
+    ($_ = $help) =~ s/=\/D/=-D/gs;
+    print $_;
+    print
+    "\n".
+    "# This can be set on the command line to point at the emcc command,\n".
+    "# if it is not on your PATH.\n".
+    "EMCC = emcc\n".
+    "\n".
+    &splitline("CFLAGS = -DSLOW_SYSTEM " .
+              (join " ", map {"-I$dirpfx$_"} @srcdirs))."\n".
+    "\n";
+    $output_js_files = join "", map { " \$(OUTPREFIX)$_.js" } &progrealnames("X");
+    print &splitline("all:" . $output_js_files);
+    print "\n\n";
+    foreach $p (&prognames("X")) {
+      ($prog, $type) = split ",", $p;
+      $objstr = &objects($p, "X.o", undef, undef);
+      $objstr =~ s/gtk\.o/emcc\.o/g;
+      print &splitline("\$(OUTPREFIX)" . $prog . ".js: " . $objstr . " emccpre.js emcclib.js emccx.json"), "\n";
+      print "\t\$(EMCC) -o \$(OUTPREFIX)".$prog.".js ".
+          "-O2 ".
+          "-s ASM_JS=1 ".
+          "--pre-js emccpre.js ".
+          "--js-library emcclib.js ".
+          "-s EXPORTED_FUNCTIONS=\"`sed 's://.*::' emccx.json | tr -d ' \\n'`\" " . $objstr . "\n\n";
+    }
+    foreach $d (&deps("X.o", undef, $dirpfx, "/")) {
+      $oobjs = $d->{obj};
+      $ddeps= join " ", @{$d->{deps}};
+      $oobjs =~ s/gtk/emcc/g;
+      $ddeps =~ s/gtk/emcc/g;
+      print &splitline(sprintf("%s: %s", $oobjs, $ddeps)),
+          "\n";
+      $deflist = join "", map { " -D$_" } @{$d->{defs}};
+      print "\t\$(EMCC) \$(CFLAGS) \$(XFLAGS)$deflist" .
+         " -c \$< -o \$\@\n";
+    }
+    print "\n";
+    print $makefile_extra{'emcc'} || "";
+    print "\nclean:\n".
+    "\trm -rf *.o $output_js_files\n";
+    select STDOUT; close OUT;
+}
+
+# All done, so do the Unix postprocessing if asked to.
+
+if ($do_unix) {
+    chdir $orig_dir;
+    system "./mkauto.sh";
+    die "mkfiles.pl: mkauto.sh returned $?\n" if $? > 0;
+    system "./configure", @confargs;
+    die "mkfiles.pl: configure returned $?\n" if $? > 0;
+}
diff --git a/nestedvm.c b/nestedvm.c
new file mode 100644 (file)
index 0000000..c859526
--- /dev/null
@@ -0,0 +1,432 @@
+/*
+ * nestedvm.c: NestedVM front end for my puzzle collection.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <time.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+
+#include <sys/time.h>
+
+#include "puzzles.h"
+
+extern void _pause();
+extern int _call_java(int cmd, int arg1, int arg2, int arg3);
+
+void fatal(char *fmt, ...)
+{
+    va_list ap;
+    fprintf(stderr, "fatal error: ");
+    va_start(ap, fmt);
+    vfprintf(stderr, fmt, ap);
+    va_end(ap);
+    fprintf(stderr, "\n");
+    exit(1);
+}
+
+struct frontend {
+    // TODO kill unneeded members!
+    midend *me;
+    int timer_active;
+    struct timeval last_time;
+    config_item *cfg;
+    int cfg_which, cfgret;
+    int ox, oy, w, h;
+};
+
+static frontend *_fe;
+
+void get_random_seed(void **randseed, int *randseedsize)
+{
+    struct timeval *tvp = snew(struct timeval);
+    gettimeofday(tvp, NULL);
+    *randseed = (void *)tvp;
+    *randseedsize = sizeof(struct timeval);
+}
+
+void frontend_default_colour(frontend *fe, float *output)
+{
+    output[0] = output[1]= output[2] = 0.8f;
+}
+
+void nestedvm_status_bar(void *handle, char *text)
+{
+    _call_java(4,0,(int)text,0);
+}
+
+void nestedvm_start_draw(void *handle)
+{
+    frontend *fe = (frontend *)handle;
+    _call_java(5, 0, fe->w, fe->h);
+    _call_java(4, 1, fe->ox, fe->oy);
+}
+
+void nestedvm_clip(void *handle, int x, int y, int w, int h)
+{
+    frontend *fe = (frontend *)handle;
+    _call_java(5, w, h, 0);
+    _call_java(4, 3, x + fe->ox, y + fe->oy);
+}
+
+void nestedvm_unclip(void *handle)
+{
+    frontend *fe = (frontend *)handle;
+    _call_java(4, 4, fe->ox, fe->oy);
+}
+
+void nestedvm_draw_text(void *handle, int x, int y, int fonttype, int fontsize,
+                  int align, int colour, char *text)
+{
+    frontend *fe = (frontend *)handle;
+    _call_java(5, x + fe->ox, y + fe->oy, 
+              (fonttype == FONT_FIXED ? 0x10 : 0x0) | align);
+    _call_java(7, fontsize, colour, (int)text);
+}
+
+void nestedvm_draw_rect(void *handle, int x, int y, int w, int h, int colour)
+{
+    frontend *fe = (frontend *)handle;
+    _call_java(5, w, h, colour);
+    _call_java(4, 5, x + fe->ox, y + fe->oy);
+}
+
+void nestedvm_draw_line(void *handle, int x1, int y1, int x2, int y2, 
+                       int colour)
+{
+    frontend *fe = (frontend *)handle;
+    _call_java(5, x2 + fe->ox, y2 + fe->oy, colour);
+    _call_java(4, 6, x1 + fe->ox, y1 + fe->oy);
+}
+
+void nestedvm_draw_poly(void *handle, int *coords, int npoints,
+                       int fillcolour, int outlinecolour)
+{
+    frontend *fe = (frontend *)handle;
+    int i;
+    _call_java(4, 7, npoints, 0);
+    for (i = 0; i < npoints; i++) {
+       _call_java(6, i, coords[i*2] + fe->ox, coords[i*2+1] + fe->oy);
+    }
+    _call_java(4, 8, outlinecolour, fillcolour);
+}
+
+void nestedvm_draw_circle(void *handle, int cx, int cy, int radius,
+                    int fillcolour, int outlinecolour)
+{
+    frontend *fe = (frontend *)handle;
+    _call_java(5, cx+fe->ox, cy+fe->oy, radius);
+    _call_java(4, 9, outlinecolour, fillcolour);
+}
+
+struct blitter {
+    int handle, w, h, x, y;
+};
+
+blitter *nestedvm_blitter_new(void *handle, int w, int h)
+{
+    blitter *bl = snew(blitter);
+    bl->handle = -1;
+    bl->w = w;
+    bl->h = h;
+    return bl;
+}
+
+void nestedvm_blitter_free(void *handle, blitter *bl)
+{
+    if (bl->handle != -1)
+       _call_java(4, 11, bl->handle, 0);
+    sfree(bl);
+}
+
+void nestedvm_blitter_save(void *handle, blitter *bl, int x, int y)
+{
+    frontend *fe = (frontend *)handle;    
+    if (bl->handle == -1)
+       bl->handle = _call_java(4,10,bl->w, bl->h);
+    bl->x = x;
+    bl->y = y;
+    _call_java(8, bl->handle, x + fe->ox, y + fe->oy);
+}
+
+void nestedvm_blitter_load(void *handle, blitter *bl, int x, int y)
+{
+    frontend *fe = (frontend *)handle;
+    assert(bl->handle != -1);
+    if (x == BLITTER_FROMSAVED && y == BLITTER_FROMSAVED) {
+        x = bl->x;
+        y = bl->y;
+    }
+    _call_java(9, bl->handle, x + fe->ox, y + fe->oy);
+}
+
+void nestedvm_end_draw(void *handle)
+{
+    _call_java(4,2,0,0);
+}
+
+char *nestedvm_text_fallback(void *handle, const char *const *strings,
+                            int nstrings)
+{
+    /*
+     * We assume Java can cope with any UTF-8 likely to be emitted
+     * by a puzzle.
+     */
+    return dupstr(strings[0]);
+}
+
+const struct drawing_api nestedvm_drawing = {
+    nestedvm_draw_text,
+    nestedvm_draw_rect,
+    nestedvm_draw_line,
+    nestedvm_draw_poly,
+    nestedvm_draw_circle,
+    NULL, // draw_update,
+    nestedvm_clip,
+    nestedvm_unclip,
+    nestedvm_start_draw,
+    nestedvm_end_draw,
+    nestedvm_status_bar,
+    nestedvm_blitter_new,
+    nestedvm_blitter_free,
+    nestedvm_blitter_save,
+    nestedvm_blitter_load,
+    NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */
+    NULL, NULL,                               /* line_width, line_dotted */
+    nestedvm_text_fallback,
+};
+
+int jcallback_key_event(int x, int y, int keyval)
+{
+    frontend *fe = (frontend *)_fe;
+    if (fe->ox == -1)
+        return 1;
+    if (keyval >= 0 &&
+        !midend_process_key(fe->me, x - fe->ox, y - fe->oy, keyval))
+       return 42;
+    return 1;
+}
+
+int jcallback_resize(int width, int height)
+{
+    frontend *fe = (frontend *)_fe;
+    int x, y;
+    x = width;
+    y = height;
+    midend_size(fe->me, &x, &y, TRUE);
+    fe->ox = (width - x) / 2;
+    fe->oy = (height - y) / 2;
+    fe->w = x;
+    fe->h = y;
+    midend_force_redraw(fe->me);
+    return 0;
+}
+
+int jcallback_timer_func()
+{
+    frontend *fe = (frontend *)_fe;
+    if (fe->timer_active) {
+       struct timeval now;
+       float elapsed;
+       gettimeofday(&now, NULL);
+       elapsed = ((now.tv_usec - fe->last_time.tv_usec) * 0.000001F +
+                  (now.tv_sec - fe->last_time.tv_sec));
+        midend_timer(fe->me, elapsed); /* may clear timer_active */
+       fe->last_time = now;
+    }
+    return fe->timer_active;
+}
+
+void deactivate_timer(frontend *fe)
+{
+    if (fe->timer_active)
+       _call_java(4, 13, 0, 0);
+    fe->timer_active = FALSE;
+}
+
+void activate_timer(frontend *fe)
+{
+    if (!fe->timer_active) {
+       _call_java(4, 12, 0, 0);
+       gettimeofday(&fe->last_time, NULL);
+    }
+    fe->timer_active = TRUE;
+}
+
+void jcallback_config_ok()
+{
+    frontend *fe = (frontend *)_fe;
+    char *err;
+
+    err = midend_set_config(fe->me, fe->cfg_which, fe->cfg);
+
+    if (err)
+       _call_java(2, (int) "Error", (int)err, 1);
+    else {
+       fe->cfgret = TRUE;
+    }
+}
+
+void jcallback_config_set_string(int item_ptr, int char_ptr) {
+    config_item *i = (config_item *)item_ptr;
+    char* newval = (char*) char_ptr;
+    sfree(i->sval);
+    i->sval = dupstr(newval);
+    free(newval);
+}
+
+void jcallback_config_set_boolean(int item_ptr, int selected) {
+    config_item *i = (config_item *)item_ptr;
+    i->ival = selected != 0 ? TRUE : FALSE;
+}
+
+void jcallback_config_set_choice(int item_ptr, int selected) {
+    config_item *i = (config_item *)item_ptr;
+    i->ival = selected;
+}
+
+static int get_config(frontend *fe, int which)
+{
+    char *title;
+    config_item *i;
+    fe->cfg = midend_get_config(fe->me, which, &title);
+    fe->cfg_which = which;
+    fe->cfgret = FALSE;
+    _call_java(10, (int)title, 0, 0);
+    for (i = fe->cfg; i->type != C_END; i++) {
+       _call_java(5, (int)i, i->type, (int)i->name);
+       _call_java(11, (int)i->sval, i->ival, 0);
+    }
+    _call_java(12,0,0,0);
+    free_cfg(fe->cfg);
+    return fe->cfgret;
+}
+
+int jcallback_menu_key_event(int key)
+{
+    frontend *fe = (frontend *)_fe;
+    if (!midend_process_key(fe->me, 0, 0, key))
+       return 42;
+    return 0;
+}
+
+static void resize_fe(frontend *fe)
+{
+    int x, y;
+
+    x = INT_MAX;
+    y = INT_MAX;
+    midend_size(fe->me, &x, &y, FALSE);
+    _call_java(3, x, y, 0);
+}
+
+int jcallback_preset_event(int ptr_game_params)
+{
+    frontend *fe = (frontend *)_fe;
+    game_params *params =
+       (game_params *)ptr_game_params;
+
+    midend_set_params(fe->me, params);
+    midend_new_game(fe->me);
+    resize_fe(fe);
+    _call_java(13, midend_which_preset(fe->me), 0, 0);
+    return 0;
+}
+
+int jcallback_solve_event()
+{
+    frontend *fe = (frontend *)_fe;
+    char *msg;
+
+    msg = midend_solve(fe->me);
+
+    if (msg)
+       _call_java(2, (int) "Error", (int)msg, 1);
+    return 0;
+}
+
+int jcallback_restart_event()
+{
+    frontend *fe = (frontend *)_fe;
+
+    midend_restart_game(fe->me);
+    return 0;
+}
+
+int jcallback_config_event(int which)
+{
+    frontend *fe = (frontend *)_fe;
+    _call_java(13, midend_which_preset(fe->me), 0, 0);
+    if (!get_config(fe, which))
+       return 0;
+    midend_new_game(fe->me);
+    resize_fe(fe);
+    _call_java(13, midend_which_preset(fe->me), 0, 0);
+    return 0;
+}
+
+int jcallback_about_event()
+{
+    char titlebuf[256];
+    char textbuf[1024];
+
+    sprintf(titlebuf, "About %.200s", thegame.name);
+    sprintf(textbuf,
+           "%.200s\n\n"
+           "from Simon Tatham's Portable Puzzle Collection\n\n"
+           "%.500s", thegame.name, ver);
+    _call_java(2, (int)&titlebuf, (int)&textbuf, 0);
+    return 0;
+}
+
+int main(int argc, char **argv)
+{
+    int i, n;
+    float* colours;
+
+    _fe = snew(frontend);
+    _fe->timer_active = FALSE;
+    _fe->me = midend_new(_fe, &thegame, &nestedvm_drawing, _fe);
+    if (argc > 1)
+       midend_game_id(_fe->me, argv[1]);   /* ignore failure */
+    midend_new_game(_fe->me);
+
+    if ((n = midend_num_presets(_fe->me)) > 0) {
+        int i;
+        for (i = 0; i < n; i++) {
+            char *name;
+            game_params *params;
+            midend_fetch_preset(_fe->me, i, &name, &params);
+           _call_java(1, (int)name, (int)params, 0);
+        }
+    }
+
+    colours = midend_colours(_fe->me, &n);
+    _fe->ox = -1;
+
+    _call_java(0, (int)thegame.name,
+              (thegame.can_configure ? 1 : 0) |
+              (midend_wants_statusbar(_fe->me) ? 2 : 0) |
+              (thegame.can_solve ? 4 : 0), n);    
+    for (i = 0; i < n; i++) {
+       _call_java(1024+ i,
+                  (int)(colours[i*3] * 0xFF),
+                  (int)(colours[i*3+1] * 0xFF),
+                  (int)(colours[i*3+2] * 0xFF));
+    }
+    resize_fe(_fe);
+
+    _call_java(13, midend_which_preset(_fe->me), 0, 0);
+
+    // Now pause the vm. The VM will be call()ed when
+    // an input event occurs.
+    _pause();
+
+    // shut down when the VM is resumed.
+    deactivate_timer(_fe);
+    midend_free(_fe->me);
+    return 0;
+}
diff --git a/net.R b/net.R
new file mode 100644 (file)
index 0000000..8e98216
--- /dev/null
+++ b/net.R
@@ -0,0 +1,23 @@
+# -*- makefile -*-
+
+NET_EXTRA = tree234 dsf findloop
+
+net      : [X] GTK COMMON net NET_EXTRA net-icon|no-icon
+
+# The Windows Net shouldn't be called `net.exe' since Windows
+# already has a reasonably important utility program by that name!
+netgame  : [G] WINDOWS COMMON net NET_EXTRA net.res|noicon.res
+
+ALL += net[COMBINED] NET_EXTRA
+
+!begin am gtk
+GAMES += net
+!end
+
+!begin >list.c
+    A(net) \
+!end
+
+!begin >gamedesc.txt
+net:netgame.exe:Net:Network jigsaw puzzle:Rotate each tile to reassemble the network.
+!end
diff --git a/net.c b/net.c
new file mode 100644 (file)
index 0000000..349b13d
--- /dev/null
+++ b/net.c
@@ -0,0 +1,3210 @@
+/*
+ * net.c: Net game.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+#include "tree234.h"
+
+/*
+ * The standard user interface for Net simply has left- and
+ * right-button mouse clicks in a square rotate it one way or the
+ * other. We also provide, by #ifdef, a separate interface based on
+ * rotational dragging motions. I initially developed this for the
+ * Mac on the basis that it might work better than the click
+ * interface with only one mouse button available, but in fact
+ * found it to be quite strange and unintuitive. Apparently it
+ * works better on stylus-driven platforms such as Palm and
+ * PocketPC, though, so we enable it by default there.
+ */
+#ifdef STYLUS_BASED
+#define USE_DRAGGING
+#endif
+
+#define MATMUL(xr,yr,m,x,y) do { \
+    float rx, ry, xx = (x), yy = (y), *mat = (m); \
+    rx = mat[0] * xx + mat[2] * yy; \
+    ry = mat[1] * xx + mat[3] * yy; \
+    (xr) = rx; (yr) = ry; \
+} while (0)
+
+/* Direction and other bitfields */
+#define R 0x01
+#define U 0x02
+#define L 0x04
+#define D 0x08
+#define LOCKED 0x10
+#define ACTIVE 0x20
+#define RLOOP (R << 6)
+#define ULOOP (U << 6)
+#define LLOOP (L << 6)
+#define DLOOP (D << 6)
+#define LOOP(dir) ((dir) << 6)
+
+/* Rotations: Anticlockwise, Clockwise, Flip, general rotate */
+#define A(x) ( (((x) & 0x07) << 1) | (((x) & 0x08) >> 3) )
+#define C(x) ( (((x) & 0x0E) >> 1) | (((x) & 0x01) << 3) )
+#define F(x) ( (((x) & 0x0C) >> 2) | (((x) & 0x03) << 2) )
+#define ROT(x, n) ( ((n)&3) == 0 ? (x) : \
+                   ((n)&3) == 1 ? A(x) : \
+                   ((n)&3) == 2 ? F(x) : C(x) )
+
+/* X and Y displacements */
+#define X(x) ( (x) == R ? +1 : (x) == L ? -1 : 0 )
+#define Y(x) ( (x) == D ? +1 : (x) == U ? -1 : 0 )
+
+/* Bit count */
+#define COUNT(x) ( (((x) & 0x08) >> 3) + (((x) & 0x04) >> 2) + \
+                  (((x) & 0x02) >> 1) + ((x) & 0x01) )
+
+#define PREFERRED_TILE_SIZE 32
+#define TILE_SIZE (ds->tilesize)
+#define TILE_BORDER 1
+#ifdef SMALL_SCREEN
+#define WINDOW_OFFSET 4
+#else
+#define WINDOW_OFFSET 16
+#endif
+
+#define ROTATE_TIME 0.13F
+#define FLASH_FRAME 0.07F
+
+/* Transform physical coords to game coords using game_drawstate ds */
+#define GX(x) (((x) + ds->org_x) % ds->width)
+#define GY(y) (((y) + ds->org_y) % ds->height)
+/* ...and game coords to physical coords */
+#define RX(x) (((x) + ds->width - ds->org_x) % ds->width)
+#define RY(y) (((y) + ds->height - ds->org_y) % ds->height)
+
+enum {
+    COL_BACKGROUND,
+    COL_LOCKED,
+    COL_BORDER,
+    COL_WIRE,
+    COL_ENDPOINT,
+    COL_POWERED,
+    COL_BARRIER,
+    COL_LOOP,
+    NCOLOURS
+};
+
+struct game_params {
+    int width;
+    int height;
+    int wrapping;
+    int unique;
+    float barrier_probability;
+};
+
+struct game_state {
+    int width, height, wrapping, completed;
+    int last_rotate_x, last_rotate_y, last_rotate_dir;
+    int used_solve;
+    unsigned char *tiles;
+    unsigned char *barriers;
+};
+
+#define OFFSETWH(x2,y2,x1,y1,dir,width,height) \
+    ( (x2) = ((x1) + width + X((dir))) % width, \
+      (y2) = ((y1) + height + Y((dir))) % height)
+
+#define OFFSET(x2,y2,x1,y1,dir,state) \
+       OFFSETWH(x2,y2,x1,y1,dir,(state)->width,(state)->height)
+
+#define index(state, a, x, y) ( a[(y) * (state)->width + (x)] )
+#define tile(state, x, y)     index(state, (state)->tiles, x, y)
+#define barrier(state, x, y)  index(state, (state)->barriers, x, y)
+
+struct xyd {
+    int x, y, direction;
+};
+
+static int xyd_cmp(const void *av, const void *bv) {
+    const struct xyd *a = (const struct xyd *)av;
+    const struct xyd *b = (const struct xyd *)bv;
+    if (a->x < b->x)
+       return -1;
+    if (a->x > b->x)
+       return +1;
+    if (a->y < b->y)
+       return -1;
+    if (a->y > b->y)
+       return +1;
+    if (a->direction < b->direction)
+       return -1;
+    if (a->direction > b->direction)
+       return +1;
+    return 0;
+}
+
+static int xyd_cmp_nc(void *av, void *bv) { return xyd_cmp(av, bv); }
+
+static struct xyd *new_xyd(int x, int y, int direction)
+{
+    struct xyd *xyd = snew(struct xyd);
+    xyd->x = x;
+    xyd->y = y;
+    xyd->direction = direction;
+    return xyd;
+}
+
+/* ----------------------------------------------------------------------
+ * Manage game parameters.
+ */
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+
+    ret->width = 5;
+    ret->height = 5;
+    ret->wrapping = FALSE;
+    ret->unique = TRUE;
+    ret->barrier_probability = 0.0;
+
+    return ret;
+}
+
+static const struct game_params net_presets[] = {
+    {5, 5, FALSE, TRUE, 0.0},
+    {7, 7, FALSE, TRUE, 0.0},
+    {9, 9, FALSE, TRUE, 0.0},
+    {11, 11, FALSE, TRUE, 0.0},
+#ifndef SMALL_SCREEN
+    {13, 11, FALSE, TRUE, 0.0},
+#endif
+    {5, 5, TRUE, TRUE, 0.0},
+    {7, 7, TRUE, TRUE, 0.0},
+    {9, 9, TRUE, TRUE, 0.0},
+    {11, 11, TRUE, TRUE, 0.0},
+#ifndef SMALL_SCREEN
+    {13, 11, TRUE, TRUE, 0.0},
+#endif
+};
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params *ret;
+    char str[80];
+
+    if (i < 0 || i >= lenof(net_presets))
+        return FALSE;
+
+    ret = snew(game_params);
+    *ret = net_presets[i];
+
+    sprintf(str, "%dx%d%s", ret->width, ret->height,
+            ret->wrapping ? " wrapping" : "");
+
+    *name = dupstr(str);
+    *params = ret;
+    return TRUE;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+static void decode_params(game_params *ret, char const *string)
+{
+    char const *p = string;
+
+    ret->width = atoi(p);
+    while (*p && isdigit((unsigned char)*p)) p++;
+    if (*p == 'x') {
+        p++;
+        ret->height = atoi(p);
+        while (*p && isdigit((unsigned char)*p)) p++;
+    } else {
+        ret->height = ret->width;
+    }
+
+    while (*p) {
+        if (*p == 'w') {
+            p++;
+           ret->wrapping = TRUE;
+       } else if (*p == 'b') {
+           p++;
+            ret->barrier_probability = (float)atof(p);
+           while (*p && (*p == '.' || isdigit((unsigned char)*p))) p++;
+       } else if (*p == 'a') {
+            p++;
+           ret->unique = FALSE;
+       } else
+           p++;                       /* skip any other gunk */
+    }
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char ret[400];
+    int len;
+
+    len = sprintf(ret, "%dx%d", params->width, params->height);
+    if (params->wrapping)
+        ret[len++] = 'w';
+    if (full && params->barrier_probability)
+        len += sprintf(ret+len, "b%g", params->barrier_probability);
+    if (full && !params->unique)
+        ret[len++] = 'a';
+    assert(len < lenof(ret));
+    ret[len] = '\0';
+
+    return dupstr(ret);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+    char buf[80];
+
+    ret = snewn(6, config_item);
+
+    ret[0].name = "Width";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->width);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Height";
+    ret[1].type = C_STRING;
+    sprintf(buf, "%d", params->height);
+    ret[1].sval = dupstr(buf);
+    ret[1].ival = 0;
+
+    ret[2].name = "Walls wrap around";
+    ret[2].type = C_BOOLEAN;
+    ret[2].sval = NULL;
+    ret[2].ival = params->wrapping;
+
+    ret[3].name = "Barrier probability";
+    ret[3].type = C_STRING;
+    sprintf(buf, "%g", params->barrier_probability);
+    ret[3].sval = dupstr(buf);
+    ret[3].ival = 0;
+
+    ret[4].name = "Ensure unique solution";
+    ret[4].type = C_BOOLEAN;
+    ret[4].sval = NULL;
+    ret[4].ival = params->unique;
+
+    ret[5].name = NULL;
+    ret[5].type = C_END;
+    ret[5].sval = NULL;
+    ret[5].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->width = atoi(cfg[0].sval);
+    ret->height = atoi(cfg[1].sval);
+    ret->wrapping = cfg[2].ival;
+    ret->barrier_probability = (float)atof(cfg[3].sval);
+    ret->unique = cfg[4].ival;
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    if (params->width <= 0 || params->height <= 0)
+       return "Width and height must both be greater than zero";
+    if (params->width <= 1 && params->height <= 1)
+       return "At least one of width and height must be greater than one";
+    if (params->barrier_probability < 0)
+       return "Barrier probability may not be negative";
+    if (params->barrier_probability > 1)
+       return "Barrier probability may not be greater than 1";
+
+    /*
+     * Specifying either grid dimension as 2 in a wrapping puzzle
+     * makes it actually impossible to ensure a unique puzzle
+     * solution.
+     * 
+     * Proof:
+     * 
+     * Without loss of generality, let us assume the puzzle _width_
+     * is 2, so we can conveniently discuss rows without having to
+     * say `rows/columns' all the time. (The height may be 2 as
+     * well, but that doesn't matter.)
+     * 
+     * In each row, there are two edges between tiles: the inner
+     * edge (running down the centre of the grid) and the outer
+     * edge (the identified left and right edges of the grid).
+     * 
+     * Lemma: In any valid 2xn puzzle there must be at least one
+     * row in which _exactly one_ of the inner edge and outer edge
+     * is connected.
+     * 
+     *   Proof: No row can have _both_ inner and outer edges
+     *   connected, because this would yield a loop. So the only
+     *   other way to falsify the lemma is for every row to have
+     *   _neither_ the inner nor outer edge connected. But this
+     *   means there is no connection at all between the left and
+     *   right columns of the puzzle, so there are two disjoint
+     *   subgraphs, which is also disallowed. []
+     * 
+     * Given such a row, it is always possible to make the
+     * disconnected edge connected and the connected edge
+     * disconnected without changing the state of any other edge.
+     * (This is easily seen by case analysis on the various tiles:
+     * left-pointing and right-pointing endpoints can be exchanged,
+     * likewise T-pieces, and a corner piece can select its
+     * horizontal connectivity independently of its vertical.) This
+     * yields a distinct valid solution.
+     * 
+     * Thus, for _every_ row in which exactly one of the inner and
+     * outer edge is connected, there are two valid states for that
+     * row, and hence the total number of solutions of the puzzle
+     * is at least 2^(number of such rows), and in particular is at
+     * least 2 since there must be at least one such row. []
+     */
+    if (full && params->unique && params->wrapping &&
+        (params->width == 2 || params->height == 2))
+        return "No wrapping puzzle with a width or height of 2 can have"
+        " a unique solution";
+
+    return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Solver used to assure solution uniqueness during generation. 
+ */
+
+/*
+ * Test cases I used while debugging all this were
+ * 
+ *   ./net --generate 1 13x11w#12300
+ * which expands under the non-unique grid generation rules to
+ *   13x11w:5eaade1bd222664436d5e2965c12656b1129dd825219e3274d558d5eb2dab5da18898e571d5a2987be79746bd95726c597447d6da96188c513add829da7681da954db113d3cd244
+ * and has two ambiguous areas.
+ * 
+ * An even better one is
+ *   13x11w#507896411361192
+ * which expands to
+ *   13x11w:b7125b1aec598eb31bd58d82572bc11494e5dee4e8db2bdd29b88d41a16bdd996d2996ddec8c83741a1e8674e78328ba71737b8894a9271b1cd1399453d1952e43951d9b712822e
+ * and has an ambiguous area _and_ a situation where loop avoidance
+ * is a necessary deductive technique.
+ * 
+ * Then there's
+ *   48x25w#820543338195187
+ * becoming
+ *   48x25w:255989d14cdd185deaa753a93821a12edc1ab97943ac127e2685d7b8b3c48861b2192416139212b316eddd35de43714ebc7628d753db32e596284d9ec52c5a7dc1b4c811a655117d16dc28921b2b4161352cab1d89d18bc836b8b891d55ea4622a1251861b5bc9a8aa3e5bcd745c95229ca6c3b5e21d5832d397e917325793d7eb442dc351b2db2a52ba8e1651642275842d8871d5534aabc6d5b741aaa2d48ed2a7dbbb3151ddb49d5b9a7ed1ab98ee75d613d656dbba347bc514c84556b43a9bc65a3256ead792488b862a9d2a8a39b4255a4949ed7dbd79443292521265896b4399c95ede89d7c8c797a6a57791a849adea489359a158aa12e5dacce862b8333b7ebea7d344d1a3c53198864b73a9dedde7b663abb1b539e1e8853b1b7edb14a2a17ebaae4dbe63598a2e7e9a2dbdad415bc1d8cb88cbab5a8c82925732cd282e641ea3bd7d2c6e776de9117a26be86deb7c82c89524b122cb9397cd1acd2284e744ea62b9279bae85479ababe315c3ac29c431333395b24e6a1e3c43a2da42d4dce84aadd5b154aea555eaddcbd6e527d228c19388d9b424d94214555a7edbdeebe569d4a56dc51a86bd9963e377bb74752bd5eaa5761ba545e297b62a1bda46ab4aee423ad6c661311783cc18786d4289236563cb4a75ec67d481c14814994464cd1b87396dee63e5ab6e952cc584baa1d4c47cb557ec84dbb63d487c8728118673a166846dd3a4ebc23d6cb9c5827d96b4556e91899db32b517eda815ae271a8911bd745447121dc8d321557bc2a435ebec1bbac35b1a291669451174e6aa2218a4a9c5a6ca31ebc45d84e3a82c121e9ced7d55e9a
+ * which has a spot (far right) where slightly more complex loop
+ * avoidance is required.
+ */
+
+struct todo {
+    unsigned char *marked;
+    int *buffer;
+    int buflen;
+    int head, tail;
+};
+
+static struct todo *todo_new(int maxsize)
+{
+    struct todo *todo = snew(struct todo);
+    todo->marked = snewn(maxsize, unsigned char);
+    memset(todo->marked, 0, maxsize);
+    todo->buflen = maxsize + 1;
+    todo->buffer = snewn(todo->buflen, int);
+    todo->head = todo->tail = 0;
+    return todo;
+}
+
+static void todo_free(struct todo *todo)
+{
+    sfree(todo->marked);
+    sfree(todo->buffer);
+    sfree(todo);
+}
+
+static void todo_add(struct todo *todo, int index)
+{
+    if (todo->marked[index])
+       return;                        /* already on the list */
+    todo->marked[index] = TRUE;
+    todo->buffer[todo->tail++] = index;
+    if (todo->tail == todo->buflen)
+       todo->tail = 0;
+}
+
+static int todo_get(struct todo *todo) {
+    int ret;
+
+    if (todo->head == todo->tail)
+       return -1;                     /* list is empty */
+    ret = todo->buffer[todo->head++];
+    if (todo->head == todo->buflen)
+       todo->head = 0;
+    todo->marked[ret] = FALSE;
+
+    return ret;
+}
+
+static int net_solver(int w, int h, unsigned char *tiles,
+                     unsigned char *barriers, int wrapping)
+{
+    unsigned char *tilestate;
+    unsigned char *edgestate;
+    int *deadends;
+    int *equivalence;
+    struct todo *todo;
+    int i, j, x, y;
+    int area;
+    int done_something;
+
+    /*
+     * Set up the solver's data structures.
+     */
+    
+    /*
+     * tilestate stores the possible orientations of each tile.
+     * There are up to four of these, so we'll index the array in
+     * fours. tilestate[(y * w + x) * 4] and its three successive
+     * members give the possible orientations, clearing to 255 from
+     * the end as things are ruled out.
+     * 
+     * In this loop we also count up the area of the grid (which is
+     * not _necessarily_ equal to w*h, because there might be one
+     * or more blank squares present. This will never happen in a
+     * grid generated _by_ this program, but it's worth keeping the
+     * solver as general as possible.)
+     */
+    tilestate = snewn(w * h * 4, unsigned char);
+    area = 0;
+    for (i = 0; i < w*h; i++) {
+       tilestate[i * 4] = tiles[i] & 0xF;
+       for (j = 1; j < 4; j++) {
+           if (tilestate[i * 4 + j - 1] == 255 ||
+               A(tilestate[i * 4 + j - 1]) == tilestate[i * 4])
+               tilestate[i * 4 + j] = 255;
+           else
+               tilestate[i * 4 + j] = A(tilestate[i * 4 + j - 1]);
+       }
+       if (tiles[i] != 0)
+           area++;
+    }
+
+    /*
+     * edgestate stores the known state of each edge. It is 0 for
+     * unknown, 1 for open (connected) and 2 for closed (not
+     * connected).
+     * 
+     * In principle we need only worry about each edge once each,
+     * but in fact it's easier to track each edge twice so that we
+     * can reference it from either side conveniently. Also I'm
+     * going to allocate _five_ bytes per tile, rather than the
+     * obvious four, so that I can index edgestate[(y*w+x) * 5 + d]
+     * where d is 1,2,4,8 and they never overlap.
+     */
+    edgestate = snewn((w * h - 1) * 5 + 9, unsigned char);
+    memset(edgestate, 0, (w * h - 1) * 5 + 9);
+
+    /*
+     * deadends tracks which edges have dead ends on them. It is
+     * indexed by tile and direction: deadends[(y*w+x) * 5 + d]
+     * tells you whether heading out of tile (x,y) in direction d
+     * can reach a limited amount of the grid. Values are area+1
+     * (no dead end known) or less than that (can reach _at most_
+     * this many other tiles by heading this way out of this tile).
+     */
+    deadends = snewn((w * h - 1) * 5 + 9, int);
+    for (i = 0; i < (w * h - 1) * 5 + 9; i++)
+       deadends[i] = area+1;
+
+    /*
+     * equivalence tracks which sets of tiles are known to be
+     * connected to one another, so we can avoid creating loops by
+     * linking together tiles which are already linked through
+     * another route.
+     * 
+     * This is a disjoint set forest structure: equivalence[i]
+     * contains the index of another member of the equivalence
+     * class containing i, or contains i itself for precisely one
+     * member in each such class. To find a representative member
+     * of the equivalence class containing i, you keep replacing i
+     * with equivalence[i] until it stops changing; then you go
+     * _back_ along the same path and point everything on it
+     * directly at the representative member so as to speed up
+     * future searches. Then you test equivalence between tiles by
+     * finding the representative of each tile and seeing if
+     * they're the same; and you create new equivalence (merge
+     * classes) by finding the representative of each tile and
+     * setting equivalence[one]=the_other.
+     */
+    equivalence = snew_dsf(w * h);
+
+    /*
+     * On a non-wrapping grid, we instantly know that all the edges
+     * round the edge are closed.
+     */
+    if (!wrapping) {
+       for (i = 0; i < w; i++) {
+           edgestate[i * 5 + 2] = edgestate[((h-1) * w + i) * 5 + 8] = 2;
+       }
+       for (i = 0; i < h; i++) {
+           edgestate[(i * w + w-1) * 5 + 1] = edgestate[(i * w) * 5 + 4] = 2;
+       }
+    }
+
+    /*
+     * If we have barriers available, we can mark those edges as
+     * closed too.
+     */
+    if (barriers) {
+       for (y = 0; y < h; y++) for (x = 0; x < w; x++) {
+           int d;
+           for (d = 1; d <= 8; d += d) {
+               if (barriers[y*w+x] & d) {
+                   int x2, y2;
+                   /*
+                    * In principle the barrier list should already
+                    * contain each barrier from each side, but
+                    * let's not take chances with our internal
+                    * consistency.
+                    */
+                   OFFSETWH(x2, y2, x, y, d, w, h);
+                   edgestate[(y*w+x) * 5 + d] = 2;
+                   edgestate[(y2*w+x2) * 5 + F(d)] = 2;
+               }
+           }
+       }
+    }
+
+    /*
+     * Since most deductions made by this solver are local (the
+     * exception is loop avoidance, where joining two tiles
+     * together on one side of the grid can theoretically permit a
+     * fresh deduction on the other), we can address the scaling
+     * problem inherent in iterating repeatedly over the entire
+     * grid by instead working with a to-do list.
+     */
+    todo = todo_new(w * h);
+
+    /*
+     * Main deductive loop.
+     */
+    done_something = TRUE;            /* prevent instant termination! */
+    while (1) {
+       int index;
+
+       /*
+        * Take a tile index off the todo list and process it.
+        */
+       index = todo_get(todo);
+       if (index == -1) {
+           /*
+            * If we have run out of immediate things to do, we
+            * have no choice but to scan the whole grid for
+            * longer-range things we've missed. Hence, I now add
+            * every square on the grid back on to the to-do list.
+            * I also set `done_something' to FALSE at this point;
+            * if we later come back here and find it still FALSE,
+            * we will know we've scanned the entire grid without
+            * finding anything new to do, and we can terminate.
+            */
+           if (!done_something)
+               break;
+           for (i = 0; i < w*h; i++)
+               todo_add(todo, i);
+           done_something = FALSE;
+
+           index = todo_get(todo);
+       }
+
+       y = index / w;
+       x = index % w;
+       {
+           int d, ourclass = dsf_canonify(equivalence, y*w+x);
+           int deadendmax[9];
+
+           deadendmax[1] = deadendmax[2] = deadendmax[4] = deadendmax[8] = 0;
+
+           for (i = j = 0; i < 4 && tilestate[(y*w+x) * 4 + i] != 255; i++) {
+               int valid;
+               int nnondeadends, nondeadends[4], deadendtotal;
+               int nequiv, equiv[5];
+               int val = tilestate[(y*w+x) * 4 + i];
+
+               valid = TRUE;
+               nnondeadends = deadendtotal = 0;
+               equiv[0] = ourclass;
+               nequiv = 1;
+               for (d = 1; d <= 8; d += d) {
+                   /*
+                    * Immediately rule out this orientation if it
+                    * conflicts with any known edge.
+                    */
+                   if ((edgestate[(y*w+x) * 5 + d] == 1 && !(val & d)) ||
+                       (edgestate[(y*w+x) * 5 + d] == 2 && (val & d)))
+                       valid = FALSE;
+
+                   if (val & d) {
+                       /*
+                        * Count up the dead-end statistics.
+                        */
+                       if (deadends[(y*w+x) * 5 + d] <= area) {
+                           deadendtotal += deadends[(y*w+x) * 5 + d];
+                       } else {
+                           nondeadends[nnondeadends++] = d;
+                       }
+
+                       /*
+                        * Ensure we aren't linking to any tiles,
+                        * through edges not already known to be
+                        * open, which create a loop.
+                        */
+                       if (edgestate[(y*w+x) * 5 + d] == 0) {
+                           int c, k, x2, y2;
+                           
+                           OFFSETWH(x2, y2, x, y, d, w, h);
+                           c = dsf_canonify(equivalence, y2*w+x2);
+                           for (k = 0; k < nequiv; k++)
+                               if (c == equiv[k])
+                                   break;
+                           if (k == nequiv)
+                               equiv[nequiv++] = c;
+                           else
+                               valid = FALSE;
+                       }
+                   }
+               }
+
+               if (nnondeadends == 0) {
+                   /*
+                    * If this orientation links together dead-ends
+                    * with a total area of less than the entire
+                    * grid, it is invalid.
+                    *
+                    * (We add 1 to deadendtotal because of the
+                    * tile itself, of course; one tile linking
+                    * dead ends of size 2 and 3 forms a subnetwork
+                    * with a total area of 6, not 5.)
+                    */
+                   if (deadendtotal > 0 && deadendtotal+1 < area)
+                       valid = FALSE;
+               } else if (nnondeadends == 1) {
+                   /*
+                    * If this orientation links together one or
+                    * more dead-ends with precisely one
+                    * non-dead-end, then we may have to mark that
+                    * non-dead-end as a dead end going the other
+                    * way. However, it depends on whether all
+                    * other orientations share the same property.
+                    */
+                   deadendtotal++;
+                   if (deadendmax[nondeadends[0]] < deadendtotal)
+                       deadendmax[nondeadends[0]] = deadendtotal;
+               } else {
+                   /*
+                    * If this orientation links together two or
+                    * more non-dead-ends, then we can rule out the
+                    * possibility of putting in new dead-end
+                    * markings in those directions.
+                    */
+                   int k;
+                   for (k = 0; k < nnondeadends; k++)
+                       deadendmax[nondeadends[k]] = area+1;
+               }
+
+               if (valid)
+                   tilestate[(y*w+x) * 4 + j++] = val;
+#ifdef SOLVER_DIAGNOSTICS
+               else
+                   printf("ruling out orientation %x at %d,%d\n", val, x, y);
+#endif
+           }
+
+           assert(j > 0);             /* we can't lose _all_ possibilities! */
+
+           if (j < i) {
+               done_something = TRUE;
+
+               /*
+                * We have ruled out at least one tile orientation.
+                * Make sure the rest are blanked.
+                */
+               while (j < 4)
+                   tilestate[(y*w+x) * 4 + j++] = 255;
+           }
+
+           /*
+            * Now go through the tile orientations again and see
+            * if we've deduced anything new about any edges.
+            */
+           {
+               int a, o;
+               a = 0xF; o = 0;
+
+               for (i = 0; i < 4 && tilestate[(y*w+x) * 4 + i] != 255; i++) {
+                   a &= tilestate[(y*w+x) * 4 + i];
+                   o |= tilestate[(y*w+x) * 4 + i];
+               }
+               for (d = 1; d <= 8; d += d)
+                   if (edgestate[(y*w+x) * 5 + d] == 0) {
+                       int x2, y2, d2;
+                       OFFSETWH(x2, y2, x, y, d, w, h);
+                       d2 = F(d);
+                       if (a & d) {
+                           /* This edge is open in all orientations. */
+#ifdef SOLVER_DIAGNOSTICS
+                           printf("marking edge %d,%d:%d open\n", x, y, d);
+#endif
+                           edgestate[(y*w+x) * 5 + d] = 1;
+                           edgestate[(y2*w+x2) * 5 + d2] = 1;
+                           dsf_merge(equivalence, y*w+x, y2*w+x2);
+                           done_something = TRUE;
+                           todo_add(todo, y2*w+x2);
+                       } else if (!(o & d)) {
+                           /* This edge is closed in all orientations. */
+#ifdef SOLVER_DIAGNOSTICS
+                           printf("marking edge %d,%d:%d closed\n", x, y, d);
+#endif
+                           edgestate[(y*w+x) * 5 + d] = 2;
+                           edgestate[(y2*w+x2) * 5 + d2] = 2;
+                           done_something = TRUE;
+                           todo_add(todo, y2*w+x2);
+                       }
+                   }
+
+           }
+
+           /*
+            * Now check the dead-end markers and see if any of
+            * them has lowered from the real ones.
+            */
+           for (d = 1; d <= 8; d += d) {
+               int x2, y2, d2;
+               OFFSETWH(x2, y2, x, y, d, w, h);
+               d2 = F(d);
+               if (deadendmax[d] > 0 &&
+                   deadends[(y2*w+x2) * 5 + d2] > deadendmax[d]) {
+#ifdef SOLVER_DIAGNOSTICS
+                   printf("setting dead end value %d,%d:%d to %d\n",
+                          x2, y2, d2, deadendmax[d]);
+#endif
+                   deadends[(y2*w+x2) * 5 + d2] = deadendmax[d];
+                   done_something = TRUE;
+                   todo_add(todo, y2*w+x2);
+               }
+           }
+
+       }
+    }
+
+    /*
+     * Mark all completely determined tiles as locked.
+     */
+    j = TRUE;
+    for (i = 0; i < w*h; i++) {
+       if (tilestate[i * 4 + 1] == 255) {
+           assert(tilestate[i * 4 + 0] != 255);
+           tiles[i] = tilestate[i * 4] | LOCKED;
+       } else {
+           tiles[i] &= ~LOCKED;
+           j = FALSE;
+       }
+    }
+
+    /*
+     * Free up working space.
+     */
+    todo_free(todo);
+    sfree(tilestate);
+    sfree(edgestate);
+    sfree(deadends);
+    sfree(equivalence);
+
+    return j;
+}
+
+/* ----------------------------------------------------------------------
+ * Randomly select a new game description.
+ */
+
+/*
+ * Function to randomly perturb an ambiguous section in a grid, to
+ * attempt to ensure unique solvability.
+ */
+static void perturb(int w, int h, unsigned char *tiles, int wrapping,
+                   random_state *rs, int startx, int starty, int startd)
+{
+    struct xyd *perimeter, *perim2, *loop[2], looppos[2];
+    int nperim, perimsize, nloop[2], loopsize[2];
+    int x, y, d, i;
+
+    /*
+     * We know that the tile at (startx,starty) is part of an
+     * ambiguous section, and we also know that its neighbour in
+     * direction startd is fully specified. We begin by tracing all
+     * the way round the ambiguous area.
+     */
+    nperim = perimsize = 0;
+    perimeter = NULL;
+    x = startx;
+    y = starty;
+    d = startd;
+#ifdef PERTURB_DIAGNOSTICS
+    printf("perturb %d,%d:%d\n", x, y, d);
+#endif
+    do {
+       int x2, y2, d2;
+
+       if (nperim >= perimsize) {
+           perimsize = perimsize * 3 / 2 + 32;
+           perimeter = sresize(perimeter, perimsize, struct xyd);
+       }
+       perimeter[nperim].x = x;
+       perimeter[nperim].y = y;
+       perimeter[nperim].direction = d;
+       nperim++;
+#ifdef PERTURB_DIAGNOSTICS
+       printf("perimeter: %d,%d:%d\n", x, y, d);
+#endif
+
+       /*
+        * First, see if we can simply turn left from where we are
+        * and find another locked square.
+        */
+       d2 = A(d);
+       OFFSETWH(x2, y2, x, y, d2, w, h);
+       if ((!wrapping && (abs(x2-x) > 1 || abs(y2-y) > 1)) ||
+           (tiles[y2*w+x2] & LOCKED)) {
+           d = d2;
+       } else {
+           /*
+            * Failing that, step left into the new square and look
+            * in front of us.
+            */
+           x = x2;
+           y = y2;
+           OFFSETWH(x2, y2, x, y, d, w, h);
+           if ((wrapping || (abs(x2-x) <= 1 && abs(y2-y) <= 1)) &&
+               !(tiles[y2*w+x2] & LOCKED)) {
+               /*
+                * And failing _that_, we're going to have to step
+                * forward into _that_ square and look right at the
+                * same locked square as we started with.
+                */
+               x = x2;
+               y = y2;
+               d = C(d);
+           }
+       }
+
+    } while (x != startx || y != starty || d != startd);
+
+    /*
+     * Our technique for perturbing this ambiguous area is to
+     * search round its edge for a join we can make: that is, an
+     * edge on the perimeter which is (a) not currently connected,
+     * and (b) connecting it would not yield a full cross on either
+     * side. Then we make that join, search round the network to
+     * find the loop thus constructed, and sever the loop at a
+     * randomly selected other point.
+     */
+    perim2 = snewn(nperim, struct xyd);
+    memcpy(perim2, perimeter, nperim * sizeof(struct xyd));
+    /* Shuffle the perimeter, so as to search it without directional bias. */
+    shuffle(perim2, nperim, sizeof(*perim2), rs);
+    for (i = 0; i < nperim; i++) {
+       int x2, y2;
+
+       x = perim2[i].x;
+       y = perim2[i].y;
+       d = perim2[i].direction;
+
+       OFFSETWH(x2, y2, x, y, d, w, h);
+       if (!wrapping && (abs(x2-x) > 1 || abs(y2-y) > 1))
+           continue;            /* can't link across non-wrapping border */
+       if (tiles[y*w+x] & d)
+           continue;                  /* already linked in this direction! */
+       if (((tiles[y*w+x] | d) & 15) == 15)
+           continue;                  /* can't turn this tile into a cross */
+       if (((tiles[y2*w+x2] | F(d)) & 15) == 15)
+           continue;                  /* can't turn other tile into a cross */
+
+       /*
+        * We've found the point at which we're going to make a new
+        * link.
+        */
+#ifdef PERTURB_DIAGNOSTICS     
+       printf("linking %d,%d:%d\n", x, y, d);
+#endif
+       tiles[y*w+x] |= d;
+       tiles[y2*w+x2] |= F(d);
+
+       break;
+    }
+    sfree(perim2);
+
+    if (i == nperim) {
+        sfree(perimeter);
+       return;                        /* nothing we can do! */
+    }
+
+    /*
+     * Now we've constructed a new link, we need to find the entire
+     * loop of which it is a part.
+     * 
+     * In principle, this involves doing a complete search round
+     * the network. However, I anticipate that in the vast majority
+     * of cases the loop will be quite small, so what I'm going to
+     * do is make _two_ searches round the network in parallel, one
+     * keeping its metaphorical hand on the left-hand wall while
+     * the other keeps its hand on the right. As soon as one of
+     * them gets back to its starting point, I abandon the other.
+     */
+    for (i = 0; i < 2; i++) {
+       loopsize[i] = nloop[i] = 0;
+       loop[i] = NULL;
+       looppos[i].x = x;
+       looppos[i].y = y;
+       looppos[i].direction = d;
+    }
+    while (1) {
+       for (i = 0; i < 2; i++) {
+           int x2, y2, j;
+
+           x = looppos[i].x;
+           y = looppos[i].y;
+           d = looppos[i].direction;
+
+           OFFSETWH(x2, y2, x, y, d, w, h);
+
+           /*
+            * Add this path segment to the loop, unless it exactly
+            * reverses the previous one on the loop in which case
+            * we take it away again.
+            */
+#ifdef PERTURB_DIAGNOSTICS
+           printf("looppos[%d] = %d,%d:%d\n", i, x, y, d);
+#endif
+           if (nloop[i] > 0 &&
+               loop[i][nloop[i]-1].x == x2 &&
+               loop[i][nloop[i]-1].y == y2 &&
+               loop[i][nloop[i]-1].direction == F(d)) {
+#ifdef PERTURB_DIAGNOSTICS
+               printf("removing path segment %d,%d:%d from loop[%d]\n",
+                      x2, y2, F(d), i);
+#endif
+               nloop[i]--;
+           } else {
+               if (nloop[i] >= loopsize[i]) {
+                   loopsize[i] = loopsize[i] * 3 / 2 + 32;
+                   loop[i] = sresize(loop[i], loopsize[i], struct xyd);
+               }
+#ifdef PERTURB_DIAGNOSTICS
+               printf("adding path segment %d,%d:%d to loop[%d]\n",
+                      x, y, d, i);
+#endif
+               loop[i][nloop[i]++] = looppos[i];
+           }
+
+#ifdef PERTURB_DIAGNOSTICS
+           printf("tile at new location is %x\n", tiles[y2*w+x2] & 0xF);
+#endif
+           d = F(d);
+           for (j = 0; j < 4; j++) {
+               if (i == 0)
+                   d = A(d);
+               else
+                   d = C(d);
+#ifdef PERTURB_DIAGNOSTICS
+               printf("trying dir %d\n", d);
+#endif
+               if (tiles[y2*w+x2] & d) {
+                   looppos[i].x = x2;
+                   looppos[i].y = y2;
+                   looppos[i].direction = d;
+                   break;
+               }
+           }
+
+           assert(j < 4);
+           assert(nloop[i] > 0);
+
+           if (looppos[i].x == loop[i][0].x &&
+               looppos[i].y == loop[i][0].y &&
+               looppos[i].direction == loop[i][0].direction) {
+#ifdef PERTURB_DIAGNOSTICS
+               printf("loop %d finished tracking\n", i);
+#endif
+
+               /*
+                * Having found our loop, we now sever it at a
+                * randomly chosen point - absolutely any will do -
+                * which is not the one we joined it at to begin
+                * with. Conveniently, the one we joined it at is
+                * loop[i][0], so we just avoid that one.
+                */
+               j = random_upto(rs, nloop[i]-1) + 1;
+               x = loop[i][j].x;
+               y = loop[i][j].y;
+               d = loop[i][j].direction;
+               OFFSETWH(x2, y2, x, y, d, w, h);
+               tiles[y*w+x] &= ~d;
+               tiles[y2*w+x2] &= ~F(d);
+
+               break;
+           }
+       }
+       if (i < 2)
+           break;
+    }
+    sfree(loop[0]);
+    sfree(loop[1]);
+
+    /*
+     * Finally, we must mark the entire disputed section as locked,
+     * to prevent the perturb function being called on it multiple
+     * times.
+     * 
+     * To do this, we _sort_ the perimeter of the area. The
+     * existing xyd_cmp function will arrange things into columns
+     * for us, in such a way that each column has the edges in
+     * vertical order. Then we can work down each column and fill
+     * in all the squares between an up edge and a down edge.
+     */
+    qsort(perimeter, nperim, sizeof(struct xyd), xyd_cmp);
+    x = y = -1;
+    for (i = 0; i <= nperim; i++) {
+       if (i == nperim || perimeter[i].x > x) {
+           /*
+            * Fill in everything from the last Up edge to the
+            * bottom of the grid, if necessary.
+            */
+           if (x != -1) {
+               while (y < h) {
+#ifdef PERTURB_DIAGNOSTICS
+                   printf("resolved: locking tile %d,%d\n", x, y);
+#endif
+                   tiles[y * w + x] |= LOCKED;
+                   y++;
+               }
+               x = y = -1;
+           }
+
+           if (i == nperim)
+               break;
+
+           x = perimeter[i].x;
+           y = 0;
+       }
+
+       if (perimeter[i].direction == U) {
+           x = perimeter[i].x;
+           y = perimeter[i].y;
+       } else if (perimeter[i].direction == D) {
+           /*
+            * Fill in everything from the last Up edge to here.
+            */
+           assert(x == perimeter[i].x && y <= perimeter[i].y);
+           while (y <= perimeter[i].y) {
+#ifdef PERTURB_DIAGNOSTICS
+               printf("resolved: locking tile %d,%d\n", x, y);
+#endif
+               tiles[y * w + x] |= LOCKED;
+               y++;
+           }
+           x = y = -1;
+       }
+    }
+
+    sfree(perimeter);
+}
+
+static int *compute_loops_inner(int w, int h, int wrapping,
+                                const unsigned char *tiles,
+                                const unsigned char *barriers);
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                          char **aux, int interactive)
+{
+    tree234 *possibilities, *barriertree;
+    int w, h, x, y, cx, cy, nbarriers;
+    unsigned char *tiles, *barriers;
+    char *desc, *p;
+
+    w = params->width;
+    h = params->height;
+
+    cx = w / 2;
+    cy = h / 2;
+
+    tiles = snewn(w * h, unsigned char);
+    barriers = snewn(w * h, unsigned char);
+
+    begin_generation:
+
+    memset(tiles, 0, w * h);
+    memset(barriers, 0, w * h);
+
+    /*
+     * Construct the unshuffled grid.
+     * 
+     * To do this, we simply start at the centre point, repeatedly
+     * choose a random possibility out of the available ways to
+     * extend a used square into an unused one, and do it. After
+     * extending the third line out of a square, we remove the
+     * fourth from the possibilities list to avoid any full-cross
+     * squares (which would make the game too easy because they
+     * only have one orientation).
+     * 
+     * The slightly worrying thing is the avoidance of full-cross
+     * squares. Can this cause our unsophisticated construction
+     * algorithm to paint itself into a corner, by getting into a
+     * situation where there are some unreached squares and the
+     * only way to reach any of them is to extend a T-piece into a
+     * full cross?
+     * 
+     * Answer: no it can't, and here's a proof.
+     * 
+     * Any contiguous group of such unreachable squares must be
+     * surrounded on _all_ sides by T-pieces pointing away from the
+     * group. (If not, then there is a square which can be extended
+     * into one of the `unreachable' ones, and so it wasn't
+     * unreachable after all.) In particular, this implies that
+     * each contiguous group of unreachable squares must be
+     * rectangular in shape (any deviation from that yields a
+     * non-T-piece next to an `unreachable' square).
+     * 
+     * So we have a rectangle of unreachable squares, with T-pieces
+     * forming a solid border around the rectangle. The corners of
+     * that border must be connected (since every tile connects all
+     * the lines arriving in it), and therefore the border must
+     * form a closed loop around the rectangle.
+     * 
+     * But this can't have happened in the first place, since we
+     * _know_ we've avoided creating closed loops! Hence, no such
+     * situation can ever arise, and the naive grid construction
+     * algorithm will guaranteeably result in a complete grid
+     * containing no unreached squares, no full crosses _and_ no
+     * closed loops. []
+     */
+    possibilities = newtree234(xyd_cmp_nc);
+
+    if (cx+1 < w)
+       add234(possibilities, new_xyd(cx, cy, R));
+    if (cy-1 >= 0)
+       add234(possibilities, new_xyd(cx, cy, U));
+    if (cx-1 >= 0)
+       add234(possibilities, new_xyd(cx, cy, L));
+    if (cy+1 < h)
+       add234(possibilities, new_xyd(cx, cy, D));
+
+    while (count234(possibilities) > 0) {
+       int i;
+       struct xyd *xyd;
+       int x1, y1, d1, x2, y2, d2, d;
+
+       /*
+        * Extract a randomly chosen possibility from the list.
+        */
+       i = random_upto(rs, count234(possibilities));
+       xyd = delpos234(possibilities, i);
+       x1 = xyd->x;
+       y1 = xyd->y;
+       d1 = xyd->direction;
+       sfree(xyd);
+
+       OFFSET(x2, y2, x1, y1, d1, params);
+       d2 = F(d1);
+#ifdef GENERATION_DIAGNOSTICS
+       printf("picked (%d,%d,%c) <-> (%d,%d,%c)\n",
+              x1, y1, "0RU3L567D9abcdef"[d1], x2, y2, "0RU3L567D9abcdef"[d2]);
+#endif
+
+       /*
+        * Make the connection. (We should be moving to an as yet
+        * unused tile.)
+        */
+       index(params, tiles, x1, y1) |= d1;
+       assert(index(params, tiles, x2, y2) == 0);
+       index(params, tiles, x2, y2) |= d2;
+
+       /*
+        * If we have created a T-piece, remove its last
+        * possibility.
+        */
+       if (COUNT(index(params, tiles, x1, y1)) == 3) {
+           struct xyd xyd1, *xydp;
+
+           xyd1.x = x1;
+           xyd1.y = y1;
+           xyd1.direction = 0x0F ^ index(params, tiles, x1, y1);
+
+           xydp = find234(possibilities, &xyd1, NULL);
+
+           if (xydp) {
+#ifdef GENERATION_DIAGNOSTICS
+               printf("T-piece; removing (%d,%d,%c)\n",
+                      xydp->x, xydp->y, "0RU3L567D9abcdef"[xydp->direction]);
+#endif
+               del234(possibilities, xydp);
+               sfree(xydp);
+           }
+       }
+
+       /*
+        * Remove all other possibilities that were pointing at the
+        * tile we've just moved into.
+        */
+       for (d = 1; d < 0x10; d <<= 1) {
+           int x3, y3, d3;
+           struct xyd xyd1, *xydp;
+
+           OFFSET(x3, y3, x2, y2, d, params);
+           d3 = F(d);
+
+           xyd1.x = x3;
+           xyd1.y = y3;
+           xyd1.direction = d3;
+
+           xydp = find234(possibilities, &xyd1, NULL);
+
+           if (xydp) {
+#ifdef GENERATION_DIAGNOSTICS
+               printf("Loop avoidance; removing (%d,%d,%c)\n",
+                      xydp->x, xydp->y, "0RU3L567D9abcdef"[xydp->direction]);
+#endif
+               del234(possibilities, xydp);
+               sfree(xydp);
+           }
+       }
+
+       /*
+        * Add new possibilities to the list for moving _out_ of
+        * the tile we have just moved into.
+        */
+       for (d = 1; d < 0x10; d <<= 1) {
+           int x3, y3;
+
+           if (d == d2)
+               continue;              /* we've got this one already */
+
+           if (!params->wrapping) {
+               if (d == U && y2 == 0)
+                   continue;
+               if (d == D && y2 == h-1)
+                   continue;
+               if (d == L && x2 == 0)
+                   continue;
+               if (d == R && x2 == w-1)
+                   continue;
+           }
+
+           OFFSET(x3, y3, x2, y2, d, params);
+
+           if (index(params, tiles, x3, y3))
+               continue;              /* this would create a loop */
+
+#ifdef GENERATION_DIAGNOSTICS
+           printf("New frontier; adding (%d,%d,%c)\n",
+                  x2, y2, "0RU3L567D9abcdef"[d]);
+#endif
+           add234(possibilities, new_xyd(x2, y2, d));
+       }
+    }
+    /* Having done that, we should have no possibilities remaining. */
+    assert(count234(possibilities) == 0);
+    freetree234(possibilities);
+
+    if (params->unique) {
+       int prevn = -1;
+
+       /*
+        * Run the solver to check unique solubility.
+        */
+       while (!net_solver(w, h, tiles, NULL, params->wrapping)) {
+           int n = 0;
+
+           /*
+            * We expect (in most cases) that most of the grid will
+            * be uniquely specified already, and the remaining
+            * ambiguous sections will be small and separate. So
+            * our strategy is to find each individual such
+            * section, and perform a perturbation on the network
+            * in that area.
+            */
+           for (y = 0; y < h; y++) for (x = 0; x < w; x++) {
+               if (x+1 < w && ((tiles[y*w+x] ^ tiles[y*w+x+1]) & LOCKED)) {
+                   n++;
+                   if (tiles[y*w+x] & LOCKED)
+                       perturb(w, h, tiles, params->wrapping, rs, x+1, y, L);
+                   else
+                       perturb(w, h, tiles, params->wrapping, rs, x, y, R);
+               }
+               if (y+1 < h && ((tiles[y*w+x] ^ tiles[(y+1)*w+x]) & LOCKED)) {
+                   n++;
+                   if (tiles[y*w+x] & LOCKED)
+                       perturb(w, h, tiles, params->wrapping, rs, x, y+1, U);
+                   else
+                       perturb(w, h, tiles, params->wrapping, rs, x, y, D);
+               }
+           }
+
+           /*
+            * Now n counts the number of ambiguous sections we
+            * have fiddled with. If we haven't managed to decrease
+            * it from the last time we ran the solver, give up and
+            * regenerate the entire grid.
+            */
+           if (prevn != -1 && prevn <= n)
+               goto begin_generation; /* (sorry) */
+
+           prevn = n;
+       }
+
+       /*
+        * The solver will have left a lot of LOCKED bits lying
+        * around in the tiles array. Remove them.
+        */
+       for (x = 0; x < w*h; x++)
+           tiles[x] &= ~LOCKED;
+    }
+
+    /*
+     * Now compute a list of the possible barrier locations.
+     */
+    barriertree = newtree234(xyd_cmp_nc);
+    for (y = 0; y < h; y++) {
+       for (x = 0; x < w; x++) {
+
+           if (!(index(params, tiles, x, y) & R) &&
+                (params->wrapping || x < w-1))
+               add234(barriertree, new_xyd(x, y, R));
+           if (!(index(params, tiles, x, y) & D) &&
+                (params->wrapping || y < h-1))
+               add234(barriertree, new_xyd(x, y, D));
+       }
+    }
+
+    /*
+     * Save the unshuffled grid in aux.
+     */
+    {
+       char *solution;
+        int i;
+
+       solution = snewn(w * h + 1, char);
+        for (i = 0; i < w * h; i++)
+            solution[i] = "0123456789abcdef"[tiles[i] & 0xF];
+        solution[w*h] = '\0';
+
+       *aux = solution;
+    }
+
+    /*
+     * Now shuffle the grid.
+     * 
+     * In order to avoid accidentally generating an already-solved
+     * grid, we will reshuffle as necessary to ensure that at least
+     * one edge has a mismatched connection.
+     *
+     * This can always be done, since validate_params() enforces a
+     * grid area of at least 2 and our generator never creates
+     * either type of rotationally invariant tile (cross and
+     * blank). Hence there must be at least one edge separating
+     * distinct tiles, and it must be possible to find orientations
+     * of those tiles such that one tile is trying to connect
+     * through that edge and the other is not.
+     * 
+     * (We could be more subtle, and allow the shuffle to generate
+     * a grid in which all tiles match up locally and the only
+     * criterion preventing the grid from being already solved is
+     * connectedness. However, that would take more effort, and
+     * it's easier to simply make sure every grid is _obviously_
+     * not solved.)
+     *
+     * We also require that our shuffle produces no loops in the
+     * initial grid state, because it's a bit rude to light up a 'HEY,
+     * YOU DID SOMETHING WRONG!' indicator when the user hasn't even
+     * had a chance to do _anything_ yet. This also is possible just
+     * by retrying the whole shuffle on failure, because it's clear
+     * that at least one non-solved shuffle with no loops must exist.
+     * (Proof: take the _solved_ state of the puzzle, and rotate one
+     * endpoint.)
+     */
+    while (1) {
+        int mismatches, prev_loopsquares, this_loopsquares, i;
+        int *loops;
+
+      shuffle:
+        for (y = 0; y < h; y++) {
+            for (x = 0; x < w; x++) {
+                int orig = index(params, tiles, x, y);
+                int rot = random_upto(rs, 4);
+                index(params, tiles, x, y) = ROT(orig, rot);
+            }
+        }
+
+        /*
+         * Check for loops, and try to fix them by reshuffling just
+         * the squares involved.
+         */
+        prev_loopsquares = w*h+1;
+        while (1) {
+            loops = compute_loops_inner(w, h, params->wrapping, tiles, NULL);
+            this_loopsquares = 0;
+            for (i = 0; i < w*h; i++) {
+                if (loops[i]) {
+                    int orig = tiles[i];
+                    int rot = random_upto(rs, 4);
+                    tiles[i] = ROT(orig, rot);
+                    this_loopsquares++;
+                }
+            }
+            sfree(loops);
+            if (this_loopsquares > prev_loopsquares) {
+                /*
+                 * We're increasing rather than reducing the number of
+                 * loops. Give up and go back to the full shuffle.
+                 */
+                goto shuffle;
+            }
+            if (this_loopsquares == 0)
+                break;
+            prev_loopsquares = this_loopsquares;
+        }
+
+        mismatches = 0;
+        /*
+         * I can't even be bothered to check for mismatches across
+         * a wrapping edge, so I'm just going to enforce that there
+         * must be a mismatch across a non-wrapping edge, which is
+         * still always possible.
+         */
+        for (y = 0; y < h; y++) for (x = 0; x < w; x++) {
+            if (x+1 < w && ((ROT(index(params, tiles, x, y), 2) ^ 
+                             index(params, tiles, x+1, y)) & L))
+                mismatches++;
+            if (y+1 < h && ((ROT(index(params, tiles, x, y), 2) ^ 
+                             index(params, tiles, x, y+1)) & U))
+                mismatches++;
+        }
+
+        if (mismatches == 0)
+            continue;
+
+        /* OK. */
+        break;
+    }
+
+    /*
+     * And now choose barrier locations. (We carefully do this
+     * _after_ shuffling, so that changing the barrier rate in the
+     * params while keeping the random seed the same will give the
+     * same shuffled grid and _only_ change the barrier locations.
+     * Also the way we choose barrier locations, by repeatedly
+     * choosing one possibility from the list until we have enough,
+     * is designed to ensure that raising the barrier rate while
+     * keeping the seed the same will provide a superset of the
+     * previous barrier set - i.e. if you ask for 10 barriers, and
+     * then decide that's still too hard and ask for 20, you'll get
+     * the original 10 plus 10 more, rather than getting 20 new
+     * ones and the chance of remembering your first 10.)
+     */
+    nbarriers = (int)(params->barrier_probability * count234(barriertree));
+    assert(nbarriers >= 0 && nbarriers <= count234(barriertree));
+
+    while (nbarriers > 0) {
+       int i;
+       struct xyd *xyd;
+       int x1, y1, d1, x2, y2, d2;
+
+       /*
+        * Extract a randomly chosen barrier from the list.
+        */
+       i = random_upto(rs, count234(barriertree));
+       xyd = delpos234(barriertree, i);
+
+       assert(xyd != NULL);
+
+       x1 = xyd->x;
+       y1 = xyd->y;
+       d1 = xyd->direction;
+       sfree(xyd);
+
+       OFFSET(x2, y2, x1, y1, d1, params);
+       d2 = F(d1);
+
+       index(params, barriers, x1, y1) |= d1;
+       index(params, barriers, x2, y2) |= d2;
+
+       nbarriers--;
+    }
+
+    /*
+     * Clean up the rest of the barrier list.
+     */
+    {
+       struct xyd *xyd;
+
+       while ( (xyd = delpos234(barriertree, 0)) != NULL)
+           sfree(xyd);
+
+       freetree234(barriertree);
+    }
+
+    /*
+     * Finally, encode the grid into a string game description.
+     * 
+     * My syntax is extremely simple: each square is encoded as a
+     * hex digit in which bit 0 means a connection on the right,
+     * bit 1 means up, bit 2 left and bit 3 down. (i.e. the same
+     * encoding as used internally). Each digit is followed by
+     * optional barrier indicators: `v' means a vertical barrier to
+     * the right of it, and `h' means a horizontal barrier below
+     * it.
+     */
+    desc = snewn(w * h * 3 + 1, char);
+    p = desc;
+    for (y = 0; y < h; y++) {
+        for (x = 0; x < w; x++) {
+            *p++ = "0123456789abcdef"[index(params, tiles, x, y)];
+            if ((params->wrapping || x < w-1) &&
+                (index(params, barriers, x, y) & R))
+                *p++ = 'v';
+            if ((params->wrapping || y < h-1) &&
+                (index(params, barriers, x, y) & D))
+                *p++ = 'h';
+        }
+    }
+    assert(p - desc <= w*h*3);
+    *p = '\0';
+
+    sfree(tiles);
+    sfree(barriers);
+
+    return desc;
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    int w = params->width, h = params->height;
+    int i;
+
+    for (i = 0; i < w*h; i++) {
+        if (*desc >= '0' && *desc <= '9')
+            /* OK */;
+        else if (*desc >= 'a' && *desc <= 'f')
+            /* OK */;
+        else if (*desc >= 'A' && *desc <= 'F')
+            /* OK */;
+        else if (!*desc)
+            return "Game description shorter than expected";
+        else
+            return "Game description contained unexpected character";
+        desc++;
+        while (*desc == 'h' || *desc == 'v')
+            desc++;
+    }
+    if (*desc)
+        return "Game description longer than expected";
+
+    return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Construct an initial game state, given a description and parameters.
+ */
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    game_state *state;
+    int w, h, x, y;
+
+    assert(params->width > 0 && params->height > 0);
+    assert(params->width > 1 || params->height > 1);
+
+    /*
+     * Create a blank game state.
+     */
+    state = snew(game_state);
+    w = state->width = params->width;
+    h = state->height = params->height;
+    state->wrapping = params->wrapping;
+    state->last_rotate_dir = state->last_rotate_x = state->last_rotate_y = 0;
+    state->completed = state->used_solve = FALSE;
+    state->tiles = snewn(state->width * state->height, unsigned char);
+    memset(state->tiles, 0, state->width * state->height);
+    state->barriers = snewn(state->width * state->height, unsigned char);
+    memset(state->barriers, 0, state->width * state->height);
+
+    /*
+     * Parse the game description into the grid.
+     */
+    for (y = 0; y < h; y++) {
+        for (x = 0; x < w; x++) {
+            if (*desc >= '0' && *desc <= '9')
+                tile(state, x, y) = *desc - '0';
+            else if (*desc >= 'a' && *desc <= 'f')
+                tile(state, x, y) = *desc - 'a' + 10;
+            else if (*desc >= 'A' && *desc <= 'F')
+                tile(state, x, y) = *desc - 'A' + 10;
+            if (*desc)
+                desc++;
+            while (*desc == 'h' || *desc == 'v') {
+                int x2, y2, d1, d2;
+                if (*desc == 'v')
+                    d1 = R;
+                else
+                    d1 = D;
+
+                OFFSET(x2, y2, x, y, d1, state);
+                d2 = F(d1);
+
+                barrier(state, x, y) |= d1;
+                barrier(state, x2, y2) |= d2;
+
+                desc++;
+            }
+        }
+    }
+
+    /*
+     * Set up border barriers if this is a non-wrapping game.
+     */
+    if (!state->wrapping) {
+       for (x = 0; x < state->width; x++) {
+           barrier(state, x, 0) |= U;
+           barrier(state, x, state->height-1) |= D;
+       }
+       for (y = 0; y < state->height; y++) {
+           barrier(state, 0, y) |= L;
+           barrier(state, state->width-1, y) |= R;
+       }
+    } else {
+        /*
+         * We check whether this is de-facto a non-wrapping game
+         * despite the parameters, in case we were passed the
+         * description of a non-wrapping game. This is so that we
+         * can change some aspects of the UI behaviour.
+         */
+        state->wrapping = FALSE;
+        for (x = 0; x < state->width; x++)
+            if (!(barrier(state, x, 0) & U) ||
+                !(barrier(state, x, state->height-1) & D))
+                state->wrapping = TRUE;
+        for (y = 0; y < state->height; y++)
+            if (!(barrier(state, 0, y) & L) ||
+                !(barrier(state, state->width-1, y) & R))
+                state->wrapping = TRUE;
+    }
+
+    return state;
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    game_state *ret;
+
+    ret = snew(game_state);
+    ret->width = state->width;
+    ret->height = state->height;
+    ret->wrapping = state->wrapping;
+    ret->completed = state->completed;
+    ret->used_solve = state->used_solve;
+    ret->last_rotate_dir = state->last_rotate_dir;
+    ret->last_rotate_x = state->last_rotate_x;
+    ret->last_rotate_y = state->last_rotate_y;
+    ret->tiles = snewn(state->width * state->height, unsigned char);
+    memcpy(ret->tiles, state->tiles, state->width * state->height);
+    ret->barriers = snewn(state->width * state->height, unsigned char);
+    memcpy(ret->barriers, state->barriers, state->width * state->height);
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    sfree(state->tiles);
+    sfree(state->barriers);
+    sfree(state);
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    unsigned char *tiles;
+    char *ret;
+    int retlen, retsize;
+    int i;
+
+    tiles = snewn(state->width * state->height, unsigned char);
+
+    if (!aux) {
+       /*
+        * Run the internal solver on the provided grid. This might
+        * not yield a complete solution.
+        */
+       memcpy(tiles, state->tiles, state->width * state->height);
+       net_solver(state->width, state->height, tiles,
+                  state->barriers, state->wrapping);
+    } else {
+        for (i = 0; i < state->width * state->height; i++) {
+            int c = aux[i];
+
+            if (c >= '0' && c <= '9')
+                tiles[i] = c - '0';
+            else if (c >= 'a' && c <= 'f')
+                tiles[i] = c - 'a' + 10;
+            else if (c >= 'A' && c <= 'F')
+                tiles[i] = c - 'A' + 10;
+
+           tiles[i] |= LOCKED;
+        }
+    }
+
+    /*
+     * Now construct a string which can be passed to execute_move()
+     * to transform the current grid into the solved one.
+     */
+    retsize = 256;
+    ret = snewn(retsize, char);
+    retlen = 0;
+    ret[retlen++] = 'S';
+
+    for (i = 0; i < state->width * state->height; i++) {
+       int from = currstate->tiles[i], to = tiles[i];
+       int ft = from & (R|L|U|D), tt = to & (R|L|U|D);
+       int x = i % state->width, y = i / state->width;
+       int chr = '\0';
+       char buf[80], *p = buf;
+
+       if (from == to)
+           continue;                  /* nothing needs doing at all */
+
+       /*
+        * To transform this tile into the desired tile: first
+        * unlock the tile if it's locked, then rotate it if
+        * necessary, then lock it if necessary.
+        */
+       if (from & LOCKED)
+           p += sprintf(p, ";L%d,%d", x, y);
+
+       if (tt == A(ft))
+           chr = 'A';
+       else if (tt == C(ft))
+           chr = 'C';
+       else if (tt == F(ft))
+           chr = 'F';
+       else {
+           assert(tt == ft);
+           chr = '\0';
+       }
+       if (chr)
+           p += sprintf(p, ";%c%d,%d", chr, x, y);
+
+       if (to & LOCKED)
+           p += sprintf(p, ";L%d,%d", x, y);
+
+       if (p > buf) {
+           if (retlen + (p - buf) >= retsize) {
+               retsize = retlen + (p - buf) + 512;
+               ret = sresize(ret, retsize, char);
+           }
+           memcpy(ret+retlen, buf, p - buf);
+           retlen += p - buf;
+       }
+    }
+
+    assert(retlen < retsize);
+    ret[retlen] = '\0';
+    ret = sresize(ret, retlen+1, char);
+
+    sfree(tiles);
+
+    return ret;
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Utility routine.
+ */
+
+/*
+ * Compute which squares are reachable from the centre square, as a
+ * quick visual aid to determining how close the game is to
+ * completion. This is also a simple way to tell if the game _is_
+ * completed - just call this function and see whether every square
+ * is marked active.
+ */
+static unsigned char *compute_active(const game_state *state, int cx, int cy)
+{
+    unsigned char *active;
+    tree234 *todo;
+    struct xyd *xyd;
+
+    active = snewn(state->width * state->height, unsigned char);
+    memset(active, 0, state->width * state->height);
+
+    /*
+     * We only store (x,y) pairs in todo, but it's easier to reuse
+     * xyd_cmp and just store direction 0 every time.
+     */
+    todo = newtree234(xyd_cmp_nc);
+    index(state, active, cx, cy) = ACTIVE;
+    add234(todo, new_xyd(cx, cy, 0));
+
+    while ( (xyd = delpos234(todo, 0)) != NULL) {
+       int x1, y1, d1, x2, y2, d2;
+
+       x1 = xyd->x;
+       y1 = xyd->y;
+       sfree(xyd);
+
+       for (d1 = 1; d1 < 0x10; d1 <<= 1) {
+           OFFSET(x2, y2, x1, y1, d1, state);
+           d2 = F(d1);
+
+           /*
+            * If the next tile in this direction is connected to
+            * us, and there isn't a barrier in the way, and it
+            * isn't already marked active, then mark it active and
+            * add it to the to-examine list.
+            */
+           if ((tile(state, x1, y1) & d1) &&
+               (tile(state, x2, y2) & d2) &&
+               !(barrier(state, x1, y1) & d1) &&
+               !index(state, active, x2, y2)) {
+               index(state, active, x2, y2) = ACTIVE;
+               add234(todo, new_xyd(x2, y2, 0));
+           }
+       }
+    }
+    /* Now we expect the todo list to have shrunk to zero size. */
+    assert(count234(todo) == 0);
+    freetree234(todo);
+
+    return active;
+}
+
+struct net_neighbour_ctx {
+    int w, h;
+    const unsigned char *tiles, *barriers;
+    int i, n, neighbours[4];
+};
+static int net_neighbour(int vertex, void *vctx)
+{
+    struct net_neighbour_ctx *ctx = (struct net_neighbour_ctx *)vctx;
+
+    if (vertex >= 0) {
+        int x = vertex % ctx->w, y = vertex / ctx->w;
+        int tile, dir, x1, y1, v1;
+
+        ctx->i = ctx->n = 0;
+
+        tile = ctx->tiles[vertex];
+        if (ctx->barriers)
+            tile &= ~ctx->barriers[vertex];
+
+        for (dir = 1; dir < 0x10; dir <<= 1) {
+            if (!(tile & dir))
+                continue;
+            OFFSETWH(x1, y1, x, y, dir, ctx->w, ctx->h);
+            v1 = y1 * ctx->w + x1;
+            if (ctx->tiles[v1] & F(dir))
+                ctx->neighbours[ctx->n++] = v1;
+        }
+    }
+
+    if (ctx->i < ctx->n)
+        return ctx->neighbours[ctx->i++];
+    else
+        return -1;
+}
+
+static int *compute_loops_inner(int w, int h, int wrapping,
+                                const unsigned char *tiles,
+                                const unsigned char *barriers)
+{
+    struct net_neighbour_ctx ctx;
+    struct findloopstate *fls;
+    int *loops;
+    int x, y;
+
+    fls = findloop_new_state(w*h);
+    ctx.w = w;
+    ctx.h = h;
+    ctx.tiles = tiles;
+    ctx.barriers = barriers;
+    findloop_run(fls, w*h, net_neighbour, &ctx);
+
+    loops = snewn(w*h, int);
+
+    for (y = 0; y < h; y++) {
+        for (x = 0; x < w; x++) {
+            int x1, y1, dir;
+            int flags = 0;
+
+            for (dir = 1; dir < 0x10; dir <<= 1) {
+                if ((tiles[y*w+x] & dir) &&
+                    !(barriers && (barriers[y*w+x] & dir))) {
+                    OFFSETWH(x1, y1, x, y, dir, w, h);
+                    if ((tiles[y1*w+x1] & F(dir)) &&
+                        findloop_is_loop_edge(fls, y*w+x, y1*w+x1))
+                        flags |= LOOP(dir);
+                }
+            }
+            loops[y*w+x] = flags;
+        }
+    }
+
+    findloop_free_state(fls);
+    return loops;
+}
+
+static int *compute_loops(const game_state *state)
+{
+    return compute_loops_inner(state->width, state->height, state->wrapping,
+                               state->tiles, state->barriers);
+}
+
+struct game_ui {
+    int org_x, org_y; /* origin */
+    int cx, cy;       /* source tile (game coordinates) */
+    int cur_x, cur_y;
+    int cur_visible;
+    random_state *rs; /* used for jumbling */
+#ifdef USE_DRAGGING
+    int dragtilex, dragtiley, dragstartx, dragstarty, dragged;
+#endif
+};
+
+static game_ui *new_ui(const game_state *state)
+{
+    void *seed;
+    int seedsize;
+    game_ui *ui = snew(game_ui);
+    ui->org_x = ui->org_y = 0;
+    ui->cur_x = ui->cx = state->width / 2;
+    ui->cur_y = ui->cy = state->height / 2;
+    ui->cur_visible = FALSE;
+    get_random_seed(&seed, &seedsize);
+    ui->rs = random_new(seed, seedsize);
+    sfree(seed);
+
+    return ui;
+}
+
+static void free_ui(game_ui *ui)
+{
+    random_free(ui->rs);
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    char buf[120];
+    /*
+     * We preserve the origin and centre-point coordinates over a
+     * serialise.
+     */
+    sprintf(buf, "O%d,%d;C%d,%d", ui->org_x, ui->org_y, ui->cx, ui->cy);
+    return dupstr(buf);
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+    sscanf(encoding, "O%d,%d;C%d,%d",
+          &ui->org_x, &ui->org_y, &ui->cx, &ui->cy);
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+}
+
+struct game_drawstate {
+    int started;
+    int width, height;
+    int org_x, org_y;
+    int tilesize;
+    int *visible;
+};
+
+/* ----------------------------------------------------------------------
+ * Process a move.
+ */
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    char *nullret;
+    int tx = -1, ty = -1, dir = 0;
+    int shift = button & MOD_SHFT, ctrl = button & MOD_CTRL;
+    enum {
+        NONE, ROTATE_LEFT, ROTATE_180, ROTATE_RIGHT, TOGGLE_LOCK, JUMBLE,
+        MOVE_ORIGIN, MOVE_SOURCE, MOVE_ORIGIN_AND_SOURCE, MOVE_CURSOR
+    } action;
+
+    button &= ~MOD_MASK;
+    nullret = NULL;
+    action = NONE;
+
+    if (button == LEFT_BUTTON ||
+       button == MIDDLE_BUTTON ||
+#ifdef USE_DRAGGING
+       button == LEFT_DRAG ||
+       button == LEFT_RELEASE ||
+       button == RIGHT_DRAG ||
+       button == RIGHT_RELEASE ||
+#endif
+       button == RIGHT_BUTTON) {
+
+       if (ui->cur_visible) {
+           ui->cur_visible = FALSE;
+           nullret = "";
+       }
+
+       /*
+        * The button must have been clicked on a valid tile.
+        */
+       x -= WINDOW_OFFSET + TILE_BORDER;
+       y -= WINDOW_OFFSET + TILE_BORDER;
+       if (x < 0 || y < 0)
+           return nullret;
+       tx = x / TILE_SIZE;
+       ty = y / TILE_SIZE;
+       if (tx >= state->width || ty >= state->height)
+           return nullret;
+        /* Transform from physical to game coords */
+        tx = (tx + ui->org_x) % state->width;
+        ty = (ty + ui->org_y) % state->height;
+       if (x % TILE_SIZE >= TILE_SIZE - TILE_BORDER ||
+           y % TILE_SIZE >= TILE_SIZE - TILE_BORDER)
+           return nullret;
+
+#ifdef USE_DRAGGING
+
+        if (button == MIDDLE_BUTTON
+#ifdef STYLUS_BASED
+           || button == RIGHT_BUTTON  /* with a stylus, `right-click' locks */
+#endif
+           ) {
+            /*
+             * Middle button never drags: it only toggles the lock.
+             */
+            action = TOGGLE_LOCK;
+        } else if (button == LEFT_BUTTON
+#ifndef STYLUS_BASED
+                   || button == RIGHT_BUTTON /* (see above) */
+#endif
+                  ) {
+            /*
+             * Otherwise, we note down the start point for a drag.
+             */
+            ui->dragtilex = tx;
+            ui->dragtiley = ty;
+            ui->dragstartx = x % TILE_SIZE;
+            ui->dragstarty = y % TILE_SIZE;
+            ui->dragged = FALSE;
+            return nullret;            /* no actual action */
+        } else if (button == LEFT_DRAG
+#ifndef STYLUS_BASED
+                   || button == RIGHT_DRAG
+#endif
+                  ) {
+            /*
+             * Find the new drag point and see if it necessitates a
+             * rotation.
+             */
+            int x0,y0, xA,yA, xC,yC, xF,yF;
+            int mx, my;
+            int d0, dA, dC, dF, dmin;
+
+            tx = ui->dragtilex;
+            ty = ui->dragtiley;
+
+            mx = x - (ui->dragtilex * TILE_SIZE);
+            my = y - (ui->dragtiley * TILE_SIZE);
+
+            x0 = ui->dragstartx;
+            y0 = ui->dragstarty;
+            xA = ui->dragstarty;
+            yA = TILE_SIZE-1 - ui->dragstartx;
+            xF = TILE_SIZE-1 - ui->dragstartx;
+            yF = TILE_SIZE-1 - ui->dragstarty;
+            xC = TILE_SIZE-1 - ui->dragstarty;
+            yC = ui->dragstartx;
+
+            d0 = (mx-x0)*(mx-x0) + (my-y0)*(my-y0);
+            dA = (mx-xA)*(mx-xA) + (my-yA)*(my-yA);
+            dF = (mx-xF)*(mx-xF) + (my-yF)*(my-yF);
+            dC = (mx-xC)*(mx-xC) + (my-yC)*(my-yC);
+
+            dmin = min(min(d0,dA),min(dF,dC));
+
+            if (d0 == dmin) {
+                return nullret;
+            } else if (dF == dmin) {
+                action = ROTATE_180;
+                ui->dragstartx = xF;
+                ui->dragstarty = yF;
+                ui->dragged = TRUE;
+            } else if (dA == dmin) {
+                action = ROTATE_LEFT;
+                ui->dragstartx = xA;
+                ui->dragstarty = yA;
+                ui->dragged = TRUE;
+            } else /* dC == dmin */ {
+                action = ROTATE_RIGHT;
+                ui->dragstartx = xC;
+                ui->dragstarty = yC;
+                ui->dragged = TRUE;
+            }
+        } else if (button == LEFT_RELEASE
+#ifndef STYLUS_BASED
+                   || button == RIGHT_RELEASE
+#endif
+                  ) {
+            if (!ui->dragged) {
+                /*
+                 * There was a click but no perceptible drag:
+                 * revert to single-click behaviour.
+                 */
+                tx = ui->dragtilex;
+                ty = ui->dragtiley;
+
+                if (button == LEFT_RELEASE)
+                    action = ROTATE_LEFT;
+                else
+                    action = ROTATE_RIGHT;
+            } else
+                return nullret;        /* no action */
+        }
+
+#else /* USE_DRAGGING */
+
+       action = (button == LEFT_BUTTON ? ROTATE_LEFT :
+                 button == RIGHT_BUTTON ? ROTATE_RIGHT : TOGGLE_LOCK);
+
+#endif /* USE_DRAGGING */
+
+    } else if (IS_CURSOR_MOVE(button)) {
+        switch (button) {
+          case CURSOR_UP:       dir = U; break;
+          case CURSOR_DOWN:     dir = D; break;
+          case CURSOR_LEFT:     dir = L; break;
+          case CURSOR_RIGHT:    dir = R; break;
+          default:              return nullret;
+        }
+        if (shift && ctrl) action = MOVE_ORIGIN_AND_SOURCE;
+        else if (shift)    action = MOVE_ORIGIN;
+        else if (ctrl)     action = MOVE_SOURCE;
+        else               action = MOVE_CURSOR;
+    } else if (button == 'a' || button == 's' || button == 'd' ||
+              button == 'A' || button == 'S' || button == 'D' ||
+               button == 'f' || button == 'F' ||
+               IS_CURSOR_SELECT(button)) {
+       tx = ui->cur_x;
+       ty = ui->cur_y;
+       if (button == 'a' || button == 'A' || button == CURSOR_SELECT)
+           action = ROTATE_LEFT;
+       else if (button == 's' || button == 'S' || button == CURSOR_SELECT2)
+           action = TOGGLE_LOCK;
+       else if (button == 'd' || button == 'D')
+           action = ROTATE_RIGHT;
+        else if (button == 'f' || button == 'F')
+            action = ROTATE_180;
+        ui->cur_visible = TRUE;
+    } else if (button == 'j' || button == 'J') {
+       /* XXX should we have some mouse control for this? */
+       action = JUMBLE;
+    } else
+       return nullret;
+
+    /*
+     * The middle button locks or unlocks a tile. (A locked tile
+     * cannot be turned, and is visually marked as being locked.
+     * This is a convenience for the player, so that once they are
+     * sure which way round a tile goes, they can lock it and thus
+     * avoid forgetting later on that they'd already done that one;
+     * and the locking also prevents them turning the tile by
+     * accident. If they change their mind, another middle click
+     * unlocks it.)
+     */
+    if (action == TOGGLE_LOCK) {
+       char buf[80];
+       sprintf(buf, "L%d,%d", tx, ty);
+       return dupstr(buf);
+    } else if (action == ROTATE_LEFT || action == ROTATE_RIGHT ||
+               action == ROTATE_180) {
+       char buf[80];
+
+        /*
+         * The left and right buttons have no effect if clicked on a
+         * locked tile.
+         */
+        if (tile(state, tx, ty) & LOCKED)
+            return nullret;
+
+        /*
+         * Otherwise, turn the tile one way or the other. Left button
+         * turns anticlockwise; right button turns clockwise.
+         */
+       sprintf(buf, "%c%d,%d", (int)(action == ROTATE_LEFT ? 'A' :
+                                      action == ROTATE_RIGHT ? 'C' : 'F'), tx, ty);
+       return dupstr(buf);
+    } else if (action == JUMBLE) {
+        /*
+         * Jumble all unlocked tiles to random orientations.
+         */
+
+        int jx, jy, maxlen;
+       char *ret, *p;
+
+       /*
+        * Maximum string length assumes no int can be converted to
+        * decimal and take more than 11 digits!
+        */
+       maxlen = state->width * state->height * 25 + 3;
+
+       ret = snewn(maxlen, char);
+       p = ret;
+       *p++ = 'J';
+
+        for (jy = 0; jy < state->height; jy++) {
+            for (jx = 0; jx < state->width; jx++) {
+                if (!(tile(state, jx, jy) & LOCKED)) {
+                    int rot = random_upto(ui->rs, 4);
+                   if (rot) {
+                       p += sprintf(p, ";%c%d,%d", "AFC"[rot-1], jx, jy);
+                   }
+                }
+            }
+        }
+       *p++ = '\0';
+       assert(p - ret < maxlen);
+       ret = sresize(ret, p - ret, char);
+
+       return ret;
+    } else if (action == MOVE_ORIGIN || action == MOVE_SOURCE ||
+               action == MOVE_ORIGIN_AND_SOURCE || action == MOVE_CURSOR) {
+        assert(dir != 0);
+        if (action == MOVE_ORIGIN || action == MOVE_ORIGIN_AND_SOURCE) {
+            if (state->wrapping) {
+                 OFFSET(ui->org_x, ui->org_y, ui->org_x, ui->org_y, dir, state);
+            } else return nullret; /* disallowed for non-wrapping grids */
+        }
+        if (action == MOVE_SOURCE || action == MOVE_ORIGIN_AND_SOURCE) {
+            OFFSET(ui->cx, ui->cy, ui->cx, ui->cy, dir, state);
+        }
+        if (action == MOVE_CURSOR) {
+            OFFSET(ui->cur_x, ui->cur_y, ui->cur_x, ui->cur_y, dir, state);
+            ui->cur_visible = TRUE;
+        }
+        return "";
+    } else {
+       return NULL;
+    }
+}
+
+static game_state *execute_move(const game_state *from, const char *move)
+{
+    game_state *ret;
+    int tx = -1, ty = -1, n, noanim, orig;
+
+    ret = dup_game(from);
+
+    if (move[0] == 'J' || move[0] == 'S') {
+       if (move[0] == 'S')
+           ret->used_solve = TRUE;
+
+       move++;
+       if (*move == ';')
+           move++;
+       noanim = TRUE;
+    } else
+       noanim = FALSE;
+
+    ret->last_rotate_dir = 0;         /* suppress animation */
+    ret->last_rotate_x = ret->last_rotate_y = 0;
+
+    while (*move) {
+       if ((move[0] == 'A' || move[0] == 'C' ||
+            move[0] == 'F' || move[0] == 'L') &&
+           sscanf(move+1, "%d,%d%n", &tx, &ty, &n) >= 2 &&
+           tx >= 0 && tx < from->width && ty >= 0 && ty < from->height) {
+           orig = tile(ret, tx, ty);
+           if (move[0] == 'A') {
+               tile(ret, tx, ty) = A(orig);
+               if (!noanim)
+                   ret->last_rotate_dir = +1;
+           } else if (move[0] == 'F') {
+               tile(ret, tx, ty) = F(orig);
+               if (!noanim)
+                    ret->last_rotate_dir = +2; /* + for sake of argument */
+           } else if (move[0] == 'C') {
+               tile(ret, tx, ty) = C(orig);
+               if (!noanim)
+                   ret->last_rotate_dir = -1;
+           } else {
+               assert(move[0] == 'L');
+               tile(ret, tx, ty) ^= LOCKED;
+           }
+
+           move += 1 + n;
+           if (*move == ';') move++;
+       } else {
+           free_game(ret);
+           return NULL;
+       }
+    }
+    if (!noanim) {
+        if (tx == -1 || ty == -1) { free_game(ret); return NULL; }
+       ret->last_rotate_x = tx;
+       ret->last_rotate_y = ty;
+    }
+
+    /*
+     * Check whether the game has been completed.
+     * 
+     * For this purpose it doesn't matter where the source square
+     * is, because we can start from anywhere and correctly
+     * determine whether the game is completed.
+     */
+    {
+       unsigned char *active = compute_active(ret, 0, 0);
+       int x1, y1;
+       int complete = TRUE;
+
+       for (x1 = 0; x1 < ret->width; x1++)
+           for (y1 = 0; y1 < ret->height; y1++)
+               if ((tile(ret, x1, y1) & 0xF) && !index(ret, active, x1, y1)) {
+                   complete = FALSE;
+                   goto break_label;  /* break out of two loops at once */
+               }
+       break_label:
+
+       sfree(active);
+
+       if (complete)
+           ret->completed = TRUE;
+    }
+
+    return ret;
+}
+
+
+/* ----------------------------------------------------------------------
+ * Routines for drawing the game position on the screen.
+ */
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    game_drawstate *ds = snew(game_drawstate);
+    int i;
+
+    ds->started = FALSE;
+    ds->width = state->width;
+    ds->height = state->height;
+    ds->org_x = ds->org_y = -1;
+    ds->visible = snewn(state->width * state->height, int);
+    ds->tilesize = 0;                  /* undecided yet */
+    for (i = 0; i < state->width * state->height; i++)
+        ds->visible[i] = -1;
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds->visible);
+    sfree(ds);
+}
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    *x = WINDOW_OFFSET * 2 + tilesize * params->width + TILE_BORDER;
+    *y = WINDOW_OFFSET * 2 + tilesize * params->height + TILE_BORDER;
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret;
+
+    ret = snewn(NCOLOURS * 3, float);
+    *ncolours = NCOLOURS;
+
+    /*
+     * Basic background colour is whatever the front end thinks is
+     * a sensible default.
+     */
+    frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
+
+    /*
+     * Wires are black.
+     */
+    ret[COL_WIRE * 3 + 0] = 0.0F;
+    ret[COL_WIRE * 3 + 1] = 0.0F;
+    ret[COL_WIRE * 3 + 2] = 0.0F;
+
+    /*
+     * Powered wires and powered endpoints are cyan.
+     */
+    ret[COL_POWERED * 3 + 0] = 0.0F;
+    ret[COL_POWERED * 3 + 1] = 1.0F;
+    ret[COL_POWERED * 3 + 2] = 1.0F;
+
+    /*
+     * Barriers are red.
+     */
+    ret[COL_BARRIER * 3 + 0] = 1.0F;
+    ret[COL_BARRIER * 3 + 1] = 0.0F;
+    ret[COL_BARRIER * 3 + 2] = 0.0F;
+
+    /*
+     * Highlighted loops are red as well.
+     */
+    ret[COL_LOOP * 3 + 0] = 1.0F;
+    ret[COL_LOOP * 3 + 1] = 0.0F;
+    ret[COL_LOOP * 3 + 2] = 0.0F;
+
+    /*
+     * Unpowered endpoints are blue.
+     */
+    ret[COL_ENDPOINT * 3 + 0] = 0.0F;
+    ret[COL_ENDPOINT * 3 + 1] = 0.0F;
+    ret[COL_ENDPOINT * 3 + 2] = 1.0F;
+
+    /*
+     * Tile borders are a darker grey than the background.
+     */
+    ret[COL_BORDER * 3 + 0] = 0.5F * ret[COL_BACKGROUND * 3 + 0];
+    ret[COL_BORDER * 3 + 1] = 0.5F * ret[COL_BACKGROUND * 3 + 1];
+    ret[COL_BORDER * 3 + 2] = 0.5F * ret[COL_BACKGROUND * 3 + 2];
+
+    /*
+     * Locked tiles are a grey in between those two.
+     */
+    ret[COL_LOCKED * 3 + 0] = 0.75F * ret[COL_BACKGROUND * 3 + 0];
+    ret[COL_LOCKED * 3 + 1] = 0.75F * ret[COL_BACKGROUND * 3 + 1];
+    ret[COL_LOCKED * 3 + 2] = 0.75F * ret[COL_BACKGROUND * 3 + 2];
+
+    return ret;
+}
+
+static void draw_filled_line(drawing *dr, int x1, int y1, int x2, int y2,
+                            int colour)
+{
+    draw_line(dr, x1-1, y1, x2-1, y2, COL_WIRE);
+    draw_line(dr, x1+1, y1, x2+1, y2, COL_WIRE);
+    draw_line(dr, x1, y1-1, x2, y2-1, COL_WIRE);
+    draw_line(dr, x1, y1+1, x2, y2+1, COL_WIRE);
+    draw_line(dr, x1, y1, x2, y2, colour);
+}
+
+static void draw_rect_coords(drawing *dr, int x1, int y1, int x2, int y2,
+                             int colour)
+{
+    int mx = (x1 < x2 ? x1 : x2);
+    int my = (y1 < y2 ? y1 : y2);
+    int dx = (x2 + x1 - 2*mx + 1);
+    int dy = (y2 + y1 - 2*my + 1);
+
+    draw_rect(dr, mx, my, dx, dy, colour);
+}
+
+/*
+ * draw_barrier_corner() and draw_barrier() are passed physical coords
+ */
+static void draw_barrier_corner(drawing *dr, game_drawstate *ds,
+                                int x, int y, int dx, int dy, int phase)
+{
+    int bx = WINDOW_OFFSET + TILE_SIZE * x;
+    int by = WINDOW_OFFSET + TILE_SIZE * y;
+    int x1, y1;
+
+    x1 = (dx > 0 ? TILE_SIZE+TILE_BORDER-1 : 0);
+    y1 = (dy > 0 ? TILE_SIZE+TILE_BORDER-1 : 0);
+
+    if (phase == 0) {
+        draw_rect_coords(dr, bx+x1+dx, by+y1,
+                         bx+x1-TILE_BORDER*dx, by+y1-(TILE_BORDER-1)*dy,
+                         COL_WIRE);
+        draw_rect_coords(dr, bx+x1, by+y1+dy,
+                         bx+x1-(TILE_BORDER-1)*dx, by+y1-TILE_BORDER*dy,
+                         COL_WIRE);
+    } else {
+        draw_rect_coords(dr, bx+x1, by+y1,
+                         bx+x1-(TILE_BORDER-1)*dx, by+y1-(TILE_BORDER-1)*dy,
+                         COL_BARRIER);
+    }
+}
+
+static void draw_barrier(drawing *dr, game_drawstate *ds,
+                         int x, int y, int dir, int phase)
+{
+    int bx = WINDOW_OFFSET + TILE_SIZE * x;
+    int by = WINDOW_OFFSET + TILE_SIZE * y;
+    int x1, y1, w, h;
+
+    x1 = (X(dir) > 0 ? TILE_SIZE : X(dir) == 0 ? TILE_BORDER : 0);
+    y1 = (Y(dir) > 0 ? TILE_SIZE : Y(dir) == 0 ? TILE_BORDER : 0);
+    w = (X(dir) ? TILE_BORDER : TILE_SIZE - TILE_BORDER);
+    h = (Y(dir) ? TILE_BORDER : TILE_SIZE - TILE_BORDER);
+
+    if (phase == 0) {
+        draw_rect(dr, bx+x1-X(dir), by+y1-Y(dir), w, h, COL_WIRE);
+    } else {
+        draw_rect(dr, bx+x1, by+y1, w, h, COL_BARRIER);
+    }
+}
+
+/*
+ * draw_tile() is passed physical coordinates
+ */
+static void draw_tile(drawing *dr, const game_state *state, game_drawstate *ds,
+                      int x, int y, int tile, int src, float angle, int cursor)
+{
+    int bx = WINDOW_OFFSET + TILE_SIZE * x;
+    int by = WINDOW_OFFSET + TILE_SIZE * y;
+    float matrix[4];
+    float cx, cy, ex, ey, tx, ty;
+    int dir, col, phase;
+
+    /*
+     * When we draw a single tile, we must draw everything up to
+     * and including the borders around the tile. This means that
+     * if the neighbouring tiles have connections to those borders,
+     * we must draw those connections on the borders themselves.
+     */
+
+    clip(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER);
+
+    /*
+     * So. First blank the tile out completely: draw a big
+     * rectangle in border colour, and a smaller rectangle in
+     * background colour to fill it in.
+     */
+    draw_rect(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER,
+              COL_BORDER);
+    draw_rect(dr, bx+TILE_BORDER, by+TILE_BORDER,
+              TILE_SIZE-TILE_BORDER, TILE_SIZE-TILE_BORDER,
+              tile & LOCKED ? COL_LOCKED : COL_BACKGROUND);
+
+    /*
+     * Draw an inset outline rectangle as a cursor, in whichever of
+     * COL_LOCKED and COL_BACKGROUND we aren't currently drawing
+     * in.
+     */
+    if (cursor) {
+       draw_line(dr, bx+TILE_SIZE/8, by+TILE_SIZE/8,
+                 bx+TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8,
+                 tile & LOCKED ? COL_BACKGROUND : COL_LOCKED);
+       draw_line(dr, bx+TILE_SIZE/8, by+TILE_SIZE/8,
+                 bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE/8,
+                 tile & LOCKED ? COL_BACKGROUND : COL_LOCKED);
+       draw_line(dr, bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE/8,
+                 bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8,
+                 tile & LOCKED ? COL_BACKGROUND : COL_LOCKED);
+       draw_line(dr, bx+TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8,
+                 bx+TILE_SIZE-TILE_SIZE/8, by+TILE_SIZE-TILE_SIZE/8,
+                 tile & LOCKED ? COL_BACKGROUND : COL_LOCKED);
+    }
+
+    /*
+     * Set up the rotation matrix.
+     */
+    matrix[0] = (float)cos(angle * PI / 180.0);
+    matrix[1] = (float)-sin(angle * PI / 180.0);
+    matrix[2] = (float)sin(angle * PI / 180.0);
+    matrix[3] = (float)cos(angle * PI / 180.0);
+
+    /*
+     * Draw the wires.
+     */
+    cx = cy = TILE_BORDER + (TILE_SIZE-TILE_BORDER) / 2.0F - 0.5F;
+    col = (tile & ACTIVE ? COL_POWERED : COL_WIRE);
+    for (dir = 1; dir < 0x10; dir <<= 1) {
+        if (tile & dir) {
+            ex = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * X(dir);
+            ey = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * Y(dir);
+            MATMUL(tx, ty, matrix, ex, ey);
+            draw_filled_line(dr, bx+(int)cx, by+(int)cy,
+                            bx+(int)(cx+tx), by+(int)(cy+ty),
+                            COL_WIRE);
+        }
+    }
+    for (dir = 1; dir < 0x10; dir <<= 1) {
+        if (tile & dir) {
+            ex = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * X(dir);
+            ey = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * Y(dir);
+            MATMUL(tx, ty, matrix, ex, ey);
+            draw_line(dr, bx+(int)cx, by+(int)cy,
+                     bx+(int)(cx+tx), by+(int)(cy+ty),
+                      (tile & LOOP(dir)) ? COL_LOOP : col);
+        }
+    }
+    /* If we've drawn any loop-highlighted arms, make sure the centre
+     * point is loop-coloured rather than a later arm overwriting it. */
+    if (tile & (RLOOP | ULOOP | LLOOP | DLOOP))
+        draw_rect(dr, bx+(int)cx, by+(int)cy, 1, 1, COL_LOOP);
+
+    /*
+     * Draw the box in the middle. We do this in blue if the tile
+     * is an unpowered endpoint, in cyan if the tile is a powered
+     * endpoint, in black if the tile is the centrepiece, and
+     * otherwise not at all.
+     */
+    col = -1;
+    if (src)
+        col = COL_WIRE;
+    else if (COUNT(tile) == 1) {
+        col = (tile & ACTIVE ? COL_POWERED : COL_ENDPOINT);
+    }
+    if (col >= 0) {
+        int i, points[8];
+
+        points[0] = +1; points[1] = +1;
+        points[2] = +1; points[3] = -1;
+        points[4] = -1; points[5] = -1;
+        points[6] = -1; points[7] = +1;
+
+        for (i = 0; i < 8; i += 2) {
+            ex = (TILE_SIZE * 0.24F) * points[i];
+            ey = (TILE_SIZE * 0.24F) * points[i+1];
+            MATMUL(tx, ty, matrix, ex, ey);
+            points[i] = bx+(int)(cx+tx);
+            points[i+1] = by+(int)(cy+ty);
+        }
+
+        draw_polygon(dr, points, 4, col, COL_WIRE);
+    }
+
+    /*
+     * Draw the points on the border if other tiles are connected
+     * to us.
+     */
+    for (dir = 1; dir < 0x10; dir <<= 1) {
+        int dx, dy, px, py, lx, ly, vx, vy, ox, oy;
+
+        dx = X(dir);
+        dy = Y(dir);
+
+        ox = x + dx;
+        oy = y + dy;
+
+        if (ox < 0 || ox >= state->width || oy < 0 || oy >= state->height)
+            continue;
+
+        if (!(tile(state, GX(ox), GY(oy)) & F(dir)))
+            continue;
+
+        px = bx + (int)(dx>0 ? TILE_SIZE + TILE_BORDER - 1 : dx<0 ? 0 : cx);
+        py = by + (int)(dy>0 ? TILE_SIZE + TILE_BORDER - 1 : dy<0 ? 0 : cy);
+        lx = dx * (TILE_BORDER-1);
+        ly = dy * (TILE_BORDER-1);
+        vx = (dy ? 1 : 0);
+        vy = (dx ? 1 : 0);
+
+        if (angle == 0.0 && (tile & dir)) {
+            /*
+             * If we are fully connected to the other tile, we must
+             * draw right across the tile border. (We can use our
+             * own ACTIVE state to determine what colour to do this
+             * in: if we are fully connected to the other tile then
+             * the two ACTIVE states will be the same.)
+             */
+            draw_rect_coords(dr, px-vx, py-vy, px+lx+vx, py+ly+vy, COL_WIRE);
+            draw_rect_coords(dr, px, py, px+lx, py+ly,
+                             ((tile & LOOP(dir)) ? COL_LOOP :
+                              (tile & ACTIVE) ? COL_POWERED :
+                              COL_WIRE));
+        } else {
+            /*
+             * The other tile extends into our border, but isn't
+             * actually connected to us. Just draw a single black
+             * dot.
+             */
+            draw_rect_coords(dr, px, py, px, py, COL_WIRE);
+        }
+    }
+
+    /*
+     * Draw barrier corners, and then barriers.
+     */
+    for (phase = 0; phase < 2; phase++) {
+        for (dir = 1; dir < 0x10; dir <<= 1) {
+            int x1, y1, corner = FALSE;
+            /*
+             * If at least one barrier terminates at the corner
+             * between dir and A(dir), draw a barrier corner.
+             */
+            if (barrier(state, GX(x), GY(y)) & (dir | A(dir))) {
+                corner = TRUE;
+            } else {
+                /*
+                 * Only count barriers terminating at this corner
+                 * if they're physically next to the corner. (That
+                 * is, if they've wrapped round from the far side
+                 * of the screen, they don't count.)
+                 */
+                x1 = x + X(dir);
+                y1 = y + Y(dir);
+                if (x1 >= 0 && x1 < state->width &&
+                    y1 >= 0 && y1 < state->height &&
+                    (barrier(state, GX(x1), GY(y1)) & A(dir))) {
+                    corner = TRUE;
+                } else {
+                    x1 = x + X(A(dir));
+                    y1 = y + Y(A(dir));
+                    if (x1 >= 0 && x1 < state->width &&
+                        y1 >= 0 && y1 < state->height &&
+                        (barrier(state, GX(x1), GY(y1)) & dir))
+                        corner = TRUE;
+                }
+            }
+
+            if (corner) {
+                /*
+                 * At least one barrier terminates here. Draw a
+                 * corner.
+                 */
+                draw_barrier_corner(dr, ds, x, y,
+                                    X(dir)+X(A(dir)), Y(dir)+Y(A(dir)),
+                                    phase);
+            }
+        }
+
+        for (dir = 1; dir < 0x10; dir <<= 1)
+            if (barrier(state, GX(x), GY(y)) & dir)
+                draw_barrier(dr, ds, x, y, dir, phase);
+    }
+
+    unclip(dr);
+
+    draw_update(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER);
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float t, float ft)
+{
+    int x, y, tx, ty, frame, last_rotate_dir, moved_origin = FALSE;
+    unsigned char *active;
+    int *loops;
+    float angle = 0.0;
+
+    /*
+     * Clear the screen, and draw the exterior barrier lines, if
+     * this is our first call or if the origin has changed.
+     */
+    if (!ds->started || ui->org_x != ds->org_x || ui->org_y != ds->org_y) {
+        int phase;
+
+        ds->started = TRUE;
+
+        draw_rect(dr, 0, 0, 
+                  WINDOW_OFFSET * 2 + TILE_SIZE * state->width + TILE_BORDER,
+                  WINDOW_OFFSET * 2 + TILE_SIZE * state->height + TILE_BORDER,
+                  COL_BACKGROUND);
+
+        ds->org_x = ui->org_x;
+        ds->org_y = ui->org_y;
+        moved_origin = TRUE;
+
+        draw_update(dr, 0, 0, 
+                    WINDOW_OFFSET*2 + TILE_SIZE*state->width + TILE_BORDER,
+                    WINDOW_OFFSET*2 + TILE_SIZE*state->height + TILE_BORDER);
+
+        for (phase = 0; phase < 2; phase++) {
+
+            for (x = 0; x < ds->width; x++) {
+                if (x+1 < ds->width) {
+                    if (barrier(state, GX(x), GY(0)) & R)
+                        draw_barrier_corner(dr, ds, x, -1, +1, +1, phase);
+                    if (barrier(state, GX(x), GY(ds->height-1)) & R)
+                        draw_barrier_corner(dr, ds, x, ds->height, +1, -1, phase);
+                }
+                if (barrier(state, GX(x), GY(0)) & U) {
+                    draw_barrier_corner(dr, ds, x, -1, -1, +1, phase);
+                    draw_barrier_corner(dr, ds, x, -1, +1, +1, phase);
+                    draw_barrier(dr, ds, x, -1, D, phase);
+                }
+                if (barrier(state, GX(x), GY(ds->height-1)) & D) {
+                    draw_barrier_corner(dr, ds, x, ds->height, -1, -1, phase);
+                    draw_barrier_corner(dr, ds, x, ds->height, +1, -1, phase);
+                    draw_barrier(dr, ds, x, ds->height, U, phase);
+                }
+            }
+
+            for (y = 0; y < ds->height; y++) {
+                if (y+1 < ds->height) {
+                    if (barrier(state, GX(0), GY(y)) & D)
+                        draw_barrier_corner(dr, ds, -1, y, +1, +1, phase);
+                    if (barrier(state, GX(ds->width-1), GY(y)) & D)
+                        draw_barrier_corner(dr, ds, ds->width, y, -1, +1, phase);
+                }
+                if (barrier(state, GX(0), GY(y)) & L) {
+                    draw_barrier_corner(dr, ds, -1, y, +1, -1, phase);
+                    draw_barrier_corner(dr, ds, -1, y, +1, +1, phase);
+                    draw_barrier(dr, ds, -1, y, R, phase);
+                }
+                if (barrier(state, GX(ds->width-1), GY(y)) & R) {
+                    draw_barrier_corner(dr, ds, ds->width, y, -1, -1, phase);
+                    draw_barrier_corner(dr, ds, ds->width, y, -1, +1, phase);
+                    draw_barrier(dr, ds, ds->width, y, L, phase);
+                }
+            }
+        }
+    }
+
+    tx = ty = -1;
+    last_rotate_dir = dir==-1 ? oldstate->last_rotate_dir :
+                                state->last_rotate_dir;
+    if (oldstate && (t < ROTATE_TIME) && last_rotate_dir) {
+        /*
+         * We're animating a single tile rotation. Find the turning
+         * tile.
+         */
+        tx = (dir==-1 ? oldstate->last_rotate_x : state->last_rotate_x);
+        ty = (dir==-1 ? oldstate->last_rotate_y : state->last_rotate_y);
+        angle = last_rotate_dir * dir * 90.0F * (t / ROTATE_TIME);
+        state = oldstate;
+    }
+
+    frame = -1;
+    if (ft > 0) {
+        /*
+         * We're animating a completion flash. Find which frame
+         * we're at.
+         */
+        frame = (int)(ft / FLASH_FRAME);
+    }
+
+    /*
+     * Draw any tile which differs from the way it was last drawn.
+     */
+    active = compute_active(state, ui->cx, ui->cy);
+    loops = compute_loops(state);
+
+    for (x = 0; x < ds->width; x++)
+        for (y = 0; y < ds->height; y++) {
+            int c = tile(state, GX(x), GY(y)) |
+                index(state, active, GX(x), GY(y)) |
+                index(state, loops, GX(x), GY(y));
+            int is_src = GX(x) == ui->cx && GY(y) == ui->cy;
+            int is_anim = GX(x) == tx && GY(y) == ty;
+            int is_cursor = ui->cur_visible &&
+                            GX(x) == ui->cur_x && GY(y) == ui->cur_y;
+
+            /*
+             * In a completion flash, we adjust the LOCKED bit
+             * depending on our distance from the centre point and
+             * the frame number.
+             */
+            if (frame >= 0) {
+                int rcx = RX(ui->cx), rcy = RY(ui->cy);
+                int xdist, ydist, dist;
+                xdist = (x < rcx ? rcx - x : x - rcx);
+                ydist = (y < rcy ? rcy - y : y - rcy);
+                dist = (xdist > ydist ? xdist : ydist);
+
+                if (frame >= dist && frame < dist+4) {
+                    int lock = (frame - dist) & 1;
+                    lock = lock ? LOCKED : 0;
+                    c = (c &~ LOCKED) | lock;
+                }
+            }
+
+            if (moved_origin ||
+                index(state, ds->visible, x, y) != c ||
+                index(state, ds->visible, x, y) == -1 ||
+                is_src || is_anim || is_cursor) {
+                draw_tile(dr, state, ds, x, y, c,
+                          is_src, (is_anim ? angle : 0.0F), is_cursor);
+                if (is_src || is_anim || is_cursor)
+                    index(state, ds->visible, x, y) = -1;
+                else
+                    index(state, ds->visible, x, y) = c;
+            }
+        }
+
+    /*
+     * Update the status bar.
+     */
+    {
+       char statusbuf[256];
+       int i, n, n2, a;
+
+       n = state->width * state->height;
+       for (i = a = n2 = 0; i < n; i++) {
+           if (active[i])
+               a++;
+            if (state->tiles[i] & 0xF)
+                n2++;
+        }
+
+       sprintf(statusbuf, "%sActive: %d/%d",
+               (state->used_solve ? "Auto-solved. " :
+                state->completed ? "COMPLETED! " : ""), a, n2);
+
+       status_bar(dr, statusbuf);
+    }
+
+    sfree(active);
+    sfree(loops);
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    int last_rotate_dir;
+
+    /*
+     * Don't animate if last_rotate_dir is zero.
+     */
+    last_rotate_dir = dir==-1 ? oldstate->last_rotate_dir :
+                                newstate->last_rotate_dir;
+    if (last_rotate_dir)
+        return ROTATE_TIME;
+
+    return 0.0F;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    /*
+     * If the game has just been completed, we display a completion
+     * flash.
+     */
+    if (!oldstate->completed && newstate->completed &&
+       !oldstate->used_solve && !newstate->used_solve) {
+        int size = 0;
+        if (size < newstate->width)
+            size = newstate->width;
+        if (size < newstate->height)
+            size = newstate->height;
+        return FLASH_FRAME * (size+4);
+    }
+
+    return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    return state->completed ? +1 : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+    int pw, ph;
+
+    /*
+     * I'll use 8mm squares by default.
+     */
+    game_compute_size(params, 800, &pw, &ph);
+    *x = pw / 100.0F;
+    *y = ph / 100.0F;
+}
+
+static void draw_diagram(drawing *dr, game_drawstate *ds, int x, int y,
+                        int topleft, int v, int drawlines, int ink)
+{
+    int tx, ty, cx, cy, r, br, k, thick;
+
+    tx = WINDOW_OFFSET + TILE_SIZE * x;
+    ty = WINDOW_OFFSET + TILE_SIZE * y;
+
+    /*
+     * Find our centre point.
+     */
+    if (topleft) {
+       cx = tx + (v & L ? TILE_SIZE / 4 : TILE_SIZE / 6);
+       cy = ty + (v & U ? TILE_SIZE / 4 : TILE_SIZE / 6);
+       r = TILE_SIZE / 8;
+       br = TILE_SIZE / 32;
+    } else {
+       cx = tx + TILE_SIZE / 2;
+       cy = ty + TILE_SIZE / 2;
+       r = TILE_SIZE / 2;
+       br = TILE_SIZE / 8;
+    }
+    thick = r / 20;
+
+    /*
+     * Draw the square block if we have an endpoint.
+     */
+    if (v == 1 || v == 2 || v == 4 || v == 8)
+       draw_rect(dr, cx - br, cy - br, br*2, br*2, ink);
+
+    /*
+     * Draw each radial line.
+     */
+    if (drawlines) {
+       for (k = 1; k < 16; k *= 2)
+           if (v & k) {
+               int x1 = min(cx, cx + (r-thick) * X(k));
+               int x2 = max(cx, cx + (r-thick) * X(k));
+               int y1 = min(cy, cy + (r-thick) * Y(k));
+               int y2 = max(cy, cy + (r-thick) * Y(k));
+               draw_rect(dr, x1 - thick, y1 - thick,
+                         (x2 - x1) + 2*thick, (y2 - y1) + 2*thick, ink);
+           }
+    }
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+    int w = state->width, h = state->height;
+    int ink = print_mono_colour(dr, 0);
+    int x, y;
+
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    game_drawstate ads, *ds = &ads;
+    game_set_size(dr, ds, NULL, tilesize);
+
+    /*
+     * Border.
+     */
+    print_line_width(dr, TILE_SIZE / (state->wrapping ? 128 : 12));
+    draw_rect_outline(dr, WINDOW_OFFSET, WINDOW_OFFSET,
+                     TILE_SIZE * w, TILE_SIZE * h, ink);
+
+    /*
+     * Grid.
+     */
+    print_line_width(dr, TILE_SIZE / 128);
+    for (x = 1; x < w; x++)
+       draw_line(dr, WINDOW_OFFSET + TILE_SIZE * x, WINDOW_OFFSET,
+                 WINDOW_OFFSET + TILE_SIZE * x, WINDOW_OFFSET + TILE_SIZE * h,
+                 ink);
+    for (y = 1; y < h; y++)
+       draw_line(dr, WINDOW_OFFSET, WINDOW_OFFSET + TILE_SIZE * y,
+                 WINDOW_OFFSET + TILE_SIZE * w, WINDOW_OFFSET + TILE_SIZE * y,
+                 ink);
+
+    /*
+     * Barriers.
+     */
+    for (y = 0; y <= h; y++)
+       for (x = 0; x <= w; x++) {
+           int b = barrier(state, x % w, y % h);
+           if (x < w && (b & U))
+               draw_rect(dr, WINDOW_OFFSET + TILE_SIZE * x - TILE_SIZE/24,
+                         WINDOW_OFFSET + TILE_SIZE * y - TILE_SIZE/24,
+                         TILE_SIZE + TILE_SIZE/24 * 2, TILE_SIZE/24 * 2, ink);
+           if (y < h && (b & L))
+               draw_rect(dr, WINDOW_OFFSET + TILE_SIZE * x - TILE_SIZE/24,
+                         WINDOW_OFFSET + TILE_SIZE * y - TILE_SIZE/24,
+                         TILE_SIZE/24 * 2, TILE_SIZE + TILE_SIZE/24 * 2, ink);
+       }
+
+    /*
+     * Grid contents.
+     */
+    for (y = 0; y < h; y++)
+       for (x = 0; x < w; x++) {
+           int vx, v = tile(state, x, y);
+           int locked = v & LOCKED;
+
+           v &= 0xF;
+
+           /*
+            * Rotate into a standard orientation for the top left
+            * corner diagram.
+            */
+           vx = v;
+           while (vx != 0 && vx != 15 && vx != 1 && vx != 9 && vx != 13 &&
+                  vx != 5)
+               vx = A(vx);
+
+           /*
+            * Draw the top left corner diagram.
+            */
+           draw_diagram(dr, ds, x, y, TRUE, vx, TRUE, ink);
+
+           /*
+            * Draw the real solution diagram, if we're doing so.
+            */
+           draw_diagram(dr, ds, x, y, FALSE, v, locked, ink);
+       }
+}
+
+#ifdef COMBINED
+#define thegame net
+#endif
+
+const struct game thegame = {
+    "Net", "games.net", "net",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    FALSE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    TRUE, FALSE, game_print_size, game_print,
+    TRUE,                             /* wants_statusbar */
+    FALSE, game_timing_state,
+    0,                                /* flags */
+};
diff --git a/netslide.R b/netslide.R
new file mode 100644 (file)
index 0000000..ecfe7c3
--- /dev/null
@@ -0,0 +1,21 @@
+# -*- makefile -*-
+
+NETSLIDE_EXTRA = tree234
+
+netslide : [X] GTK COMMON netslide NETSLIDE_EXTRA netslide-icon|no-icon
+
+netslide : [G] WINDOWS COMMON netslide NETSLIDE_EXTRA netslide.res|noicon.res
+
+ALL += netslide[COMBINED] NETSLIDE_EXTRA
+
+!begin am gtk
+GAMES += netslide
+!end
+
+!begin >list.c
+    A(netslide) \
+!end
+
+!begin >gamedesc.txt
+netslide:netslide.exe:Netslide:Toroidal sliding network puzzle:Slide a row at a time to reassemble the network.
+!end
diff --git a/netslide.c b/netslide.c
new file mode 100644 (file)
index 0000000..89b0edf
--- /dev/null
@@ -0,0 +1,1893 @@
+/*
+ * netslide.c: cross between Net and Sixteen, courtesy of Richard
+ * Boulton.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+#include "tree234.h"
+
+#define MATMUL(xr,yr,m,x,y) do { \
+    float rx, ry, xx = (x), yy = (y), *mat = (m); \
+    rx = mat[0] * xx + mat[2] * yy; \
+    ry = mat[1] * xx + mat[3] * yy; \
+    (xr) = rx; (yr) = ry; \
+} while (0)
+
+/* Direction and other bitfields */
+#define R 0x01
+#define U 0x02
+#define L 0x04
+#define D 0x08
+#define FLASHING 0x10
+#define ACTIVE 0x20
+/* Corner flags go in the barriers array */
+#define RU 0x10
+#define UL 0x20
+#define LD 0x40
+#define DR 0x80
+
+/* Get tile at given coordinate */
+#define T(state, x, y) ( (y) * (state)->width + (x) )
+
+/* Rotations: Anticlockwise, Clockwise, Flip, general rotate */
+#define A(x) ( (((x) & 0x07) << 1) | (((x) & 0x08) >> 3) )
+#define C(x) ( (((x) & 0x0E) >> 1) | (((x) & 0x01) << 3) )
+#define F(x) ( (((x) & 0x0C) >> 2) | (((x) & 0x03) << 2) )
+#define ROT(x, n) ( ((n)&3) == 0 ? (x) : \
+                   ((n)&3) == 1 ? A(x) : \
+                   ((n)&3) == 2 ? F(x) : C(x) )
+
+/* X and Y displacements */
+#define X(x) ( (x) == R ? +1 : (x) == L ? -1 : 0 )
+#define Y(x) ( (x) == D ? +1 : (x) == U ? -1 : 0 )
+
+/* Bit count */
+#define COUNT(x) ( (((x) & 0x08) >> 3) + (((x) & 0x04) >> 2) + \
+                  (((x) & 0x02) >> 1) + ((x) & 0x01) )
+
+#define PREFERRED_TILE_SIZE 48
+#define TILE_SIZE (ds->tilesize)
+#define BORDER TILE_SIZE
+#define TILE_BORDER 1
+#define WINDOW_OFFSET 0
+
+#define ANIM_TIME 0.13F
+#define FLASH_FRAME 0.07F
+
+enum {
+    COL_BACKGROUND,
+    COL_FLASHING,
+    COL_BORDER,
+    COL_WIRE,
+    COL_ENDPOINT,
+    COL_POWERED,
+    COL_BARRIER,
+    COL_LOWLIGHT,
+    COL_TEXT,
+    NCOLOURS
+};
+
+struct game_params {
+    int width;
+    int height;
+    int wrapping;
+    float barrier_probability;
+    int movetarget;
+};
+
+struct game_state {
+    int width, height, cx, cy, wrapping, completed;
+    int used_solve;
+    int move_count, movetarget;
+
+    /* position (row or col number, starting at 0) of last move. */
+    int last_move_row, last_move_col;
+
+    /* direction of last move: +1 or -1 */
+    int last_move_dir;
+
+    unsigned char *tiles;
+    unsigned char *barriers;
+};
+
+#define OFFSET(x2,y2,x1,y1,dir,state) \
+    ( (x2) = ((x1) + (state)->width + X((dir))) % (state)->width, \
+      (y2) = ((y1) + (state)->height + Y((dir))) % (state)->height)
+
+#define index(state, a, x, y) ( a[(y) * (state)->width + (x)] )
+#define tile(state, x, y)     index(state, (state)->tiles, x, y)
+#define barrier(state, x, y)  index(state, (state)->barriers, x, y)
+
+struct xyd {
+    int x, y, direction;
+};
+
+static int xyd_cmp(void *av, void *bv) {
+    struct xyd *a = (struct xyd *)av;
+    struct xyd *b = (struct xyd *)bv;
+    if (a->x < b->x)
+       return -1;
+    if (a->x > b->x)
+       return +1;
+    if (a->y < b->y)
+       return -1;
+    if (a->y > b->y)
+       return +1;
+    if (a->direction < b->direction)
+       return -1;
+    if (a->direction > b->direction)
+       return +1;
+    return 0;
+}
+
+static struct xyd *new_xyd(int x, int y, int direction)
+{
+    struct xyd *xyd = snew(struct xyd);
+    xyd->x = x;
+    xyd->y = y;
+    xyd->direction = direction;
+    return xyd;
+}
+
+static void slide_col(game_state *state, int dir, int col);
+static void slide_col_int(int w, int h, unsigned char *tiles, int dir, int col);
+static void slide_row(game_state *state, int dir, int row);
+static void slide_row_int(int w, int h, unsigned char *tiles, int dir, int row);
+
+/* ----------------------------------------------------------------------
+ * Manage game parameters.
+ */
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+
+    ret->width = 3;
+    ret->height = 3;
+    ret->wrapping = FALSE;
+    ret->barrier_probability = 1.0;
+    ret->movetarget = 0;
+
+    return ret;
+}
+
+static const struct { int x, y, wrap, bprob; const char* desc; }
+netslide_presets[] = {
+    {3, 3, FALSE, 1, " easy"},
+    {3, 3, FALSE, 0, " medium"},
+    {3, 3, TRUE,  0, " hard"},
+    {4, 4, FALSE, 1, " easy"},
+    {4, 4, FALSE, 0, " medium"},
+    {4, 4, TRUE,  0, " hard"},
+    {5, 5, FALSE, 1, " easy"},
+    {5, 5, FALSE, 0, " medium"},
+    {5, 5, TRUE,  0, " hard"},
+};
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params *ret;
+    char str[80];
+
+    if (i < 0 || i >= lenof(netslide_presets))
+        return FALSE;
+
+    ret = snew(game_params);
+    ret->width = netslide_presets[i].x;
+    ret->height = netslide_presets[i].y;
+    ret->wrapping = netslide_presets[i].wrap;
+    ret->barrier_probability = (float)netslide_presets[i].bprob;
+    ret->movetarget = 0;
+
+    sprintf(str, "%dx%d%s", ret->width, ret->height, netslide_presets[i].desc);
+
+    *name = dupstr(str);
+    *params = ret;
+    return TRUE;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+static void decode_params(game_params *ret, char const *string)
+{
+    char const *p = string;
+
+    ret->wrapping = FALSE;
+    ret->barrier_probability = 0.0;
+    ret->movetarget = 0;
+
+    ret->width = atoi(p);
+    while (*p && isdigit((unsigned char)*p)) p++;
+    if (*p == 'x') {
+        p++;
+        ret->height = atoi(p);
+        while (*p && isdigit((unsigned char)*p)) p++;
+        if ( (ret->wrapping = (*p == 'w')) != 0 )
+            p++;
+        if (*p == 'b') {
+            ret->barrier_probability = (float)atof(++p);
+            while (*p && (isdigit((unsigned char)*p) || *p == '.')) p++;
+        }
+        if (*p == 'm') {
+            ret->movetarget = atoi(++p);
+        }
+    } else {
+        ret->height = ret->width;
+    }
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char ret[400];
+    int len;
+
+    len = sprintf(ret, "%dx%d", params->width, params->height);
+    if (params->wrapping)
+        ret[len++] = 'w';
+    if (full && params->barrier_probability)
+        len += sprintf(ret+len, "b%g", params->barrier_probability);
+    /* Shuffle limit is part of the limited parameters, because we have to
+     * provide the target move count. */
+    if (params->movetarget)
+        len += sprintf(ret+len, "m%d", params->movetarget);
+    assert(len < lenof(ret));
+    ret[len] = '\0';
+
+    return dupstr(ret);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+    char buf[80];
+
+    ret = snewn(6, config_item);
+
+    ret[0].name = "Width";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->width);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Height";
+    ret[1].type = C_STRING;
+    sprintf(buf, "%d", params->height);
+    ret[1].sval = dupstr(buf);
+    ret[1].ival = 0;
+
+    ret[2].name = "Walls wrap around";
+    ret[2].type = C_BOOLEAN;
+    ret[2].sval = NULL;
+    ret[2].ival = params->wrapping;
+
+    ret[3].name = "Barrier probability";
+    ret[3].type = C_STRING;
+    sprintf(buf, "%g", params->barrier_probability);
+    ret[3].sval = dupstr(buf);
+    ret[3].ival = 0;
+
+    ret[4].name = "Number of shuffling moves";
+    ret[4].type = C_STRING;
+    sprintf(buf, "%d", params->movetarget);
+    ret[4].sval = dupstr(buf);
+    ret[4].ival = 0;
+
+    ret[5].name = NULL;
+    ret[5].type = C_END;
+    ret[5].sval = NULL;
+    ret[5].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->width = atoi(cfg[0].sval);
+    ret->height = atoi(cfg[1].sval);
+    ret->wrapping = cfg[2].ival;
+    ret->barrier_probability = (float)atof(cfg[3].sval);
+    ret->movetarget = atoi(cfg[4].sval);
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    if (params->width <= 1 || params->height <= 1)
+       return "Width and height must both be greater than one";
+    if (params->barrier_probability < 0)
+       return "Barrier probability may not be negative";
+    if (params->barrier_probability > 1)
+       return "Barrier probability may not be greater than 1";
+    return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Randomly select a new game description.
+ */
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                          char **aux, int interactive)
+{
+    tree234 *possibilities, *barriertree;
+    int w, h, x, y, cx, cy, nbarriers;
+    unsigned char *tiles, *barriers;
+    char *desc, *p;
+
+    w = params->width;
+    h = params->height;
+
+    tiles = snewn(w * h, unsigned char);
+    memset(tiles, 0, w * h);
+    barriers = snewn(w * h, unsigned char);
+    memset(barriers, 0, w * h);
+
+    cx = w / 2;
+    cy = h / 2;
+
+    /*
+     * Construct the unshuffled grid.
+     * 
+     * To do this, we simply start at the centre point, repeatedly
+     * choose a random possibility out of the available ways to
+     * extend a used square into an unused one, and do it. After
+     * extending the third line out of a square, we remove the
+     * fourth from the possibilities list to avoid any full-cross
+     * squares (which would make the game too easy because they
+     * only have one orientation).
+     * 
+     * The slightly worrying thing is the avoidance of full-cross
+     * squares. Can this cause our unsophisticated construction
+     * algorithm to paint itself into a corner, by getting into a
+     * situation where there are some unreached squares and the
+     * only way to reach any of them is to extend a T-piece into a
+     * full cross?
+     * 
+     * Answer: no it can't, and here's a proof.
+     * 
+     * Any contiguous group of such unreachable squares must be
+     * surrounded on _all_ sides by T-pieces pointing away from the
+     * group. (If not, then there is a square which can be extended
+     * into one of the `unreachable' ones, and so it wasn't
+     * unreachable after all.) In particular, this implies that
+     * each contiguous group of unreachable squares must be
+     * rectangular in shape (any deviation from that yields a
+     * non-T-piece next to an `unreachable' square).
+     * 
+     * So we have a rectangle of unreachable squares, with T-pieces
+     * forming a solid border around the rectangle. The corners of
+     * that border must be connected (since every tile connects all
+     * the lines arriving in it), and therefore the border must
+     * form a closed loop around the rectangle.
+     * 
+     * But this can't have happened in the first place, since we
+     * _know_ we've avoided creating closed loops! Hence, no such
+     * situation can ever arise, and the naive grid construction
+     * algorithm will guaranteeably result in a complete grid
+     * containing no unreached squares, no full crosses _and_ no
+     * closed loops. []
+     */
+    possibilities = newtree234(xyd_cmp);
+
+    if (cx+1 < w)
+       add234(possibilities, new_xyd(cx, cy, R));
+    if (cy-1 >= 0)
+       add234(possibilities, new_xyd(cx, cy, U));
+    if (cx-1 >= 0)
+       add234(possibilities, new_xyd(cx, cy, L));
+    if (cy+1 < h)
+       add234(possibilities, new_xyd(cx, cy, D));
+
+    while (count234(possibilities) > 0) {
+       int i;
+       struct xyd *xyd;
+       int x1, y1, d1, x2, y2, d2, d;
+
+       /*
+        * Extract a randomly chosen possibility from the list.
+        */
+       i = random_upto(rs, count234(possibilities));
+       xyd = delpos234(possibilities, i);
+       x1 = xyd->x;
+       y1 = xyd->y;
+       d1 = xyd->direction;
+       sfree(xyd);
+
+       OFFSET(x2, y2, x1, y1, d1, params);
+       d2 = F(d1);
+#ifdef GENERATION_DIAGNOSTICS
+       printf("picked (%d,%d,%c) <-> (%d,%d,%c)\n",
+              x1, y1, "0RU3L567D9abcdef"[d1], x2, y2, "0RU3L567D9abcdef"[d2]);
+#endif
+
+       /*
+        * Make the connection. (We should be moving to an as yet
+        * unused tile.)
+        */
+       index(params, tiles, x1, y1) |= d1;
+       assert(index(params, tiles, x2, y2) == 0);
+       index(params, tiles, x2, y2) |= d2;
+
+       /*
+        * If we have created a T-piece, remove its last
+        * possibility.
+        */
+       if (COUNT(index(params, tiles, x1, y1)) == 3) {
+           struct xyd xyd1, *xydp;
+
+           xyd1.x = x1;
+           xyd1.y = y1;
+           xyd1.direction = 0x0F ^ index(params, tiles, x1, y1);
+
+           xydp = find234(possibilities, &xyd1, NULL);
+
+           if (xydp) {
+#ifdef GENERATION_DIAGNOSTICS
+               printf("T-piece; removing (%d,%d,%c)\n",
+                      xydp->x, xydp->y, "0RU3L567D9abcdef"[xydp->direction]);
+#endif
+               del234(possibilities, xydp);
+               sfree(xydp);
+           }
+       }
+
+       /*
+        * Remove all other possibilities that were pointing at the
+        * tile we've just moved into.
+        */
+       for (d = 1; d < 0x10; d <<= 1) {
+           int x3, y3, d3;
+           struct xyd xyd1, *xydp;
+
+           OFFSET(x3, y3, x2, y2, d, params);
+           d3 = F(d);
+
+           xyd1.x = x3;
+           xyd1.y = y3;
+           xyd1.direction = d3;
+
+           xydp = find234(possibilities, &xyd1, NULL);
+
+           if (xydp) {
+#ifdef GENERATION_DIAGNOSTICS
+               printf("Loop avoidance; removing (%d,%d,%c)\n",
+                      xydp->x, xydp->y, "0RU3L567D9abcdef"[xydp->direction]);
+#endif
+               del234(possibilities, xydp);
+               sfree(xydp);
+           }
+       }
+
+       /*
+        * Add new possibilities to the list for moving _out_ of
+        * the tile we have just moved into.
+        */
+       for (d = 1; d < 0x10; d <<= 1) {
+           int x3, y3;
+
+           if (d == d2)
+               continue;              /* we've got this one already */
+
+           if (!params->wrapping) {
+               if (d == U && y2 == 0)
+                   continue;
+               if (d == D && y2 == h-1)
+                   continue;
+               if (d == L && x2 == 0)
+                   continue;
+               if (d == R && x2 == w-1)
+                   continue;
+           }
+
+           OFFSET(x3, y3, x2, y2, d, params);
+
+           if (index(params, tiles, x3, y3))
+               continue;              /* this would create a loop */
+
+#ifdef GENERATION_DIAGNOSTICS
+           printf("New frontier; adding (%d,%d,%c)\n",
+                  x2, y2, "0RU3L567D9abcdef"[d]);
+#endif
+           add234(possibilities, new_xyd(x2, y2, d));
+       }
+    }
+    /* Having done that, we should have no possibilities remaining. */
+    assert(count234(possibilities) == 0);
+    freetree234(possibilities);
+
+    /*
+     * Now compute a list of the possible barrier locations.
+     */
+    barriertree = newtree234(xyd_cmp);
+    for (y = 0; y < h; y++) {
+       for (x = 0; x < w; x++) {
+
+           if (!(index(params, tiles, x, y) & R) &&
+                (params->wrapping || x < w-1))
+               add234(barriertree, new_xyd(x, y, R));
+           if (!(index(params, tiles, x, y) & D) &&
+                (params->wrapping || y < h-1))
+               add234(barriertree, new_xyd(x, y, D));
+       }
+    }
+
+    /*
+     * Save the unshuffled grid in aux.
+     */
+    {
+       char *solution;
+        int i;
+
+        /*
+         * String format is exactly the same as a solve move, so we
+         * can just dupstr this in solve_game().
+         */
+
+       solution = snewn(w * h + 2, char);
+        solution[0] = 'S';
+        for (i = 0; i < w * h; i++)
+            solution[i+1] = "0123456789abcdef"[tiles[i] & 0xF];
+        solution[w*h+1] = '\0';
+
+       *aux = solution;
+    }
+
+    /*
+     * Now shuffle the grid.
+     * FIXME - this simply does a set of random moves to shuffle the pieces,
+     * although we make a token effort to avoid boring cases by avoiding moves
+     * that directly undo the previous one, or that repeat so often as to
+     * turn into fewer moves.
+     *
+     * A better way would be to number all the pieces, generate a placement
+     * for all the numbers as for "sixteen", observing parity constraints if
+     * neccessary, and then place the pieces according to their numbering.
+     * BUT - I'm not sure if this will work, since we disallow movement of
+     * the middle row and column.
+     */
+    {
+        int i;
+        int cols = w - 1;
+        int rows = h - 1;
+        int moves = params->movetarget;
+        int prevdir = -1, prevrowcol = -1, nrepeats = 0;
+        if (!moves) moves = cols * rows * 2;
+        for (i = 0; i < moves; /* incremented conditionally */) {
+            /* Choose a direction: 0,1,2,3 = up, right, down, left. */
+            int dir = random_upto(rs, 4);
+            int rowcol;
+            if (dir % 2 == 0) {
+                int col = random_upto(rs, cols);
+                if (col >= cx) col += 1;    /* avoid centre */
+                if (col == prevrowcol) {
+                    if (dir == 2-prevdir)
+                        continue;   /* undoes last move */
+                    else if (dir == prevdir && (nrepeats+1)*2 > h)
+                        continue;   /* makes fewer moves */
+                }
+                slide_col_int(w, h, tiles, 1 - dir, col);
+                rowcol = col;
+            } else {
+                int row = random_upto(rs, rows);
+                if (row >= cy) row += 1;    /* avoid centre */
+                if (row == prevrowcol) {
+                    if (dir == 4-prevdir)
+                        continue;   /* undoes last move */
+                    else if (dir == prevdir && (nrepeats+1)*2 > w)
+                        continue;   /* makes fewer moves */
+                }
+                slide_row_int(w, h, tiles, 2 - dir, row);
+                rowcol = row;
+            }
+            if (dir == prevdir && rowcol == prevrowcol)
+                nrepeats++;
+            else
+                nrepeats = 1;
+            prevdir = dir;
+            prevrowcol = rowcol;
+            i++;    /* if we got here, the move was accepted */
+        }
+    }
+
+    /*
+     * And now choose barrier locations. (We carefully do this
+     * _after_ shuffling, so that changing the barrier rate in the
+     * params while keeping the random seed the same will give the
+     * same shuffled grid and _only_ change the barrier locations.
+     * Also the way we choose barrier locations, by repeatedly
+     * choosing one possibility from the list until we have enough,
+     * is designed to ensure that raising the barrier rate while
+     * keeping the seed the same will provide a superset of the
+     * previous barrier set - i.e. if you ask for 10 barriers, and
+     * then decide that's still too hard and ask for 20, you'll get
+     * the original 10 plus 10 more, rather than getting 20 new
+     * ones and the chance of remembering your first 10.)
+     */
+    nbarriers = (int)(params->barrier_probability * count234(barriertree));
+    assert(nbarriers >= 0 && nbarriers <= count234(barriertree));
+
+    while (nbarriers > 0) {
+       int i;
+       struct xyd *xyd;
+       int x1, y1, d1, x2, y2, d2;
+
+       /*
+        * Extract a randomly chosen barrier from the list.
+        */
+       i = random_upto(rs, count234(barriertree));
+       xyd = delpos234(barriertree, i);
+
+       assert(xyd != NULL);
+
+       x1 = xyd->x;
+       y1 = xyd->y;
+       d1 = xyd->direction;
+       sfree(xyd);
+
+       OFFSET(x2, y2, x1, y1, d1, params);
+       d2 = F(d1);
+
+       index(params, barriers, x1, y1) |= d1;
+       index(params, barriers, x2, y2) |= d2;
+
+       nbarriers--;
+    }
+
+    /*
+     * Clean up the rest of the barrier list.
+     */
+    {
+       struct xyd *xyd;
+
+       while ( (xyd = delpos234(barriertree, 0)) != NULL)
+           sfree(xyd);
+
+       freetree234(barriertree);
+    }
+
+    /*
+     * Finally, encode the grid into a string game description.
+     * 
+     * My syntax is extremely simple: each square is encoded as a
+     * hex digit in which bit 0 means a connection on the right,
+     * bit 1 means up, bit 2 left and bit 3 down. (i.e. the same
+     * encoding as used internally). Each digit is followed by
+     * optional barrier indicators: `v' means a vertical barrier to
+     * the right of it, and `h' means a horizontal barrier below
+     * it.
+     */
+    desc = snewn(w * h * 3 + 1, char);
+    p = desc;
+    for (y = 0; y < h; y++) {
+        for (x = 0; x < w; x++) {
+            *p++ = "0123456789abcdef"[index(params, tiles, x, y)];
+            if ((params->wrapping || x < w-1) &&
+                (index(params, barriers, x, y) & R))
+                *p++ = 'v';
+            if ((params->wrapping || y < h-1) &&
+                (index(params, barriers, x, y) & D))
+                *p++ = 'h';
+        }
+    }
+    assert(p - desc <= w*h*3);
+    *p = '\0';
+
+    sfree(tiles);
+    sfree(barriers);
+
+    return desc;
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    int w = params->width, h = params->height;
+    int i;
+
+    for (i = 0; i < w*h; i++) {
+        if (*desc >= '0' && *desc <= '9')
+            /* OK */;
+        else if (*desc >= 'a' && *desc <= 'f')
+            /* OK */;
+        else if (*desc >= 'A' && *desc <= 'F')
+            /* OK */;
+        else if (!*desc)
+            return "Game description shorter than expected";
+        else
+            return "Game description contained unexpected character";
+        desc++;
+        while (*desc == 'h' || *desc == 'v')
+            desc++;
+    }
+    if (*desc)
+        return "Game description longer than expected";
+
+    return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Construct an initial game state, given a description and parameters.
+ */
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    game_state *state;
+    int w, h, x, y;
+
+    assert(params->width > 0 && params->height > 0);
+    assert(params->width > 1 || params->height > 1);
+
+    /*
+     * Create a blank game state.
+     */
+    state = snew(game_state);
+    w = state->width = params->width;
+    h = state->height = params->height;
+    state->cx = state->width / 2;
+    state->cy = state->height / 2;
+    state->wrapping = params->wrapping;
+    state->movetarget = params->movetarget;
+    state->completed = 0;
+    state->used_solve = FALSE;
+    state->move_count = 0;
+    state->last_move_row = -1;
+    state->last_move_col = -1;
+    state->last_move_dir = 0;
+    state->tiles = snewn(state->width * state->height, unsigned char);
+    memset(state->tiles, 0, state->width * state->height);
+    state->barriers = snewn(state->width * state->height, unsigned char);
+    memset(state->barriers, 0, state->width * state->height);
+
+
+    /*
+     * Parse the game description into the grid.
+     */
+    for (y = 0; y < h; y++) {
+        for (x = 0; x < w; x++) {
+            if (*desc >= '0' && *desc <= '9')
+                tile(state, x, y) = *desc - '0';
+            else if (*desc >= 'a' && *desc <= 'f')
+                tile(state, x, y) = *desc - 'a' + 10;
+            else if (*desc >= 'A' && *desc <= 'F')
+                tile(state, x, y) = *desc - 'A' + 10;
+            if (*desc)
+                desc++;
+            while (*desc == 'h' || *desc == 'v') {
+                int x2, y2, d1, d2;
+                if (*desc == 'v')
+                    d1 = R;
+                else
+                    d1 = D;
+
+                OFFSET(x2, y2, x, y, d1, state);
+                d2 = F(d1);
+
+                barrier(state, x, y) |= d1;
+                barrier(state, x2, y2) |= d2;
+
+                desc++;
+            }
+        }
+    }
+
+    /*
+     * Set up border barriers if this is a non-wrapping game.
+     */
+    if (!state->wrapping) {
+       for (x = 0; x < state->width; x++) {
+           barrier(state, x, 0) |= U;
+           barrier(state, x, state->height-1) |= D;
+       }
+       for (y = 0; y < state->height; y++) {
+           barrier(state, 0, y) |= L;
+           barrier(state, state->width-1, y) |= R;
+       }
+    }
+
+    /*
+     * Set up the barrier corner flags, for drawing barriers
+     * prettily when they meet.
+     */
+    for (y = 0; y < state->height; y++) {
+       for (x = 0; x < state->width; x++) {
+            int dir;
+
+            for (dir = 1; dir < 0x10; dir <<= 1) {
+                int dir2 = A(dir);
+                int x1, y1, x2, y2, x3, y3;
+                int corner = FALSE;
+
+                if (!(barrier(state, x, y) & dir))
+                    continue;
+
+                if (barrier(state, x, y) & dir2)
+                    corner = TRUE;
+
+                x1 = x + X(dir), y1 = y + Y(dir);
+                if (x1 >= 0 && x1 < state->width &&
+                    y1 >= 0 && y1 < state->height &&
+                    (barrier(state, x1, y1) & dir2))
+                    corner = TRUE;
+
+                x2 = x + X(dir2), y2 = y + Y(dir2);
+                if (x2 >= 0 && x2 < state->width &&
+                    y2 >= 0 && y2 < state->height &&
+                    (barrier(state, x2, y2) & dir))
+                    corner = TRUE;
+
+                if (corner) {
+                    barrier(state, x, y) |= (dir << 4);
+                    if (x1 >= 0 && x1 < state->width &&
+                        y1 >= 0 && y1 < state->height)
+                        barrier(state, x1, y1) |= (A(dir) << 4);
+                    if (x2 >= 0 && x2 < state->width &&
+                        y2 >= 0 && y2 < state->height)
+                        barrier(state, x2, y2) |= (C(dir) << 4);
+                    x3 = x + X(dir) + X(dir2), y3 = y + Y(dir) + Y(dir2);
+                    if (x3 >= 0 && x3 < state->width &&
+                        y3 >= 0 && y3 < state->height)
+                        barrier(state, x3, y3) |= (F(dir) << 4);
+                }
+            }
+       }
+    }
+
+    return state;
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    game_state *ret;
+
+    ret = snew(game_state);
+    ret->width = state->width;
+    ret->height = state->height;
+    ret->cx = state->cx;
+    ret->cy = state->cy;
+    ret->wrapping = state->wrapping;
+    ret->movetarget = state->movetarget;
+    ret->completed = state->completed;
+    ret->used_solve = state->used_solve;
+    ret->move_count = state->move_count;
+    ret->last_move_row = state->last_move_row;
+    ret->last_move_col = state->last_move_col;
+    ret->last_move_dir = state->last_move_dir;
+    ret->tiles = snewn(state->width * state->height, unsigned char);
+    memcpy(ret->tiles, state->tiles, state->width * state->height);
+    ret->barriers = snewn(state->width * state->height, unsigned char);
+    memcpy(ret->barriers, state->barriers, state->width * state->height);
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    sfree(state->tiles);
+    sfree(state->barriers);
+    sfree(state);
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    if (!aux) {
+       *error = "Solution not known for this puzzle";
+       return NULL;
+    }
+
+    return dupstr(aux);
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Utility routine.
+ */
+
+/*
+ * Compute which squares are reachable from the centre square, as a
+ * quick visual aid to determining how close the game is to
+ * completion. This is also a simple way to tell if the game _is_
+ * completed - just call this function and see whether every square
+ * is marked active.
+ *
+ * squares in the moving_row and moving_col are always inactive - this
+ * is so that "current" doesn't appear to jump across moving lines.
+ */
+static unsigned char *compute_active(const game_state *state,
+                                     int moving_row, int moving_col)
+{
+    unsigned char *active;
+    tree234 *todo;
+    struct xyd *xyd;
+
+    active = snewn(state->width * state->height, unsigned char);
+    memset(active, 0, state->width * state->height);
+
+    /*
+     * We only store (x,y) pairs in todo, but it's easier to reuse
+     * xyd_cmp and just store direction 0 every time.
+     */
+    todo = newtree234(xyd_cmp);
+    index(state, active, state->cx, state->cy) = ACTIVE;
+    add234(todo, new_xyd(state->cx, state->cy, 0));
+
+    while ( (xyd = delpos234(todo, 0)) != NULL) {
+       int x1, y1, d1, x2, y2, d2;
+
+       x1 = xyd->x;
+       y1 = xyd->y;
+       sfree(xyd);
+
+       for (d1 = 1; d1 < 0x10; d1 <<= 1) {
+           OFFSET(x2, y2, x1, y1, d1, state);
+           d2 = F(d1);
+
+           /*
+            * If the next tile in this direction is connected to
+            * us, and there isn't a barrier in the way, and it
+            * isn't already marked active, then mark it active and
+            * add it to the to-examine list.
+            */
+           if ((x2 != moving_col && y2 != moving_row) &&
+                (tile(state, x1, y1) & d1) &&
+               (tile(state, x2, y2) & d2) &&
+               !(barrier(state, x1, y1) & d1) &&
+               !index(state, active, x2, y2)) {
+               index(state, active, x2, y2) = ACTIVE;
+               add234(todo, new_xyd(x2, y2, 0));
+           }
+       }
+    }
+    /* Now we expect the todo list to have shrunk to zero size. */
+    assert(count234(todo) == 0);
+    freetree234(todo);
+
+    return active;
+}
+
+struct game_ui {
+    int cur_x, cur_y;
+    int cur_visible;
+};
+
+static game_ui *new_ui(const game_state *state)
+{
+    game_ui *ui = snew(game_ui);
+    ui->cur_x = 0;
+    ui->cur_y = -1;
+    ui->cur_visible = FALSE;
+
+    return ui;
+}
+
+static void free_ui(game_ui *ui)
+{
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+}
+
+/* ----------------------------------------------------------------------
+ * Process a move.
+ */
+
+static void slide_row_int(int w, int h, unsigned char *tiles, int dir, int row)
+{
+    int x = dir > 0 ? -1 : w;
+    int tx = x + dir;
+    int n = w - 1;
+    unsigned char endtile = tiles[row * w + tx];
+    do {
+        x = tx;
+        tx = (x + dir + w) % w;
+        tiles[row * w + x] = tiles[row * w + tx];
+    } while (--n > 0);
+    tiles[row * w + tx] = endtile;
+}
+
+static void slide_col_int(int w, int h, unsigned char *tiles, int dir, int col)
+{
+    int y = dir > 0 ? -1 : h;
+    int ty = y + dir;
+    int n = h - 1;
+    unsigned char endtile = tiles[ty * w + col];
+    do {
+        y = ty;
+        ty = (y + dir + h) % h;
+        tiles[y * w + col] = tiles[ty * w + col];
+    } while (--n > 0);
+    tiles[ty * w + col] = endtile;
+}
+
+static void slide_row(game_state *state, int dir, int row)
+{
+    slide_row_int(state->width, state->height, state->tiles, dir, row);
+}
+
+static void slide_col(game_state *state, int dir, int col)
+{
+    slide_col_int(state->width, state->height, state->tiles, dir, col);
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+}
+
+struct game_drawstate {
+    int started;
+    int width, height;
+    int tilesize;
+    unsigned char *visible;
+    int cur_x, cur_y;
+};
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    int cx, cy;
+    int dx, dy;
+    char buf[80];
+
+    button &= ~MOD_MASK;
+
+    if (IS_CURSOR_MOVE(button)) {
+        int cpos, diff = 0;
+        cpos = c2pos(state->width, state->height, ui->cur_x, ui->cur_y);
+        diff = c2diff(state->width, state->height, ui->cur_x, ui->cur_y, button);
+
+        if (diff != 0) {
+            do { /* we might have to do this more than once to skip missing arrows */
+                cpos += diff;
+                pos2c(state->width, state->height, cpos, &ui->cur_x, &ui->cur_y);
+            } while (ui->cur_x == state->cx || ui->cur_y == state->cy);
+        }
+
+        ui->cur_visible = 1;
+        return "";
+    }
+
+    if (button == LEFT_BUTTON || button == RIGHT_BUTTON) {
+        cx = (x - (BORDER + WINDOW_OFFSET + TILE_BORDER) + 2*TILE_SIZE) / TILE_SIZE - 2;
+        cy = (y - (BORDER + WINDOW_OFFSET + TILE_BORDER) + 2*TILE_SIZE) / TILE_SIZE - 2;
+        ui->cur_visible = 0;
+    } else if (IS_CURSOR_SELECT(button)) {
+        if (ui->cur_visible) {
+            cx = ui->cur_x;
+            cy = ui->cur_y;
+        } else {
+            /* 'click' when cursor is invisible just makes cursor visible. */
+            ui->cur_visible = 1;
+            return "";
+        }
+    } else
+        return NULL;
+
+    if (cy >= 0 && cy < state->height && cy != state->cy)
+    {
+        if (cx == -1) dx = +1;
+        else if (cx == state->width) dx = -1;
+        else return NULL;
+        dy = 0;
+    }
+    else if (cx >= 0 && cx < state->width && cx != state->cx)
+    {
+        if (cy == -1) dy = +1;
+        else if (cy == state->height) dy = -1;
+        else return NULL;
+        dx = 0;
+    }
+    else
+        return NULL;
+
+    /* reverse direction if right hand button is pressed */
+    if (button == RIGHT_BUTTON)
+    {
+        dx = -dx;
+        dy = -dy;
+    }
+
+    if (dx == 0)
+       sprintf(buf, "C%d,%d", cx, dy);
+    else
+       sprintf(buf, "R%d,%d", cy, dx);
+    return dupstr(buf);
+}
+
+static game_state *execute_move(const game_state *from, const char *move)
+{
+    game_state *ret;
+    int c, d, col;
+
+    if ((move[0] == 'C' || move[0] == 'R') &&
+       sscanf(move+1, "%d,%d", &c, &d) == 2 &&
+       c >= 0 && c < (move[0] == 'C' ? from->width : from->height)) {
+       col = (move[0] == 'C');
+    } else if (move[0] == 'S' &&
+              strlen(move) == from->width * from->height + 1) {
+       int i;
+       ret = dup_game(from);
+       ret->used_solve = TRUE;
+       ret->completed = ret->move_count = 1;
+
+       for (i = 0; i < from->width * from->height; i++) {
+           c = move[i+1];
+           if (c >= '0' && c <= '9')
+               c -= '0';
+           else if (c >= 'A' && c <= 'F')
+               c -= 'A' - 10;
+           else if (c >= 'a' && c <= 'f')
+               c -= 'a' - 10;
+           else {
+               free_game(ret);
+               return NULL;
+           }
+           ret->tiles[i] = c;
+       }
+       return ret;
+    } else
+       return NULL;                   /* can't parse move string */
+
+    ret = dup_game(from);
+
+    if (col)
+       slide_col(ret, d, c);
+    else
+       slide_row(ret, d, c);
+
+    ret->move_count++;
+    ret->last_move_row = col ? -1 : c;
+    ret->last_move_col = col ? c : -1;
+    ret->last_move_dir = d;
+
+    /*
+     * See if the game has been completed.
+     */
+    if (!ret->completed) {
+       unsigned char *active = compute_active(ret, -1, -1);
+       int x1, y1;
+       int complete = TRUE;
+
+       for (x1 = 0; x1 < ret->width; x1++)
+           for (y1 = 0; y1 < ret->height; y1++)
+               if (!index(ret, active, x1, y1)) {
+                   complete = FALSE;
+                   goto break_label;  /* break out of two loops at once */
+               }
+       break_label:
+
+       sfree(active);
+
+       if (complete)
+           ret->completed = ret->move_count;
+    }
+
+    return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * Routines for drawing the game position on the screen.
+ */
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    game_drawstate *ds = snew(game_drawstate);
+
+    ds->started = FALSE;
+    ds->width = state->width;
+    ds->height = state->height;
+    ds->visible = snewn(state->width * state->height, unsigned char);
+    ds->tilesize = 0;                  /* not decided yet */
+    memset(ds->visible, 0xFF, state->width * state->height);
+    ds->cur_x = ds->cur_y = -1;
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds->visible);
+    sfree(ds);
+}
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    struct { int tilesize; } ads, *ds = &ads;
+    ads.tilesize = tilesize;
+
+    *x = BORDER * 2 + WINDOW_OFFSET * 2 + TILE_SIZE * params->width + TILE_BORDER;
+    *y = BORDER * 2 + WINDOW_OFFSET * 2 + TILE_SIZE * params->height + TILE_BORDER;
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret;
+
+    ret = snewn(NCOLOURS * 3, float);
+    *ncolours = NCOLOURS;
+
+    /*
+     * Basic background colour is whatever the front end thinks is
+     * a sensible default.
+     */
+    frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
+
+    /*
+     * Wires are black.
+     */
+    ret[COL_WIRE * 3 + 0] = 0.0F;
+    ret[COL_WIRE * 3 + 1] = 0.0F;
+    ret[COL_WIRE * 3 + 2] = 0.0F;
+
+    /*
+     * Powered wires and powered endpoints are cyan.
+     */
+    ret[COL_POWERED * 3 + 0] = 0.0F;
+    ret[COL_POWERED * 3 + 1] = 1.0F;
+    ret[COL_POWERED * 3 + 2] = 1.0F;
+
+    /*
+     * Barriers are red.
+     */
+    ret[COL_BARRIER * 3 + 0] = 1.0F;
+    ret[COL_BARRIER * 3 + 1] = 0.0F;
+    ret[COL_BARRIER * 3 + 2] = 0.0F;
+
+    /*
+     * Unpowered endpoints are blue.
+     */
+    ret[COL_ENDPOINT * 3 + 0] = 0.0F;
+    ret[COL_ENDPOINT * 3 + 1] = 0.0F;
+    ret[COL_ENDPOINT * 3 + 2] = 1.0F;
+
+    /*
+     * Tile borders are a darker grey than the background.
+     */
+    ret[COL_BORDER * 3 + 0] = 0.5F * ret[COL_BACKGROUND * 3 + 0];
+    ret[COL_BORDER * 3 + 1] = 0.5F * ret[COL_BACKGROUND * 3 + 1];
+    ret[COL_BORDER * 3 + 2] = 0.5F * ret[COL_BACKGROUND * 3 + 2];
+
+    /*
+     * Flashing tiles are a grey in between those two.
+     */
+    ret[COL_FLASHING * 3 + 0] = 0.75F * ret[COL_BACKGROUND * 3 + 0];
+    ret[COL_FLASHING * 3 + 1] = 0.75F * ret[COL_BACKGROUND * 3 + 1];
+    ret[COL_FLASHING * 3 + 2] = 0.75F * ret[COL_BACKGROUND * 3 + 2];
+
+    ret[COL_LOWLIGHT * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 0.8F;
+    ret[COL_LOWLIGHT * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 0.8F;
+    ret[COL_LOWLIGHT * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] * 0.8F;
+    ret[COL_TEXT * 3 + 0] = 0.0;
+    ret[COL_TEXT * 3 + 1] = 0.0;
+    ret[COL_TEXT * 3 + 2] = 0.0;
+
+    return ret;
+}
+
+static void draw_filled_line(drawing *dr, int x1, int y1, int x2, int y2,
+                            int colour)
+{
+    draw_line(dr, x1-1, y1, x2-1, y2, COL_WIRE);
+    draw_line(dr, x1+1, y1, x2+1, y2, COL_WIRE);
+    draw_line(dr, x1, y1-1, x2, y2-1, COL_WIRE);
+    draw_line(dr, x1, y1+1, x2, y2+1, COL_WIRE);
+    draw_line(dr, x1, y1, x2, y2, colour);
+}
+
+static void draw_rect_coords(drawing *dr, int x1, int y1, int x2, int y2,
+                             int colour)
+{
+    int mx = (x1 < x2 ? x1 : x2);
+    int my = (y1 < y2 ? y1 : y2);
+    int dx = (x2 + x1 - 2*mx + 1);
+    int dy = (y2 + y1 - 2*my + 1);
+
+    draw_rect(dr, mx, my, dx, dy, colour);
+}
+
+static void draw_barrier_corner(drawing *dr, game_drawstate *ds,
+                                int x, int y, int dir, int phase)
+{
+    int bx = BORDER + WINDOW_OFFSET + TILE_SIZE * x;
+    int by = BORDER + WINDOW_OFFSET + TILE_SIZE * y;
+    int x1, y1, dx, dy, dir2;
+
+    dir >>= 4;
+
+    dir2 = A(dir);
+    dx = X(dir) + X(dir2);
+    dy = Y(dir) + Y(dir2);
+    x1 = (dx > 0 ? TILE_SIZE+TILE_BORDER-1 : 0);
+    y1 = (dy > 0 ? TILE_SIZE+TILE_BORDER-1 : 0);
+
+    if (phase == 0) {
+        draw_rect_coords(dr, bx+x1, by+y1,
+                         bx+x1-TILE_BORDER*dx, by+y1-(TILE_BORDER-1)*dy,
+                         COL_WIRE);
+        draw_rect_coords(dr, bx+x1, by+y1,
+                         bx+x1-(TILE_BORDER-1)*dx, by+y1-TILE_BORDER*dy,
+                         COL_WIRE);
+    } else {
+        draw_rect_coords(dr, bx+x1, by+y1,
+                         bx+x1-(TILE_BORDER-1)*dx, by+y1-(TILE_BORDER-1)*dy,
+                         COL_BARRIER);
+    }
+}
+
+static void draw_barrier(drawing *dr, game_drawstate *ds,
+                         int x, int y, int dir, int phase)
+{
+    int bx = BORDER + WINDOW_OFFSET + TILE_SIZE * x;
+    int by = BORDER + WINDOW_OFFSET + TILE_SIZE * y;
+    int x1, y1, w, h;
+
+    x1 = (X(dir) > 0 ? TILE_SIZE : X(dir) == 0 ? TILE_BORDER : 0);
+    y1 = (Y(dir) > 0 ? TILE_SIZE : Y(dir) == 0 ? TILE_BORDER : 0);
+    w = (X(dir) ? TILE_BORDER : TILE_SIZE - TILE_BORDER);
+    h = (Y(dir) ? TILE_BORDER : TILE_SIZE - TILE_BORDER);
+
+    if (phase == 0) {
+        draw_rect(dr, bx+x1-X(dir), by+y1-Y(dir), w, h, COL_WIRE);
+    } else {
+        draw_rect(dr, bx+x1, by+y1, w, h, COL_BARRIER);
+    }
+}
+
+static void draw_tile(drawing *dr, game_drawstate *ds, const game_state *state,
+                      int x, int y, int tile, float xshift, float yshift)
+{
+    int bx = BORDER + WINDOW_OFFSET + TILE_SIZE * x + (int)(xshift * TILE_SIZE);
+    int by = BORDER + WINDOW_OFFSET + TILE_SIZE * y + (int)(yshift * TILE_SIZE);
+    float cx, cy, ex, ey;
+    int dir, col;
+
+    /*
+     * When we draw a single tile, we must draw everything up to
+     * and including the borders around the tile. This means that
+     * if the neighbouring tiles have connections to those borders,
+     * we must draw those connections on the borders themselves.
+     *
+     * This would be terribly fiddly if we ever had to draw a tile
+     * while its neighbour was in mid-rotate, because we'd have to
+     * arrange to _know_ that the neighbour was being rotated and
+     * hence had an anomalous effect on the redraw of this tile.
+     * Fortunately, the drawing algorithm avoids ever calling us in
+     * this circumstance: we're either drawing lots of straight
+     * tiles at game start or after a move is complete, or we're
+     * repeatedly drawing only the rotating tile. So no problem.
+     */
+
+    /*
+     * So. First blank the tile out completely: draw a big
+     * rectangle in border colour, and a smaller rectangle in
+     * background colour to fill it in.
+     */
+    draw_rect(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER,
+              COL_BORDER);
+    draw_rect(dr, bx+TILE_BORDER, by+TILE_BORDER,
+              TILE_SIZE-TILE_BORDER, TILE_SIZE-TILE_BORDER,
+              tile & FLASHING ? COL_FLASHING : COL_BACKGROUND);
+
+    /*
+     * Draw the wires.
+     */
+    cx = cy = TILE_BORDER + (TILE_SIZE-TILE_BORDER) / 2.0F - 0.5F;
+    col = (tile & ACTIVE ? COL_POWERED : COL_WIRE);
+    for (dir = 1; dir < 0x10; dir <<= 1) {
+        if (tile & dir) {
+            ex = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * X(dir);
+            ey = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * Y(dir);
+            draw_filled_line(dr, bx+(int)cx, by+(int)cy,
+                            bx+(int)(cx+ex), by+(int)(cy+ey),
+                            COL_WIRE);
+        }
+    }
+    for (dir = 1; dir < 0x10; dir <<= 1) {
+        if (tile & dir) {
+            ex = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * X(dir);
+            ey = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * Y(dir);
+            draw_line(dr, bx+(int)cx, by+(int)cy,
+                     bx+(int)(cx+ex), by+(int)(cy+ey), col);
+        }
+    }
+
+    /*
+     * Draw the box in the middle. We do this in blue if the tile
+     * is an unpowered endpoint, in cyan if the tile is a powered
+     * endpoint, in black if the tile is the centrepiece, and
+     * otherwise not at all.
+     */
+    col = -1;
+    if (x == state->cx && y == state->cy)
+        col = COL_WIRE;
+    else if (COUNT(tile) == 1) {
+        col = (tile & ACTIVE ? COL_POWERED : COL_ENDPOINT);
+    }
+    if (col >= 0) {
+        int i, points[8];
+
+        points[0] = +1; points[1] = +1;
+        points[2] = +1; points[3] = -1;
+        points[4] = -1; points[5] = -1;
+        points[6] = -1; points[7] = +1;
+
+        for (i = 0; i < 8; i += 2) {
+            ex = (TILE_SIZE * 0.24F) * points[i];
+            ey = (TILE_SIZE * 0.24F) * points[i+1];
+            points[i] = bx+(int)(cx+ex);
+            points[i+1] = by+(int)(cy+ey);
+        }
+
+        draw_polygon(dr, points, 4, col, COL_WIRE);
+    }
+
+    /*
+     * Draw the points on the border if other tiles are connected
+     * to us.
+     */
+    for (dir = 1; dir < 0x10; dir <<= 1) {
+        int dx, dy, px, py, lx, ly, vx, vy, ox, oy;
+
+        dx = X(dir);
+        dy = Y(dir);
+
+        ox = x + dx;
+        oy = y + dy;
+
+        if (ox < 0 || ox >= state->width || oy < 0 || oy >= state->height)
+            continue;
+
+        if (!(tile(state, ox, oy) & F(dir)))
+            continue;
+
+        px = bx + (int)(dx>0 ? TILE_SIZE + TILE_BORDER - 1 : dx<0 ? 0 : cx);
+        py = by + (int)(dy>0 ? TILE_SIZE + TILE_BORDER - 1 : dy<0 ? 0 : cy);
+        lx = dx * (TILE_BORDER-1);
+        ly = dy * (TILE_BORDER-1);
+        vx = (dy ? 1 : 0);
+        vy = (dx ? 1 : 0);
+
+        if (xshift == 0.0 && yshift == 0.0 && (tile & dir)) {
+            /*
+             * If we are fully connected to the other tile, we must
+             * draw right across the tile border. (We can use our
+             * own ACTIVE state to determine what colour to do this
+             * in: if we are fully connected to the other tile then
+             * the two ACTIVE states will be the same.)
+             */
+            draw_rect_coords(dr, px-vx, py-vy, px+lx+vx, py+ly+vy, COL_WIRE);
+            draw_rect_coords(dr, px, py, px+lx, py+ly,
+                             (tile & ACTIVE) ? COL_POWERED : COL_WIRE);
+        } else {
+            /*
+             * The other tile extends into our border, but isn't
+             * actually connected to us. Just draw a single black
+             * dot.
+             */
+            draw_rect_coords(dr, px, py, px, py, COL_WIRE);
+        }
+    }
+
+    draw_update(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER);
+}
+
+static void draw_tile_barriers(drawing *dr, game_drawstate *ds,
+                               const game_state *state, int x, int y)
+{
+    int phase;
+    int dir;
+    int bx = BORDER + WINDOW_OFFSET + TILE_SIZE * x;
+    int by = BORDER + WINDOW_OFFSET + TILE_SIZE * y;
+    /*
+     * Draw barrier corners, and then barriers.
+     */
+    for (phase = 0; phase < 2; phase++) {
+        for (dir = 1; dir < 0x10; dir <<= 1)
+            if (barrier(state, x, y) & (dir << 4))
+                draw_barrier_corner(dr, ds, x, y, dir << 4, phase);
+        for (dir = 1; dir < 0x10; dir <<= 1)
+            if (barrier(state, x, y) & dir)
+                draw_barrier(dr, ds, x, y, dir, phase);
+    }
+
+    draw_update(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER);
+}
+
+static void draw_arrow(drawing *dr, game_drawstate *ds,
+                       int x, int y, int xdx, int xdy, int cur)
+{
+    int coords[14];
+    int ydy = -xdx, ydx = xdy;
+
+    x = x * TILE_SIZE + BORDER + WINDOW_OFFSET;
+    y = y * TILE_SIZE + BORDER + WINDOW_OFFSET;
+
+#define POINT(n, xx, yy) ( \
+    coords[2*(n)+0] = x + (xx)*xdx + (yy)*ydx, \
+    coords[2*(n)+1] = y + (xx)*xdy + (yy)*ydy)
+
+    POINT(0, TILE_SIZE / 2, 3 * TILE_SIZE / 4);   /* top of arrow */
+    POINT(1, 3 * TILE_SIZE / 4, TILE_SIZE / 2);   /* right corner */
+    POINT(2, 5 * TILE_SIZE / 8, TILE_SIZE / 2);   /* right concave */
+    POINT(3, 5 * TILE_SIZE / 8, TILE_SIZE / 4);   /* bottom right */
+    POINT(4, 3 * TILE_SIZE / 8, TILE_SIZE / 4);   /* bottom left */
+    POINT(5, 3 * TILE_SIZE / 8, TILE_SIZE / 2);   /* left concave */
+    POINT(6,     TILE_SIZE / 4, TILE_SIZE / 2);   /* left corner */
+
+    draw_polygon(dr, coords, 7, cur ? COL_POWERED : COL_LOWLIGHT, COL_TEXT);
+}
+
+static void draw_arrow_for_cursor(drawing *dr, game_drawstate *ds,
+                                  int cur_x, int cur_y, int cur)
+{
+    if (cur_x == -1 && cur_y == -1)
+        return; /* 'no cursur here */
+    else if (cur_x == -1) /* LH column. */
+        draw_arrow(dr, ds, 0, cur_y+1, 0, -1, cur);
+    else if (cur_x == ds->width) /* RH column */
+        draw_arrow(dr, ds, ds->width, cur_y, 0, +1, cur);
+    else if (cur_y == -1) /* Top row */
+        draw_arrow(dr, ds, cur_x, 0, +1, 0, cur);
+    else if (cur_y == ds->height) /* Bottom row */
+        draw_arrow(dr, ds, cur_x+1, ds->height, -1, 0, cur);
+    else
+        assert(!"Invalid cursor position");
+
+    draw_update(dr,
+                cur_x * TILE_SIZE + BORDER + WINDOW_OFFSET,
+                cur_y * TILE_SIZE + BORDER + WINDOW_OFFSET,
+                TILE_SIZE, TILE_SIZE);
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float t, float ft)
+{
+    int x, y, frame;
+    unsigned char *active;
+    float xshift = 0.0;
+    float yshift = 0.0;
+    int cur_x = -1, cur_y = -1;
+
+    /*
+     * Clear the screen and draw the exterior barrier lines if this
+     * is our first call.
+     */
+    if (!ds->started) {
+        int phase;
+
+        ds->started = TRUE;
+
+        draw_rect(dr, 0, 0, 
+                  BORDER * 2 + WINDOW_OFFSET * 2 + TILE_SIZE * state->width + TILE_BORDER,
+                  BORDER * 2 + WINDOW_OFFSET * 2 + TILE_SIZE * state->height + TILE_BORDER,
+                  COL_BACKGROUND);
+        draw_update(dr, 0, 0, 
+                    BORDER * 2 + WINDOW_OFFSET*2 + TILE_SIZE*state->width + TILE_BORDER,
+                    BORDER * 2 + WINDOW_OFFSET*2 + TILE_SIZE*state->height + TILE_BORDER);
+
+        for (phase = 0; phase < 2; phase++) {
+
+            for (x = 0; x < ds->width; x++) {
+                if (barrier(state, x, 0) & UL)
+                    draw_barrier_corner(dr, ds, x, -1, LD, phase);
+                if (barrier(state, x, 0) & RU)
+                    draw_barrier_corner(dr, ds, x, -1, DR, phase);
+                if (barrier(state, x, 0) & U)
+                    draw_barrier(dr, ds, x, -1, D, phase);
+                if (barrier(state, x, ds->height-1) & DR)
+                    draw_barrier_corner(dr, ds, x, ds->height, RU, phase);
+                if (barrier(state, x, ds->height-1) & LD)
+                    draw_barrier_corner(dr, ds, x, ds->height, UL, phase);
+                if (barrier(state, x, ds->height-1) & D)
+                    draw_barrier(dr, ds, x, ds->height, U, phase);
+            }
+
+            for (y = 0; y < ds->height; y++) {
+                if (barrier(state, 0, y) & UL)
+                    draw_barrier_corner(dr, ds, -1, y, RU, phase);
+                if (barrier(state, 0, y) & LD)
+                    draw_barrier_corner(dr, ds, -1, y, DR, phase);
+                if (barrier(state, 0, y) & L)
+                    draw_barrier(dr, ds, -1, y, R, phase);
+                if (barrier(state, ds->width-1, y) & RU)
+                    draw_barrier_corner(dr, ds, ds->width, y, UL, phase);
+                if (barrier(state, ds->width-1, y) & DR)
+                    draw_barrier_corner(dr, ds, ds->width, y, LD, phase);
+                if (barrier(state, ds->width-1, y) & R)
+                    draw_barrier(dr, ds, ds->width, y, L, phase);
+            }
+        }
+
+        /*
+         * Arrows for making moves.
+         */
+        for (x = 0; x < ds->width; x++) {
+            if (x == state->cx) continue;
+            draw_arrow(dr, ds, x, 0, +1, 0, 0);
+            draw_arrow(dr, ds, x+1, ds->height, -1, 0, 0);
+        }
+        for (y = 0; y < ds->height; y++) {
+            if (y == state->cy) continue;
+            draw_arrow(dr, ds, ds->width, y, 0, +1, 0);
+            draw_arrow(dr, ds, 0, y+1, 0, -1, 0);
+        }
+    }
+    if (ui->cur_visible) {
+        cur_x = ui->cur_x; cur_y = ui->cur_y;
+    }
+    if (cur_x != ds->cur_x || cur_y != ds->cur_y) {
+        /* Cursor has changed; redraw two (prev and curr) arrows. */
+        assert(cur_x != state->cx && cur_y != state->cy);
+
+        draw_arrow_for_cursor(dr, ds, cur_x, cur_y, 1);
+        draw_arrow_for_cursor(dr, ds, ds->cur_x, ds->cur_y, 0);
+        ds->cur_x = cur_x; ds->cur_y = cur_y;
+    }
+
+    /* Check if this is an undo.  If so, we will need to run any animation
+     * backwards.
+     */
+    if (oldstate && oldstate->move_count > state->move_count) {
+        const game_state * tmpstate = state;
+        state = oldstate;
+        oldstate = tmpstate;
+        t = ANIM_TIME - t;
+    }
+
+    if (oldstate && (t < ANIM_TIME)) {
+        /*
+         * We're animating a slide, of row/column number
+         * state->last_move_pos, in direction
+         * state->last_move_dir
+         */
+        xshift = state->last_move_row == -1 ? 0.0F :
+                (1 - t / ANIM_TIME) * state->last_move_dir;
+        yshift = state->last_move_col == -1 ? 0.0F :
+                (1 - t / ANIM_TIME) * state->last_move_dir;
+    }
+    
+    frame = -1;
+    if (ft > 0) {
+        /*
+         * We're animating a completion flash. Find which frame
+         * we're at.
+         */
+        frame = (int)(ft / FLASH_FRAME);
+    }
+
+    /*
+     * Draw any tile which differs from the way it was last drawn.
+     */
+    if (xshift != 0.0 || yshift != 0.0) {
+        active = compute_active(state,
+                                state->last_move_row, state->last_move_col);
+    } else {
+        active = compute_active(state, -1, -1);
+    }
+
+    clip(dr,
+         BORDER + WINDOW_OFFSET, BORDER + WINDOW_OFFSET,
+         TILE_SIZE * state->width + TILE_BORDER,
+         TILE_SIZE * state->height + TILE_BORDER);
+    
+    for (x = 0; x < ds->width; x++)
+        for (y = 0; y < ds->height; y++) {
+            unsigned char c = tile(state, x, y) | index(state, active, x, y);
+
+            /*
+             * In a completion flash, we adjust the FLASHING bit
+             * depending on our distance from the centre point and
+             * the frame number.
+             */
+            if (frame >= 0) {
+                int xdist, ydist, dist;
+                xdist = (x < state->cx ? state->cx - x : x - state->cx);
+                ydist = (y < state->cy ? state->cy - y : y - state->cy);
+                dist = (xdist > ydist ? xdist : ydist);
+
+                if (frame >= dist && frame < dist+4) {
+                    int flash = (frame - dist) & 1;
+                    flash = flash ? FLASHING : 0;
+                    c = (c &~ FLASHING) | flash;
+                }
+            }
+
+            if (index(state, ds->visible, x, y) != c ||
+                index(state, ds->visible, x, y) == 0xFF ||
+                (x == state->last_move_col || y == state->last_move_row))
+            {
+                float xs = (y == state->last_move_row ? xshift : (float)0.0);
+                float ys = (x == state->last_move_col ? yshift : (float)0.0);
+
+                draw_tile(dr, ds, state, x, y, c, xs, ys);
+                if (xs < 0 && x == 0)
+                    draw_tile(dr, ds, state, state->width, y, c, xs, ys);
+                else if (xs > 0 && x == state->width - 1)
+                    draw_tile(dr, ds, state, -1, y, c, xs, ys);
+                else if (ys < 0 && y == 0)
+                    draw_tile(dr, ds, state, x, state->height, c, xs, ys);
+                else if (ys > 0 && y == state->height - 1)
+                    draw_tile(dr, ds, state, x, -1, c, xs, ys);
+
+                if (x == state->last_move_col || y == state->last_move_row)
+                    index(state, ds->visible, x, y) = 0xFF;
+                else
+                    index(state, ds->visible, x, y) = c;
+            }
+        }
+
+    for (x = 0; x < ds->width; x++)
+        for (y = 0; y < ds->height; y++)
+            draw_tile_barriers(dr, ds, state, x, y);
+
+    unclip(dr);
+
+    /*
+     * Update the status bar.
+     */
+    {
+       char statusbuf[256];
+       int i, n, a;
+
+       n = state->width * state->height;
+       for (i = a = 0; i < n; i++)
+           if (active[i])
+               a++;
+
+       if (state->used_solve)
+           sprintf(statusbuf, "Moves since auto-solve: %d",
+                   state->move_count - state->completed);
+       else
+           sprintf(statusbuf, "%sMoves: %d",
+                   (state->completed ? "COMPLETED! " : ""),
+                   (state->completed ? state->completed : state->move_count));
+
+        if (state->movetarget)
+            sprintf(statusbuf + strlen(statusbuf), " (target %d)",
+                    state->movetarget);
+
+       sprintf(statusbuf + strlen(statusbuf), " Active: %d/%d", a, n);
+
+       status_bar(dr, statusbuf);
+    }
+
+    sfree(active);
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return ANIM_TIME;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    /*
+     * If the game has just been completed, we display a completion
+     * flash.
+     */
+    if (!oldstate->completed && newstate->completed &&
+       !oldstate->used_solve && !newstate->used_solve) {
+        int size;
+        size = 0;
+        if (size < newstate->cx+1)
+            size = newstate->cx+1;
+        if (size < newstate->cy+1)
+            size = newstate->cy+1;
+        if (size < newstate->width - newstate->cx)
+            size = newstate->width - newstate->cx;
+        if (size < newstate->height - newstate->cy)
+            size = newstate->height - newstate->cy;
+        return FLASH_FRAME * (size+4);
+    }
+
+    return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    return state->completed ? +1 : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return FALSE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+}
+
+#ifdef COMBINED
+#define thegame netslide
+#endif
+
+const struct game thegame = {
+    "Netslide", "games.netslide", "netslide",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    FALSE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    FALSE, FALSE, game_print_size, game_print,
+    TRUE,                             /* wants_statusbar */
+    FALSE, game_timing_state,
+    0,                                /* flags */
+};
+
+/* vim: set shiftwidth=4 tabstop=8: */
diff --git a/no-icon.c b/no-icon.c
new file mode 100644 (file)
index 0000000..114b2c5
--- /dev/null
+++ b/no-icon.c
@@ -0,0 +1,8 @@
+
+/*
+ * Dummy source file which replaces the files generated in the
+ * `icons' subdirectory, when they're absent.
+ */
+
+const char *const *const xpm_icons[] = { 0 };
+const int n_xpm_icons = 0;
diff --git a/noicon.rc b/noicon.rc
new file mode 100644 (file)
index 0000000..1de605d
--- /dev/null
+++ b/noicon.rc
@@ -0,0 +1,11 @@
+/* Puzzle resource file without an icon, used in the absence of icons/foo.rc */
+
+#include "puzzles.rc2"
+
+/* XXX this probably isn't the right test, but it'll do. */
+#ifdef MINGW32_FIX
+/* XXX The MinGW toolchain (specifically, windres) doesn't like a resource
+ * file with no resources. Give it a dummy one.
+ * This can go if/when VERSIONINFO resources are added. */
+200 RCDATA { 0 }
+#endif
diff --git a/nullfe.c b/nullfe.c
new file mode 100644 (file)
index 0000000..4c9975b
--- /dev/null
+++ b/nullfe.c
@@ -0,0 +1,69 @@
+/*
+ * nullfe.c: Null front-end code containing a bunch of boring stub
+ * functions. Used to ensure successful linking when building the
+ * various stand-alone solver binaries.
+ */
+
+#include <stdarg.h>
+
+#include "puzzles.h"
+
+void frontend_default_colour(frontend *fe, float *output) {}
+void draw_text(drawing *dr, int x, int y, int fonttype, int fontsize,
+               int align, int colour, char *text) {}
+void draw_rect(drawing *dr, int x, int y, int w, int h, int colour) {}
+void draw_line(drawing *dr, int x1, int y1, int x2, int y2, int colour) {}
+void draw_thick_line(drawing *dr, float thickness,
+                    float x1, float y1, float x2, float y2, int colour) {}
+void draw_polygon(drawing *dr, int *coords, int npoints,
+                  int fillcolour, int outlinecolour) {}
+void draw_circle(drawing *dr, int cx, int cy, int radius,
+                 int fillcolour, int outlinecolour) {}
+char *text_fallback(drawing *dr, const char *const *strings, int nstrings)
+{ return dupstr(strings[0]); }
+void clip(drawing *dr, int x, int y, int w, int h) {}
+void unclip(drawing *dr) {}
+void start_draw(drawing *dr) {}
+void draw_update(drawing *dr, int x, int y, int w, int h) {}
+void end_draw(drawing *dr) {}
+blitter *blitter_new(drawing *dr, int w, int h) {return NULL;}
+void blitter_free(drawing *dr, blitter *bl) {}
+void blitter_save(drawing *dr, blitter *bl, int x, int y) {}
+void blitter_load(drawing *dr, blitter *bl, int x, int y) {}
+int print_mono_colour(drawing *dr, int grey) { return 0; }
+int print_grey_colour(drawing *dr, float grey) { return 0; }
+int print_hatched_colour(drawing *dr, int hatch) { return 0; }
+int print_rgb_mono_colour(drawing *dr, float r, float g, float b, int grey)
+{ return 0; }
+int print_rgb_grey_colour(drawing *dr, float r, float g, float b, float grey)
+{ return 0; }
+int print_rgb_hatched_colour(drawing *dr, float r, float g, float b, int hatch)
+{ return 0; }
+void print_line_width(drawing *dr, int width) {}
+void print_line_dotted(drawing *dr, int dotted) {}
+void midend_supersede_game_desc(midend *me, char *desc, char *privdesc) {}
+void status_bar(drawing *dr, char *text) {}
+
+void fatal(char *fmt, ...)
+{
+    va_list ap;
+
+    fprintf(stderr, "fatal error: ");
+
+    va_start(ap, fmt);
+    vfprintf(stderr, fmt, ap);
+    va_end(ap);
+
+    fprintf(stderr, "\n");
+    exit(1);
+}
+
+#ifdef DEBUGGING
+void debug_printf(char *fmt, ...)
+{
+    va_list ap;
+    va_start(ap, fmt);
+    vfprintf(stdout, fmt, ap);
+    va_end(ap);
+}
+#endif
diff --git a/nullgame.R b/nullgame.R
new file mode 100644 (file)
index 0000000..41bdb85
--- /dev/null
@@ -0,0 +1,12 @@
+# -*- makefile -*-
+
+# The `nullgame' source file is a largely blank one, which contains
+# all the correct function definitions to compile and link, but
+# which defines the null game in which nothing is ever drawn and
+# there are no valid moves. Its main purpose is to act as a
+# template for writing new game definition source files. I include
+# it in the Makefile because it will be worse than useless if it
+# ever fails to compile, so it's important that it should actually
+# be built on a regular basis.
+nullgame : [X] GTK COMMON nullgame nullgame-icon|no-icon
+nullgame : [G] WINDOWS COMMON nullgame nullgame.res|noicon.res
diff --git a/nullgame.c b/nullgame.c
new file mode 100644 (file)
index 0000000..5e5a073
--- /dev/null
@@ -0,0 +1,303 @@
+/*
+ * nullgame.c [FIXME]: Template defining the null game (in which no
+ * moves are permitted and nothing is ever drawn). This file exists
+ * solely as a basis for constructing new game definitions - it
+ * helps to have something which will compile from the word go and
+ * merely doesn't _do_ very much yet.
+ * 
+ * Parts labelled FIXME actually want _removing_ (e.g. the dummy
+ * field in each of the required data structures, and this entire
+ * comment itself) when converting this source file into one
+ * describing a real game.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+
+enum {
+    COL_BACKGROUND,
+    NCOLOURS
+};
+
+struct game_params {
+    int FIXME;
+};
+
+struct game_state {
+    int FIXME;
+};
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+
+    ret->FIXME = 0;
+
+    return ret;
+}
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    return FALSE;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+static void decode_params(game_params *params, char const *string)
+{
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    return dupstr("FIXME");
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    return NULL;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    return NULL;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    return NULL;
+}
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                          char **aux, int interactive)
+{
+    return dupstr("FIXME");
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    return NULL;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    game_state *state = snew(game_state);
+
+    state->FIXME = 0;
+
+    return state;
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    game_state *ret = snew(game_state);
+
+    ret->FIXME = state->FIXME;
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    sfree(state);
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    return NULL;
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    return NULL;
+}
+
+static game_ui *new_ui(const game_state *state)
+{
+    return NULL;
+}
+
+static void free_ui(game_ui *ui)
+{
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+}
+
+struct game_drawstate {
+    int tilesize;
+    int FIXME;
+};
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    return NULL;
+}
+
+static game_state *execute_move(const game_state *state, const char *move)
+{
+    return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    *x = *y = 10 * tilesize;          /* FIXME */
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+
+    frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    struct game_drawstate *ds = snew(struct game_drawstate);
+
+    ds->tilesize = 0;
+    ds->FIXME = 0;
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds);
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    /*
+     * The initial contents of the window are not guaranteed and
+     * can vary with front ends. To be on the safe side, all games
+     * should start by drawing a big background-colour rectangle
+     * covering the whole window.
+     */
+    draw_rect(dr, 0, 0, 10*ds->tilesize, 10*ds->tilesize, COL_BACKGROUND);
+    draw_update(dr, 0, 0, 10*ds->tilesize, 10*ds->tilesize);
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return 0.0F;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    return 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+}
+
+#ifdef COMBINED
+#define thegame nullgame
+#endif
+
+const struct game thegame = {
+    "Null Game", NULL, NULL,
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    FALSE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    FALSE, solve_game,
+    FALSE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    20 /* FIXME */, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    FALSE, FALSE, game_print_size, game_print,
+    FALSE,                            /* wants_statusbar */
+    FALSE, game_timing_state,
+    0,                                /* flags */
+};
diff --git a/obfusc.c b/obfusc.c
new file mode 100644 (file)
index 0000000..dc0656c
--- /dev/null
+++ b/obfusc.c
@@ -0,0 +1,130 @@
+/*
+ * Stand-alone tool to access the Puzzles obfuscation algorithm.
+ * 
+ * To deobfuscate, use "obfusc -d":
+ * 
+ *   obfusc -d                 reads binary data from stdin, writes to stdout
+ *   obfusc -d <hex string>    works on the given hex string instead of stdin
+ *   obfusc -d -h              writes a hex string instead of binary to stdout
+ *
+ * To obfuscate, "obfusc -e":
+ * 
+ *   obfusc -e                 reads binary from stdin, writes hex to stdout
+ *   obfusc -e <hex string>    works on the given hex string instead of stdin
+ *   obfusc -e -b              writes binary instead of text to stdout
+ *
+ * The default output format is hex for -e and binary for -d
+ * because that's the way obfuscation is generally used in
+ * Puzzles. Either of -b and -h can always be specified to set it
+ * explicitly.
+ *
+ * Data read from standard input is assumed always to be binary;
+ * data provided on the command line is taken to be hex.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+
+#include "puzzles.h"
+
+int main(int argc, char **argv)
+{
+    enum { BINARY, DEFAULT, HEX } outputmode = DEFAULT;
+    char *inhex = NULL;
+    unsigned char *data;
+    int datalen;
+    int decode = -1;
+    int doing_opts = TRUE;
+
+    while (--argc > 0) {
+       char *p = *++argv;
+
+       if (doing_opts && *p == '-') {
+           if (!strcmp(p, "--")) {
+               doing_opts = 0;
+               continue;
+           }
+           p++;
+           while (*p) {
+               switch (*p) {
+                 case 'e':
+                   decode = 0;
+                   break;
+                 case 'd':
+                   decode = 1;
+                   break;
+                 case 'b':
+                   outputmode = BINARY;
+                   break;
+                 case 'h':
+                   outputmode = HEX;
+                   break;
+                 default:
+                   fprintf(stderr, "obfusc: unrecognised option '-%c'\n",
+                           *p);
+                   return 1;
+               }
+               p++;
+           }
+       } else {
+           if (!inhex) {
+               inhex = p;
+           } else {
+               fprintf(stderr, "obfusc: expected at most one argument\n");
+               return 1;
+           }
+       }
+    }
+
+    if (decode < 0) {
+       fprintf(stderr, "usage: obfusc < -e | -d > [ -b | -h ] [hex data]\n");
+       return 0;
+    }
+
+    if (outputmode == DEFAULT)
+       outputmode = (decode ? BINARY : HEX);
+
+    if (inhex) {
+       datalen = strlen(inhex) / 2;
+       data = hex2bin(inhex, datalen);
+    } else {
+       int datasize = 4096;
+       datalen = 0;
+       data = snewn(datasize, unsigned char);
+       while (1) {
+           int ret = fread(data + datalen, 1, datasize - datalen, stdin);
+           if (ret < 0) {
+               fprintf(stderr, "obfusc: read: %s\n", strerror(errno));
+               return 1;
+           } else if (ret == 0) {
+               break;
+           } else {
+               datalen += ret;
+               if (datasize - datalen < 4096) {
+                   datasize = datalen * 5 / 4 + 4096;
+                   data = sresize(data, datasize, unsigned char);
+               }
+           }
+       }
+    }
+
+    obfuscate_bitmap(data, datalen * 8, decode);
+
+    if (outputmode == BINARY) {
+       int ret = fwrite(data, 1, datalen, stdout);
+        if (ret < 0) {
+            fprintf(stderr, "obfusc: write: %s\n", strerror(errno));
+            return 1;
+        }
+    } else {
+       int i;
+       for (i = 0; i < datalen; i++)
+           printf("%02x", data[i]);
+       printf("\n");
+    }
+
+    return 0;
+}
diff --git a/osx-help.but b/osx-help.but
new file mode 100644 (file)
index 0000000..fa45996
--- /dev/null
@@ -0,0 +1,14 @@
+\# Additional Halibut fragment to set up the HTML output
+\# appropriately for MacOS online help.
+
+\cfg{html-head-end}{
+<style type="text/css">
+body \{ font-family: "Lucida Grande", Helvetica, Arial; font-size: 9pt \}
+h1 \{ font-size: 12pt \}
+h2 \{ font-size: 10pt \}
+h3 \{ font-size: 9pt \}
+h4 \{ font-size: 9pt \}
+h5 \{ font-size: 9pt \}
+h6 \{ font-size: 9pt \}
+</style>
+}
diff --git a/osx-info.plist b/osx-info.plist
new file mode 100644 (file)
index 0000000..a6592c1
--- /dev/null
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+       <key>CFBundleIconFile</key>
+       <string>Puzzles.icns</string>
+       <key>CFBundleHelpBookFolder</key>
+       <string>Help</string>
+       <key>CFBundleHelpBookName</key>
+       <string>Puzzles Help</string>
+       <key>CFBundleName</key>
+       <string>Puzzles</string>
+       <key>CFBundleDisplayName</key>
+       <string>Puzzles</string>
+       <key>CFBundleExecutable</key>
+       <string>Puzzles</string>
+       <key>CFBundleVersion</key>
+       <string>20161228.7cae89f</string>
+       <key>CFBundleShortVersionString</key>
+       <string>20161228.7cae89f</string>
+       <key>CFBundleDevelopmentRegion</key>
+       <string>en</string>
+       <key>CFBundleIdentifier</key>
+       <string>uk.org.greenend.chiark.sgtatham.puzzles</string>
+       <key>CFBundleInfoDictionaryVersion</key>
+       <string>6.0</string>
+       <key>CFBundlePackageType</key>
+       <string>APPL</string>
+       <key>CFBundleSignature</key>
+       <string>????</string>
+       <key>NSHumanReadableCopyright</key>
+       <string>This software is copyright (c) 2004-2014 Simon Tatham</string>
+</dict>
+</plist>
diff --git a/osx.icns b/osx.icns
new file mode 100644 (file)
index 0000000..b4346a0
Binary files /dev/null and b/osx.icns differ
diff --git a/osx.m b/osx.m
new file mode 100644 (file)
index 0000000..4740124
--- /dev/null
+++ b/osx.m
@@ -0,0 +1,1724 @@
+/*
+ * Mac OS X / Cocoa front end to puzzles.
+ *
+ * Still to do:
+ * 
+ *  - I'd like to be able to call up context help for a specific
+ *    game at a time.
+ * 
+ * Mac interface issues that possibly could be done better:
+ * 
+ *  - is there a better approach to frontend_default_colour?
+ *
+ *  - do we need any more options in the Window menu?
+ *
+ *  - can / should we be doing anything with the titles of the
+ *    configuration boxes?
+ * 
+ *  - not sure what I should be doing about default window
+ *    placement. Centring new windows is a bit feeble, but what's
+ *    better? Is there a standard way to tell the OS "here's the
+ *    _size_ of window I want, now use your best judgment about the
+ *    initial position"?
+ *     + there's a standard _policy_ on window placement, given in
+ *      the HI guidelines. Have to implement it ourselves though,
+ *      bah.
+ *
+ *  - a brief frob of the Mac numeric keypad suggests that it
+ *    generates numbers no matter what you do. I wonder if I should
+ *    try to figure out a way of detecting keypad codes so I can
+ *    implement UP_LEFT and friends. Alternatively, perhaps I
+ *    should simply assign the number keys to UP_LEFT et al?
+ *    They're not in use for anything else right now.
+ *
+ *  - see if we can do anything to one-button-ise the multi-button
+ *    dependent puzzle UIs:
+ *     - Pattern is a _little_ unwieldy but not too bad (since
+ *      generally you never need the middle button unless you've
+ *      made a mistake, so it's just click versus command-click).
+ *     - Net is utterly vile; having normal click be one rotate and
+ *      command-click be the other introduces a horrid asymmetry,
+ *      and yet requiring a shift key for _each_ click would be
+ *      even worse because rotation feels as if it ought to be the
+ *      default action. I fear this is why the Flash Net had the
+ *      UI it did...
+ *       + I've tried out an alternative dragging interface for
+ *         Net; it might work nicely for stylus-based platforms
+ *         where you have better hand/eye feedback for the thing
+ *         you're clicking on, but it's rather unwieldy on the
+ *         Mac. I fear even shift-clicking is better than that.
+ *
+ *  - Should we _return_ to a game configuration sheet once an
+ *    error is reported by midend_set_config, to allow the user to
+ *    correct the one faulty input and keep the other five OK ones?
+ *    The Apple `one sheet at a time' restriction would require me
+ *    to do this by closing the config sheet, opening the alert
+ *    sheet, and then reopening the config sheet when the alert is
+ *    closed; and the human interface types, who presumably
+ *    invented the one-sheet-at-a-time rule for good reasons, might
+ *    look with disfavour on me trying to get round them to fake a
+ *    nested sheet. On the other hand I think there are good
+ *    practical reasons for wanting it that way. Uncertain.
+ * 
+ *  - User feedback dislikes nothing happening when you start the
+ *    app; they suggest a finder-like window containing an icon for
+ *    each puzzle type, enabling you to start one easily. Needs
+ *    thought.
+ * 
+ * Grotty implementation details that could probably be improved:
+ * 
+ *  - I am _utterly_ unconvinced that NSImageView was the right way
+ *    to go about having a window with a reliable backing store! It
+ *    just doesn't feel right; NSImageView is a _control_. Is there
+ *    a simpler way?
+ * 
+ *  - Resizing is currently very bad; rather than bother to work
+ *    out how to resize the NSImageView, I just splatter and
+ *    recreate it.
+ */
+
+#define COMBINED /* we put all the puzzles in one binary in this port */
+
+#include <ctype.h>
+#include <time.h>
+#include <sys/time.h>
+#import <Cocoa/Cocoa.h>
+#include "puzzles.h"
+
+/* ----------------------------------------------------------------------
+ * Global variables.
+ */
+
+/*
+ * The `Type' menu. We frob this dynamically to allow the user to
+ * choose a preset set of settings from the current game.
+ */
+NSMenu *typemenu;
+
+/*
+ * Forward reference.
+ */
+extern const struct drawing_api osx_drawing;
+
+/*
+ * The NSApplication shared instance, which I'll want to refer to from
+ * a few places here and there.
+ */
+NSApplication *app;
+
+/* ----------------------------------------------------------------------
+ * Miscellaneous support routines that aren't part of any object or
+ * clearly defined subsystem.
+ */
+
+void fatal(char *fmt, ...)
+{
+    va_list ap;
+    char errorbuf[2048];
+    NSAlert *alert;
+
+    va_start(ap, fmt);
+    vsnprintf(errorbuf, lenof(errorbuf), fmt, ap);
+    va_end(ap);
+
+    alert = [NSAlert alloc];
+    /*
+     * We may have come here because we ran out of memory, in which
+     * case it's entirely likely that that alloc will fail, so we
+     * should have a fallback of some sort.
+     */
+    if (!alert) {
+       fprintf(stderr, "fatal error (and NSAlert failed): %s\n", errorbuf);
+    } else {
+       alert = [[alert init] autorelease];
+       [alert addButtonWithTitle:@"Oh dear"];
+       [alert setInformativeText:[NSString stringWithUTF8String:errorbuf]];
+       [alert runModal];
+    }
+    exit(1);
+}
+
+void frontend_default_colour(frontend *fe, float *output)
+{
+    /* FIXME: Is there a system default we can tap into for this? */
+    output[0] = output[1] = output[2] = 0.8F;
+}
+
+void get_random_seed(void **randseed, int *randseedsize)
+{
+    time_t *tp = snew(time_t);
+    time(tp);
+    *randseed = (void *)tp;
+    *randseedsize = sizeof(time_t);
+}
+
+static void savefile_write(void *wctx, void *buf, int len)
+{
+    FILE *fp = (FILE *)wctx;
+    fwrite(buf, 1, len, fp);
+}
+
+static int savefile_read(void *wctx, void *buf, int len)
+{
+    FILE *fp = (FILE *)wctx;
+    int ret;
+
+    ret = fread(buf, 1, len, fp);
+    return (ret == len);
+}
+
+/*
+ * Since this front end does not support printing (yet), we need
+ * this stub to satisfy the reference in midend_print_puzzle().
+ */
+void document_add_puzzle(document *doc, const game *game, game_params *par,
+                        game_state *st, game_state *st2)
+{
+}
+
+/*
+ * setAppleMenu isn't listed in the NSApplication header, but an
+ * NSApp responds to it, so we're adding it here to silence
+ * warnings. (This was removed from the headers in 10.4, so we
+ * only need to include it for 10.4+.)
+ */
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1040
+@interface NSApplication(NSAppleMenu)
+- (void)setAppleMenu:(NSMenu *)menu;
+@end
+#endif
+
+/* ----------------------------------------------------------------------
+ * Tiny extension to NSMenuItem which carries a payload of a `void
+ * *', allowing several menu items to invoke the same message but
+ * pass different data through it.
+ */
+@interface DataMenuItem : NSMenuItem
+{
+    void *payload;
+}
+- (void)setPayload:(void *)d;
+- (void *)getPayload;
+@end
+@implementation DataMenuItem
+- (void)setPayload:(void *)d
+{
+    payload = d;
+}
+- (void *)getPayload
+{
+    return payload;
+}
+@end
+
+/* ----------------------------------------------------------------------
+ * Utility routines for constructing OS X menus.
+ */
+
+NSMenu *newmenu(const char *title)
+{
+    return [[[NSMenu allocWithZone:[NSMenu menuZone]]
+            initWithTitle:[NSString stringWithUTF8String:title]]
+           autorelease];
+}
+
+NSMenu *newsubmenu(NSMenu *parent, const char *title)
+{
+    NSMenuItem *item;
+    NSMenu *child;
+
+    item = [[[NSMenuItem allocWithZone:[NSMenu menuZone]]
+            initWithTitle:[NSString stringWithUTF8String:title]
+            action:NULL
+            keyEquivalent:@""]
+           autorelease];
+    child = newmenu(title);
+    [item setEnabled:YES];
+    [item setSubmenu:child];
+    [parent addItem:item];
+    return child;
+}
+
+id initnewitem(NSMenuItem *item, NSMenu *parent, const char *title,
+              const char *key, id target, SEL action)
+{
+    unsigned mask = NSCommandKeyMask;
+
+    if (key[strcspn(key, "-")]) {
+       while (*key && *key != '-') {
+           int c = tolower((unsigned char)*key);
+           if (c == 's') {
+               mask |= NSShiftKeyMask;
+           } else if (c == 'o' || c == 'a') {
+               mask |= NSAlternateKeyMask;
+           }
+           key++;
+       }
+       if (*key)
+           key++;
+    }
+
+    item = [[item initWithTitle:[NSString stringWithUTF8String:title]
+            action:NULL
+            keyEquivalent:[NSString stringWithUTF8String:key]]
+           autorelease];
+
+    if (*key)
+       [item setKeyEquivalentModifierMask: mask];
+
+    [item setEnabled:YES];
+    [item setTarget:target];
+    [item setAction:action];
+
+    [parent addItem:item];
+
+    return item;
+}
+
+NSMenuItem *newitem(NSMenu *parent, char *title, char *key,
+                   id target, SEL action)
+{
+    return initnewitem([NSMenuItem allocWithZone:[NSMenu menuZone]],
+                      parent, title, key, target, action);
+}
+
+/* ----------------------------------------------------------------------
+ * About box.
+ */
+
+@class AboutBox;
+
+@interface AboutBox : NSWindow
+{
+}
+- (id)init;
+@end
+
+@implementation AboutBox
+- (id)init
+{
+    NSRect totalrect;
+    NSView *views[16];
+    int nviews = 0;
+    NSImageView *iv;
+    NSTextField *tf;
+    NSFont *font1 = [NSFont systemFontOfSize:0];
+    NSFont *font2 = [NSFont boldSystemFontOfSize:[font1 pointSize] * 1.1];
+    const int border = 24;
+    int i;
+    double y;
+
+    /*
+     * Construct the controls that go in the About box.
+     */
+
+    iv = [[NSImageView alloc] initWithFrame:NSMakeRect(0,0,64,64)];
+    [iv setImage:[NSImage imageNamed:@"NSApplicationIcon"]];
+    views[nviews++] = iv;
+
+    tf = [[NSTextField alloc]
+         initWithFrame:NSMakeRect(0,0,400,1)];
+    [tf setEditable:NO];
+    [tf setSelectable:NO];
+    [tf setBordered:NO];
+    [tf setDrawsBackground:NO];
+    [tf setFont:font2];
+    [tf setStringValue:@"Simon Tatham's Portable Puzzle Collection"];
+    [tf sizeToFit];
+    views[nviews++] = tf;
+
+    tf = [[NSTextField alloc]
+         initWithFrame:NSMakeRect(0,0,400,1)];
+    [tf setEditable:NO];
+    [tf setSelectable:NO];
+    [tf setBordered:NO];
+    [tf setDrawsBackground:NO];
+    [tf setFont:font1];
+    [tf setStringValue:[NSString stringWithUTF8String:ver]];
+    [tf sizeToFit];
+    views[nviews++] = tf;
+
+    /*
+     * Lay the controls out.
+     */
+    totalrect = NSMakeRect(0,0,0,0);
+    for (i = 0; i < nviews; i++) {
+       NSRect r = [views[i] frame];
+       if (totalrect.size.width < r.size.width)
+           totalrect.size.width = r.size.width;
+       totalrect.size.height += border + r.size.height;
+    }
+    totalrect.size.width += 2 * border;
+    totalrect.size.height += border;
+    y = totalrect.size.height;
+    for (i = 0; i < nviews; i++) {
+       NSRect r = [views[i] frame];
+       r.origin.x = (totalrect.size.width - r.size.width) / 2;
+       y -= border + r.size.height;
+       r.origin.y = y;
+       [views[i] setFrame:r];
+    }
+
+    self = [super initWithContentRect:totalrect
+           styleMask:(NSTitledWindowMask | NSMiniaturizableWindowMask |
+                      NSClosableWindowMask)
+           backing:NSBackingStoreBuffered
+           defer:YES];
+
+    for (i = 0; i < nviews; i++)
+       [[self contentView] addSubview:views[i]];
+
+    [self center];                    /* :-) */
+
+    return self;
+}
+@end
+
+/* ----------------------------------------------------------------------
+ * The front end presented to midend.c.
+ * 
+ * This is mostly a subclass of NSWindow. The actual `frontend'
+ * structure passed to the midend contains a variety of pointers,
+ * including that window object but also including the image we
+ * draw on, an ImageView to display it in the window, and so on.
+ */
+
+@class GameWindow;
+@class MyImageView;
+
+struct frontend {
+    GameWindow *window;
+    NSImage *image;
+    MyImageView *view;
+    NSColor **colours;
+    int ncolours;
+    int clipped;
+    int w, h;
+};
+
+@interface MyImageView : NSImageView
+{
+    GameWindow *ourwin;
+}
+- (void)setWindow:(GameWindow *)win;
+- (void)mouseEvent:(NSEvent *)ev button:(int)b;
+- (void)mouseDown:(NSEvent *)ev;
+- (void)mouseDragged:(NSEvent *)ev;
+- (void)mouseUp:(NSEvent *)ev;
+- (void)rightMouseDown:(NSEvent *)ev;
+- (void)rightMouseDragged:(NSEvent *)ev;
+- (void)rightMouseUp:(NSEvent *)ev;
+- (void)otherMouseDown:(NSEvent *)ev;
+- (void)otherMouseDragged:(NSEvent *)ev;
+- (void)otherMouseUp:(NSEvent *)ev;
+@end
+
+@interface GameWindow : NSWindow
+{
+    const game *ourgame;
+    midend *me;
+    struct frontend fe;
+    struct timeval last_time;
+    NSTimer *timer;
+    NSWindow *sheet;
+    config_item *cfg;
+    int cfg_which;
+    NSView **cfg_controls;
+    int cfg_ncontrols;
+    NSTextField *status;
+}
+- (id)initWithGame:(const game *)g;
+- (void)dealloc;
+- (void)processButton:(int)b x:(int)x y:(int)y;
+- (void)processKey:(int)b;
+- (void)keyDown:(NSEvent *)ev;
+- (void)activateTimer;
+- (void)deactivateTimer;
+- (void)setStatusLine:(char *)text;
+- (void)resizeForNewGameParams;
+- (void)updateTypeMenuTick;
+@end
+
+@implementation MyImageView
+
+- (void)setWindow:(GameWindow *)win
+{
+    ourwin = win;
+}
+
+- (void)mouseEvent:(NSEvent *)ev button:(int)b
+{
+    NSPoint point = [self convertPoint:[ev locationInWindow] fromView:nil];
+    [ourwin processButton:b x:point.x y:point.y];
+}
+
+- (void)mouseDown:(NSEvent *)ev
+{
+    unsigned mod = [ev modifierFlags];
+    [self mouseEvent:ev button:((mod & NSCommandKeyMask) ? RIGHT_BUTTON :
+                               (mod & NSShiftKeyMask) ? MIDDLE_BUTTON :
+                               LEFT_BUTTON)];
+}
+- (void)mouseDragged:(NSEvent *)ev
+{
+    unsigned mod = [ev modifierFlags];
+    [self mouseEvent:ev button:((mod & NSCommandKeyMask) ? RIGHT_DRAG :
+                               (mod & NSShiftKeyMask) ? MIDDLE_DRAG :
+                               LEFT_DRAG)];
+}
+- (void)mouseUp:(NSEvent *)ev
+{
+    unsigned mod = [ev modifierFlags];
+    [self mouseEvent:ev button:((mod & NSCommandKeyMask) ? RIGHT_RELEASE :
+                               (mod & NSShiftKeyMask) ? MIDDLE_RELEASE :
+                               LEFT_RELEASE)];
+}
+- (void)rightMouseDown:(NSEvent *)ev
+{
+    unsigned mod = [ev modifierFlags];
+    [self mouseEvent:ev button:((mod & NSShiftKeyMask) ? MIDDLE_BUTTON :
+                               RIGHT_BUTTON)];
+}
+- (void)rightMouseDragged:(NSEvent *)ev
+{
+    unsigned mod = [ev modifierFlags];
+    [self mouseEvent:ev button:((mod & NSShiftKeyMask) ? MIDDLE_DRAG :
+                               RIGHT_DRAG)];
+}
+- (void)rightMouseUp:(NSEvent *)ev
+{
+    unsigned mod = [ev modifierFlags];
+    [self mouseEvent:ev button:((mod & NSShiftKeyMask) ? MIDDLE_RELEASE :
+                               RIGHT_RELEASE)];
+}
+- (void)otherMouseDown:(NSEvent *)ev
+{
+    [self mouseEvent:ev button:MIDDLE_BUTTON];
+}
+- (void)otherMouseDragged:(NSEvent *)ev
+{
+    [self mouseEvent:ev button:MIDDLE_DRAG];
+}
+- (void)otherMouseUp:(NSEvent *)ev
+{
+    [self mouseEvent:ev button:MIDDLE_RELEASE];
+}
+@end
+
+@implementation GameWindow
+- (void)setupContentView
+{
+    NSRect frame;
+    int w, h;
+
+    if (status) {
+       frame = [status frame];
+       frame.origin.y = frame.size.height;
+    } else
+       frame.origin.y = 0;
+    frame.origin.x = 0;
+
+    w = h = INT_MAX;
+    midend_size(me, &w, &h, FALSE);
+    frame.size.width = w;
+    frame.size.height = h;
+    fe.w = w;
+    fe.h = h;
+
+    fe.image = [[NSImage alloc] initWithSize:frame.size];
+    fe.view = [[MyImageView alloc] initWithFrame:frame];
+    [fe.view setImage:fe.image];
+    [fe.view setWindow:self];
+
+    midend_redraw(me);
+
+    [[self contentView] addSubview:fe.view];
+}
+- (id)initWithGame:(const game *)g
+{
+    NSRect rect = { {0,0}, {0,0} }, rect2;
+    int w, h;
+
+    ourgame = g;
+
+    fe.window = self;
+
+    me = midend_new(&fe, ourgame, &osx_drawing, &fe);
+    /*
+     * If we ever need to open a fresh window using a provided game
+     * ID, I think the right thing is to move most of this method
+     * into a new initWithGame:gameID: method, and have
+     * initWithGame: simply call that one and pass it NULL.
+     */
+    midend_new_game(me);
+    w = h = INT_MAX;
+    midend_size(me, &w, &h, FALSE);
+    rect.size.width = w;
+    rect.size.height = h;
+    fe.w = w;
+    fe.h = h;
+
+    /*
+     * Create the status bar, which will just be an NSTextField.
+     */
+    if (midend_wants_statusbar(me)) {
+       status = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,100,50)];
+       [status setEditable:NO];
+       [status setSelectable:NO];
+       [status setBordered:YES];
+       [status setBezeled:YES];
+       [status setBezelStyle:NSTextFieldSquareBezel];
+       [status setDrawsBackground:YES];
+       [[status cell] setTitle:@DEFAULT_STATUSBAR_TEXT];
+       [status sizeToFit];
+       rect2 = [status frame];
+       rect.size.height += rect2.size.height;
+       rect2.size.width = rect.size.width;
+       rect2.origin.x = rect2.origin.y = 0;
+       [status setFrame:rect2];
+    } else
+       status = nil;
+
+    self = [super initWithContentRect:rect
+           styleMask:(NSTitledWindowMask | NSMiniaturizableWindowMask |
+                      NSClosableWindowMask)
+           backing:NSBackingStoreBuffered
+           defer:YES];
+    [self setTitle:[NSString stringWithUTF8String:ourgame->name]];
+
+    {
+       float *colours;
+       int i, ncolours;
+
+       colours = midend_colours(me, &ncolours);
+       fe.ncolours = ncolours;
+       fe.colours = snewn(ncolours, NSColor *);
+
+       for (i = 0; i < ncolours; i++) {
+           fe.colours[i] = [[NSColor colorWithDeviceRed:colours[i*3]
+                             green:colours[i*3+1] blue:colours[i*3+2]
+                             alpha:1.0] retain];
+       }
+    }
+
+    [self setupContentView];
+    if (status)
+       [[self contentView] addSubview:status];
+    [self setIgnoresMouseEvents:NO];
+
+    [self center];                    /* :-) */
+
+    return self;
+}
+
+- (void)dealloc
+{
+    int i;
+    for (i = 0; i < fe.ncolours; i++) {
+       [fe.colours[i] release];
+    }
+    sfree(fe.colours);
+    midend_free(me);
+    [super dealloc];
+}
+
+- (void)processButton:(int)b x:(int)x y:(int)y
+{
+    if (!midend_process_key(me, x, fe.h - 1 - y, b))
+       [self close];
+}
+
+- (void)processKey:(int)b
+{
+    if (!midend_process_key(me, -1, -1, b))
+       [self close];
+}
+
+- (void)keyDown:(NSEvent *)ev
+{
+    NSString *s = [ev characters];
+    int i, n = [s length];
+
+    for (i = 0; i < n; i++) {
+       int c = [s characterAtIndex:i];
+
+       /*
+        * ASCII gets passed straight to midend_process_key.
+        * Anything above that has to be translated to our own
+        * function key codes.
+        */
+       if (c >= 0x80) {
+           int mods = FALSE;
+           switch (c) {
+             case NSUpArrowFunctionKey:
+               c = CURSOR_UP;
+               mods = TRUE;
+               break;
+             case NSDownArrowFunctionKey:
+               c = CURSOR_DOWN;
+               mods = TRUE;
+               break;
+             case NSLeftArrowFunctionKey:
+               c = CURSOR_LEFT;
+               mods = TRUE;
+               break;
+             case NSRightArrowFunctionKey:
+               c = CURSOR_RIGHT;
+               mods = TRUE;
+               break;
+             default:
+               continue;
+           }
+
+           if (mods) {
+               if ([ev modifierFlags] & NSShiftKeyMask)
+                   c |= MOD_SHFT;
+               if ([ev modifierFlags] & NSControlKeyMask)
+                   c |= MOD_CTRL;
+           }
+       }
+
+       if (c >= '0' && c <= '9' && ([ev modifierFlags] & NSNumericPadKeyMask))
+           c |= MOD_NUM_KEYPAD;
+
+       [self processKey:c];
+    }
+}
+
+- (void)activateTimer
+{
+    if (timer != nil)
+       return;
+
+    timer = [NSTimer scheduledTimerWithTimeInterval:0.02
+            target:self selector:@selector(timerTick:)
+            userInfo:nil repeats:YES];
+    gettimeofday(&last_time, NULL);
+}
+
+- (void)deactivateTimer
+{
+    if (timer == nil)
+       return;
+
+    [timer invalidate];
+    timer = nil;
+}
+
+- (void)timerTick:(id)sender
+{
+    struct timeval now;
+    float elapsed;
+    gettimeofday(&now, NULL);
+    elapsed = ((now.tv_usec - last_time.tv_usec) * 0.000001F +
+              (now.tv_sec - last_time.tv_sec));
+    midend_timer(me, elapsed);
+    last_time = now;
+}
+
+- (void)showError:(char *)message
+{
+    NSAlert *alert;
+
+    alert = [[[NSAlert alloc] init] autorelease];
+    [alert addButtonWithTitle:@"Bah"];
+    [alert setInformativeText:[NSString stringWithUTF8String:message]];
+    [alert beginSheetModalForWindow:self modalDelegate:nil
+     didEndSelector:NULL contextInfo:nil];
+}
+
+- (void)newGame:(id)sender
+{
+    [self processKey:'n'];
+}
+- (void)restartGame:(id)sender
+{
+    midend_restart_game(me);
+}
+- (void)saveGame:(id)sender
+{
+    NSSavePanel *sp = [NSSavePanel savePanel];
+
+    if ([sp runModal] == NSFileHandlingPanelOKButton) {
+       const char *name = [[sp filename] UTF8String];
+
+        FILE *fp = fopen(name, "w");
+
+        if (!fp) {
+            [self showError:"Unable to open save file"];
+            return;
+        }
+
+        midend_serialise(me, savefile_write, fp);
+
+        fclose(fp);
+    }
+}
+- (void)loadSavedGame:(id)sender
+{
+    NSOpenPanel *op = [NSOpenPanel openPanel];
+
+    [op setAllowsMultipleSelection:NO];
+
+    if ([op runModalForTypes:nil] == NSOKButton) {
+        /*
+         * This used to be
+         *
+         *    [[[op filenames] objectAtIndex:0] cString]
+         *
+         * but the plain cString method became deprecated and Xcode 7
+         * started complaining about it. Since OS X 10.9 we can
+         * apparently use the more modern API
+         *
+         *    [[[op URLs] objectAtIndex:0] fileSystemRepresentation]
+         *
+         * but the alternative below still compiles with Xcode 7 and
+         * is a bit more backwards compatible, so I'll try it for the
+         * moment.
+         */
+       const char *name = [[[op filenames] objectAtIndex:0]
+                               cStringUsingEncoding:
+                                   [NSString defaultCStringEncoding]];
+       char *err;
+
+        FILE *fp = fopen(name, "r");
+
+        if (!fp) {
+            [self showError:"Unable to open saved game file"];
+            return;
+        }
+
+        err = midend_deserialise(me, savefile_read, fp);
+
+        fclose(fp);
+
+        if (err) {
+            [self showError:err];
+            return;
+        }
+
+       [self resizeForNewGameParams];
+       [self updateTypeMenuTick];
+    }
+}
+- (void)undoMove:(id)sender
+{
+    [self processKey:'u'];
+}
+- (void)redoMove:(id)sender
+{
+    [self processKey:'r'&0x1F];
+}
+
+- (void)copy:(id)sender
+{
+    char *text;
+
+    if ((text = midend_text_format(me)) != NULL) {
+       NSPasteboard *pb = [NSPasteboard generalPasteboard];
+       NSArray *a = [NSArray arrayWithObject:NSStringPboardType];
+       [pb declareTypes:a owner:nil];
+       [pb setString:[NSString stringWithUTF8String:text]
+        forType:NSStringPboardType];
+    } else
+       NSBeep();
+}
+
+- (void)solveGame:(id)sender
+{
+    char *msg;
+
+    msg = midend_solve(me);
+
+    if (msg)
+       [self showError:msg];
+}
+
+- (BOOL)validateMenuItem:(NSMenuItem *)item
+{
+    if ([item action] == @selector(copy:))
+       return (ourgame->can_format_as_text_ever &&
+               midend_can_format_as_text_now(me) ? YES : NO);
+    else if ([item action] == @selector(solveGame:))
+       return (ourgame->can_solve ? YES : NO);
+    else
+       return [super validateMenuItem:item];
+}
+
+- (void)clearTypeMenu
+{
+    while ([typemenu numberOfItems] > 1)
+       [typemenu removeItemAtIndex:0];
+    [[typemenu itemAtIndex:0] setState:NSOffState];
+}
+
+- (void)updateTypeMenuTick
+{
+    int i, total, n;
+
+    total = [typemenu numberOfItems];
+    n = midend_which_preset(me);
+    if (n < 0)
+       n = total - 1;                 /* that's always where "Custom" lives */
+    for (i = 0; i < total; i++)
+       [[typemenu itemAtIndex:i] setState:(i == n ? NSOnState : NSOffState)];
+}
+
+- (void)becomeKeyWindow
+{
+    int n;
+
+    [self clearTypeMenu];
+
+    [super becomeKeyWindow];
+
+    n = midend_num_presets(me);
+
+    if (n > 0) {
+       [typemenu insertItem:[NSMenuItem separatorItem] atIndex:0];
+       while (n--) {
+           char *name;
+           game_params *params;
+           DataMenuItem *item;
+
+           midend_fetch_preset(me, n, &name, &params);
+
+           item = [[[DataMenuItem alloc]
+                    initWithTitle:[NSString stringWithUTF8String:name]
+                    action:NULL keyEquivalent:@""]
+                   autorelease];
+
+           [item setEnabled:YES];
+           [item setTarget:self];
+           [item setAction:@selector(presetGame:)];
+           [item setPayload:params];
+
+           [typemenu insertItem:item atIndex:0];
+       }
+    }
+
+    [self updateTypeMenuTick];
+}
+
+- (void)resignKeyWindow
+{
+    [self clearTypeMenu];
+    [super resignKeyWindow];
+}
+
+- (void)close
+{
+    [self clearTypeMenu];
+    [super close];
+}
+
+- (void)resizeForNewGameParams
+{
+    NSSize size = {0,0};
+    int w, h;
+
+    w = h = INT_MAX;
+    midend_size(me, &w, &h, FALSE);
+    size.width = w;
+    size.height = h;
+    fe.w = w;
+    fe.h = h;
+
+    if (status) {
+       NSRect frame = [status frame];
+       size.height += frame.size.height;
+       frame.size.width = size.width;
+       [status setFrame:frame];
+    }
+
+#ifndef GNUSTEP
+    NSDisableScreenUpdates();
+#endif
+    [self setContentSize:size];
+    [self setupContentView];
+#ifndef GNUSTEP
+    NSEnableScreenUpdates();
+#endif
+}
+
+- (void)presetGame:(id)sender
+{
+    game_params *params = [sender getPayload];
+
+    midend_set_params(me, params);
+    midend_new_game(me);
+
+    [self resizeForNewGameParams];
+    [self updateTypeMenuTick];
+}
+
+- (void)startConfigureSheet:(int)which
+{
+    NSButton *ok, *cancel;
+    int actw, acth, leftw, rightw, totalw, h, thish, y;
+    int k;
+    NSRect rect, tmprect;
+    const int SPACING = 16;
+    char *title;
+    config_item *i;
+    int cfg_controlsize;
+    NSTextField *tf;
+    NSButton *b;
+    NSPopUpButton *pb;
+
+    assert(sheet == NULL);
+
+    /*
+     * Every control we create here is going to have this size
+     * until we tell it to calculate a better one.
+     */
+    tmprect = NSMakeRect(0, 0, 100, 50);
+
+    /*
+     * Set up OK and Cancel buttons. (Actually, MacOS doesn't seem
+     * to be fond of generic OK and Cancel wording, so I'm going to
+     * rename them to something nicer.)
+     */
+    actw = acth = 0;
+
+    cancel = [[NSButton alloc] initWithFrame:tmprect];
+    [cancel setBezelStyle:NSRoundedBezelStyle];
+    [cancel setTitle:@"Abandon"];
+    [cancel setTarget:self];
+    [cancel setKeyEquivalent:@"\033"];
+    [cancel setAction:@selector(sheetCancelButton:)];
+    [cancel sizeToFit];
+    rect = [cancel frame];
+    if (actw < rect.size.width) actw = rect.size.width;
+    if (acth < rect.size.height) acth = rect.size.height;
+
+    ok = [[NSButton alloc] initWithFrame:tmprect];
+    [ok setBezelStyle:NSRoundedBezelStyle];
+    [ok setTitle:@"Accept"];
+    [ok setTarget:self];
+    [ok setKeyEquivalent:@"\r"];
+    [ok setAction:@selector(sheetOKButton:)];
+    [ok sizeToFit];
+    rect = [ok frame];
+    if (actw < rect.size.width) actw = rect.size.width;
+    if (acth < rect.size.height) acth = rect.size.height;
+
+    totalw = SPACING + 2 * actw;
+    h = 2 * SPACING + acth;
+
+    /*
+     * Now fetch the midend config data and go through it creating
+     * controls.
+     */
+    cfg = midend_get_config(me, which, &title);
+    sfree(title);                     /* FIXME: should we use this somehow? */
+    cfg_which = which;
+
+    cfg_ncontrols = cfg_controlsize = 0;
+    cfg_controls = NULL;
+    leftw = rightw = 0;
+    for (i = cfg; i->type != C_END; i++) {
+       if (cfg_controlsize < cfg_ncontrols + 5) {
+           cfg_controlsize = cfg_ncontrols + 32;
+           cfg_controls = sresize(cfg_controls, cfg_controlsize, NSView *);
+       }
+
+       thish = 0;
+
+       switch (i->type) {
+         case C_STRING:
+           /*
+            * Two NSTextFields, one being a label and the other
+            * being an edit box.
+            */
+
+           tf = [[NSTextField alloc] initWithFrame:tmprect];
+           [tf setEditable:NO];
+           [tf setSelectable:NO];
+           [tf setBordered:NO];
+           [tf setDrawsBackground:NO];
+           [[tf cell] setTitle:[NSString stringWithUTF8String:i->name]];
+           [tf sizeToFit];
+           rect = [tf frame];
+           if (thish < rect.size.height + 1) thish = rect.size.height + 1;
+           if (leftw < rect.size.width + 1) leftw = rect.size.width + 1;
+           cfg_controls[cfg_ncontrols++] = tf;
+
+           tf = [[NSTextField alloc] initWithFrame:tmprect];
+           [tf setEditable:YES];
+           [tf setSelectable:YES];
+           [tf setBordered:YES];
+           [[tf cell] setTitle:[NSString stringWithUTF8String:i->sval]];
+           [tf sizeToFit];
+           rect = [tf frame];
+           /*
+            * We impose a minimum and maximum width on editable
+            * NSTextFields. If we allow them to size themselves to
+            * the contents of the text within them, then they will
+            * look very silly if that text is only one or two
+            * characters, and equally silly if it's an absolutely
+            * enormous Rectangles or Pattern game ID!
+            */
+           if (rect.size.width < 75) rect.size.width = 75;
+           if (rect.size.width > 400) rect.size.width = 400;
+
+           if (thish < rect.size.height + 1) thish = rect.size.height + 1;
+           if (rightw < rect.size.width + 1) rightw = rect.size.width + 1;
+           cfg_controls[cfg_ncontrols++] = tf;
+           break;
+
+         case C_BOOLEAN:
+           /*
+            * A checkbox is an NSButton with a type of
+            * NSSwitchButton.
+            */
+           b = [[NSButton alloc] initWithFrame:tmprect];
+           [b setBezelStyle:NSRoundedBezelStyle];
+           [b setButtonType:NSSwitchButton];
+           [b setTitle:[NSString stringWithUTF8String:i->name]];
+           [b sizeToFit];
+           [b setState:(i->ival ? NSOnState : NSOffState)];
+           rect = [b frame];
+           if (totalw < rect.size.width + 1) totalw = rect.size.width + 1;
+           if (thish < rect.size.height + 1) thish = rect.size.height + 1;
+           cfg_controls[cfg_ncontrols++] = b;
+           break;
+
+         case C_CHOICES:
+           /*
+            * A pop-up menu control is an NSPopUpButton, which
+            * takes an embedded NSMenu. We also need an
+            * NSTextField to act as a label.
+            */
+
+           tf = [[NSTextField alloc] initWithFrame:tmprect];
+           [tf setEditable:NO];
+           [tf setSelectable:NO];
+           [tf setBordered:NO];
+           [tf setDrawsBackground:NO];
+           [[tf cell] setTitle:[NSString stringWithUTF8String:i->name]];
+           [tf sizeToFit];
+           rect = [tf frame];
+           if (thish < rect.size.height + 1) thish = rect.size.height + 1;
+           if (leftw < rect.size.width + 1) leftw = rect.size.width + 1;
+           cfg_controls[cfg_ncontrols++] = tf;
+
+           pb = [[NSPopUpButton alloc] initWithFrame:tmprect pullsDown:NO];
+           [pb setBezelStyle:NSRoundedBezelStyle];
+           {
+               char c, *p;
+
+               p = i->sval;
+               c = *p++;
+               while (*p) {
+                   char *q, *copy;
+
+                   q = p;
+                   while (*p && *p != c) p++;
+
+                    copy = snewn((p-q) + 1, char);
+                    memcpy(copy, q, p-q);
+                    copy[p-q] = '\0';
+                   [pb addItemWithTitle:[NSString stringWithUTF8String:copy]];
+                    sfree(copy);
+
+                   if (*p) p++;
+               }
+           }
+           [pb selectItemAtIndex:i->ival];
+           [pb sizeToFit];
+
+           rect = [pb frame];
+           if (rightw < rect.size.width + 1) rightw = rect.size.width + 1;
+           if (thish < rect.size.height + 1) thish = rect.size.height + 1;
+           cfg_controls[cfg_ncontrols++] = pb;
+           break;
+       }
+
+       h += SPACING + thish;
+    }
+
+    if (totalw < leftw + SPACING + rightw)
+       totalw = leftw + SPACING + rightw;
+    if (totalw > leftw + SPACING + rightw) {
+       int excess = totalw - (leftw + SPACING + rightw);
+       int leftexcess = leftw * excess / (leftw + rightw);
+       int rightexcess = excess - leftexcess;
+       leftw += leftexcess;
+       rightw += rightexcess;
+    }
+
+    /*
+     * Now go through the list again, setting the final position
+     * for each control.
+     */
+    k = 0;
+    y = h;
+    for (i = cfg; i->type != C_END; i++) {
+       y -= SPACING;
+       thish = 0;
+       switch (i->type) {
+         case C_STRING:
+         case C_CHOICES:
+           /*
+            * These two are treated identically, since both expect
+            * a control on the left and another on the right.
+            */
+           rect = [cfg_controls[k] frame];
+           if (thish < rect.size.height + 1)
+               thish = rect.size.height + 1;
+           rect = [cfg_controls[k+1] frame];
+           if (thish < rect.size.height + 1)
+               thish = rect.size.height + 1;
+           rect = [cfg_controls[k] frame];
+           rect.origin.y = y - thish/2 - rect.size.height/2;
+           rect.origin.x = SPACING;
+           rect.size.width = leftw;
+           [cfg_controls[k] setFrame:rect];
+           rect = [cfg_controls[k+1] frame];
+           rect.origin.y = y - thish/2 - rect.size.height/2;
+           rect.origin.x = 2 * SPACING + leftw;
+           rect.size.width = rightw;
+           [cfg_controls[k+1] setFrame:rect];
+           k += 2;
+           break;
+
+         case C_BOOLEAN:
+           rect = [cfg_controls[k] frame];
+           if (thish < rect.size.height + 1)
+               thish = rect.size.height + 1;
+           rect.origin.y = y - thish/2 - rect.size.height/2;
+           rect.origin.x = SPACING;
+           rect.size.width = totalw;
+           [cfg_controls[k] setFrame:rect];
+           k++;
+           break;
+       }
+       y -= thish;
+    }
+
+    assert(k == cfg_ncontrols);
+
+    [cancel setFrame:NSMakeRect(SPACING+totalw/4-actw/2, SPACING, actw, acth)];
+    [ok setFrame:NSMakeRect(SPACING+3*totalw/4-actw/2, SPACING, actw, acth)];
+
+    sheet = [[NSWindow alloc]
+            initWithContentRect:NSMakeRect(0,0,totalw + 2*SPACING,h)
+            styleMask:NSTitledWindowMask | NSClosableWindowMask
+            backing:NSBackingStoreBuffered
+            defer:YES];
+
+    [[sheet contentView] addSubview:cancel];
+    [[sheet contentView] addSubview:ok];
+
+    for (k = 0; k < cfg_ncontrols; k++)
+       [[sheet contentView] addSubview:cfg_controls[k]];
+
+    [app beginSheet:sheet modalForWindow:self
+     modalDelegate:nil didEndSelector:NULL contextInfo:nil];
+}
+
+- (void)specificGame:(id)sender
+{
+    [self startConfigureSheet:CFG_DESC];
+}
+
+- (void)specificRandomGame:(id)sender
+{
+    [self startConfigureSheet:CFG_SEED];
+}
+
+- (void)customGameType:(id)sender
+{
+    [self startConfigureSheet:CFG_SETTINGS];
+}
+
+- (void)sheetEndWithStatus:(BOOL)update
+{
+    assert(sheet != NULL);
+    [app endSheet:sheet];
+    [sheet orderOut:self];
+    sheet = NULL;
+    if (update) {
+       int k;
+       config_item *i;
+       char *error;
+
+       k = 0;
+       for (i = cfg; i->type != C_END; i++) {
+           switch (i->type) {
+             case C_STRING:
+               sfree(i->sval);
+               i->sval = dupstr([[[(id)cfg_controls[k+1] cell]
+                                  title] UTF8String]);
+               k += 2;
+               break;
+             case C_BOOLEAN:
+               i->ival = [(id)cfg_controls[k] state] == NSOnState;
+               k++;
+               break;
+             case C_CHOICES:
+               i->ival = [(id)cfg_controls[k+1] indexOfSelectedItem];
+               k += 2;
+               break;
+           }
+       }
+
+       error = midend_set_config(me, cfg_which, cfg);
+       if (error) {
+           NSAlert *alert = [[[NSAlert alloc] init] autorelease];
+           [alert addButtonWithTitle:@"Bah"];
+           [alert setInformativeText:[NSString stringWithUTF8String:error]];
+           [alert beginSheetModalForWindow:self modalDelegate:nil
+            didEndSelector:NULL contextInfo:nil];
+       } else {
+           midend_new_game(me);
+           [self resizeForNewGameParams];
+           [self updateTypeMenuTick];
+       }
+    }
+    sfree(cfg_controls);
+    cfg_controls = NULL;
+}
+- (void)sheetOKButton:(id)sender
+{
+    [self sheetEndWithStatus:YES];
+}
+- (void)sheetCancelButton:(id)sender
+{
+    [self sheetEndWithStatus:NO];
+}
+
+- (void)setStatusLine:(char *)text
+{
+    [[status cell] setTitle:[NSString stringWithUTF8String:text]];
+}
+
+@end
+
+/*
+ * Drawing routines called by the midend.
+ */
+static void osx_draw_polygon(void *handle, int *coords, int npoints,
+                            int fillcolour, int outlinecolour)
+{
+    frontend *fe = (frontend *)handle;
+    NSBezierPath *path = [NSBezierPath bezierPath];
+    int i;
+
+    [[NSGraphicsContext currentContext] setShouldAntialias:YES];
+
+    for (i = 0; i < npoints; i++) {
+       NSPoint p = { coords[i*2] + 0.5, fe->h - coords[i*2+1] - 0.5 };
+       if (i == 0)
+           [path moveToPoint:p];
+       else
+           [path lineToPoint:p];
+    }
+
+    [path closePath];
+
+    if (fillcolour >= 0) {
+       assert(fillcolour >= 0 && fillcolour < fe->ncolours);
+       [fe->colours[fillcolour] set];
+       [path fill];
+    }
+
+    assert(outlinecolour >= 0 && outlinecolour < fe->ncolours);
+    [fe->colours[outlinecolour] set];
+    [path stroke];
+}
+static void osx_draw_circle(void *handle, int cx, int cy, int radius,
+                           int fillcolour, int outlinecolour)
+{
+    frontend *fe = (frontend *)handle;
+    NSBezierPath *path = [NSBezierPath bezierPath];
+
+    [[NSGraphicsContext currentContext] setShouldAntialias:YES];
+
+    [path appendBezierPathWithArcWithCenter:NSMakePoint(cx+0.5, fe->h-cy-0.5)
+        radius:radius startAngle:0.0 endAngle:360.0];
+
+    [path closePath];
+
+    if (fillcolour >= 0) {
+       assert(fillcolour >= 0 && fillcolour < fe->ncolours);
+       [fe->colours[fillcolour] set];
+       [path fill];
+    }
+
+    assert(outlinecolour >= 0 && outlinecolour < fe->ncolours);
+    [fe->colours[outlinecolour] set];
+    [path stroke];
+}
+static void osx_draw_line(void *handle, int x1, int y1, int x2, int y2, int colour)
+{
+    frontend *fe = (frontend *)handle;
+    NSBezierPath *path = [NSBezierPath bezierPath];
+    NSPoint p1 = { x1 + 0.5, fe->h - y1 - 0.5 };
+    NSPoint p2 = { x2 + 0.5, fe->h - y2 - 0.5 };
+
+    [[NSGraphicsContext currentContext] setShouldAntialias:NO];
+
+    assert(colour >= 0 && colour < fe->ncolours);
+    [fe->colours[colour] set];
+
+    [path moveToPoint:p1];
+    [path lineToPoint:p2];
+    [path stroke];
+    NSRectFill(NSMakeRect(x1, fe->h-y1-1, 1, 1));
+    NSRectFill(NSMakeRect(x2, fe->h-y2-1, 1, 1));
+}
+
+static void osx_draw_thick_line(
+    void *handle, float thickness,
+    float x1, float y1,
+    float x2, float y2,
+    int colour)
+{
+    frontend *fe = (frontend *)handle;
+    NSBezierPath *path = [NSBezierPath bezierPath];
+
+    assert(colour >= 0 && colour < fe->ncolours);
+    [fe->colours[colour] set];
+    [[NSGraphicsContext currentContext] setShouldAntialias: YES];
+    [path setLineWidth: thickness];
+    [path setLineCapStyle: NSButtLineCapStyle];
+    [path moveToPoint: NSMakePoint(x1, fe->h-y1)];
+    [path lineToPoint: NSMakePoint(x2, fe->h-y2)];
+    [path stroke];
+}
+
+static void osx_draw_rect(void *handle, int x, int y, int w, int h, int colour)
+{
+    frontend *fe = (frontend *)handle;
+    NSRect r = { {x, fe->h - y - h}, {w,h} };
+    
+    [[NSGraphicsContext currentContext] setShouldAntialias:NO];
+
+    assert(colour >= 0 && colour < fe->ncolours);
+    [fe->colours[colour] set];
+
+    NSRectFill(r);
+}
+static void osx_draw_text(void *handle, int x, int y, int fonttype,
+                         int fontsize, int align, int colour, char *text)
+{
+    frontend *fe = (frontend *)handle;
+    NSString *string = [NSString stringWithUTF8String:text];
+    NSDictionary *attr;
+    NSFont *font;
+    NSSize size;
+    NSPoint point;
+
+    [[NSGraphicsContext currentContext] setShouldAntialias:YES];
+
+    assert(colour >= 0 && colour < fe->ncolours);
+
+    if (fonttype == FONT_FIXED)
+       font = [NSFont userFixedPitchFontOfSize:fontsize];
+    else
+       font = [NSFont userFontOfSize:fontsize];
+
+    attr = [NSDictionary dictionaryWithObjectsAndKeys:
+           fe->colours[colour], NSForegroundColorAttributeName,
+           font, NSFontAttributeName, nil];
+
+    point.x = x;
+    point.y = fe->h - y;
+
+    size = [string sizeWithAttributes:attr];
+    if (align & ALIGN_HRIGHT)
+       point.x -= size.width;
+    else if (align & ALIGN_HCENTRE)
+       point.x -= size.width / 2;
+    if (align & ALIGN_VCENTRE)
+        point.y -= size.height / 2;
+
+    [string drawAtPoint:point withAttributes:attr];
+}
+static char *osx_text_fallback(void *handle, const char *const *strings,
+                              int nstrings)
+{
+    /*
+     * We assume OS X can cope with any UTF-8 likely to be emitted
+     * by a puzzle.
+     */
+    return dupstr(strings[0]);
+}
+struct blitter {
+    int w, h;
+    int x, y;
+    NSImage *img;
+};
+static blitter *osx_blitter_new(void *handle, int w, int h)
+{
+    blitter *bl = snew(blitter);
+    bl->x = bl->y = -1;
+    bl->w = w;
+    bl->h = h;
+    bl->img = [[NSImage alloc] initWithSize:NSMakeSize(w, h)];
+    return bl;
+}
+static void osx_blitter_free(void *handle, blitter *bl)
+{
+    [bl->img release];
+    sfree(bl);
+}
+static void osx_blitter_save(void *handle, blitter *bl, int x, int y)
+{
+    frontend *fe = (frontend *)handle;
+    int sx, sy, sX, sY, dx, dy, dX, dY;
+    [fe->image unlockFocus];
+    [bl->img lockFocus];
+
+    /*
+     * Find the intersection of the source and destination rectangles,
+     * so as to avoid trying to copy from outside the source image,
+     * which GNUstep dislikes.
+     *
+     * Lower-case x,y coordinates are bottom left box corners;
+     * upper-case X,Y are the top right.
+     */
+    sx = x; sy = fe->h - y - bl->h;
+    sX = sx + bl->w; sY = sy + bl->h;
+    dx = dy = 0;
+    dX = bl->w; dY = bl->h;
+    if (sx < 0) {
+        dx += -sx;
+        sx = 0;
+    }
+    if (sy < 0) {
+        dy += -sy;
+        sy = 0;
+    }
+    if (sX > fe->w) {
+        dX -= (sX - fe->w);
+        sX = fe->w;
+    }
+    if (sY > fe->h) {
+        dY -= (sY - fe->h);
+        sY = fe->h;
+    }
+
+    [fe->image drawInRect:NSMakeRect(dx, dy, dX-dx, dY-dy)
+                 fromRect:NSMakeRect(sx, sy, sX-sx, sY-sy)
+                operation:NSCompositeCopy fraction:1.0];
+    [bl->img unlockFocus];
+    [fe->image lockFocus];
+    bl->x = x;
+    bl->y = y;
+}
+static void osx_blitter_load(void *handle, blitter *bl, int x, int y)
+{
+    frontend *fe = (frontend *)handle;
+    if (x == BLITTER_FROMSAVED && y == BLITTER_FROMSAVED) {
+        x = bl->x;
+        y = bl->y;
+    }
+    [bl->img drawInRect:NSMakeRect(x, fe->h - y - bl->h, bl->w, bl->h)
+       fromRect:NSMakeRect(0, 0, bl->w, bl->h)
+       operation:NSCompositeCopy fraction:1.0];
+}
+static void osx_draw_update(void *handle, int x, int y, int w, int h)
+{
+    frontend *fe = (frontend *)handle;
+    [fe->view setNeedsDisplayInRect:NSMakeRect(x, fe->h - y - h, w, h)];
+}
+static void osx_clip(void *handle, int x, int y, int w, int h)
+{
+    frontend *fe = (frontend *)handle;
+    NSRect r = { {x, fe->h - y - h}, {w, h} };
+    
+    if (!fe->clipped)
+       [[NSGraphicsContext currentContext] saveGraphicsState];
+    [NSBezierPath clipRect:r];
+    fe->clipped = TRUE;
+}
+static void osx_unclip(void *handle)
+{
+    frontend *fe = (frontend *)handle;
+    if (fe->clipped)
+       [[NSGraphicsContext currentContext] restoreGraphicsState];
+    fe->clipped = FALSE;
+}
+static void osx_start_draw(void *handle)
+{
+    frontend *fe = (frontend *)handle;
+    [fe->image lockFocus];
+    fe->clipped = FALSE;
+}
+static void osx_end_draw(void *handle)
+{
+    frontend *fe = (frontend *)handle;
+    [fe->image unlockFocus];
+}
+static void osx_status_bar(void *handle, char *text)
+{
+    frontend *fe = (frontend *)handle;
+    [fe->window setStatusLine:text];
+}
+
+const struct drawing_api osx_drawing = {
+    osx_draw_text,
+    osx_draw_rect,
+    osx_draw_line,
+    osx_draw_polygon,
+    osx_draw_circle,
+    osx_draw_update,
+    osx_clip,
+    osx_unclip,
+    osx_start_draw,
+    osx_end_draw,
+    osx_status_bar,
+    osx_blitter_new,
+    osx_blitter_free,
+    osx_blitter_save,
+    osx_blitter_load,
+    NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */
+    NULL, NULL,                               /* line_width, line_dotted */
+    osx_text_fallback,
+    osx_draw_thick_line,
+};
+
+void deactivate_timer(frontend *fe)
+{
+    [fe->window deactivateTimer];
+}
+void activate_timer(frontend *fe)
+{
+    [fe->window activateTimer];
+}
+
+/* ----------------------------------------------------------------------
+ * AppController: the object which receives the messages from all
+ * menu selections that aren't standard OS X functions.
+ */
+@interface AppController : NSObject <NSApplicationDelegate>
+{
+}
+- (void)newGameWindow:(id)sender;
+- (void)about:(id)sender;
+@end
+
+@implementation AppController
+
+- (void)newGameWindow:(id)sender
+{
+    const game *g = [sender getPayload];
+    id win;
+
+    win = [[GameWindow alloc] initWithGame:g];
+    [win makeKeyAndOrderFront:self];
+}
+
+- (void)about:(id)sender
+{
+    id win;
+
+    win = [[AboutBox alloc] init];
+    [win makeKeyAndOrderFront:self];    
+}
+
+- (NSMenu *)applicationDockMenu:(NSApplication *)sender
+{
+    NSMenu *menu = newmenu("Dock Menu");
+    {
+       int i;
+
+       for (i = 0; i < gamecount; i++) {
+           id item =
+               initnewitem([DataMenuItem allocWithZone:[NSMenu menuZone]],
+                           menu, gamelist[i]->name, "", self,
+                           @selector(newGameWindow:));
+           [item setPayload:(void *)gamelist[i]];
+       }
+    }
+    return menu;
+}
+
+@end
+
+/* ----------------------------------------------------------------------
+ * Main program. Constructs the menus and runs the application.
+ */
+int main(int argc, char **argv)
+{
+    NSAutoreleasePool *pool;
+    NSMenu *menu;
+    AppController *controller;
+    NSImage *icon;
+
+    pool = [[NSAutoreleasePool alloc] init];
+
+    icon = [NSImage imageNamed:@"NSApplicationIcon"];
+    app = [NSApplication sharedApplication];
+    [app setApplicationIconImage:icon];
+
+    controller = [[[AppController alloc] init] autorelease];
+    [app setDelegate:controller];
+
+    [app setMainMenu: newmenu("Main Menu")];
+
+    menu = newsubmenu([app mainMenu], "Apple Menu");
+    newitem(menu, "About Puzzles", "", NULL, @selector(about:));
+    [menu addItem:[NSMenuItem separatorItem]];
+    [app setServicesMenu:newsubmenu(menu, "Services")];
+    [menu addItem:[NSMenuItem separatorItem]];
+    newitem(menu, "Hide Puzzles", "h", app, @selector(hide:));
+    newitem(menu, "Hide Others", "o-h", app, @selector(hideOtherApplications:));
+    newitem(menu, "Show All", "", app, @selector(unhideAllApplications:));
+    [menu addItem:[NSMenuItem separatorItem]];
+    newitem(menu, "Quit", "q", app, @selector(terminate:));
+    [app setAppleMenu: menu];
+
+    menu = newsubmenu([app mainMenu], "File");
+    newitem(menu, "Open", "o", NULL, @selector(loadSavedGame:));
+    newitem(menu, "Save As", "s", NULL, @selector(saveGame:));
+    newitem(menu, "New Game", "n", NULL, @selector(newGame:));
+    newitem(menu, "Restart Game", "r", NULL, @selector(restartGame:));
+    newitem(menu, "Specific Game", "", NULL, @selector(specificGame:));
+    newitem(menu, "Specific Random Seed", "", NULL,
+                   @selector(specificRandomGame:));
+    [menu addItem:[NSMenuItem separatorItem]];
+    {
+       NSMenu *submenu = newsubmenu(menu, "New Window");
+       int i;
+
+       for (i = 0; i < gamecount; i++) {
+           id item =
+               initnewitem([DataMenuItem allocWithZone:[NSMenu menuZone]],
+                           submenu, gamelist[i]->name, "", controller,
+                           @selector(newGameWindow:));
+           [item setPayload:(void *)gamelist[i]];
+       }
+    }
+    [menu addItem:[NSMenuItem separatorItem]];
+    newitem(menu, "Close", "w", NULL, @selector(performClose:));
+
+    menu = newsubmenu([app mainMenu], "Edit");
+    newitem(menu, "Undo", "z", NULL, @selector(undoMove:));
+    newitem(menu, "Redo", "S-z", NULL, @selector(redoMove:));
+    [menu addItem:[NSMenuItem separatorItem]];
+    newitem(menu, "Cut", "x", NULL, @selector(cut:));
+    newitem(menu, "Copy", "c", NULL, @selector(copy:));
+    newitem(menu, "Paste", "v", NULL, @selector(paste:));
+    [menu addItem:[NSMenuItem separatorItem]];
+    newitem(menu, "Solve", "S-s", NULL, @selector(solveGame:));
+
+    menu = newsubmenu([app mainMenu], "Type");
+    typemenu = menu;
+    newitem(menu, "Custom", "", NULL, @selector(customGameType:));
+
+    menu = newsubmenu([app mainMenu], "Window");
+    [app setWindowsMenu: menu];
+    newitem(menu, "Minimise Window", "m", NULL, @selector(performMiniaturize:));
+
+    menu = newsubmenu([app mainMenu], "Help");
+    newitem(menu, "Puzzles Help", "?", app, @selector(showHelp:));
+
+    [app run];
+    [pool release];
+
+    return 0;
+}
diff --git a/palisade.R b/palisade.R
new file mode 100644 (file)
index 0000000..de4bd83
--- /dev/null
@@ -0,0 +1,21 @@
+# -*- makefile -*-
+
+PALISADE_EXTRA = divvy dsf
+
+palisade    : [X] GTK COMMON palisade PALISADE_EXTRA palisade-icon|no-icon
+
+palisade    : [G] WINDOWS COMMON palisade PALISADE_EXTRA palisade.res|noicon.res
+
+ALL += palisade[COMBINED] PALISADE_EXTRA
+
+!begin am gtk
+GAMES += palisade
+!end
+
+!begin >list.c
+    A(palisade) \
+!end
+
+!begin >gamedesc.txt
+palisade:palisade.exe:Palisade:Grid-division puzzle:Divide the grid into equal-sized areas in accordance with the clues.
+!end
diff --git a/palisade.c b/palisade.c
new file mode 100644 (file)
index 0000000..b5fb165
--- /dev/null
@@ -0,0 +1,1383 @@
+/* -*- indent-tabs-mode: nil; tab-width: 1000 -*- */
+
+/*
+ * palisade.c: Nikoli's `Five Cells' puzzle.
+ *
+ * See http://nikoli.co.jp/en/puzzles/five_cells.html
+ */
+
+/* TODO:
+ *
+ * - better solver: implement the sketched-out deductions
+ *
+ * - improve the victory flash?
+ *    - the LINE_NOs look ugly against COL_FLASH.
+ *    - white-blink the edges (instead), a la loopy?
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "puzzles.h"
+
+#define setmem(ptr, byte, len) memset((ptr), (byte), (len) * sizeof (ptr)[0])
+#define scopy(dst, src, len) memcpy((dst), (src), (len) * sizeof (dst)[0])
+#define dupmem(p, n) memcpy(smalloc(n * sizeof (*p)), p, n * sizeof (*p))
+#define snewa(ptr, len) (ptr) = smalloc((len) * sizeof (*ptr))
+#define clone(ptr) (dupmem((ptr), 1))
+
+static char *string(int n, const char *fmt, ...)
+{
+    va_list va;
+    char *ret;
+    int m;
+    va_start(va, fmt);
+    m = vsprintf(snewa(ret, n + 1), fmt, va);
+    va_end(va);
+    if (m > n) fatal("memory corruption");
+    return ret;
+}
+
+struct game_params {
+    int w, h, k;
+};
+
+typedef char clue;
+typedef unsigned char borderflag;
+
+typedef struct shared_state {
+    game_params params;
+    clue *clues;
+    int refcount;
+} shared_state;
+
+struct game_state {
+    shared_state *shared;
+    borderflag *borders; /* length w*h */
+
+    unsigned int completed: 1;
+    unsigned int cheated: 1;
+};
+
+#define DEFAULT_PRESET 0
+static struct game_params presets[] = {
+    {5, 5, 5}, {8, 6, 6}, {10, 8, 8}, {15, 12, 10}
+    /* I definitely want 5x5n5 since that gives "Five Cells" its name.
+     * But how about the others?  By which criteria do I choose? */
+};
+
+static game_params *default_params(void)
+{
+    return clone(&presets[DEFAULT_PRESET]);
+}
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    if (i < 0 || i >= lenof(presets)) return FALSE;
+
+    *params = clone(&presets[i]);
+    *name = string(60, "%d x %d, regions of size %d",
+                   presets[i].w, presets[i].h, presets[i].k);
+
+    return TRUE;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    return clone(params);
+}
+
+static void decode_params(game_params *params, char const *string)
+{
+    params->w = params->h = params->k = atoi(string);
+    while (*string && isdigit((unsigned char)*string)) ++string;
+    if (*string == 'x') {
+        params->h = atoi(++string);
+        while (*string && isdigit((unsigned char)*string)) ++string;
+    }
+    if (*string == 'n') params->k = atoi(++string);
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    return string(40, "%dx%dn%d", params->w, params->h, params->k);
+}
+
+#define CONFIG(i, nm, ty, iv, sv) \
+    (ret[i].name = nm, ret[i].type = ty, ret[i].ival = iv, ret[i].sval = sv)
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret = snewn(4, config_item);
+
+    CONFIG(0, "Width",       C_STRING, 0, string(20, "%d", params->w));
+    CONFIG(1, "Height",      C_STRING, 0, string(20, "%d", params->h));
+    CONFIG(2, "Region size", C_STRING, 0, string(20, "%d", params->k));
+    CONFIG(3, NULL,          C_END,    0, NULL);
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *params = snew(game_params);
+
+    params->w = atoi(cfg[0].sval);
+    params->h = atoi(cfg[1].sval);
+    params->k = atoi(cfg[2].sval);
+
+    return params;
+}
+
+/* +---+  <<  The one possible domino (up to symmetry).      +---+---+
+ * | 3 |                                                     | 3 | 3 |
+ * |   |   If two dominos are adjacent as depicted here  >>  +---+---+
+ * | 3 |   then it's ambiguous whether the edge between      | 3 | 3 |
+ * +---+   the dominos is horizontal or vertical.            +---+---+
+ */
+
+static char *validate_params(const game_params *params, int full)
+{
+    int w = params->w, h = params->h, k = params->k, wh = w * h;
+
+    if (k < 1) return "Region size must be at least one";
+    if (w < 1) return "Width must be at least one";
+    if (h < 1) return "Height must be at least one";
+    if (wh % k) return "Region size must divide grid area";
+
+    if (!full) return NULL; /* succeed partial validation */
+
+    /* MAYBE FIXME: we (just?) don't have the UI for winning these. */
+    if (k == wh) return "Region size must be less than the grid area";
+    assert (k < wh); /* or wh % k != 0 */
+
+    if (k == 2 && w != 1 && h != 1)
+        return "Region size can't be two unless width or height is one";
+
+    return NULL; /* succeed full validation */
+}
+
+/* --- Solver ------------------------------------------------------- */
+
+/* the solver may write at will to these arrays, but shouldn't free them */
+/* it's up to the client to dup/free as needed */
+typedef struct solver_ctx {
+    const game_params *params;  /* also in shared_state */
+    clue *clues;                /* also in shared_state */
+    borderflag *borders;        /* also in game_state */
+    int *dsf;                   /* particular to the solver */
+} solver_ctx;
+
+/* Deductions:
+ *
+ * - If two adjacent clues do not have a border between them, this
+ *   gives a lower limit on the size of their region (which is also an
+ *   upper limit if both clues are 3).  Rule out any non-border which
+ *   would make its region either too large or too small.
+ *
+ * - If a clue, k, is adjacent to k borders or (4 - k) non-borders,
+ *   the remaining edges incident to the clue are readily decided.
+ *
+ * - If a region has only one other region (e.g. square) to grow into
+ *   and it's not of full size yet, grow it into that one region.
+ *
+ * - If two regions are adjacent and their combined size would be too
+ *   large, put an edge between them.
+ *
+ * - If a border is adjacent to two non-borders, its last vertex-mate
+ *   must also be a border.  If a maybe-border is adjacent to three
+ *   nonborders, the maybe-border is a non-border.
+ *
+ * - If a clue square is adjacent to several squares belonging to the
+ *   same region, and enabling (disabling) those borders would violate
+ *   the clue, those borders must be disabled (enabled).
+ *
+ * - If there's a path crossing only non-borders between two squares,
+ *   the maybe-border between them is a non-border.
+ *   (This is implicitly computed in the dsf representation)
+ */
+
+/* TODO deductions:
+ *
+ * If a vertex is adjacent to a LINE_YES and (4-3)*LINE_NO, at least
+ * one of the last two edges are LINE_YES.  If they're adjacent to a
+ * 1, then the other two edges incident to that 1 are LINE_NO.
+ *
+ * For each square: set all as unknown, then for each k-omino and each
+ * way of placing it on that square, if that way is consistent with
+ * the board, mark its edges and interior as possible LINE_YES and
+ * LINE_NO, respectively.  When all k-ominos are through, see what
+ * isn't possible and remove those impossibilities from the board.
+ * (Sounds pretty nasty for k > 4 or so.)
+ *
+ * A black-bordered subregion must have a size divisible by k.  So,
+ * draw a graph with one node per dsf component and edges between
+ * those dsf components which have adjacent squares.  Identify cut
+ * vertices and edges.  If a cut-vertex-delimited component contains a
+ * number of squares not divisible by k, cut vertex not included, then
+ * the cut vertex must belong to the component.  If it has exactly one
+ * edge _out_ of the component, the line(s) corresponding to that edge
+ * are all LINE_YES (i.e. a BORDER()).
+ * (This sounds complicated, but visually it is rather easy.)
+ *
+ * [Look at loopy and see how the at-least/-most k out of m edges
+ * thing is done.  See how it is propagated across multiple squares.]
+ */
+
+#define EMPTY (~0)
+
+#define BIT(i) (1 << (i))
+#define BORDER(i) BIT(i)
+#define BORDER_U BORDER(0)
+#define BORDER_R BORDER(1)
+#define BORDER_D BORDER(2)
+#define BORDER_L BORDER(3)
+#define FLIP(i) ((i) ^ 2)
+#define BORDER_MASK (BORDER_U|BORDER_R|BORDER_D|BORDER_L)
+#define DISABLED(border) ((border) << 4)
+#define UNDISABLED(border) ((border) >> 4)
+
+static const int dx[4] = { 0, +1,  0, -1};
+static const int dy[4] = {-1,  0, +1,  0};
+static const int bitcount[16] = {0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4};
+/* bitcount[x & BORDER_MASK] == number of enabled borders */
+
+#define COMPUTE_J (-1)
+
+static void connect(solver_ctx *ctx, int i, int j)
+{
+    dsf_merge(ctx->dsf, i, j);
+}
+
+static int connected(solver_ctx *ctx, int i, int j, int dir)
+{
+    if (j == COMPUTE_J) j = i + dx[dir] + ctx->params->w*dy[dir];
+    return dsf_canonify(ctx->dsf, i) == dsf_canonify(ctx->dsf, j);
+}
+
+static void disconnect(solver_ctx *ctx, int i, int j, int dir)
+{
+    if (j == COMPUTE_J) j = i + dx[dir] + ctx->params->w*dy[dir];
+    ctx->borders[i] |= BORDER(dir);
+    ctx->borders[j] |= BORDER(FLIP(dir));
+}
+
+static int disconnected(solver_ctx *ctx, int i, int j, int dir)
+{
+    assert (j == COMPUTE_J || j == i + dx[dir] + ctx->params->w*dy[dir]);
+    return ctx->borders[i] & BORDER(dir);
+}
+
+static int maybe(solver_ctx *ctx, int i, int j, int dir)
+{
+    assert (j == COMPUTE_J || j == i + dx[dir] + ctx->params->w*dy[dir]);
+    return !disconnected(ctx, i, j, dir) && !connected(ctx, i, j, dir);
+    /* the ordering is important: disconnected works for invalid
+     * squares (i.e. out of bounds), connected doesn't. */
+}
+
+static void solver_connected_clues_versus_region_size(solver_ctx *ctx)
+{
+    int w = ctx->params->w, h = ctx->params->h, wh = w*h, i, dir;
+
+    /* If i is connected to j and i has borders with p of the
+     * remaining three squares and j with q of the remaining three
+     * squares, then the region has size at least 1+(3-p) + 1+(3-q).
+     * If p = q = 3 then the region has size exactly 2. */
+
+    for (i = 0; i < wh; ++i) {
+        if (ctx->clues[i] == EMPTY) continue;
+        for (dir = 0; dir < 4; ++dir) {
+            int j = i + dx[dir] + w*dy[dir];
+            if (disconnected(ctx, i, j, dir)) continue;
+            if (ctx->clues[j] == EMPTY) continue;
+            if ((8 - ctx->clues[i] - ctx->clues[j] > ctx->params->k) ||
+                (ctx->clues[i] == 3 && ctx->clues[j] == 3 &&
+                 ctx->params->k != 2))
+            {
+                disconnect(ctx, i, j, dir);
+                /* changed = TRUE, but this is a one-shot... */
+            }
+        }
+    }
+}
+
+static int solver_number_exhausted(solver_ctx *ctx)
+{
+    int w = ctx->params->w, h = ctx->params->h, wh = w*h, i, dir, off;
+    int changed = FALSE;
+
+    for (i = 0; i < wh; ++i) {
+        if (ctx->clues[i] == EMPTY) continue;
+
+        if (bitcount[(ctx->borders[i] & BORDER_MASK)] == ctx->clues[i]) {
+            for (dir = 0; dir < 4; ++dir) {
+                int j = i + dx[dir] + w*dy[dir];
+                if (!maybe(ctx, i, j, dir)) continue;
+                connect(ctx, i, j);
+                changed = TRUE;
+            }
+            continue;
+        }
+
+        for (off = dir = 0; dir < 4; ++dir) {
+            int j = i + dx[dir] + w*dy[dir];
+            if (!disconnected(ctx, i, j, dir) && connected(ctx, i, j, dir))
+                ++off; /* ^^^ bounds checking before ^^^^^ */
+        }
+
+        if (ctx->clues[i] == 4 - off)
+            for (dir = 0; dir < 4; ++dir) {
+                int j = i + dx[dir] + w*dy[dir];
+                if (!maybe(ctx, i, j, dir)) continue;
+                disconnect(ctx, i, j, dir);
+                changed = TRUE;
+            }
+    }
+
+    return changed;
+}
+
+static int solver_not_too_big(solver_ctx *ctx)
+{
+    int w = ctx->params->w, h = ctx->params->h, wh = w*h, i, dir;
+    int changed = FALSE;
+
+    for (i = 0; i < wh; ++i) {
+        int size = dsf_size(ctx->dsf, i);
+        for (dir = 0; dir < 4; ++dir) {
+            int j = i + dx[dir] + w*dy[dir];
+            if (!maybe(ctx, i, j, dir)) continue;
+            if (size + dsf_size(ctx->dsf, j) <= ctx->params->k) continue;
+            disconnect(ctx, i, j, dir);
+            changed = TRUE;
+        }
+    }
+
+    return changed;
+}
+
+static int solver_not_too_small(solver_ctx *ctx)
+{
+    int w = ctx->params->w, h = ctx->params->h, wh = w*h, i, dir;
+    int *outs, k = ctx->params->k, ci, changed = FALSE;
+
+    snewa(outs, wh);
+    setmem(outs, -1, wh);
+
+    for (i = 0; i < wh; ++i) {
+        ci = dsf_canonify(ctx->dsf, i);
+        if (dsf_size(ctx->dsf, ci) == k) continue;
+        for (dir = 0; dir < 4; ++dir) {
+            int j = i + dx[dir] + w*dy[dir];
+            if (!maybe(ctx, i, j, dir)) continue;
+            if (outs[ci] == -1) outs[ci] = dsf_canonify(ctx->dsf, j);
+            else if (outs[ci] != dsf_canonify(ctx->dsf, j)) outs[ci] = -2;
+        }
+    }
+
+    for (i = 0; i < wh; ++i) {
+        int j = outs[i];
+        if (i != dsf_canonify(ctx->dsf, i)) continue;
+        if (j < 0) continue;
+        connect(ctx, i, j); /* only one place for i to grow */
+        changed = TRUE;
+    }
+
+    sfree(outs);
+    return changed;
+}
+
+static int solver_no_dangling_edges(solver_ctx *ctx)
+{
+    int w = ctx->params->w, h = ctx->params->h, r, c;
+    int changed = FALSE;
+
+    /* for each vertex */
+    for (r = 1; r < h; ++r)
+        for (c = 1; c < w; ++c) {
+            int i = r * w + c, j = i - w - 1, noline = 0, dir;
+            int squares[4], e = -1, f = -1, de = -1, df = -1;
+
+            /* feels hacky: I align these with BORDER_[U0 R1 D2 L3] */
+            squares[1] = squares[2] = j;
+            squares[0] = squares[3] = i;
+
+            /* for each edge adjacent to the vertex */
+            for (dir = 0; dir < 4; ++dir)
+                if (!connected(ctx, squares[dir], COMPUTE_J, dir)) {
+                    df = dir;
+                    f = squares[df];
+                    if (e != -1) continue;
+                    e = f;
+                    de = df;
+                } else ++noline;
+
+            if (4 - noline == 1) {
+                assert (e != -1);
+                disconnect(ctx, e, COMPUTE_J, de);
+                changed = TRUE;
+                continue;
+            }
+
+            if (4 - noline != 2) continue;
+
+            assert (e != -1);
+            assert (f != -1);
+
+            if (ctx->borders[e] & BORDER(de)) {
+                if (!(ctx->borders[f] & BORDER(df))) {
+                    disconnect(ctx, f, COMPUTE_J, df);
+                    changed = TRUE;
+                }
+            } else if (ctx->borders[f] & BORDER(df)) {
+                disconnect(ctx, e, COMPUTE_J, de);
+                changed = TRUE;
+            }
+        }
+
+    return changed;
+}
+
+static int solver_equivalent_edges(solver_ctx *ctx)
+{
+    int w = ctx->params->w, h = ctx->params->h, wh = w*h, i, dirj;
+    int changed = FALSE;
+
+    /* if a square is adjacent to two connected squares, the two
+     * borders (i,j) and (i,k) are either both on or both off. */
+
+    for (i = 0; i < wh; ++i) {
+        int n_on = 0, n_off = 0;
+        if (ctx->clues[i] < 1 || ctx->clues[i] > 3) continue;
+
+        if (ctx->clues[i] == 2 /* don't need it otherwise */)
+            for (dirj = 0; dirj < 4; ++dirj) {
+                int j = i + dx[dirj] + w*dy[dirj];
+                if (disconnected(ctx, i, j, dirj)) ++n_on;
+                else if (connected(ctx, i, j, dirj)) ++n_off;
+            }
+
+        for (dirj = 0; dirj < 4; ++dirj) {
+            int j = i + dx[dirj] + w*dy[dirj], dirk;
+            if (!maybe(ctx, i, j, dirj)) continue;
+
+            for (dirk = dirj + 1; dirk < 4; ++dirk) {
+                int k = i + dx[dirk] + w*dy[dirk];
+                if (!maybe(ctx, i, k, dirk)) continue;
+                if (!connected(ctx, j, k, -1)) continue;
+
+                if (n_on + 2 > ctx->clues[i]) {
+                    connect(ctx, i, j);
+                    connect(ctx, i, k);
+                    changed = TRUE;
+                } else if (n_off + 2 > 4 - ctx->clues[i]) {
+                    disconnect(ctx, i, j, dirj);
+                    disconnect(ctx, i, k, dirk);
+                    changed = TRUE;
+                }
+            }
+        }
+    }
+
+    return changed;
+}
+
+#define UNVISITED 6
+
+/* build connected components in `dsf', along the lines of `borders'. */
+static void dfs_dsf(int i, int w, borderflag *border, int *dsf, int black)
+{
+    int dir;
+    for (dir = 0; dir < 4; ++dir) {
+        int ii = i + dx[dir] + w*dy[dir], bdir = BORDER(dir);
+        if (black ? (border[i] & bdir) : !(border[i] & DISABLED(bdir)))
+            continue;
+        if (dsf[ii] != UNVISITED) continue;
+        dsf_merge(dsf, i, ii);
+        dfs_dsf(ii, w, border, dsf, black);
+    }
+}
+
+static int is_solved(const game_params *params, clue *clues,
+                     borderflag *border)
+{
+    int w = params->w, h = params->h, wh = w*h, k = params->k;
+    int i, x, y;
+    int *dsf = snew_dsf(wh);
+
+    assert (dsf[0] == UNVISITED); /* check: UNVISITED and dsf.c match up */
+
+    /*
+     * A game is solved if:
+     *
+     *  - the borders drawn on the grid divide it into connected
+     *    components such that every square is in a component of the
+     *    correct size
+     *  - the borders also satisfy the clue set
+     */
+    for (i = 0; i < wh; ++i) {
+        if (dsf[i] == UNVISITED) dfs_dsf(i, params->w, border, dsf, TRUE);
+        if (dsf_size(dsf, i) != k) goto error;
+        if (clues[i] == EMPTY) continue;
+        if (clues[i] != bitcount[border[i] & BORDER_MASK]) goto error;
+    }
+
+    /*
+     * ... and thirdly:
+     *
+     *  - there are no *stray* borders, in that every border is
+     *    actually part of the division between two components.
+     *    Otherwise you could cheat by finding a subdivision which did
+     *    not *exceed* any clue square's counter, and then adding a
+     *    few extra edges.
+     */
+    for (y = 0; y < h; y++) {
+        for (x = 0; x < w; x++) {
+            if (x+1 < w && (border[y*w+x] & BORDER_R) &&
+                dsf_canonify(dsf, y*w+x) == dsf_canonify(dsf, y*w+(x+1)))
+                goto error;
+            if (y+1 < h && (border[y*w+x] & BORDER_D) &&
+                dsf_canonify(dsf, y*w+x) == dsf_canonify(dsf, (y+1)*w+x))
+                goto error;
+        }
+    }
+
+    sfree(dsf);
+    return TRUE;
+
+error:
+    sfree(dsf);
+    return FALSE;
+}
+
+static int solver(const game_params *params, clue *clues, borderflag *borders)
+{
+    int w = params->w, h = params->h, wh = w*h, changed;
+    solver_ctx ctx;
+
+    ctx.params = params;
+    ctx.clues = clues;
+    ctx.borders = borders;
+    ctx.dsf = snew_dsf(wh);
+
+    solver_connected_clues_versus_region_size(&ctx); /* idempotent */
+    do {
+        changed  = FALSE;
+        changed |= solver_number_exhausted(&ctx);
+        changed |= solver_not_too_big(&ctx);
+        changed |= solver_not_too_small(&ctx);
+        changed |= solver_no_dangling_edges(&ctx);
+        changed |= solver_equivalent_edges(&ctx);
+    } while (changed);
+
+    sfree(ctx.dsf);
+
+    return is_solved(params, clues, borders);
+}
+
+/* --- Generator ---------------------------------------------------- */
+
+static void init_borders(int w, int h, borderflag *borders)
+{
+    int r, c;
+    setmem(borders, 0, w*h);
+    for (c = 0; c < w; ++c) {
+        borders[c] |= BORDER_U;
+        borders[w*h-1 - c] |= BORDER_D;
+    }
+    for (r = 0; r < h; ++r) {
+        borders[r*w] |= BORDER_L;
+        borders[w*h-1 - r*w] |= BORDER_R;
+    }
+}
+
+#define OUT_OF_BOUNDS(x, y, w, h) \
+    ((x) < 0 || (x) >= (w) || (y) < 0 || (y) >= (h))
+
+#define xshuffle(ptr, len, rs) shuffle((ptr), (len), sizeof (ptr)[0], (rs))
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                           char **aux, int interactive)
+{
+    int w = params->w, h = params->h, wh = w*h, k = params->k;
+
+    clue *numbers = snewn(wh + 1, clue), *p;
+    borderflag *rim = snewn(wh, borderflag);
+    borderflag *scratch_borders = snewn(wh, borderflag);
+
+    char *soln = snewa(*aux, wh + 2);
+    int *shuf = snewn(wh, int);
+    int *dsf = NULL, i, r, c;
+
+    int attempts = 0;
+
+    for (i = 0; i < wh; ++i) shuf[i] = i;
+    xshuffle(shuf, wh, rs);
+
+    init_borders(w, h, rim);
+
+    assert (!('@' & BORDER_MASK));
+    *soln++ = 'S';
+    soln[wh] = '\0';
+
+    do {
+        ++attempts;
+        setmem(soln, '@', wh);
+
+        sfree(dsf);
+        dsf = divvy_rectangle(w, h, k, rs);
+
+        for (r = 0; r < h; ++r)
+            for (c = 0; c < w; ++c) {
+                int i = r * w + c, dir;
+                numbers[i] = 0;
+                for (dir = 0; dir < 4; ++dir) {
+                    int rr = r + dy[dir], cc = c + dx[dir], ii = rr * w + cc;
+                    if (OUT_OF_BOUNDS(cc, rr, w, h) ||
+                        dsf_canonify(dsf, i) != dsf_canonify(dsf, ii)) {
+                        ++numbers[i];
+                        soln[i] |= BORDER(dir);
+                    }
+                }
+            }
+
+        scopy(scratch_borders, rim, wh);
+    } while (!solver(params, numbers, scratch_borders));
+
+    for (i = 0; i < wh; ++i) {
+        int j = shuf[i];
+        clue copy = numbers[j];
+
+        scopy(scratch_borders, rim, wh);
+        numbers[j] = EMPTY; /* strip away unnecssary clues */
+        if (!solver(params, numbers, scratch_borders))
+            numbers[j] = copy;
+    }
+
+    numbers[wh] = '\0';
+
+    sfree(scratch_borders);
+    sfree(rim);
+    sfree(shuf);
+    sfree(dsf);
+
+    p = numbers;
+    r = 0;
+    for (i = 0; i < wh; ++i) {
+        if (numbers[i] != EMPTY) {
+            while (r) {
+                while (r > 26) {
+                    *p++ = 'z';
+                    r -= 26;
+                }
+                *p++ = 'a'-1 + r;
+                r = 0;
+            }
+            *p++ = '0' + numbers[i];
+        } else ++r;
+    }
+    *p++ = '\0';
+
+    return sresize(numbers, p - numbers, clue);
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+
+    int w = params->w, h = params->h, wh = w*h, squares = 0;
+
+    for (/* nop */; *desc; ++desc) {
+        if (islower((unsigned char)*desc)) {
+            squares += *desc - 'a' + 1;
+        } else if (isdigit((unsigned char)*desc)) {
+            if (*desc > '4') {
+                static char buf[] = "Invalid (too large) number: '5'";
+                assert (isdigit((unsigned char)buf[lenof(buf) - 3]));
+                buf[lenof(buf) - 3] = *desc; /* ... or 6, 7, 8, 9 :-) */
+                return buf;
+            }
+            ++squares;
+        } else if (isprint((unsigned char)*desc)) {
+            static char buf[] = "Invalid character in data: '?'";
+            buf[lenof(buf) - 3] = *desc;
+            return buf;
+        } else return "Invalid (unprintable) character in data";
+    }
+
+    if (squares > wh) return "Data describes too many squares";
+
+    return NULL;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    int w = params->w, h = params->h, wh = w*h, i;
+    game_state *state = snew(game_state);
+
+    state->shared = snew(shared_state);
+    state->shared->refcount = 1;
+    state->shared->params = *params; /* struct copy */
+    snewa(state->shared->clues, wh);
+
+    setmem(state->shared->clues, EMPTY, wh);
+    for (i = 0; *desc; ++desc) {
+        if (isdigit((unsigned char)*desc)) state->shared->clues[i++] = *desc - '0';
+        else if (isalpha((unsigned char)*desc)) i += *desc - 'a' + 1;
+    }
+
+    snewa(state->borders, wh);
+    init_borders(w, h, state->borders);
+
+    state->completed = (params->k == wh);
+    state->cheated = FALSE;
+
+    return state;
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    int wh = state->shared->params.w * state->shared->params.h;
+    game_state *ret = snew(game_state);
+
+    ret->borders = dupmem(state->borders, wh);
+
+    ret->shared = state->shared;
+    ++ret->shared->refcount;
+
+    ret->completed = state->completed;
+    ret->cheated = state->cheated;
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    if (--state->shared->refcount == 0) {
+        sfree(state->shared->clues);
+        sfree(state->shared);
+    }
+    sfree(state->borders);
+    sfree(state);
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    int w = state->shared->params.w, h = state->shared->params.h, wh = w*h;
+    borderflag *move;
+
+    if (aux) return dupstr(aux);
+
+    snewa(move, wh + 2);
+
+    move[0] = 'S';
+    init_borders(w, h, move + 1);
+    move[wh + 1] = '\0';
+
+    if (solver(&state->shared->params, state->shared->clues, move + 1)) {
+        int i;
+        for (i = 0; i < wh; i++)
+            move[i+1] |= '@';          /* turn into sensible ASCII */
+        return (char *) move;
+    }
+
+    *error = "Sorry, I can't solve this puzzle";
+    sfree(move);
+    return NULL;
+
+    {
+        /* compile-time-assert (borderflag is-a-kind-of char).
+         *
+         * depends on zero-size arrays being disallowed.  GCC says
+         * ISO C forbids this, pointing to [-Werror=edantic].  Also,
+         * it depends on type-checking of (obviously) dead code. */
+        borderflag b[sizeof (borderflag) == sizeof (char)];
+        char c = b[0]; b[0] = c;
+        /* we could at least in principle put this anywhere, but it
+         * seems silly to not put it where the assumption is used. */
+    }
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    int w = state->shared->params.w, h = state->shared->params.h, r, c;
+    int cw = 4, ch = 2, gw = cw*w + 2, gh = ch * h + 1, len = gw * gh;
+    char *board;
+
+    setmem(snewa(board, len + 1), ' ', len);
+    for (r = 0; r < h; ++r) {
+        for (c = 0; c < w; ++c) {
+            int cell = r*ch*gw + cw*c, center = cell + gw*ch/2 + cw/2;
+            int i = r * w + c, clue = state->shared->clues[i];
+
+            if (clue != EMPTY) board[center] = '0' + clue;
+
+            board[cell] = '+';
+
+            if (state->borders[i] & BORDER_U)
+                setmem(board + cell + 1, '-', cw - 1);
+            else if (state->borders[i] & DISABLED(BORDER_U))
+                board[cell + cw / 2] = 'x';
+
+            if (state->borders[i] & BORDER_L)
+                board[cell + gw] = '|';
+            else if (state->borders[i] & DISABLED(BORDER_L))
+                board[cell + gw] = 'x';
+        }
+
+        for (c = 0; c < ch; ++c) {
+            board[(r*ch + c)*gw + gw - 2] = c ? '|' : '+';
+            board[(r*ch + c)*gw + gw - 1] = '\n';
+        }
+    }
+
+    scopy(board + len - gw, board, gw);
+    board[len] = '\0';
+
+    return board;
+}
+
+struct game_ui {
+    int x, y;
+    unsigned int show: 1;
+};
+
+static game_ui *new_ui(const game_state *state)
+{
+    game_ui *ui = snew(game_ui);
+    ui->x = ui->y = 0;
+    ui->show = FALSE;
+    return ui;
+}
+
+static void free_ui(game_ui *ui)
+{
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+    assert (encoding == NULL);
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+}
+
+typedef unsigned short dsflags;
+
+struct game_drawstate {
+    int tilesize;
+    dsflags *grid;
+};
+
+#define TILESIZE (ds->tilesize)
+#define MARGIN (ds->tilesize / 2)
+#define WIDTH (1 + (TILESIZE >= 16) + (TILESIZE >= 32) + (TILESIZE >= 64))
+#define CENTER ((ds->tilesize / 2) + WIDTH/2)
+
+#define FROMCOORD(x) (((x) - MARGIN) / TILESIZE)
+
+enum {MAYBE_LEFT, MAYBE_RIGHT, ON_LEFT, ON_RIGHT, OFF_LEFT, OFF_RIGHT};
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds, int x, int y, int button)
+{
+    int w = state->shared->params.w, h = state->shared->params.h;
+    int control = button & MOD_CTRL, shift = button & MOD_SHFT;
+
+    button &= ~MOD_MASK;
+
+    if (button == LEFT_BUTTON || button == RIGHT_BUTTON) {
+        int gx = FROMCOORD(x), gy = FROMCOORD(y), possible = BORDER_MASK;
+        int px = (x - MARGIN) % TILESIZE, py = (y - MARGIN) % TILESIZE;
+        int hx, hy, dir, i;
+
+        if (OUT_OF_BOUNDS(gx, gy, w, h)) return NULL;
+
+        ui->x = gx;
+        ui->y = gy;
+
+        /* find edge closest to click point */
+        possible &=~ (2*px < TILESIZE ? BORDER_R : BORDER_L);
+        possible &=~ (2*py < TILESIZE ? BORDER_D : BORDER_U);
+        px = min(px, TILESIZE - px);
+        py = min(py, TILESIZE - py);
+        possible &=~ (px < py ? (BORDER_U|BORDER_D) : (BORDER_L|BORDER_R));
+
+        for (dir = 0; dir < 4 && BORDER(dir) != possible; ++dir);
+        if (dir == 4) return NULL; /* there's not exactly one such edge */
+
+        hx = gx + dx[dir];
+        hy = gy + dy[dir];
+
+        if (OUT_OF_BOUNDS(hx, hy, w, h)) return NULL;
+
+        ui->show = FALSE;
+
+        i = gy * w + gx;
+        switch ((button == RIGHT_BUTTON) |
+                ((state->borders[i] & BORDER(dir)) >> dir << 1) |
+                ((state->borders[i] & DISABLED(BORDER(dir))) >> dir >> 2)) {
+
+        case MAYBE_LEFT:
+        case ON_LEFT:
+        case ON_RIGHT:
+            return string(80, "F%d,%d,%dF%d,%d,%d",
+                          gx, gy, BORDER(dir),
+                          hx, hy, BORDER(FLIP(dir)));
+
+        case MAYBE_RIGHT:
+        case OFF_LEFT:
+        case OFF_RIGHT:
+            return string(80, "F%d,%d,%dF%d,%d,%d",
+                          gx, gy, DISABLED(BORDER(dir)),
+                          hx, hy, DISABLED(BORDER(FLIP(dir))));
+        }
+    }
+
+    if (IS_CURSOR_MOVE(button)) {
+        ui->show = TRUE;
+        if (control || shift) {
+            borderflag flag = 0, newflag;
+            int dir, i =  ui->y * w + ui->x;
+            x = ui->x;
+            y = ui->y;
+            move_cursor(button, &x, &y, w, h, FALSE);
+            if (OUT_OF_BOUNDS(x, y, w, h)) return NULL;
+
+            for (dir = 0; dir < 4; ++dir)
+                if (dx[dir] == x - ui->x && dy[dir] == y - ui->y) break;
+            if (dir == 4) return NULL; /* how the ... ?! */
+
+            if (control) flag |= BORDER(dir);
+            if (shift) flag |= DISABLED(BORDER(dir));
+
+            newflag = state->borders[i] ^ flag;
+            if (newflag & BORDER(dir) && newflag & DISABLED(BORDER(dir)))
+                return NULL;
+
+            newflag = 0;
+            if (control) newflag |= BORDER(FLIP(dir));
+            if (shift) newflag |= DISABLED(BORDER(FLIP(dir)));
+            return string(80, "F%d,%d,%dF%d,%d,%d",
+                          ui->x, ui->y, flag, x, y, newflag);
+        } else {
+            move_cursor(button, &ui->x, &ui->y, w, h, FALSE);
+            return "";
+        }
+    }
+
+    return NULL;
+}
+
+static game_state *execute_move(const game_state *state, const char *move)
+{
+    int w = state->shared->params.w, h = state->shared->params.h, wh = w * h;
+    game_state *ret = dup_game(state);
+    int nchars, x, y, flag;
+
+    if (*move == 'S') {
+        int i;
+        ++move;
+        for (i = 0; i < wh && move[i]; ++i)
+            ret->borders[i] =
+                (move[i] & BORDER_MASK) | DISABLED(~move[i] & BORDER_MASK);
+        if (i < wh || move[i]) return NULL; /* leaks `ret', then we die */
+        ret->cheated = ret->completed = TRUE;
+        return ret;
+    }
+
+    while (sscanf(move, "F%d,%d,%d%n", &x, &y, &flag, &nchars) == 3 &&
+           !OUT_OF_BOUNDS(x, y, w, h)) {
+        move += nchars;
+        ret->borders[y*w + x] ^= flag;
+    }
+
+    if (*move) return NULL; /* leaks `ret', then we die */
+
+    if (!ret->completed)
+        ret->completed = is_solved(&ret->shared->params, ret->shared->clues,
+                                   ret->borders);
+
+    return ret;
+}
+
+/* --- Drawing routines --------------------------------------------- */
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    *x = (params->w + 1) * tilesize;
+    *y = (params->h + 1) * tilesize;
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+}
+
+enum {
+    COL_BACKGROUND,
+    COL_FLASH,
+    COL_GRID,
+    COL_CLUE = COL_GRID,
+    COL_LINE_YES = COL_GRID,
+    COL_LINE_MAYBE,
+    COL_LINE_NO,
+    COL_ERROR,
+
+    NCOLOURS
+};
+
+#define COLOUR(i, r, g, b) \
+   ((ret[3*(i)+0] = (r)), (ret[3*(i)+1] = (g)), (ret[3*(i)+2] = (b)))
+#define DARKER 0.9F
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+
+    game_mkhighlight(fe, ret, COL_BACKGROUND, -1, COL_FLASH);
+
+    COLOUR(COL_GRID,   0.0F, 0.0F, 0.0F); /* black */
+    COLOUR(COL_ERROR,  1.0F, 0.0F, 0.0F); /* red */
+
+    COLOUR(COL_LINE_MAYBE, /* yellow */
+           ret[COL_BACKGROUND*3 + 0] * DARKER,
+           ret[COL_BACKGROUND*3 + 1] * DARKER,
+           0.0F);
+
+    COLOUR(COL_LINE_NO,
+           ret[COL_BACKGROUND*3 + 0] * DARKER,
+           ret[COL_BACKGROUND*3 + 1] * DARKER,
+           ret[COL_BACKGROUND*3 + 2] * DARKER);
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+#undef COLOUR
+
+#define BORDER_ERROR(x) ((x) << 8)
+#define F_ERROR_U BORDER_ERROR(BORDER_U) /* BIT( 8) */
+#define F_ERROR_R BORDER_ERROR(BORDER_R) /* BIT( 9) */
+#define F_ERROR_D BORDER_ERROR(BORDER_D) /* BIT(10) */
+#define F_ERROR_L BORDER_ERROR(BORDER_L) /* BIT(11) */
+#define F_ERROR_CLUE BIT(12)
+#define F_FLASH BIT(13)
+#define F_CURSOR BIT(14)
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    struct game_drawstate *ds = snew(struct game_drawstate);
+
+    ds->tilesize = 0;
+    ds->grid = NULL;
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds->grid);
+    sfree(ds);
+}
+
+#define COLOUR(border)                                                  \
+    (flags & BORDER_ERROR((border)) ? COL_ERROR :                       \
+     flags & (border)               ? COL_LINE_YES :                    \
+     flags & DISABLED((border))     ? COL_LINE_NO :                     \
+                                      COL_LINE_MAYBE)
+
+static void draw_tile(drawing *dr, game_drawstate *ds, int r, int c,
+                      dsflags flags, int clue)
+{
+    int x = MARGIN + TILESIZE * c, y = MARGIN + TILESIZE * r;
+
+    clip(dr, x, y, TILESIZE + WIDTH, TILESIZE + WIDTH); /* { */
+
+    draw_rect(dr, x + WIDTH, y + WIDTH, TILESIZE - WIDTH, TILESIZE - WIDTH,
+              (flags & F_FLASH ? COL_FLASH : COL_BACKGROUND));
+
+    if (flags & F_CURSOR)
+        draw_rect_corners(dr, x + CENTER, y + CENTER, TILESIZE / 3, COL_GRID);
+
+    if (clue != EMPTY) {
+        char buf[2];
+        buf[0] = '0' + clue;
+        buf[1] = '\0';
+        draw_text(dr, x + CENTER, y + CENTER, FONT_VARIABLE,
+                  TILESIZE / 2, ALIGN_VCENTRE | ALIGN_HCENTRE,
+                  (flags & F_ERROR_CLUE ? COL_ERROR : COL_CLUE), buf);
+    }
+
+
+#define ts TILESIZE
+#define w WIDTH
+    draw_rect(dr, x + w,  y,      ts - w, w,      COLOUR(BORDER_U));
+    draw_rect(dr, x + ts, y + w,  w,      ts - w, COLOUR(BORDER_R));
+    draw_rect(dr, x + w,  y + ts, ts - w, w,      COLOUR(BORDER_D));
+    draw_rect(dr, x,      y + w,  w,      ts - w, COLOUR(BORDER_L));
+#undef ts
+#undef w
+
+    unclip(dr); /* } */
+    draw_update(dr, x, y, TILESIZE + WIDTH, TILESIZE + WIDTH);
+}
+
+#define FLASH_TIME 0.7F
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int w = state->shared->params.w, h = state->shared->params.h, wh = w*h;
+    int r, c, i, flash = ((int) (flashtime * 5 / FLASH_TIME)) % 2;
+    int *black_border_dsf = snew_dsf(wh), *yellow_border_dsf = snew_dsf(wh);
+    int k = state->shared->params.k;
+
+    if (!ds->grid) {
+        char buf[40];
+        int bgw = (w+1) * ds->tilesize, bgh = (h+1) * ds->tilesize;
+        draw_rect(dr, 0, 0, bgw, bgh, COL_BACKGROUND);
+
+        for (r = 0; r <= h; ++r)
+            for (c = 0; c <= w; ++c)
+                draw_rect(dr, MARGIN + TILESIZE * c, MARGIN + TILESIZE * r,
+                          WIDTH, WIDTH, COL_GRID);
+        draw_update(dr, 0, 0, bgw, bgh);
+
+        snewa(ds->grid, wh);
+        setmem(ds->grid, ~0, wh);
+
+        sprintf(buf, "Region size: %d", state->shared->params.k);
+        status_bar(dr, buf);
+    }
+
+    for (i = 0; i < wh; ++i) {
+        if (black_border_dsf[i] == UNVISITED)
+            dfs_dsf(i, w, state->borders, black_border_dsf, TRUE);
+        if (yellow_border_dsf[i] == UNVISITED)
+            dfs_dsf(i, w, state->borders, yellow_border_dsf, FALSE);
+    }
+
+    for (r = 0; r < h; ++r)
+        for (c = 0; c < w; ++c) {
+            int i = r * w + c, clue = state->shared->clues[i], flags, dir;
+            int on = bitcount[state->borders[i] & BORDER_MASK];
+            int off = bitcount[(state->borders[i] >> 4) & BORDER_MASK];
+
+            flags = state->borders[i];
+
+            if (flash) flags |= F_FLASH;
+
+            if (clue != EMPTY && (on > clue || clue > 4 - off))
+                flags |= F_ERROR_CLUE;
+
+            if (ui->show && ui->x == c && ui->y == r)
+                flags |= F_CURSOR;
+
+            /* border errors */
+            for (dir = 0; dir < 4; ++dir) {
+                int rr = r + dy[dir], cc = c + dx[dir], ii = rr * w + cc;
+
+                if (OUT_OF_BOUNDS(cc, rr, w, h)) continue;
+
+                /* we draw each border twice, except the outermost
+                 * big border, so we have to check for errors on
+                 * both sides of each border.*/
+                if (/* region too large */
+                    ((dsf_size(yellow_border_dsf, i) > k ||
+                      dsf_size(yellow_border_dsf, ii) > k) &&
+                     (dsf_canonify(yellow_border_dsf, i) !=
+                      dsf_canonify(yellow_border_dsf, ii)))
+
+                    ||
+                    /* region too small */
+                    ((dsf_size(black_border_dsf, i) < k ||
+                      dsf_size(black_border_dsf, ii) < k) &&
+                     dsf_canonify(black_border_dsf, i) !=
+                     dsf_canonify(black_border_dsf, ii))
+
+                    ||
+                    /* dangling borders within a single region */
+                    ((state->borders[i] & BORDER(dir)) &&
+                     /* we know it's a single region because there's a
+                      * path crossing no border from i to ii... */
+                     (dsf_canonify(yellow_border_dsf, i) ==
+                      dsf_canonify(yellow_border_dsf, ii) ||
+                      /* or because any such border would be an error */
+                      (dsf_size(black_border_dsf, i) <= k &&
+                       dsf_canonify(black_border_dsf, i) ==
+                       dsf_canonify(black_border_dsf, ii)))))
+
+                    flags |= BORDER_ERROR(BORDER(dir));
+            }
+
+            if (flags == ds->grid[i]) continue;
+            ds->grid[i] = flags;
+            draw_tile(dr, ds, r, c, ds->grid[i], clue);
+        }
+
+    sfree(black_border_dsf);
+    sfree(yellow_border_dsf);
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate,
+                              int dir, game_ui *ui)
+{
+    return 0.0F;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate,
+                               int dir, game_ui *ui)
+{
+    if (newstate->completed && !newstate->cheated && !oldstate->completed)
+        return FLASH_TIME;
+    return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    return state->completed ? +1 : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    assert (!"this shouldn't get called");
+    return 0;                          /* placate optimiser */
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+    int pw, ph;
+
+    game_compute_size(params, 700, &pw, &ph); /* 7mm, like loopy */
+
+    *x = pw / 100.0F;
+    *y = ph / 100.0F;
+}
+
+static void print_line(drawing *dr, int x1, int y1, int x2, int y2,
+                       int colour, int full)
+{
+    if (!full) {
+        int i, subdivisions = 8;
+        for (i = 1; i < subdivisions; ++i) {
+            int x = (x1 * (subdivisions - i) + x2 * i) / subdivisions;
+            int y = (y1 * (subdivisions - i) + y2 * i) / subdivisions;
+            draw_circle(dr, x, y, 3, colour, colour);
+        }
+    } else draw_line(dr, x1, y1, x2, y2, colour);
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+    int w = state->shared->params.w, h = state->shared->params.h;
+    int ink = print_mono_colour(dr, 0);
+    game_drawstate for_tilesize_macros, *ds = &for_tilesize_macros;
+    int r, c;
+
+    ds->tilesize = tilesize;
+
+    for (r = 0; r < h; ++r)
+        for (c = 0; c < w; ++c) {
+            int x = MARGIN + TILESIZE * c, y = MARGIN + TILESIZE * r;
+            int i = r * w + c, clue = state->shared->clues[i];
+
+            if (clue != EMPTY) {
+                char buf[2];
+                buf[0] = '0' + clue;
+                buf[1] = '\0';
+                draw_text(dr, x + CENTER, y + CENTER, FONT_VARIABLE,
+                          TILESIZE / 2, ALIGN_VCENTRE | ALIGN_HCENTRE,
+                          ink, buf);
+            }
+
+#define ts TILESIZE
+#define FULL(DIR) (state->borders[i] & (BORDER_ ## DIR))
+            print_line(dr, x,      y,      x + ts, y,      ink, FULL(U));
+            print_line(dr, x + ts, y,      x + ts, y + ts, ink, FULL(R));
+            print_line(dr, x,      y + ts, x + ts, y + ts, ink, FULL(D));
+            print_line(dr, x,      y,      x,      y + ts, ink, FULL(L));
+#undef ts
+#undef FULL
+        }
+
+    for (r = 1; r < h; ++r)
+        for (c = 1; c < w; ++c) {
+            int j = r * w + c, i = j - 1 - w;
+            int x = MARGIN + TILESIZE * c, y = MARGIN + TILESIZE * r;
+            if (state->borders[i] & (BORDER_D|BORDER_R)) continue;
+            if (state->borders[j] & (BORDER_U|BORDER_L)) continue;
+            draw_circle(dr, x, y, 3, ink, ink);
+        }
+}
+
+#ifdef COMBINED
+#define thegame palisade
+#endif
+
+const struct game thegame = {
+    "Palisade", "games.palisade", "palisade",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    TRUE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    48, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    TRUE, FALSE, game_print_size, game_print,
+    TRUE,                                     /* wants_statusbar */
+    FALSE, game_timing_state,
+    0,                                         /* flags */
+};
diff --git a/pattern.R b/pattern.R
new file mode 100644 (file)
index 0000000..d669571
--- /dev/null
+++ b/pattern.R
@@ -0,0 +1,25 @@
+# -*- makefile -*-
+
+pattern  : [X] GTK COMMON pattern pattern-icon|no-icon
+
+pattern  : [G] WINDOWS COMMON pattern pattern.res|noicon.res
+
+patternsolver : [U] pattern[STANDALONE_SOLVER] STANDALONE
+patternsolver : [C] pattern[STANDALONE_SOLVER] STANDALONE
+
+patternpicture : [U] pattern[STANDALONE_PICTURE_GENERATOR] STANDALONE
+patternpicture : [C] pattern[STANDALONE_PICTURE_GENERATOR] STANDALONE
+
+ALL += pattern[COMBINED]
+
+!begin am gtk
+GAMES += pattern
+!end
+
+!begin >list.c
+    A(pattern) \
+!end
+
+!begin >gamedesc.txt
+pattern:pattern.exe:Pattern:Pattern puzzle:Fill in the pattern in the grid, given only the lengths of runs of black squares.
+!end
diff --git a/pattern.c b/pattern.c
new file mode 100644 (file)
index 0000000..78d6b5e
--- /dev/null
+++ b/pattern.c
@@ -0,0 +1,2255 @@
+/*
+ * pattern.c: the pattern-reconstruction game known as `nonograms'.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+
+enum {
+    COL_BACKGROUND,
+    COL_EMPTY,
+    COL_FULL,
+    COL_TEXT,
+    COL_UNKNOWN,
+    COL_GRID,
+    COL_CURSOR,
+    COL_ERROR,
+    NCOLOURS
+};
+
+#define PREFERRED_TILE_SIZE 24
+#define TILE_SIZE (ds->tilesize)
+#define BORDER (3 * TILE_SIZE / 4)
+#define TLBORDER(d) ( (d) / 5 + 2 )
+#define GUTTER (TILE_SIZE / 2)
+
+#define FROMCOORD(d, x) \
+        ( ((x) - (BORDER + GUTTER + TILE_SIZE * TLBORDER(d))) / TILE_SIZE )
+
+#define SIZE(d) (2*BORDER + GUTTER + TILE_SIZE * (TLBORDER(d) + (d)))
+#define GETTILESIZE(d, w) ((double)w / (2.0 + (double)TLBORDER(d) + (double)(d)))
+
+#define TOCOORD(d, x) (BORDER + GUTTER + TILE_SIZE * (TLBORDER(d) + (x)))
+
+struct game_params {
+    int w, h;
+};
+
+#define GRID_UNKNOWN 2
+#define GRID_FULL 1
+#define GRID_EMPTY 0
+
+typedef struct game_state_common {
+    /* Parts of the game state that don't change during play. */
+    int w, h;
+    int rowsize;
+    int *rowdata, *rowlen;
+    unsigned char *immutable;
+    int refcount;
+} game_state_common;
+
+struct game_state {
+    game_state_common *common;
+    unsigned char *grid;
+    int completed, cheated;
+};
+
+#define FLASH_TIME 0.13F
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = ret->h = 15;
+
+    return ret;
+}
+
+static const struct game_params pattern_presets[] = {
+    {10, 10},
+    {15, 15},
+    {20, 20},
+#ifndef SLOW_SYSTEM
+    {25, 25},
+    {30, 30},
+#endif
+};
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params *ret;
+    char str[80];
+
+    if (i < 0 || i >= lenof(pattern_presets))
+        return FALSE;
+
+    ret = snew(game_params);
+    *ret = pattern_presets[i];
+
+    sprintf(str, "%dx%d", ret->w, ret->h);
+
+    *name = dupstr(str);
+    *params = ret;
+    return TRUE;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+static void decode_params(game_params *ret, char const *string)
+{
+    char const *p = string;
+
+    ret->w = atoi(p);
+    while (*p && isdigit((unsigned char)*p)) p++;
+    if (*p == 'x') {
+        p++;
+        ret->h = atoi(p);
+        while (*p && isdigit((unsigned char)*p)) p++;
+    } else {
+        ret->h = ret->w;
+    }
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char ret[400];
+    int len;
+
+    len = sprintf(ret, "%dx%d", params->w, params->h);
+    assert(len < lenof(ret));
+    ret[len] = '\0';
+
+    return dupstr(ret);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+    char buf[80];
+
+    ret = snewn(3, config_item);
+
+    ret[0].name = "Width";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->w);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Height";
+    ret[1].type = C_STRING;
+    sprintf(buf, "%d", params->h);
+    ret[1].sval = dupstr(buf);
+    ret[1].ival = 0;
+
+    ret[2].name = NULL;
+    ret[2].type = C_END;
+    ret[2].sval = NULL;
+    ret[2].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = atoi(cfg[0].sval);
+    ret->h = atoi(cfg[1].sval);
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    if (params->w <= 0 || params->h <= 0)
+       return "Width and height must both be greater than zero";
+    return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Puzzle generation code.
+ * 
+ * For this particular puzzle, it seemed important to me to ensure
+ * a unique solution. I do this the brute-force way, by having a
+ * solver algorithm alongside the generator, and repeatedly
+ * generating a random grid until I find one whose solution is
+ * unique. It turns out that this isn't too onerous on a modern PC
+ * provided you keep grid size below around 30. Any offers of
+ * better algorithms, however, will be very gratefully received.
+ * 
+ * Another annoyance of this approach is that it limits the
+ * available puzzles to those solvable by the algorithm I've used.
+ * My algorithm only ever considers a single row or column at any
+ * one time, which means it's incapable of solving the following
+ * difficult example (found by Bella Image around 1995/6, when she
+ * and I were both doing maths degrees):
+ * 
+ *        2  1  2  1 
+ *
+ *      +--+--+--+--+
+ * 1 1  |  |  |  |  |
+ *      +--+--+--+--+
+ *   2  |  |  |  |  |
+ *      +--+--+--+--+
+ *   1  |  |  |  |  |
+ *      +--+--+--+--+
+ *   1  |  |  |  |  |
+ *      +--+--+--+--+
+ * 
+ * Obviously this cannot be solved by a one-row-or-column-at-a-time
+ * algorithm (it would require at least one row or column reading
+ * `2 1', `1 2', `3' or `4' to get started). However, it can be
+ * proved to have a unique solution: if the top left square were
+ * empty, then the only option for the top row would be to fill the
+ * two squares in the 1 columns, which would imply the squares
+ * below those were empty, leaving no place for the 2 in the second
+ * row. Contradiction. Hence the top left square is full, and the
+ * unique solution follows easily from that starting point.
+ * 
+ * (The game ID for this puzzle is 4x4:2/1/2/1/1.1/2/1/1 , in case
+ * it's useful to anyone.)
+ */
+
+#ifndef STANDALONE_PICTURE_GENERATOR
+static int float_compare(const void *av, const void *bv)
+{
+    const float *a = (const float *)av;
+    const float *b = (const float *)bv;
+    if (*a < *b)
+        return -1;
+    else if (*a > *b)
+        return +1;
+    else
+        return 0;
+}
+
+static void generate(random_state *rs, int w, int h, unsigned char *retgrid)
+{
+    float *fgrid;
+    float *fgrid2;
+    int step, i, j;
+    float threshold;
+
+    fgrid = snewn(w*h, float);
+
+    for (i = 0; i < h; i++) {
+        for (j = 0; j < w; j++) {
+            fgrid[i*w+j] = random_upto(rs, 100000000UL) / 100000000.F;
+        }
+    }
+
+    /*
+     * The above gives a completely random splattering of black and
+     * white cells. We want to gently bias this in favour of _some_
+     * reasonably thick areas of white and black, while retaining
+     * some randomness and fine detail.
+     * 
+     * So we evolve the starting grid using a cellular automaton.
+     * Currently, I'm doing something very simple indeed, which is
+     * to set each square to the average of the surrounding nine
+     * cells (or the average of fewer, if we're on a corner).
+     */
+    for (step = 0; step < 1; step++) {
+        fgrid2 = snewn(w*h, float);
+
+        for (i = 0; i < h; i++) {
+            for (j = 0; j < w; j++) {
+                float sx, xbar;
+                int n, p, q;
+
+                /*
+                 * Compute the average of the surrounding cells.
+                 */
+                n = 0;
+                sx = 0.F;
+                for (p = -1; p <= +1; p++) {
+                    for (q = -1; q <= +1; q++) {
+                        if (i+p < 0 || i+p >= h || j+q < 0 || j+q >= w)
+                            continue;
+                       /*
+                        * An additional special case not mentioned
+                        * above: if a grid dimension is 2xn then
+                        * we do not average across that dimension
+                        * at all. Otherwise a 2x2 grid would
+                        * contain four identical squares.
+                        */
+                       if ((h==2 && p!=0) || (w==2 && q!=0))
+                           continue;
+                        n++;
+                        sx += fgrid[(i+p)*w+(j+q)];
+                    }
+                }
+                xbar = sx / n;
+
+                fgrid2[i*w+j] = xbar;
+            }
+        }
+
+        sfree(fgrid);
+        fgrid = fgrid2;
+    }
+
+    fgrid2 = snewn(w*h, float);
+    memcpy(fgrid2, fgrid, w*h*sizeof(float));
+    qsort(fgrid2, w*h, sizeof(float), float_compare);
+    threshold = fgrid2[w*h/2];
+    sfree(fgrid2);
+
+    for (i = 0; i < h; i++) {
+        for (j = 0; j < w; j++) {
+            retgrid[i*w+j] = (fgrid[i*w+j] >= threshold ? GRID_FULL :
+                              GRID_EMPTY);
+        }
+    }
+
+    sfree(fgrid);
+}
+#endif
+
+static int compute_rowdata(int *ret, unsigned char *start, int len, int step)
+{
+    int i, n;
+
+    n = 0;
+
+    for (i = 0; i < len; i++) {
+        if (start[i*step] == GRID_FULL) {
+            int runlen = 1;
+            while (i+runlen < len && start[(i+runlen)*step] == GRID_FULL)
+                runlen++;
+            ret[n++] = runlen;
+            i += runlen;
+        }
+
+        if (i < len && start[i*step] == GRID_UNKNOWN)
+            return -1;
+    }
+
+    return n;
+}
+
+#define UNKNOWN 0
+#define BLOCK 1
+#define DOT 2
+#define STILL_UNKNOWN 3
+
+#ifdef STANDALONE_SOLVER
+int verbose = FALSE;
+#endif
+
+static int do_recurse(unsigned char *known, unsigned char *deduced,
+                       unsigned char *row,
+                      unsigned char *minpos_done, unsigned char *maxpos_done,
+                      unsigned char *minpos_ok, unsigned char *maxpos_ok,
+                      int *data, int len,
+                       int freespace, int ndone, int lowest)
+{
+    int i, j, k;
+
+
+    /* This algorithm basically tries all possible ways the given rows of
+     * black blocks can be laid out in the row/column being examined.
+     * Special care is taken to avoid checking the tail of a row/column
+     * if the same conditions have already been checked during this recursion
+     * The algorithm also takes care to cut its losses as soon as an
+     * invalid (partial) solution is detected.
+     */
+    if (data[ndone]) {
+       if (lowest >= minpos_done[ndone] && lowest <= maxpos_done[ndone]) {
+           if (lowest >= minpos_ok[ndone] && lowest <= maxpos_ok[ndone]) {
+               for (i=0; i<lowest; i++)
+                   deduced[i] |= row[i];
+           }
+           return lowest >= minpos_ok[ndone] && lowest <= maxpos_ok[ndone];
+       } else {
+           if (lowest < minpos_done[ndone]) minpos_done[ndone] = lowest;
+           if (lowest > maxpos_done[ndone]) maxpos_done[ndone] = lowest;
+       }
+       for (i=0; i<=freespace; i++) {
+           j = lowest;
+           for (k=0; k<i; k++) {
+               if (known[j] == BLOCK) goto next_iter;
+               row[j++] = DOT;
+           }
+           for (k=0; k<data[ndone]; k++) {
+               if (known[j] == DOT) goto next_iter;
+               row[j++] = BLOCK;
+           }
+           if (j < len) {
+               if (known[j] == BLOCK) goto next_iter;
+               row[j++] = DOT;
+           }
+           if (do_recurse(known, deduced, row, minpos_done, maxpos_done,
+                          minpos_ok, maxpos_ok, data, len, freespace-i, ndone+1, j)) {
+               if (lowest < minpos_ok[ndone]) minpos_ok[ndone] = lowest;
+               if (lowest + i > maxpos_ok[ndone]) maxpos_ok[ndone] = lowest + i;
+               if (lowest + i > maxpos_done[ndone]) maxpos_done[ndone] = lowest + i;
+           }
+           next_iter:
+           j++;
+       }
+       return lowest >= minpos_ok[ndone] && lowest <= maxpos_ok[ndone];
+    } else {
+       for (i=lowest; i<len; i++) {
+           if (known[i] == BLOCK) return FALSE;
+           row[i] = DOT;
+           }
+       for (i=0; i<len; i++)
+           deduced[i] |= row[i];
+       return TRUE;
+    }
+}
+
+
+static int do_row(unsigned char *known, unsigned char *deduced,
+                  unsigned char *row,
+                  unsigned char *minpos_done, unsigned char *maxpos_done,
+                 unsigned char *minpos_ok, unsigned char *maxpos_ok,
+                  unsigned char *start, int len, int step, int *data,
+                 unsigned int *changed
+#ifdef STANDALONE_SOLVER
+                 , const char *rowcol, int index, int cluewid
+#endif
+                 )
+{
+    int rowlen, i, freespace, done_any;
+
+    freespace = len+1;
+    for (rowlen = 0; data[rowlen]; rowlen++) {
+       minpos_done[rowlen] = minpos_ok[rowlen] = len - 1;
+       maxpos_done[rowlen] = maxpos_ok[rowlen] = 0;
+       freespace -= data[rowlen]+1;
+    }
+
+    for (i = 0; i < len; i++) {
+       known[i] = start[i*step];
+       deduced[i] = 0;
+    }
+    for (i = len - 1; i >= 0 && known[i] == DOT; i--)
+        freespace--;
+
+    if (rowlen == 0) {
+        memset(deduced, DOT, len);
+    } else {
+        do_recurse(known, deduced, row, minpos_done, maxpos_done, minpos_ok,
+                   maxpos_ok, data, len, freespace, 0, 0);
+    }
+
+    done_any = FALSE;
+    for (i=0; i<len; i++)
+       if (deduced[i] && deduced[i] != STILL_UNKNOWN && !known[i]) {
+           start[i*step] = deduced[i];
+           if (changed) changed[i]++;
+           done_any = TRUE;
+       }
+#ifdef STANDALONE_SOLVER
+    if (verbose && done_any) {
+       char buf[80];
+       int thiscluewid;
+       printf("%s %2d: [", rowcol, index);
+       for (thiscluewid = -1, i = 0; data[i]; i++)
+           thiscluewid += sprintf(buf, " %d", data[i]);
+       printf("%*s", cluewid - thiscluewid, "");
+       for (i = 0; data[i]; i++)
+           printf(" %d", data[i]);
+       printf(" ] ");
+       for (i = 0; i < len; i++)
+           putchar(known[i] == BLOCK ? '#' :
+                   known[i] == DOT ? '.' : '?');
+       printf(" -> ");
+       for (i = 0; i < len; i++)
+           putchar(start[i*step] == BLOCK ? '#' :
+                   start[i*step] == DOT ? '.' : '?');
+       putchar('\n');
+    }
+#endif
+    return done_any;
+}
+
+static int solve_puzzle(const game_state *state, unsigned char *grid,
+                        int w, int h,
+                       unsigned char *matrix, unsigned char *workspace,
+                       unsigned int *changed_h, unsigned int *changed_w,
+                       int *rowdata
+#ifdef STANDALONE_SOLVER
+                       , int cluewid
+#else
+                       , int dummy
+#endif
+                       )
+{
+    int i, j, ok, max;
+    int max_h, max_w;
+
+    assert((state!=NULL && state->common->rowdata!=NULL) ^ (grid!=NULL));
+
+    max = max(w, h);
+
+    memset(matrix, 0, w*h);
+    if (state) {
+        for (i=0; i<w*h; i++) {
+            if (state->common->immutable[i])
+                matrix[i] = state->grid[i];
+        }
+    }
+
+    /* For each column, compute how many squares can be deduced
+     * from just the row-data and initial clues.
+     * Later, changed_* will hold how many squares were changed
+     * in every row/column in the previous iteration
+     * Changed_* is used to choose the next rows / cols to re-examine
+     */
+    for (i=0; i<h; i++) {
+       int freespace, rowlen;
+       if (state && state->common->rowdata) {
+            memcpy(rowdata, state->common->rowdata + state->common->rowsize*(w+i), max*sizeof(int));
+           rowlen = state->common->rowlen[w+i];
+       } else {
+           rowlen = compute_rowdata(rowdata, grid+i*w, w, 1);
+       }
+        rowdata[rowlen] = 0;
+        if (rowlen == 0) {
+            changed_h[i] = w;
+        } else {
+            for (j=0, freespace=w+1; rowdata[j]; j++)
+                freespace -= rowdata[j] + 1;
+            for (j=0, changed_h[i]=0; rowdata[j]; j++)
+                if (rowdata[j] > freespace)
+                    changed_h[i] += rowdata[j] - freespace;
+        }
+        for (j = 0; j < w; j++)
+            if (matrix[i*w+j])
+                changed_h[i]++;
+    }
+    for (i=0,max_h=0; i<h; i++)
+       if (changed_h[i] > max_h)
+           max_h = changed_h[i];
+    for (i=0; i<w; i++) {
+       int freespace, rowlen;
+       if (state && state->common->rowdata) {
+           memcpy(rowdata, state->common->rowdata + state->common->rowsize*i, max*sizeof(int));
+           rowlen = state->common->rowlen[i];
+       } else {
+            rowlen = compute_rowdata(rowdata, grid+i, h, w);
+       }
+        rowdata[rowlen] = 0;
+        if (rowlen == 0) {
+            changed_w[i] = h;
+        } else {
+            for (j=0, freespace=h+1; rowdata[j]; j++)
+                freespace -= rowdata[j] + 1;
+            for (j=0, changed_w[i]=0; rowdata[j]; j++)
+                if (rowdata[j] > freespace)
+                    changed_w[i] += rowdata[j] - freespace;
+        }
+        for (j = 0; j < h; j++)
+            if (matrix[j*w+i])
+                changed_w[i]++;
+    }
+    for (i=0,max_w=0; i<w; i++)
+       if (changed_w[i] > max_w)
+           max_w = changed_w[i];
+
+    /* Solve the puzzle.
+     * Process rows/columns individually. Deductions involving more than one
+     * row and/or column at a time are not supported.
+     * Take care to only process rows/columns which have been changed since they
+     * were previously processed.
+     * Also, prioritize rows/columns which have had the most changes since their
+     * previous processing, as they promise the greatest benefit.
+     * Extremely rectangular grids (e.g. 10x20, 15x40, etc.) are not treated specially.
+     */
+    do {
+       for (; max_h && max_h >= max_w; max_h--) {
+           for (i=0; i<h; i++) {
+               if (changed_h[i] >= max_h) {
+                   if (state && state->common->rowdata) {
+                       memcpy(rowdata, state->common->rowdata + state->common->rowsize*(w+i), max*sizeof(int));
+                       rowdata[state->common->rowlen[w+i]] = 0;
+                   } else {
+                       rowdata[compute_rowdata(rowdata, grid+i*w, w, 1)] = 0;
+                   }
+                   do_row(workspace, workspace+max, workspace+2*max,
+                          workspace+3*max, workspace+4*max,
+                          workspace+5*max, workspace+6*max,
+                          matrix+i*w, w, 1, rowdata, changed_w
+#ifdef STANDALONE_SOLVER
+                          , "row", i+1, cluewid
+#endif
+                          );
+                   changed_h[i] = 0;
+               }
+           }
+           for (i=0,max_w=0; i<w; i++)
+               if (changed_w[i] > max_w)
+                   max_w = changed_w[i];
+       }
+       for (; max_w && max_w >= max_h; max_w--) {
+           for (i=0; i<w; i++) {
+               if (changed_w[i] >= max_w) {
+                   if (state && state->common->rowdata) {
+                       memcpy(rowdata, state->common->rowdata + state->common->rowsize*i, max*sizeof(int));
+                       rowdata[state->common->rowlen[i]] = 0;
+                   } else {
+                       rowdata[compute_rowdata(rowdata, grid+i, h, w)] = 0;
+                   }
+                   do_row(workspace, workspace+max, workspace+2*max,
+                          workspace+3*max, workspace+4*max,
+                          workspace+5*max, workspace+6*max,
+                          matrix+i, h, w, rowdata, changed_h
+#ifdef STANDALONE_SOLVER
+                          , "col", i+1, cluewid
+#endif
+                          );
+                   changed_w[i] = 0;
+               }
+           }
+           for (i=0,max_h=0; i<h; i++)
+               if (changed_h[i] > max_h)
+                   max_h = changed_h[i];
+       }
+    } while (max_h>0 || max_w>0);
+
+    ok = TRUE;
+    for (i=0; i<h; i++) {
+       for (j=0; j<w; j++) {
+           if (matrix[i*w+j] == UNKNOWN)
+               ok = FALSE;
+       }
+    }
+
+    return ok;
+}
+
+#ifndef STANDALONE_PICTURE_GENERATOR
+static unsigned char *generate_soluble(random_state *rs, int w, int h)
+{
+    int i, j, ok, ntries, max;
+    unsigned char *grid, *matrix, *workspace;
+    unsigned int *changed_h, *changed_w;
+    int *rowdata;
+
+    max = max(w, h);
+
+    grid = snewn(w*h, unsigned char);
+    /* Allocate this here, to avoid having to reallocate it again for every geneerated grid */
+    matrix = snewn(w*h, unsigned char);
+    workspace = snewn(max*7, unsigned char);
+    changed_h = snewn(max+1, unsigned int);
+    changed_w = snewn(max+1, unsigned int);
+    rowdata = snewn(max+1, int);
+
+    ntries = 0;
+
+    do {
+        ntries++;
+
+        generate(rs, w, h, grid);
+
+        /*
+         * The game is a bit too easy if any row or column is
+         * completely black or completely white. An exception is
+         * made for rows/columns that are under 3 squares,
+         * otherwise nothing will ever be successfully generated.
+         */
+        ok = TRUE;
+        if (w > 2) {
+            for (i = 0; i < h; i++) {
+                int colours = 0;
+                for (j = 0; j < w; j++)
+                    colours |= (grid[i*w+j] == GRID_FULL ? 2 : 1);
+                if (colours != 3)
+                    ok = FALSE;
+            }
+        }
+        if (h > 2) {
+            for (j = 0; j < w; j++) {
+                int colours = 0;
+                for (i = 0; i < h; i++)
+                    colours |= (grid[i*w+j] == GRID_FULL ? 2 : 1);
+                if (colours != 3)
+                    ok = FALSE;
+            }
+        }
+        if (!ok)
+            continue;
+
+       ok = solve_puzzle(NULL, grid, w, h, matrix, workspace,
+                         changed_h, changed_w, rowdata, 0);
+    } while (!ok);
+
+    sfree(matrix);
+    sfree(workspace);
+    sfree(changed_h);
+    sfree(changed_w);
+    sfree(rowdata);
+    return grid;
+}
+#endif
+
+#ifdef STANDALONE_PICTURE_GENERATOR
+unsigned char *picture;
+#endif
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                          char **aux, int interactive)
+{
+    unsigned char *grid;
+    int i, j, max, rowlen, *rowdata;
+    char intbuf[80], *desc;
+    int desclen, descpos;
+#ifdef STANDALONE_PICTURE_GENERATOR
+    game_state *state;
+    int *index;
+#endif
+
+    max = max(params->w, params->h);
+
+#ifdef STANDALONE_PICTURE_GENERATOR
+    /*
+     * Fixed input picture.
+     */
+    grid = snewn(params->w * params->h, unsigned char);
+    memcpy(grid, picture, params->w * params->h);
+
+    /*
+     * Now winnow the immutable square set as far as possible.
+     */
+    state = snew(game_state);
+    state->grid = grid;
+    state->common = snew(game_state_common);
+    state->common->rowdata = NULL;
+    state->common->immutable = snewn(params->w * params->h, unsigned char);
+    memset(state->common->immutable, 1, params->w * params->h);
+
+    index = snewn(params->w * params->h, int);
+    for (i = 0; i < params->w * params->h; i++)
+        index[i] = i;
+    shuffle(index, params->w * params->h, sizeof(*index), rs);
+
+    {
+        unsigned char *matrix = snewn(params->w*params->h, unsigned char);
+        unsigned char *workspace = snewn(max*7, unsigned char);
+        unsigned int *changed_h = snewn(max+1, unsigned int);
+        unsigned int *changed_w = snewn(max+1, unsigned int);
+        int *rowdata = snewn(max+1, int);
+        for (i = 0; i < params->w * params->h; i++) {
+            state->common->immutable[index[i]] = 0;
+            if (!solve_puzzle(state, grid, params->w, params->h,
+                              matrix, workspace, changed_h, changed_w,
+                              rowdata, 0))
+                state->common->immutable[index[i]] = 1;
+        }
+        sfree(workspace);
+        sfree(changed_h);
+        sfree(changed_w);
+        sfree(rowdata);
+        sfree(matrix);
+    }
+#else
+    grid = generate_soluble(rs, params->w, params->h);
+#endif
+    rowdata = snewn(max, int);
+
+    /*
+     * Save the solved game in aux.
+     */
+    if (aux) {
+       char *ai = snewn(params->w * params->h + 2, char);
+
+        /*
+         * String format is exactly the same as a solve move, so we
+         * can just dupstr this in solve_game().
+         */
+
+        ai[0] = 'S';
+
+        for (i = 0; i < params->w * params->h; i++)
+            ai[i+1] = grid[i] ? '1' : '0';
+
+        ai[params->w * params->h + 1] = '\0';
+
+       *aux = ai;
+    }
+
+    /*
+     * Seed is a slash-separated list of row contents; each row
+     * contents section is a dot-separated list of integers. Row
+     * contents are listed in the order (columns left to right,
+     * then rows top to bottom).
+     * 
+     * Simplest way to handle memory allocation is to make two
+     * passes, first computing the seed size and then writing it
+     * out.
+     */
+    desclen = 0;
+    for (i = 0; i < params->w + params->h; i++) {
+        if (i < params->w)
+            rowlen = compute_rowdata(rowdata, grid+i, params->h, params->w);
+        else
+            rowlen = compute_rowdata(rowdata, grid+(i-params->w)*params->w,
+                                     params->w, 1);
+        if (rowlen > 0) {
+            for (j = 0; j < rowlen; j++) {
+                desclen += 1 + sprintf(intbuf, "%d", rowdata[j]);
+            }
+        } else {
+            desclen++;
+        }
+    }
+    desc = snewn(desclen, char);
+    descpos = 0;
+    for (i = 0; i < params->w + params->h; i++) {
+        if (i < params->w)
+            rowlen = compute_rowdata(rowdata, grid+i, params->h, params->w);
+        else
+            rowlen = compute_rowdata(rowdata, grid+(i-params->w)*params->w,
+                                     params->w, 1);
+        if (rowlen > 0) {
+            for (j = 0; j < rowlen; j++) {
+                int len = sprintf(desc+descpos, "%d", rowdata[j]);
+                if (j+1 < rowlen)
+                    desc[descpos + len] = '.';
+                else
+                    desc[descpos + len] = '/';
+                descpos += len+1;
+            }
+        } else {
+            desc[descpos++] = '/';
+        }
+    }
+    assert(descpos == desclen);
+    assert(desc[desclen-1] == '/');
+    desc[desclen-1] = '\0';
+#ifdef STANDALONE_PICTURE_GENERATOR
+    for (i = 0; i < params->w * params->h; i++)
+        if (state->common->immutable[i])
+            break;
+    if (i < params->w * params->h) {
+        /*
+         * At least one immutable square, so we need a suffix.
+         */
+        int run;
+
+        desc = sresize(desc, desclen + params->w * params->h + 3, char);
+        desc[descpos-1] = ',';
+
+        run = 0;
+        for (i = 0; i < params->w * params->h; i++) {
+            if (!state->common->immutable[i]) {
+                run++;
+                if (run == 25) {
+                    desc[descpos++] = 'z';
+                    run = 0;
+                }
+            } else {
+                desc[descpos++] = run + (grid[i] == GRID_FULL ? 'A' : 'a');
+                run = 0;
+            }
+        }
+        if (run > 0)
+            desc[descpos++] = run + 'a';
+        desc[descpos] = '\0';
+    }
+    sfree(state->common->immutable);
+    sfree(state->common);
+    sfree(state);
+#endif
+    sfree(rowdata);
+    sfree(grid);
+    return desc;
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    int i, n, rowspace;
+    const char *p;
+
+    for (i = 0; i < params->w + params->h; i++) {
+        if (i < params->w)
+            rowspace = params->h + 1;
+        else
+            rowspace = params->w + 1;
+
+        if (*desc && isdigit((unsigned char)*desc)) {
+            do {
+                p = desc;
+                while (*desc && isdigit((unsigned char)*desc)) desc++;
+                n = atoi(p);
+                rowspace -= n+1;
+
+                if (rowspace < 0) {
+                    if (i < params->w)
+                        return "at least one column contains more numbers than will fit";
+                    else
+                        return "at least one row contains more numbers than will fit";
+                }
+            } while (*desc++ == '.');
+        } else {
+            desc++;                    /* expect a slash immediately */
+        }
+
+        if (desc[-1] == '/') {
+            if (i+1 == params->w + params->h)
+                return "too many row/column specifications";
+        } else if (desc[-1] == '\0' || desc[-1] == ',') {
+            if (i+1 < params->w + params->h)
+                return "too few row/column specifications";
+        } else
+            return "unrecognised character in game specification";
+    }
+
+    if (desc[-1] == ',') {
+        /*
+         * Optional extra piece of game description which fills in
+         * some grid squares as extra clues.
+         */
+        i = 0;
+        while (i < params->w * params->h) {
+            int c = (unsigned char)*desc++;
+            if ((c >= 'a' && c <= 'z') ||
+                (c >= 'A' && c <= 'Z')) {
+                int len = tolower(c) - 'a';
+                i += len;
+                if (len < 25 && i < params->w*params->h)
+                    i++;
+                if (i > params->w * params->h) {
+                    return "too much data in clue-squares section";
+                }
+            } else if (!c) {
+                return "too little data in clue-squares section";
+            } else {
+                return "unrecognised character in clue-squares section";
+            }
+        }
+        if (*desc) {
+            return "too much data in clue-squares section";
+        }
+    }
+
+    return NULL;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    int i;
+    const char *p;
+    game_state *state = snew(game_state);
+
+    state->common = snew(game_state_common);
+    state->common->refcount = 1;
+
+    state->common->w = params->w;
+    state->common->h = params->h;
+
+    state->grid = snewn(state->common->w * state->common->h, unsigned char);
+    memset(state->grid, GRID_UNKNOWN, state->common->w * state->common->h);
+
+    state->common->immutable = snewn(state->common->w * state->common->h,
+                                     unsigned char);
+    memset(state->common->immutable, 0, state->common->w * state->common->h);
+
+    state->common->rowsize = max(state->common->w, state->common->h);
+    state->common->rowdata = snewn(state->common->rowsize * (state->common->w + state->common->h), int);
+    state->common->rowlen = snewn(state->common->w + state->common->h, int);
+
+    state->completed = state->cheated = FALSE;
+
+    for (i = 0; i < params->w + params->h; i++) {
+        state->common->rowlen[i] = 0;
+        if (*desc && isdigit((unsigned char)*desc)) {
+            do {
+                p = desc;
+                while (*desc && isdigit((unsigned char)*desc)) desc++;
+                state->common->rowdata[state->common->rowsize * i + state->common->rowlen[i]++] =
+                    atoi(p);
+            } while (*desc++ == '.');
+        } else {
+            desc++;                    /* expect a slash immediately */
+        }
+    }
+
+    if (desc[-1] == ',') {
+        /*
+         * Optional extra piece of game description which fills in
+         * some grid squares as extra clues.
+         */
+        i = 0;
+        while (i < params->w * params->h) {
+            int c = (unsigned char)*desc++;
+            int full = isupper(c), len = tolower(c) - 'a';
+            i += len;
+            if (len < 25 && i < params->w*params->h) {
+                state->grid[i] = full ? GRID_FULL : GRID_EMPTY;
+                state->common->immutable[i] = TRUE;
+                i++;
+            }
+        }
+    }
+
+    return state;
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    game_state *ret = snew(game_state);
+
+    ret->common = state->common;
+    ret->common->refcount++;
+
+    ret->grid = snewn(ret->common->w * ret->common->h, unsigned char);
+    memcpy(ret->grid, state->grid, ret->common->w * ret->common->h);
+
+    ret->completed = state->completed;
+    ret->cheated = state->cheated;
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    if (--state->common->refcount == 0) {
+        sfree(state->common->rowdata);
+        sfree(state->common->rowlen);
+        sfree(state->common->immutable);
+        sfree(state->common);
+    }
+    sfree(state->grid);
+    sfree(state);
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *ai, char **error)
+{
+    unsigned char *matrix;
+    int w = state->common->w, h = state->common->h;
+    int i;
+    char *ret;
+    int max, ok;
+    unsigned char *workspace;
+    unsigned int *changed_h, *changed_w;
+    int *rowdata;
+
+    /*
+     * If we already have the solved state in ai, copy it out.
+     */
+    if (ai)
+        return dupstr(ai);
+
+    max = max(w, h);
+    matrix = snewn(w*h, unsigned char);
+    workspace = snewn(max*7, unsigned char);
+    changed_h = snewn(max+1, unsigned int);
+    changed_w = snewn(max+1, unsigned int);
+    rowdata = snewn(max+1, int);
+
+    ok = solve_puzzle(state, NULL, w, h, matrix, workspace,
+                     changed_h, changed_w, rowdata, 0);
+
+    sfree(workspace);
+    sfree(changed_h);
+    sfree(changed_w);
+    sfree(rowdata);
+
+    if (!ok) {
+       sfree(matrix);
+       *error = "Solving algorithm cannot complete this puzzle";
+       return NULL;
+    }
+
+    ret = snewn(w*h+2, char);
+    ret[0] = 'S';
+    for (i = 0; i < w*h; i++) {
+       assert(matrix[i] == BLOCK || matrix[i] == DOT);
+       ret[i+1] = (matrix[i] == BLOCK ? '1' : '0');
+    }
+    ret[w*h+1] = '\0';
+
+    sfree(matrix);
+
+    return ret;
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    int w = state->common->w, h = state->common->h, i, j;
+    int left_gap = 0, top_gap = 0, ch = 2, cw = 1, limit = 1;
+
+    int len, topleft, lw, lh, gw, gh; /* {line,grid}_{width,height} */
+    char *board, *buf;
+
+    for (i = 0; i < w; ++i) {
+       top_gap = max(top_gap, state->common->rowlen[i]);
+       for (j = 0; j < state->common->rowlen[i]; ++j)
+           while (state->common->rowdata[i*state->common->rowsize + j] >= limit) {
+               ++cw;
+               limit *= 10;
+           }
+    }
+    for (i = 0; i < h; ++i) {
+       int rowlen = 0, predecessors = FALSE;
+       for (j = 0; j < state->common->rowlen[i+w]; ++j) {
+           int copy = state->common->rowdata[(i+w)*state->common->rowsize + j];
+           rowlen += predecessors;
+           predecessors = TRUE;
+           do ++rowlen; while (copy /= 10);
+       }
+       left_gap = max(left_gap, rowlen);
+    }
+
+    cw = max(cw, 3);
+
+    gw = w*cw + 2;
+    gh = h*ch + 1;
+    lw = gw + left_gap;
+    lh = gh + top_gap;
+    len = lw * lh;
+    topleft = lw * top_gap + left_gap;
+
+    board = snewn(len + 1, char);
+    sprintf(board, "%*s\n", len - 2, "");
+
+    for (i = 0; i < lh; ++i) {
+       board[lw - 1 + i*lw] = '\n';
+       if (i < top_gap) continue;
+       board[lw - 2 + i*lw] = ((i - top_gap) % ch ? '|' : '+');
+    }
+
+    for (i = 0; i < w; ++i) {
+       for (j = 0; j < state->common->rowlen[i]; ++j) {
+           int cell = topleft + i*cw + 1 + lw*(j - state->common->rowlen[i]);
+           int nch = sprintf(board + cell, "%*d", cw - 1,
+                             state->common->rowdata[i*state->common->rowsize + j]);
+           board[cell + nch] = ' '; /* de-NUL-ify */
+       }
+    }
+
+    buf = snewn(left_gap, char);
+    for (i = 0; i < h; ++i) {
+       char *p = buf, *start = board + top_gap*lw + left_gap + (i*ch+1)*lw;
+       for (j = 0; j < state->common->rowlen[i+w]; ++j) {
+           if (p > buf) *p++ = ' ';
+           p += sprintf(p, "%d", state->common->rowdata[(i+w)*state->common->rowsize + j]);
+       }
+       memcpy(start - (p - buf), buf, p - buf);
+    }
+
+    for (i = 0; i < w; ++i) {
+       for (j = 0; j < h; ++j) {
+           int cell = topleft + i*cw + j*ch*lw;
+           int center = cell + cw/2 + (ch/2)*lw;
+           int dx, dy;
+           board[cell] = 0 ? center : '+';
+           for (dx = 1; dx < cw; ++dx) board[cell + dx] = '-';
+           for (dy = 1; dy < ch; ++dy) board[cell + dy*lw] = '|';
+           if (state->grid[i*w+j] == GRID_UNKNOWN) continue;
+           for (dx = 1; dx < cw; ++dx)
+               for (dy = 1; dy < ch; ++dy)
+                   board[cell + dx + dy*lw] =
+                       state->grid[i*w+j] == GRID_FULL ? '#' : '.';
+       }
+    }
+
+    memcpy(board + topleft + h*ch*lw, board + topleft, gw - 1);
+
+    sfree(buf);
+
+    return board;
+}
+
+struct game_ui {
+    int dragging;
+    int drag_start_x;
+    int drag_start_y;
+    int drag_end_x;
+    int drag_end_y;
+    int drag, release, state;
+    int cur_x, cur_y, cur_visible;
+};
+
+static game_ui *new_ui(const game_state *state)
+{
+    game_ui *ret;
+
+    ret = snew(game_ui);
+    ret->dragging = FALSE;
+    ret->cur_x = ret->cur_y = ret->cur_visible = 0;
+
+    return ret;
+}
+
+static void free_ui(game_ui *ui)
+{
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+}
+
+struct game_drawstate {
+    int started;
+    int w, h;
+    int tilesize;
+    unsigned char *visible, *numcolours;
+    int cur_x, cur_y;
+};
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    int control = button & MOD_CTRL, shift = button & MOD_SHFT;
+    button &= ~MOD_MASK;
+
+    x = FROMCOORD(state->common->w, x);
+    y = FROMCOORD(state->common->h, y);
+
+    if (x >= 0 && x < state->common->w && y >= 0 && y < state->common->h &&
+        (button == LEFT_BUTTON || button == RIGHT_BUTTON ||
+         button == MIDDLE_BUTTON)) {
+#ifdef STYLUS_BASED
+        int currstate = state->grid[y * state->common->w + x];
+#endif
+
+        ui->dragging = TRUE;
+
+        if (button == LEFT_BUTTON) {
+            ui->drag = LEFT_DRAG;
+            ui->release = LEFT_RELEASE;
+#ifdef STYLUS_BASED
+            ui->state = (currstate + 2) % 3; /* FULL -> EMPTY -> UNKNOWN */
+#else
+            ui->state = GRID_FULL;
+#endif
+        } else if (button == RIGHT_BUTTON) {
+            ui->drag = RIGHT_DRAG;
+            ui->release = RIGHT_RELEASE;
+#ifdef STYLUS_BASED
+            ui->state = (currstate + 1) % 3; /* EMPTY -> FULL -> UNKNOWN */
+#else
+            ui->state = GRID_EMPTY;
+#endif
+        } else /* if (button == MIDDLE_BUTTON) */ {
+            ui->drag = MIDDLE_DRAG;
+            ui->release = MIDDLE_RELEASE;
+            ui->state = GRID_UNKNOWN;
+        }
+
+        ui->drag_start_x = ui->drag_end_x = x;
+        ui->drag_start_y = ui->drag_end_y = y;
+        ui->cur_visible = 0;
+
+        return "";                    /* UI activity occurred */
+    }
+
+    if (ui->dragging && button == ui->drag) {
+        /*
+         * There doesn't seem much point in allowing a rectangle
+         * drag; people will generally only want to drag a single
+         * horizontal or vertical line, so we make that easy by
+         * snapping to it.
+         * 
+         * Exception: if we're _middle_-button dragging to tag
+         * things as UNKNOWN, we may well want to trash an entire
+         * area and start over!
+         */
+        if (ui->state != GRID_UNKNOWN) {
+            if (abs(x - ui->drag_start_x) > abs(y - ui->drag_start_y))
+                y = ui->drag_start_y;
+            else
+                x = ui->drag_start_x;
+        }
+
+        if (x < 0) x = 0;
+        if (y < 0) y = 0;
+        if (x >= state->common->w) x = state->common->w - 1;
+        if (y >= state->common->h) y = state->common->h - 1;
+
+        ui->drag_end_x = x;
+        ui->drag_end_y = y;
+
+        return "";                    /* UI activity occurred */
+    }
+
+    if (ui->dragging && button == ui->release) {
+        int x1, x2, y1, y2, xx, yy;
+        int move_needed = FALSE;
+
+        x1 = min(ui->drag_start_x, ui->drag_end_x);
+        x2 = max(ui->drag_start_x, ui->drag_end_x);
+        y1 = min(ui->drag_start_y, ui->drag_end_y);
+        y2 = max(ui->drag_start_y, ui->drag_end_y);
+
+        for (yy = y1; yy <= y2; yy++)
+            for (xx = x1; xx <= x2; xx++)
+                if (!state->common->immutable[yy * state->common->w + xx] &&
+                    state->grid[yy * state->common->w + xx] != ui->state)
+                    move_needed = TRUE;
+
+        ui->dragging = FALSE;
+
+        if (move_needed) {
+           char buf[80];
+           sprintf(buf, "%c%d,%d,%d,%d",
+                   (char)(ui->state == GRID_FULL ? 'F' :
+                          ui->state == GRID_EMPTY ? 'E' : 'U'),
+                   x1, y1, x2-x1+1, y2-y1+1);
+           return dupstr(buf);
+        } else
+            return "";                /* UI activity occurred */
+    }
+
+    if (IS_CURSOR_MOVE(button)) {
+       int x = ui->cur_x, y = ui->cur_y, newstate;
+       char buf[80];
+        move_cursor(button, &ui->cur_x, &ui->cur_y, state->common->w, state->common->h, 0);
+        ui->cur_visible = 1;
+       if (!control && !shift) return "";
+
+       newstate = control ? shift ? GRID_UNKNOWN : GRID_FULL : GRID_EMPTY;
+       if (state->grid[y * state->common->w + x] == newstate &&
+           state->grid[ui->cur_y * state->common->w + ui->cur_x] == newstate)
+           return "";
+
+       sprintf(buf, "%c%d,%d,%d,%d", control ? shift ? 'U' : 'F' : 'E',
+               min(x, ui->cur_x), min(y, ui->cur_y),
+               abs(x - ui->cur_x) + 1, abs(y - ui->cur_y) + 1);
+       return dupstr(buf);
+    }
+
+    if (IS_CURSOR_SELECT(button)) {
+        int currstate = state->grid[ui->cur_y * state->common->w + ui->cur_x];
+        int newstate;
+        char buf[80];
+
+        if (!ui->cur_visible) {
+            ui->cur_visible = 1;
+            return "";
+        }
+
+        if (button == CURSOR_SELECT2)
+            newstate = currstate == GRID_UNKNOWN ? GRID_EMPTY :
+                currstate == GRID_EMPTY ? GRID_FULL : GRID_UNKNOWN;
+        else
+            newstate = currstate == GRID_UNKNOWN ? GRID_FULL :
+                currstate == GRID_FULL ? GRID_EMPTY : GRID_UNKNOWN;
+
+        sprintf(buf, "%c%d,%d,%d,%d",
+                (char)(newstate == GRID_FULL ? 'F' :
+                      newstate == GRID_EMPTY ? 'E' : 'U'),
+                ui->cur_x, ui->cur_y, 1, 1);
+        return dupstr(buf);
+    }
+
+    return NULL;
+}
+
+static game_state *execute_move(const game_state *from, const char *move)
+{
+    game_state *ret;
+    int x1, x2, y1, y2, xx, yy;
+    int val;
+
+    if (move[0] == 'S' &&
+        strlen(move) == from->common->w * from->common->h + 1) {
+       int i;
+
+       ret = dup_game(from);
+
+       for (i = 0; i < ret->common->w * ret->common->h; i++)
+           ret->grid[i] = (move[i+1] == '1' ? GRID_FULL : GRID_EMPTY);
+
+       ret->completed = ret->cheated = TRUE;
+
+       return ret;
+    } else if ((move[0] == 'F' || move[0] == 'E' || move[0] == 'U') &&
+       sscanf(move+1, "%d,%d,%d,%d", &x1, &y1, &x2, &y2) == 4 &&
+       x1 >= 0 && x2 >= 0 && x1+x2 <= from->common->w &&
+       y1 >= 0 && y2 >= 0 && y1+y2 <= from->common->h) {
+
+       x2 += x1;
+       y2 += y1;
+       val = (move[0] == 'F' ? GRID_FULL :
+                move[0] == 'E' ? GRID_EMPTY : GRID_UNKNOWN);
+
+       ret = dup_game(from);
+       for (yy = y1; yy < y2; yy++)
+           for (xx = x1; xx < x2; xx++)
+                if (!ret->common->immutable[yy * ret->common->w + xx])
+                    ret->grid[yy * ret->common->w + xx] = val;
+
+       /*
+        * An actual change, so check to see if we've completed the
+        * game.
+        */
+       if (!ret->completed) {
+           int *rowdata = snewn(ret->common->rowsize, int);
+           int i, len;
+
+           ret->completed = TRUE;
+
+           for (i=0; i<ret->common->w; i++) {
+               len = compute_rowdata(rowdata, ret->grid+i,
+                                      ret->common->h, ret->common->w);
+               if (len != ret->common->rowlen[i] ||
+                   memcmp(ret->common->rowdata+i*ret->common->rowsize,
+                           rowdata, len * sizeof(int))) {
+                   ret->completed = FALSE;
+                   break;
+               }
+           }
+           for (i=0; i<ret->common->h; i++) {
+               len = compute_rowdata(rowdata, ret->grid+i*ret->common->w,
+                                      ret->common->w, 1);
+               if (len != ret->common->rowlen[i+ret->common->w] ||
+                   memcmp(ret->common->rowdata +
+                           (i+ret->common->w)*ret->common->rowsize,
+                           rowdata, len * sizeof(int))) {
+                   ret->completed = FALSE;
+                   break;
+               }
+           }
+
+           sfree(rowdata);
+       }
+
+       return ret;
+    } else
+       return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Error-checking during gameplay.
+ */
+
+/*
+ * The difficulty in error-checking Pattern is to make the error check
+ * _weak_ enough. The most obvious way would be to check each row and
+ * column by calling (a modified form of) do_row() to recursively
+ * analyse the row contents against the clue set and see if the
+ * GRID_UNKNOWNs could be filled in in any way that would end up
+ * correct. However, this turns out to be such a strong error check as
+ * to constitute a spoiler in many situations: you make a typo while
+ * trying to fill in one row, and not only does the row light up to
+ * indicate an error, but several columns crossed by the move also
+ * light up and draw your attention to deductions you hadn't even
+ * noticed you could make.
+ *
+ * So instead I restrict error-checking to 'complete runs' within a
+ * row, by which I mean contiguous sequences of GRID_FULL bounded at
+ * both ends by either GRID_EMPTY or the ends of the row. We identify
+ * all the complete runs in a row, and verify that _those_ are
+ * consistent with the row's clue list. Sequences of complete runs
+ * separated by solid GRID_EMPTY are required to match contiguous
+ * sequences in the clue list, whereas if there's at least one
+ * GRID_UNKNOWN between any two complete runs then those two need not
+ * be contiguous in the clue list.
+ *
+ * To simplify the edge cases, I pretend that the clue list for the
+ * row is extended with a 0 at each end, and I also pretend that the
+ * grid data for the row is extended with a GRID_EMPTY and a
+ * zero-length run at each end. This permits the contiguity checker to
+ * handle the fiddly end effects (e.g. if the first contiguous
+ * sequence of complete runs in the grid matches _something_ in the
+ * clue list but not at the beginning, this is allowable iff there's a
+ * GRID_UNKNOWN before the first one) with minimal faff, since the end
+ * effects just drop out as special cases of the normal inter-run
+ * handling (in this code the above case is not 'at the end of the
+ * clue list' at all, but between the implicit initial zero run and
+ * the first nonzero one).
+ *
+ * We must also be a little careful about how we search for a
+ * contiguous sequence of runs. In the clue list (1 1 2 1 2 3),
+ * suppose we see a GRID_UNKNOWN and then a length-1 run. We search
+ * for 1 in the clue list and find it at the very beginning. But now
+ * suppose we find a length-2 run with no GRID_UNKNOWN before it. We
+ * can't naively look at the next clue from the 1 we found, because
+ * that'll be the second 1 and won't match. Instead, we must backtrack
+ * by observing that the 2 we've just found must be contiguous with
+ * the 1 we've already seen, so we search for the sequence (1 2) and
+ * find it starting at the second 1. Now if we see a 3, we must
+ * rethink again and search for (1 2 3).
+ */
+
+struct errcheck_state {
+    /*
+     * rowdata and rowlen point at the clue data for this row in the
+     * game state.
+     */
+    int *rowdata;
+    int rowlen;
+    /*
+     * rowpos indicates the lowest position where it would be valid to
+     * see our next run length. It might be equal to rowlen,
+     * indicating that the next run would have to be the terminating 0.
+     */
+    int rowpos;
+    /*
+     * ncontig indicates how many runs we've seen in a contiguous
+     * block. This is taken into account when searching for the next
+     * run we find, unless ncontig is zeroed out first by encountering
+     * a GRID_UNKNOWN.
+     */
+    int ncontig;
+};
+
+static int errcheck_found_run(struct errcheck_state *es, int r)
+{
+/* Macro to handle the pretence that rowdata has a 0 at each end */
+#define ROWDATA(k) ((k)<0 || (k)>=es->rowlen ? 0 : es->rowdata[(k)])
+
+    /*
+     * See if we can find this new run length at a position where it
+     * also matches the last 'ncontig' runs we've seen.
+     */
+    int i, newpos;
+    for (newpos = es->rowpos; newpos <= es->rowlen; newpos++) {
+
+        if (ROWDATA(newpos) != r)
+            goto notfound;
+
+        for (i = 1; i <= es->ncontig; i++)
+            if (ROWDATA(newpos - i) != ROWDATA(es->rowpos - i))
+                goto notfound;
+
+        es->rowpos = newpos+1;
+        es->ncontig++;
+        return TRUE;
+
+      notfound:;
+    }
+
+    return FALSE;
+
+#undef ROWDATA
+}
+
+static int check_errors(const game_state *state, int i)
+{
+    int start, step, end, j;
+    int val, runlen;
+    struct errcheck_state aes, *es = &aes;
+
+    es->rowlen = state->common->rowlen[i];
+    es->rowdata = state->common->rowdata + state->common->rowsize * i;
+    /* Pretend that we've already encountered the initial zero run */
+    es->ncontig = 1;
+    es->rowpos = 0;
+
+    if (i < state->common->w) {
+        start = i;
+        step = state->common->w;
+        end = start + step * state->common->h;
+    } else {
+        start = (i - state->common->w) * state->common->w;
+        step = 1;
+        end = start + step * state->common->w;
+    }
+
+    runlen = -1;
+    for (j = start - step; j <= end; j += step) {
+        if (j < start || j == end)
+            val = GRID_EMPTY;
+        else
+            val = state->grid[j];
+
+        if (val == GRID_UNKNOWN) {
+            runlen = -1;
+            es->ncontig = 0;
+        } else if (val == GRID_FULL) {
+            if (runlen >= 0)
+                runlen++;
+        } else if (val == GRID_EMPTY) {
+            if (runlen > 0) {
+                if (!errcheck_found_run(es, runlen))
+                    return TRUE;       /* error! */
+            }
+            runlen = 0;
+        }
+    }
+
+    /* Signal end-of-row by sending errcheck_found_run the terminating
+     * zero run, which will be marked as contiguous with the previous
+     * run if and only if there hasn't been a GRID_UNKNOWN before. */
+    if (!errcheck_found_run(es, 0))
+        return TRUE;                   /* error at the last minute! */
+
+    return FALSE;                      /* no error */
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    struct { int tilesize; } ads, *ds = &ads;
+    ads.tilesize = tilesize;
+
+    *x = SIZE(params->w);
+    *y = SIZE(params->h);
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+    int i;
+
+    frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
+
+    for (i = 0; i < 3; i++) {
+        ret[COL_GRID    * 3 + i] = 0.3F;
+        ret[COL_UNKNOWN * 3 + i] = 0.5F;
+        ret[COL_TEXT    * 3 + i] = 0.0F;
+        ret[COL_FULL    * 3 + i] = 0.0F;
+        ret[COL_EMPTY   * 3 + i] = 1.0F;
+    }
+    ret[COL_CURSOR * 3 + 0] = 1.0F;
+    ret[COL_CURSOR * 3 + 1] = 0.25F;
+    ret[COL_CURSOR * 3 + 2] = 0.25F;
+    ret[COL_ERROR * 3 + 0] = 1.0F;
+    ret[COL_ERROR * 3 + 1] = 0.0F;
+    ret[COL_ERROR * 3 + 2] = 0.0F;
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    struct game_drawstate *ds = snew(struct game_drawstate);
+
+    ds->started = FALSE;
+    ds->w = state->common->w;
+    ds->h = state->common->h;
+    ds->visible = snewn(ds->w * ds->h, unsigned char);
+    ds->tilesize = 0;                  /* not decided yet */
+    memset(ds->visible, 255, ds->w * ds->h);
+    ds->numcolours = snewn(ds->w + ds->h, unsigned char);
+    memset(ds->numcolours, 255, ds->w + ds->h);
+    ds->cur_x = ds->cur_y = 0;
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds->visible);
+    sfree(ds);
+}
+
+static void grid_square(drawing *dr, game_drawstate *ds,
+                        int y, int x, int state, int cur)
+{
+    int xl, xr, yt, yb, dx, dy, dw, dh;
+
+    draw_rect(dr, TOCOORD(ds->w, x), TOCOORD(ds->h, y),
+              TILE_SIZE, TILE_SIZE, COL_GRID);
+
+    xl = (x % 5 == 0 ? 1 : 0);
+    yt = (y % 5 == 0 ? 1 : 0);
+    xr = (x % 5 == 4 || x == ds->w-1 ? 1 : 0);
+    yb = (y % 5 == 4 || y == ds->h-1 ? 1 : 0);
+
+    dx = TOCOORD(ds->w, x) + 1 + xl;
+    dy = TOCOORD(ds->h, y) + 1 + yt;
+    dw = TILE_SIZE - xl - xr - 1;
+    dh = TILE_SIZE - yt - yb - 1;
+
+    draw_rect(dr, dx, dy, dw, dh,
+              (state == GRID_FULL ? COL_FULL :
+               state == GRID_EMPTY ? COL_EMPTY : COL_UNKNOWN));
+    if (cur) {
+        draw_rect_outline(dr, dx, dy, dw, dh, COL_CURSOR);
+        draw_rect_outline(dr, dx+1, dy+1, dw-2, dh-2, COL_CURSOR);
+    }
+
+    draw_update(dr, TOCOORD(ds->w, x), TOCOORD(ds->h, y),
+                TILE_SIZE, TILE_SIZE);
+}
+
+/*
+ * Draw the numbers for a single row or column.
+ */
+static void draw_numbers(drawing *dr, game_drawstate *ds,
+                         const game_state *state, int i, int erase, int colour)
+{
+    int rowlen = state->common->rowlen[i];
+    int *rowdata = state->common->rowdata + state->common->rowsize * i;
+    int nfit;
+    int j;
+
+    if (erase) {
+        if (i < state->common->w) {
+            draw_rect(dr, TOCOORD(state->common->w, i), 0,
+                      TILE_SIZE, BORDER + TLBORDER(state->common->h) * TILE_SIZE,
+                      COL_BACKGROUND);
+        } else {
+            draw_rect(dr, 0, TOCOORD(state->common->h, i - state->common->w),
+                      BORDER + TLBORDER(state->common->w) * TILE_SIZE, TILE_SIZE,
+                      COL_BACKGROUND);
+        }
+    }
+
+    /*
+     * Normally I space the numbers out by the same distance as the
+     * tile size. However, if there are more numbers than available
+     * spaces, I have to squash them up a bit.
+     */
+    if (i < state->common->w)
+        nfit = TLBORDER(state->common->h);
+    else
+        nfit = TLBORDER(state->common->w);
+    nfit = max(rowlen, nfit) - 1;
+    assert(nfit > 0);
+
+    for (j = 0; j < rowlen; j++) {
+        int x, y;
+        char str[80];
+
+        if (i < state->common->w) {
+            x = TOCOORD(state->common->w, i);
+            y = BORDER + TILE_SIZE * (TLBORDER(state->common->h)-1);
+            y -= ((rowlen-j-1)*TILE_SIZE) * (TLBORDER(state->common->h)-1) / nfit;
+        } else {
+            y = TOCOORD(state->common->h, i - state->common->w);
+            x = BORDER + TILE_SIZE * (TLBORDER(state->common->w)-1);
+            x -= ((rowlen-j-1)*TILE_SIZE) * (TLBORDER(state->common->w)-1) / nfit;
+        }
+
+        sprintf(str, "%d", rowdata[j]);
+        draw_text(dr, x+TILE_SIZE/2, y+TILE_SIZE/2, FONT_VARIABLE,
+                  TILE_SIZE/2, ALIGN_HCENTRE | ALIGN_VCENTRE, colour, str);
+    }
+
+    if (i < state->common->w) {
+        draw_update(dr, TOCOORD(state->common->w, i), 0,
+                    TILE_SIZE, BORDER + TLBORDER(state->common->h) * TILE_SIZE);
+    } else {
+        draw_update(dr, 0, TOCOORD(state->common->h, i - state->common->w),
+                    BORDER + TLBORDER(state->common->w) * TILE_SIZE, TILE_SIZE);
+    }
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int i, j;
+    int x1, x2, y1, y2;
+    int cx, cy, cmoved;
+
+    if (!ds->started) {
+        /*
+         * The initial contents of the window are not guaranteed
+         * and can vary with front ends. To be on the safe side,
+         * all games should start by drawing a big background-
+         * colour rectangle covering the whole window.
+         */
+        draw_rect(dr, 0, 0, SIZE(ds->w), SIZE(ds->h), COL_BACKGROUND);
+
+        /*
+         * Draw the grid outline.
+         */
+        draw_rect(dr, TOCOORD(ds->w, 0) - 1, TOCOORD(ds->h, 0) - 1,
+                  ds->w * TILE_SIZE + 3, ds->h * TILE_SIZE + 3,
+                  COL_GRID);
+
+        ds->started = TRUE;
+
+       draw_update(dr, 0, 0, SIZE(ds->w), SIZE(ds->h));
+    }
+
+    if (ui->dragging) {
+        x1 = min(ui->drag_start_x, ui->drag_end_x);
+        x2 = max(ui->drag_start_x, ui->drag_end_x);
+        y1 = min(ui->drag_start_y, ui->drag_end_y);
+        y2 = max(ui->drag_start_y, ui->drag_end_y);
+    } else {
+        x1 = x2 = y1 = y2 = -1;        /* placate gcc warnings */
+    }
+
+    if (ui->cur_visible) {
+        cx = ui->cur_x; cy = ui->cur_y;
+    } else {
+        cx = cy = -1;
+    }
+    cmoved = (cx != ds->cur_x || cy != ds->cur_y);
+
+    /*
+     * Now draw any grid squares which have changed since last
+     * redraw.
+     */
+    for (i = 0; i < ds->h; i++) {
+        for (j = 0; j < ds->w; j++) {
+            int val, cc = 0;
+
+            /*
+             * Work out what state this square should be drawn in,
+             * taking any current drag operation into account.
+             */
+            if (ui->dragging && x1 <= j && j <= x2 && y1 <= i && i <= y2 &&
+                !state->common->immutable[i * state->common->w + j])
+                val = ui->state;
+            else
+                val = state->grid[i * state->common->w + j];
+
+            if (cmoved) {
+                /* the cursor has moved; if we were the old or
+                 * the new cursor position we need to redraw. */
+                if (j == cx && i == cy) cc = 1;
+                if (j == ds->cur_x && i == ds->cur_y) cc = 1;
+            }
+
+            /*
+             * Briefly invert everything twice during a completion
+             * flash.
+             */
+            if (flashtime > 0 &&
+                (flashtime <= FLASH_TIME/3 || flashtime >= FLASH_TIME*2/3) &&
+                val != GRID_UNKNOWN)
+                val = (GRID_FULL ^ GRID_EMPTY) ^ val;
+
+            if (ds->visible[i * ds->w + j] != val || cc) {
+                grid_square(dr, ds, i, j, val,
+                            (j == cx && i == cy));
+                ds->visible[i * ds->w + j] = val;
+            }
+        }
+    }
+    ds->cur_x = cx; ds->cur_y = cy;
+
+    /*
+     * Redraw any numbers which have changed their colour due to error
+     * indication.
+     */
+    for (i = 0; i < state->common->w + state->common->h; i++) {
+        int colour = check_errors(state, i) ? COL_ERROR : COL_TEXT;
+        if (ds->numcolours[i] != colour) {
+            draw_numbers(dr, ds, state, i, TRUE, colour);
+            ds->numcolours[i] = colour;
+        }
+    }
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return 0.0F;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    if (!oldstate->completed && newstate->completed &&
+       !oldstate->cheated && !newstate->cheated)
+        return FLASH_TIME;
+    return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    return state->completed ? +1 : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+    int pw, ph;
+
+    /*
+     * I'll use 5mm squares by default.
+     */
+    game_compute_size(params, 500, &pw, &ph);
+    *x = pw / 100.0F;
+    *y = ph / 100.0F;
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+    int w = state->common->w, h = state->common->h;
+    int ink = print_mono_colour(dr, 0);
+    int x, y, i;
+
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    game_drawstate ads, *ds = &ads;
+    game_set_size(dr, ds, NULL, tilesize);
+
+    /*
+     * Border.
+     */
+    print_line_width(dr, TILE_SIZE / 16);
+    draw_rect_outline(dr, TOCOORD(w, 0), TOCOORD(h, 0),
+                     w*TILE_SIZE, h*TILE_SIZE, ink);
+
+    /*
+     * Grid.
+     */
+    for (x = 1; x < w; x++) {
+       print_line_width(dr, TILE_SIZE / (x % 5 ? 128 : 24));
+       draw_line(dr, TOCOORD(w, x), TOCOORD(h, 0),
+                 TOCOORD(w, x), TOCOORD(h, h), ink);
+    }
+    for (y = 1; y < h; y++) {
+       print_line_width(dr, TILE_SIZE / (y % 5 ? 128 : 24));
+       draw_line(dr, TOCOORD(w, 0), TOCOORD(h, y),
+                 TOCOORD(w, w), TOCOORD(h, y), ink);
+    }
+
+    /*
+     * Clues.
+     */
+    for (i = 0; i < state->common->w + state->common->h; i++)
+        draw_numbers(dr, ds, state, i, FALSE, ink);
+
+    /*
+     * Solution.
+     */
+    print_line_width(dr, TILE_SIZE / 128);
+    for (y = 0; y < h; y++)
+       for (x = 0; x < w; x++) {
+           if (state->grid[y*w+x] == GRID_FULL)
+               draw_rect(dr, TOCOORD(w, x), TOCOORD(h, y),
+                         TILE_SIZE, TILE_SIZE, ink);
+           else if (state->grid[y*w+x] == GRID_EMPTY)
+               draw_circle(dr, TOCOORD(w, x) + TILE_SIZE/2,
+                           TOCOORD(h, y) + TILE_SIZE/2,
+                           TILE_SIZE/12, ink, ink);
+       }
+}
+
+#ifdef COMBINED
+#define thegame pattern
+#endif
+
+const struct game thegame = {
+    "Pattern", "games.pattern", "pattern",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    TRUE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    TRUE, FALSE, game_print_size, game_print,
+    FALSE,                            /* wants_statusbar */
+    FALSE, game_timing_state,
+    REQUIRE_RBUTTON,                  /* flags */
+};
+
+#ifdef STANDALONE_SOLVER
+
+int main(int argc, char **argv)
+{
+    game_params *p;
+    game_state *s;
+    char *id = NULL, *desc, *err;
+
+    while (--argc > 0) {
+        char *p = *++argv;
+       if (*p == '-') {
+           if (!strcmp(p, "-v")) {
+               verbose = TRUE;
+           } else {
+               fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p);
+               return 1;
+           }
+        } else {
+            id = p;
+        }
+    }
+
+    if (!id) {
+        fprintf(stderr, "usage: %s <game_id>\n", argv[0]);
+        return 1;
+    }
+
+    desc = strchr(id, ':');
+    if (!desc) {
+        fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]);
+        return 1;
+    }
+    *desc++ = '\0';
+
+    p = default_params();
+    decode_params(p, id);
+    err = validate_desc(p, desc);
+    if (err) {
+        fprintf(stderr, "%s: %s\n", argv[0], err);
+        return 1;
+    }
+    s = new_game(NULL, p, desc);
+
+    {
+       int w = p->w, h = p->h, i, j, max, cluewid = 0;
+       unsigned char *matrix, *workspace;
+       unsigned int *changed_h, *changed_w;
+       int *rowdata;
+
+       matrix = snewn(w*h, unsigned char);
+       max = max(w, h);
+       workspace = snewn(max*7, unsigned char);
+       changed_h = snewn(max+1, unsigned int);
+       changed_w = snewn(max+1, unsigned int);
+       rowdata = snewn(max+1, int);
+
+       if (verbose) {
+           int thiswid;
+           /*
+            * Work out the maximum text width of the clue numbers
+            * in a row or column, so we can print the solver's
+            * working in a nicely lined up way.
+            */
+           for (i = 0; i < (w+h); i++) {
+               char buf[80];
+               for (thiswid = -1, j = 0; j < s->common->rowlen[i]; j++)
+                   thiswid += sprintf
+                        (buf, " %d",
+                         s->common->rowdata[s->common->rowsize*i+j]);
+               if (cluewid < thiswid)
+                   cluewid = thiswid;
+           }
+       }
+
+       solve_puzzle(s, NULL, w, h, matrix, workspace,
+                    changed_h, changed_w, rowdata, cluewid);
+
+       for (i = 0; i < h; i++) {
+           for (j = 0; j < w; j++) {
+               int c = (matrix[i*w+j] == UNKNOWN ? '?' :
+                        matrix[i*w+j] == BLOCK ? '#' :
+                        matrix[i*w+j] == DOT ? '.' :
+                        '!');
+               putchar(c);
+           }
+           printf("\n");
+       }
+    }
+
+    return 0;
+}
+
+#endif
+
+#ifdef STANDALONE_PICTURE_GENERATOR
+
+/*
+ * Main program for the standalone picture generator. To use it,
+ * simply provide it with an XBM-format bitmap file (note XBM, not
+ * XPM) on standard input, and it will output a game ID in return.
+ * For example:
+ *
+ *   $ ./patternpicture < calligraphic-A.xbm
+ *   15x15:2/4/2/2/2/3/3/3.1/3.1/3.1/11/14/12/6/1/2/2/3/4/5/1.3/2.3/1.3/2.3/1.4/9/1.1.3/2.2.3/5.4/3.2
+ *
+ * That looks easy, of course - all the program has done is to count
+ * up the clue numbers! But in fact, it's done more than that: it's
+ * also checked that the result is uniquely soluble from just the
+ * numbers. If it hadn't been, then it would have also left some
+ * filled squares in the playing area as extra clues.
+ *
+ *   $ ./patternpicture < cube.xbm
+ *   15x15:10/2.1/1.1.1/1.1.1/1.1.1/1.1.1/1.1.1/1.1.1/1.1.1/1.10/1.1.1/1.1.1/1.1.1/2.1/10/10/1.2/1.1.1/1.1.1/1.1.1/10.1/1.1.1/1.1.1/1.1.1/1.1.1/1.1.1/1.1.1/1.1.1/1.2/10,TNINzzzzGNzw
+ *
+ * This enables a reasonably convenient design workflow for coming up
+ * with pictorial Pattern puzzles which _are_ uniquely soluble without
+ * those inelegant pre-filled squares. Fire up a bitmap editor (X11
+ * bitmap(1) is good enough), save a trial .xbm, and then test it by
+ * running a command along the lines of
+ *
+ *   $ ./pattern $(./patternpicture < test.xbm)
+ *
+ * If the resulting window pops up with some pre-filled squares, then
+ * that tells you which parts of the image are giving rise to
+ * ambiguities, so try making tweaks in those areas, try the test
+ * command again, and see if it helps. Once you have a design for
+ * which the Pattern starting grid comes out empty, there's your game
+ * ID.
+ */
+
+#include <time.h>
+
+int main(int argc, char **argv)
+{
+    game_params *par;
+    char *params, *desc;
+    random_state *rs;
+    time_t seed = time(NULL);
+    char buf[4096];
+    int i;
+    int x, y;
+
+    par = default_params();
+    if (argc > 1)
+       decode_params(par, argv[1]);   /* get difficulty */
+    par->w = par->h = -1;
+
+    /*
+     * Now read an XBM file from standard input. This is simple and
+     * hacky and will do very little error detection, so don't feed
+     * it bogus data.
+     */
+    picture = NULL;
+    x = y = 0;
+    while (fgets(buf, sizeof(buf), stdin)) {
+       buf[strcspn(buf, "\r\n")] = '\0';
+       if (!strncmp(buf, "#define", 7)) {
+           /*
+            * Lines starting `#define' give the width and height.
+            */
+           char *num = buf + strlen(buf);
+           char *symend;
+
+           while (num > buf && isdigit((unsigned char)num[-1]))
+               num--;
+           symend = num;
+           while (symend > buf && isspace((unsigned char)symend[-1]))
+               symend--;
+
+           if (symend-5 >= buf && !strncmp(symend-5, "width", 5))
+               par->w = atoi(num);
+           else if (symend-6 >= buf && !strncmp(symend-6, "height", 6))
+               par->h = atoi(num);
+       } else {
+           /*
+            * Otherwise, break the string up into words and take
+            * any word of the form `0x' plus hex digits to be a
+            * byte.
+            */
+           char *p, *wordstart;
+
+           if (!picture) {
+               if (par->w < 0 || par->h < 0) {
+                   printf("failed to read width and height\n");
+                   return 1;
+               }
+               picture = snewn(par->w * par->h, unsigned char);
+               for (i = 0; i < par->w * par->h; i++)
+                   picture[i] = GRID_UNKNOWN;
+           }
+
+           p = buf;
+           while (*p) {
+               while (*p && (*p == ',' || isspace((unsigned char)*p)))
+                   p++;
+               wordstart = p;
+               while (*p && !(*p == ',' || *p == '}' ||
+                              isspace((unsigned char)*p)))
+                   p++;
+               if (*p)
+                   *p++ = '\0';
+
+               if (wordstart[0] == '0' &&
+                   (wordstart[1] == 'x' || wordstart[1] == 'X') &&
+                   !wordstart[2 + strspn(wordstart+2,
+                                         "0123456789abcdefABCDEF")]) {
+                   unsigned long byte = strtoul(wordstart+2, NULL, 16);
+                   for (i = 0; i < 8; i++) {
+                       int bit = (byte >> i) & 1;
+                       if (y < par->h && x < par->w)
+                           picture[y * par->w + x] =
+                                bit ? GRID_FULL : GRID_EMPTY;
+                       x++;
+                   }
+
+                   if (x >= par->w) {
+                       x = 0;
+                       y++;
+                   }
+               }
+           }
+       }
+    }
+
+    for (i = 0; i < par->w * par->h; i++)
+       if (picture[i] == GRID_UNKNOWN) {
+           fprintf(stderr, "failed to read enough bitmap data\n");
+           return 1;
+       }
+
+    rs = random_new((void*)&seed, sizeof(time_t));
+
+    desc = new_game_desc(par, rs, NULL, FALSE);
+    params = encode_params(par, FALSE);
+    printf("%s:%s\n", params, desc);
+
+    sfree(desc);
+    sfree(params);
+    free_params(par);
+    random_free(rs);
+
+    return 0;
+}
+
+#endif
+
+/* vim: set shiftwidth=4 tabstop=8: */
diff --git a/pearl.R b/pearl.R
new file mode 100644 (file)
index 0000000..79bc732
--- /dev/null
+++ b/pearl.R
@@ -0,0 +1,23 @@
+# -*- makefile -*-
+
+PEARL_EXTRA    = dsf tree234 grid penrose loopgen tdq
+
+pearl          : [X] GTK COMMON pearl PEARL_EXTRA pearl-icon|no-icon
+pearl          : [G] WINDOWS COMMON pearl PEARL_EXTRA pearl.res?
+
+pearlbench     : [U] pearl[STANDALONE_SOLVER] PEARL_EXTRA STANDALONE m.lib
+pearlbench     : [C] pearl[STANDALONE_SOLVER] PEARL_EXTRA STANDALONE
+
+ALL += pearl[COMBINED] PEARL_EXTRA
+
+!begin am gtk
+GAMES += pearl
+!end
+
+!begin >list.c
+    A(pearl) \
+!end
+
+!begin >gamedesc.txt
+pearl:pearl.exe:Pearl:Loop-drawing puzzle:Draw a single closed loop, given clues about corner and straight squares.
+!end
diff --git a/pearl.c b/pearl.c
new file mode 100644 (file)
index 0000000..4e4290e
--- /dev/null
+++ b/pearl.c
@@ -0,0 +1,2770 @@
+/*
+ * pearl.c: Nikoli's `Masyu' puzzle. 
+ */
+
+/*
+ * TODO:
+ *
+ *  - The current keyboard cursor mechanism works well on ordinary PC
+ *    keyboards, but for platforms with only arrow keys and a select
+ *    button or two, we may at some point need a simpler one which can
+ *    handle 'x' markings without needing shift keys. For instance, a
+ *    cursor with twice the grid resolution, so that it can range
+ *    across face centres, edge centres and vertices; 'clicks' on face
+ *    centres begin a drag as currently, clicks on edges toggle
+ *    markings, and clicks on vertices are ignored (but it would be
+ *    too confusing not to let the cursor rest on them). But I'm
+ *    pretty sure that would be less pleasant to play on a full
+ *    keyboard, so probably a #ifdef would be the thing.
+ *
+ *  - Generation is still pretty slow, due to difficulty coming up in
+ *    the first place with a loop that makes a soluble puzzle even
+ *    with all possible clues filled in.
+ *     + A possible alternative strategy to further tuning of the
+ *      existing loop generator would be to throw the entire
+ *      mechanism out and instead write a different generator from
+ *      scratch which evolves the solution along with the puzzle:
+ *      place a few clues, nail down a bit of the loop, place another
+ *      clue, nail down some more, etc. However, I don't have a
+ *      detailed plan for any such mechanism, so it may be a pipe
+ *      dream.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+#include "grid.h"
+#include "loopgen.h"
+
+#define SWAP(i,j) do { int swaptmp = (i); (i) = (j); (j) = swaptmp; } while (0)
+
+#define NOCLUE 0
+#define CORNER 1
+#define STRAIGHT 2
+
+#define R 1
+#define U 2
+#define L 4
+#define D 8
+
+#define DX(d) ( ((d)==R) - ((d)==L) )
+#define DY(d) ( ((d)==D) - ((d)==U) )
+
+#define F(d) (((d << 2) | (d >> 2)) & 0xF)
+#define C(d) (((d << 3) | (d >> 1)) & 0xF)
+#define A(d) (((d << 1) | (d >> 3)) & 0xF)
+
+#define LR (L | R)
+#define RL (R | L)
+#define UD (U | D)
+#define DU (D | U)
+#define LU (L | U)
+#define UL (U | L)
+#define LD (L | D)
+#define DL (D | L)
+#define RU (R | U)
+#define UR (U | R)
+#define RD (R | D)
+#define DR (D | R)
+#define BLANK 0
+#define UNKNOWN 15
+
+#define bLR (1 << LR)
+#define bRL (1 << RL)
+#define bUD (1 << UD)
+#define bDU (1 << DU)
+#define bLU (1 << LU)
+#define bUL (1 << UL)
+#define bLD (1 << LD)
+#define bDL (1 << DL)
+#define bRU (1 << RU)
+#define bUR (1 << UR)
+#define bRD (1 << RD)
+#define bDR (1 << DR)
+#define bBLANK (1 << BLANK)
+
+enum {
+    COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT,
+    COL_CURSOR_BACKGROUND = COL_LOWLIGHT,
+    COL_BLACK, COL_WHITE,
+    COL_ERROR, COL_GRID, COL_FLASH,
+    COL_DRAGON, COL_DRAGOFF,
+    NCOLOURS
+};
+
+/* Macro ickery copied from slant.c */
+#define DIFFLIST(A) \
+    A(EASY,Easy,e) \
+    A(TRICKY,Tricky,t)
+#define ENUM(upper,title,lower) DIFF_ ## upper,
+#define TITLE(upper,title,lower) #title,
+#define ENCODE(upper,title,lower) #lower
+#define CONFIG(upper,title,lower) ":" #title
+enum { DIFFLIST(ENUM) DIFFCOUNT };
+static char const *const pearl_diffnames[] = { DIFFLIST(TITLE) "(count)" };
+static char const pearl_diffchars[] = DIFFLIST(ENCODE);
+#define DIFFCONFIG DIFFLIST(CONFIG)
+
+struct game_params {
+    int w, h;
+    int difficulty;
+    int nosolve;        /* XXX remove me! */
+};
+
+struct shared_state {
+    int w, h, sz;
+    char *clues;         /* size w*h */
+    int refcnt;
+};
+
+#define INGRID(state, gx, gy) ((gx) >= 0 && (gx) < (state)->shared->w && \
+                               (gy) >= 0 && (gy) < (state)->shared->h)
+struct game_state {
+    struct shared_state *shared;
+    char *lines;        /* size w*h: lines placed */
+    char *errors;       /* size w*h: errors detected */
+    char *marks;        /* size w*h: 'no line here' marks placed. */
+    int completed, used_solve;
+};
+
+#define DEFAULT_PRESET 3
+
+static const struct game_params pearl_presets[] = {
+    {6, 6,      DIFF_EASY},
+    {6, 6,      DIFF_TRICKY},
+    {8, 8,      DIFF_EASY},
+    {8, 8,      DIFF_TRICKY},
+    {10, 10,    DIFF_EASY},
+    {10, 10,    DIFF_TRICKY},
+    {12, 8,     DIFF_EASY},
+    {12, 8,     DIFF_TRICKY},
+};
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+
+    *ret = pearl_presets[DEFAULT_PRESET];
+    ret->nosolve = FALSE;
+
+    return ret;
+}
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params *ret;
+    char buf[64];
+
+    if (i < 0 || i >= lenof(pearl_presets)) return FALSE;
+
+    ret = default_params();
+    *ret = pearl_presets[i]; /* struct copy */
+    *params = ret;
+
+    sprintf(buf, "%dx%d %s",
+            pearl_presets[i].w, pearl_presets[i].h,
+            pearl_diffnames[pearl_presets[i].difficulty]);
+    *name = dupstr(buf);
+
+    return TRUE;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+static void decode_params(game_params *ret, char const *string)
+{
+    ret->w = ret->h = atoi(string);
+    while (*string && isdigit((unsigned char) *string)) ++string;
+    if (*string == 'x') {
+        string++;
+        ret->h = atoi(string);
+        while (*string && isdigit((unsigned char)*string)) string++;
+    }
+
+    ret->difficulty = DIFF_EASY;
+    if (*string == 'd') {
+       int i;
+       string++;
+       for (i = 0; i < DIFFCOUNT; i++)
+           if (*string == pearl_diffchars[i])
+               ret->difficulty = i;
+       if (*string) string++;
+    }
+
+    ret->nosolve = FALSE;
+    if (*string == 'n') {
+        ret->nosolve = TRUE;
+        string++;
+    }
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char buf[256];
+    sprintf(buf, "%dx%d", params->w, params->h);
+    if (full)
+        sprintf(buf + strlen(buf), "d%c%s",
+                pearl_diffchars[params->difficulty],
+                params->nosolve ? "n" : "");
+    return dupstr(buf);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+    char buf[64];
+
+    ret = snewn(5, config_item);
+
+    ret[0].name = "Width";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->w);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Height";
+    ret[1].type = C_STRING;
+    sprintf(buf, "%d", params->h);
+    ret[1].sval = dupstr(buf);
+    ret[1].ival = 0;
+
+    ret[2].name = "Difficulty";
+    ret[2].type = C_CHOICES;
+    ret[2].sval = DIFFCONFIG;
+    ret[2].ival = params->difficulty;
+
+    ret[3].name = "Allow unsoluble";
+    ret[3].type = C_BOOLEAN;
+    ret[3].sval = NULL;
+    ret[3].ival = params->nosolve;
+
+    ret[4].name = NULL;
+    ret[4].type = C_END;
+    ret[4].sval = NULL;
+    ret[4].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = atoi(cfg[0].sval);
+    ret->h = atoi(cfg[1].sval);
+    ret->difficulty = cfg[2].ival;
+    ret->nosolve = cfg[3].ival;
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    if (params->w < 5) return "Width must be at least five";
+    if (params->h < 5) return "Height must be at least five";
+    if (params->difficulty < 0 || params->difficulty >= DIFFCOUNT)
+        return "Unknown difficulty level";
+
+    return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Solver.
+ */
+
+int pearl_solve(int w, int h, char *clues, char *result,
+                int difficulty, int partial)
+{
+    int W = 2*w+1, H = 2*h+1;
+    short *workspace;
+    int *dsf, *dsfsize;
+    int x, y, b, d;
+    int ret = -1;
+
+    /*
+     * workspace[(2*y+1)*W+(2*x+1)] indicates the possible nature
+     * of the square (x,y), as a logical OR of bitfields.
+     * 
+     * workspace[(2*y)*W+(2*x+1)], for x odd and y even, indicates
+     * whether the horizontal edge between (x,y) and (x+1,y) is
+     * connected (1), disconnected (2) or unknown (3).
+     * 
+     * workspace[(2*y+1)*W+(2*x)], indicates the same about the
+     * vertical edge between (x,y) and (x,y+1).
+     * 
+     * Initially, every square is considered capable of being in
+     * any of the seven possible states (two straights, four
+     * corners and empty), except those corresponding to clue
+     * squares which are more restricted.
+     * 
+     * Initially, all edges are unknown, except the ones around the
+     * grid border which are known to be disconnected.
+     */
+    workspace = snewn(W*H, short);
+    for (x = 0; x < W*H; x++)
+       workspace[x] = 0;
+    /* Square states */
+    for (y = 0; y < h; y++)
+       for (x = 0; x < w; x++)
+           switch (clues[y*w+x]) {
+             case CORNER:
+               workspace[(2*y+1)*W+(2*x+1)] = bLU|bLD|bRU|bRD;
+               break;
+             case STRAIGHT:
+               workspace[(2*y+1)*W+(2*x+1)] = bLR|bUD;
+               break;
+             default:
+               workspace[(2*y+1)*W+(2*x+1)] = bLR|bUD|bLU|bLD|bRU|bRD|bBLANK;
+               break;
+           }
+    /* Horizontal edges */
+    for (y = 0; y <= h; y++)
+       for (x = 0; x < w; x++)
+           workspace[(2*y)*W+(2*x+1)] = (y==0 || y==h ? 2 : 3);
+    /* Vertical edges */
+    for (y = 0; y < h; y++)
+       for (x = 0; x <= w; x++)
+           workspace[(2*y+1)*W+(2*x)] = (x==0 || x==w ? 2 : 3);
+
+    /*
+     * We maintain a dsf of connected squares, together with a
+     * count of the size of each equivalence class.
+     */
+    dsf = snewn(w*h, int);
+    dsfsize = snewn(w*h, int);
+
+    /*
+     * Now repeatedly try to find something we can do.
+     */
+    while (1) {
+       int done_something = FALSE;
+
+#ifdef SOLVER_DIAGNOSTICS
+       for (y = 0; y < H; y++) {
+           for (x = 0; x < W; x++)
+               printf("%*x", (x&1) ? 5 : 2, workspace[y*W+x]);
+           printf("\n");
+       }
+#endif
+
+       /*
+        * Go through the square state words, and discard any
+        * square state which is inconsistent with known facts
+        * about the edges around the square.
+        */
+       for (y = 0; y < h; y++)
+           for (x = 0; x < w; x++) {
+               for (b = 0; b < 0xD; b++)
+                   if (workspace[(2*y+1)*W+(2*x+1)] & (1<<b)) {
+                       /*
+                        * If any edge of this square is known to
+                        * be connected when state b would require
+                        * it disconnected, or vice versa, discard
+                        * the state.
+                        */
+                       for (d = 1; d <= 8; d += d) {
+                           int ex = 2*x+1 + DX(d), ey = 2*y+1 + DY(d);
+                           if (workspace[ey*W+ex] ==
+                               ((b & d) ? 2 : 1)) {
+                               workspace[(2*y+1)*W+(2*x+1)] &= ~(1<<b);
+#ifdef SOLVER_DIAGNOSTICS
+                               printf("edge (%d,%d)-(%d,%d) rules out state"
+                                      " %d for square (%d,%d)\n",
+                                      ex/2, ey/2, (ex+1)/2, (ey+1)/2,
+                                      b, x, y);
+#endif
+                               done_something = TRUE;
+                               break;
+                           }
+                       }
+                   }
+
+               /*
+                * Consistency check: each square must have at
+                * least one state left!
+                */
+               if (!workspace[(2*y+1)*W+(2*x+1)]) {
+#ifdef SOLVER_DIAGNOSTICS
+                   printf("edge check at (%d,%d): inconsistency\n", x, y);
+#endif
+                   ret = 0;
+                   goto cleanup;
+               }
+           }
+
+       /*
+        * Now go through the states array again, and nail down any
+        * unknown edge if one of its neighbouring squares makes it
+        * known.
+        */
+       for (y = 0; y < h; y++)
+           for (x = 0; x < w; x++) {
+               int edgeor = 0, edgeand = 15;
+
+               for (b = 0; b < 0xD; b++)
+                   if (workspace[(2*y+1)*W+(2*x+1)] & (1<<b)) {
+                       edgeor |= b;
+                       edgeand &= b;
+                   }
+
+               /*
+                * Now any bit clear in edgeor marks a disconnected
+                * edge, and any bit set in edgeand marks a
+                * connected edge.
+                */
+
+               /* First check consistency: neither bit is both! */
+               if (edgeand & ~edgeor) {
+#ifdef SOLVER_DIAGNOSTICS
+                   printf("square check at (%d,%d): inconsistency\n", x, y);
+#endif
+                   ret = 0;
+                   goto cleanup;
+               }
+
+               for (d = 1; d <= 8; d += d) {
+                   int ex = 2*x+1 + DX(d), ey = 2*y+1 + DY(d);
+
+                   if (!(edgeor & d) && workspace[ey*W+ex] == 3) {
+                       workspace[ey*W+ex] = 2;
+                       done_something = TRUE;
+#ifdef SOLVER_DIAGNOSTICS
+                       printf("possible states of square (%d,%d) force edge"
+                              " (%d,%d)-(%d,%d) to be disconnected\n",
+                              x, y, ex/2, ey/2, (ex+1)/2, (ey+1)/2);
+#endif
+                   } else if ((edgeand & d) && workspace[ey*W+ex] == 3) {
+                       workspace[ey*W+ex] = 1;
+                       done_something = TRUE;
+#ifdef SOLVER_DIAGNOSTICS
+                       printf("possible states of square (%d,%d) force edge"
+                              " (%d,%d)-(%d,%d) to be connected\n",
+                              x, y, ex/2, ey/2, (ex+1)/2, (ey+1)/2);
+#endif
+                   }
+               }
+           }
+
+       if (done_something)
+           continue;
+
+       /*
+        * Now for longer-range clue-based deductions (using the
+        * rules that a corner clue must connect to two straight
+        * squares, and a straight clue must connect to at least
+        * one corner square).
+        */
+       for (y = 0; y < h; y++)
+           for (x = 0; x < w; x++)
+               switch (clues[y*w+x]) {
+                 case CORNER:
+                   for (d = 1; d <= 8; d += d) {
+                       int ex = 2*x+1 + DX(d), ey = 2*y+1 + DY(d);
+                       int fx = ex + DX(d), fy = ey + DY(d);
+                       int type = d | F(d);
+
+                       if (workspace[ey*W+ex] == 1) {
+                           /*
+                            * If a corner clue is connected on any
+                            * edge, then we can immediately nail
+                            * down the square beyond that edge as
+                            * being a straight in the appropriate
+                            * direction.
+                            */
+                           if (workspace[fy*W+fx] != (1<<type)) {
+                               workspace[fy*W+fx] = (1<<type);
+                               done_something = TRUE;
+#ifdef SOLVER_DIAGNOSTICS
+                               printf("corner clue at (%d,%d) forces square "
+                                      "(%d,%d) into state %d\n", x, y,
+                                      fx/2, fy/2, type);
+#endif
+                               
+                           }
+                       } else if (workspace[ey*W+ex] == 3) {
+                           /*
+                            * Conversely, if a corner clue is
+                            * separated by an unknown edge from a
+                            * square which _cannot_ be a straight
+                            * in the appropriate direction, we can
+                            * mark that edge as disconnected.
+                            */
+                           if (!(workspace[fy*W+fx] & (1<<type))) {
+                               workspace[ey*W+ex] = 2;
+                               done_something = TRUE;
+#ifdef SOLVER_DIAGNOSTICS
+                               printf("corner clue at (%d,%d), plus square "
+                                      "(%d,%d) not being state %d, "
+                                      "disconnects edge (%d,%d)-(%d,%d)\n",
+                                      x, y, fx/2, fy/2, type,
+                                      ex/2, ey/2, (ex+1)/2, (ey+1)/2);
+#endif
+
+                           }
+                       }
+                   }
+
+                   break;
+                 case STRAIGHT:
+                   /*
+                    * If a straight clue is between two squares
+                    * neither of which is capable of being a
+                    * corner connected to it, then the straight
+                    * clue cannot point in that direction.
+                    */
+                   for (d = 1; d <= 2; d += d) {
+                       int fx = 2*x+1 + 2*DX(d), fy = 2*y+1 + 2*DY(d);
+                       int gx = 2*x+1 - 2*DX(d), gy = 2*y+1 - 2*DY(d);
+                       int type = d | F(d);
+
+                       if (!(workspace[(2*y+1)*W+(2*x+1)] & (1<<type)))
+                           continue;
+
+                       if (!(workspace[fy*W+fx] & ((1<<(F(d)|A(d))) |
+                                                   (1<<(F(d)|C(d))))) &&
+                           !(workspace[gy*W+gx] & ((1<<(  d |A(d))) |
+                                                   (1<<(  d |C(d)))))) {
+                           workspace[(2*y+1)*W+(2*x+1)] &= ~(1<<type);
+                           done_something = TRUE;
+#ifdef SOLVER_DIAGNOSTICS
+                           printf("straight clue at (%d,%d) cannot corner at "
+                                  "(%d,%d) or (%d,%d) so is not state %d\n",
+                                  x, y, fx/2, fy/2, gx/2, gy/2, type);
+#endif
+                       }
+                                                   
+                   }
+
+                   /*
+                    * If a straight clue with known direction is
+                    * connected on one side to a known straight,
+                    * then on the other side it must be a corner.
+                    */
+                   for (d = 1; d <= 8; d += d) {
+                       int fx = 2*x+1 + 2*DX(d), fy = 2*y+1 + 2*DY(d);
+                       int gx = 2*x+1 - 2*DX(d), gy = 2*y+1 - 2*DY(d);
+                       int type = d | F(d);
+
+                       if (workspace[(2*y+1)*W+(2*x+1)] != (1<<type))
+                           continue;
+
+                       if (!(workspace[fy*W+fx] &~ (bLR|bUD)) &&
+                           (workspace[gy*W+gx] &~ (bLU|bLD|bRU|bRD))) {
+                           workspace[gy*W+gx] &= (bLU|bLD|bRU|bRD);
+                           done_something = TRUE;
+#ifdef SOLVER_DIAGNOSTICS
+                           printf("straight clue at (%d,%d) connecting to "
+                                  "straight at (%d,%d) makes (%d,%d) a "
+                                  "corner\n", x, y, fx/2, fy/2, gx/2, gy/2);
+#endif
+                       }
+                                                   
+                   }
+                   break;
+               }
+
+       if (done_something)
+           continue;
+
+       /*
+        * Now detect shortcut loops.
+        */
+
+       {
+           int nonblanks, loopclass;
+
+           dsf_init(dsf, w*h);
+           for (x = 0; x < w*h; x++)
+               dsfsize[x] = 1;
+
+           /*
+            * First go through the edge entries and update the dsf
+            * of which squares are connected to which others. We
+            * also track the number of squares in each equivalence
+            * class, and count the overall number of
+            * known-non-blank squares.
+            *
+            * In the process of doing this, we must notice if a
+            * loop has already been formed. If it has, we blank
+            * out any square which isn't part of that loop
+            * (failing a consistency check if any such square does
+            * not have BLANK as one of its remaining options) and
+            * exit the deduction loop with success.
+            */
+           nonblanks = 0;
+           loopclass = -1;
+           for (y = 1; y < H-1; y++)
+               for (x = 1; x < W-1; x++)
+                   if ((y ^ x) & 1) {
+                       /*
+                        * (x,y) are the workspace coordinates of
+                        * an edge field. Compute the normal-space
+                        * coordinates of the squares it connects.
+                        */
+                       int ax = (x-1)/2, ay = (y-1)/2, ac = ay*w+ax;
+                       int bx = x/2, by = y/2, bc = by*w+bx;
+
+                       /*
+                        * If the edge is connected, do the dsf
+                        * thing.
+                        */
+                       if (workspace[y*W+x] == 1) {
+                           int ae, be;
+
+                           ae = dsf_canonify(dsf, ac);
+                           be = dsf_canonify(dsf, bc);
+
+                           if (ae == be) {
+                               /*
+                                * We have a loop!
+                                */
+                               if (loopclass != -1) {
+                                   /*
+                                    * In fact, we have two
+                                    * separate loops, which is
+                                    * doom.
+                                    */
+#ifdef SOLVER_DIAGNOSTICS
+                                   printf("two loops found in grid!\n");
+#endif
+                                   ret = 0;
+                                   goto cleanup;
+                               }
+                               loopclass = ae;
+                           } else {
+                               /*
+                                * Merge the two equivalence
+                                * classes.
+                                */
+                               int size = dsfsize[ae] + dsfsize[be];
+                               dsf_merge(dsf, ac, bc);
+                               ae = dsf_canonify(dsf, ac);
+                               dsfsize[ae] = size;
+                           }
+                       }
+                   } else if ((y & x) & 1) {
+                       /*
+                        * (x,y) are the workspace coordinates of a
+                        * square field. If the square is
+                        * definitely not blank, count it.
+                        */
+                       if (!(workspace[y*W+x] & bBLANK))
+                           nonblanks++;
+                   }
+
+           /*
+            * If we discovered an existing loop above, we must now
+            * blank every square not part of it, and exit the main
+            * deduction loop.
+            */
+           if (loopclass != -1) {
+#ifdef SOLVER_DIAGNOSTICS
+               printf("loop found in grid!\n");
+#endif
+               for (y = 0; y < h; y++)
+                   for (x = 0; x < w; x++)
+                       if (dsf_canonify(dsf, y*w+x) != loopclass) {
+                           if (workspace[(y*2+1)*W+(x*2+1)] & bBLANK) {
+                               workspace[(y*2+1)*W+(x*2+1)] = bBLANK;
+                           } else {
+                               /*
+                                * This square is not part of the
+                                * loop, but is known non-blank. We
+                                * have goofed.
+                                */
+#ifdef SOLVER_DIAGNOSTICS
+                               printf("non-blank square (%d,%d) found outside"
+                                      " loop!\n", x, y);
+#endif
+                               ret = 0;
+                               goto cleanup;
+                           }
+                       }
+               /*
+                * And we're done.
+                */
+               ret = 1;
+               break;
+           }
+
+            /* Further deductions are considered 'tricky'. */
+            if (difficulty == DIFF_EASY) goto done_deductions;
+
+           /*
+            * Now go through the workspace again and mark any edge
+            * which would cause a shortcut loop (i.e. would
+            * connect together two squares in the same equivalence
+            * class, and that equivalence class does not contain
+            * _all_ the known-non-blank squares currently in the
+            * grid) as disconnected. Also, mark any _square state_
+            * which would cause a shortcut loop as disconnected.
+            */
+           for (y = 1; y < H-1; y++)
+               for (x = 1; x < W-1; x++)
+                   if ((y ^ x) & 1) {
+                       /*
+                        * (x,y) are the workspace coordinates of
+                        * an edge field. Compute the normal-space
+                        * coordinates of the squares it connects.
+                        */
+                       int ax = (x-1)/2, ay = (y-1)/2, ac = ay*w+ax;
+                       int bx = x/2, by = y/2, bc = by*w+bx;
+
+                       /*
+                        * If the edge is currently unknown, and
+                        * sits between two squares in the same
+                        * equivalence class, and the size of that
+                        * class is less than nonblanks, then
+                        * connecting this edge would be a shortcut
+                        * loop and so we must not do so.
+                        */
+                       if (workspace[y*W+x] == 3) {
+                           int ae, be;
+
+                           ae = dsf_canonify(dsf, ac);
+                           be = dsf_canonify(dsf, bc);
+
+                           if (ae == be) {
+                               /*
+                                * We have a loop. Is it a shortcut?
+                                */
+                               if (dsfsize[ae] < nonblanks) {
+                                   /*
+                                    * Yes! Mark this edge disconnected.
+                                    */
+                                   workspace[y*W+x] = 2;
+                                   done_something = TRUE;
+#ifdef SOLVER_DIAGNOSTICS
+                                   printf("edge (%d,%d)-(%d,%d) would create"
+                                          " a shortcut loop, hence must be"
+                                          " disconnected\n", x/2, y/2,
+                                          (x+1)/2, (y+1)/2);
+#endif
+                               }
+                           }
+                       }
+                   } else if ((y & x) & 1) {
+                       /*
+                        * (x,y) are the workspace coordinates of a
+                        * square field. Go through its possible
+                        * (non-blank) states and see if any gives
+                        * rise to a shortcut loop.
+                        * 
+                        * This is slightly fiddly, because we have
+                        * to check whether this square is already
+                        * part of the same equivalence class as
+                        * the things it's joining.
+                        */
+                       int ae = dsf_canonify(dsf, (y/2)*w+(x/2));
+
+                       for (b = 2; b < 0xD; b++)
+                           if (workspace[y*W+x] & (1<<b)) {
+                               /*
+                                * Find the equivalence classes of
+                                * the two squares this one would
+                                * connect if it were in this
+                                * state.
+                                */
+                               int e = -1;
+
+                               for (d = 1; d <= 8; d += d) if (b & d) {
+                                   int xx = x/2 + DX(d), yy = y/2 + DY(d);
+                                   int ee = dsf_canonify(dsf, yy*w+xx);
+
+                                   if (e == -1)
+                                       ee = e;
+                                   else if (e != ee)
+                                       e = -2;
+                               }
+
+                               if (e >= 0) {
+                                   /*
+                                    * This square state would form
+                                    * a loop on equivalence class
+                                    * e. Measure the size of that
+                                    * loop, and see if it's a
+                                    * shortcut.
+                                    */
+                                   int loopsize = dsfsize[e];
+                                   if (e != ae)
+                                       loopsize++;/* add the square itself */
+                                   if (loopsize < nonblanks) {
+                                       /*
+                                        * It is! Mark this square
+                                        * state invalid.
+                                        */
+                                       workspace[y*W+x] &= ~(1<<b);
+                                       done_something = TRUE;
+#ifdef SOLVER_DIAGNOSTICS
+                                       printf("square (%d,%d) would create a "
+                                              "shortcut loop in state %d, "
+                                              "hence cannot be\n",
+                                              x/2, y/2, b);
+#endif
+                                   }
+                               }
+                           }
+                   }
+       }
+
+done_deductions:
+
+       if (done_something)
+           continue;
+
+       /*
+        * If we reach here, there is nothing left we can do.
+        * Return 2 for ambiguous puzzle.
+        */
+       ret = 2;
+       break;
+    }
+
+cleanup:
+
+    /*
+     * If ret = 1 then we've successfully achieved a solution. This
+     * means that we expect every square to be nailed down to
+     * exactly one possibility. If this is the case, or if the caller
+     * asked for a partial solution anyway, transcribe those
+     * possibilities into the result array.
+     */
+    if (ret == 1 || partial) {
+        for (y = 0; y < h; y++) {
+            for (x = 0; x < w; x++) {
+                for (b = 0; b < 0xD; b++)
+                    if (workspace[(2*y+1)*W+(2*x+1)] == (1<<b)) {
+                        result[y*w+x] = b;
+                        break;
+                    }
+               if (ret == 1) assert(b < 0xD); /* we should have had a break by now */
+            }
+        }
+    }
+
+    sfree(dsfsize);
+    sfree(dsf);
+    sfree(workspace);
+    assert(ret >= 0);
+    return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * Loop generator.
+ */
+
+/*
+ * We use the loop generator code from loopy, hard-coding to a square
+ * grid of the appropriate size. Knowing the grid layout and the tile
+ * size we can shrink that to our small grid and then make our line
+ * layout from the face colour info.
+ *
+ * We provide a bias function to the loop generator which tries to
+ * bias in favour of loops with more scope for Pearl black clues. This
+ * seems to improve the success rate of the puzzle generator, in that
+ * such loops have a better chance of being soluble with all valid
+ * clues put in.
+ */
+
+struct pearl_loopgen_bias_ctx {
+    /*
+     * Our bias function counts the number of 'black clue' corners
+     * (i.e. corners adjacent to two straights) in both the
+     * BLACK/nonBLACK and WHITE/nonWHITE boundaries. In order to do
+     * this, we must:
+     *
+     *  - track the edges that are part of each of those loops
+     *  - track the types of vertex in each loop (corner, straight,
+     *    none)
+     *  - track the current black-clue status of each vertex in each
+     *    loop.
+     *
+     * Each of these chunks of data is updated incrementally from the
+     * previous one, to avoid slowdown due to the bias function
+     * rescanning the whole grid every time it's called.
+     *
+     * So we need a lot of separate arrays, plus a tdq for each one,
+     * and we must repeat it all twice for the BLACK and WHITE
+     * boundaries.
+     */
+    struct pearl_loopgen_bias_ctx_boundary {
+        int colour;                    /* FACE_WHITE or FACE_BLACK */
+
+        char *edges;                   /* is each edge part of the loop? */
+        tdq *edges_todo;
+
+        char *vertextypes;             /* bits 0-3 == outgoing edge bitmap;
+                                        * bit 4 set iff corner clue.
+                                        * Hence, 0 means non-vertex;
+                                        * nonzero but bit 4 zero = straight. */
+        int *neighbour[2];          /* indices of neighbour vertices in loop */
+        tdq *vertextypes_todo;
+
+        char *blackclues;              /* is each vertex a black clue site? */
+        tdq *blackclues_todo;
+    } boundaries[2];                   /* boundaries[0]=WHITE, [1]=BLACK */
+
+    char *faces;          /* remember last-seen colour of each face */
+    tdq *faces_todo;
+
+    int score;
+
+    grid *g;
+};
+int pearl_loopgen_bias(void *vctx, char *board, int face)
+{
+    struct pearl_loopgen_bias_ctx *ctx = (struct pearl_loopgen_bias_ctx *)vctx;
+    grid *g = ctx->g;
+    int oldface, newface;
+    int i, j, k;
+
+    tdq_add(ctx->faces_todo, face);
+    while ((j = tdq_remove(ctx->faces_todo)) >= 0) {
+        oldface = ctx->faces[j];
+        ctx->faces[j] = newface = board[j];
+        for (i = 0; i < 2; i++) {
+            struct pearl_loopgen_bias_ctx_boundary *b = &ctx->boundaries[i];
+            int c = b->colour;
+
+            /*
+             * If the face has changed either from or to colour c, we need
+             * to reprocess the edges for this boundary.
+             */
+            if (oldface == c || newface == c) {
+                grid_face *f = &g->faces[face];
+                for (k = 0; k < f->order; k++)
+                    tdq_add(b->edges_todo, f->edges[k] - g->edges);
+            }
+        }
+    }
+
+    for (i = 0; i < 2; i++) {
+        struct pearl_loopgen_bias_ctx_boundary *b = &ctx->boundaries[i];
+        int c = b->colour;
+
+        /*
+         * Go through the to-do list of edges. For each edge, decide
+         * anew whether it's part of this boundary or not. Any edge
+         * that changes state has to have both its endpoints put on
+         * the vertextypes_todo list.
+         */
+        while ((j = tdq_remove(b->edges_todo)) >= 0) {
+            grid_edge *e = &g->edges[j];
+            int fc1 = e->face1 ? board[e->face1 - g->faces] : FACE_BLACK;
+            int fc2 = e->face2 ? board[e->face2 - g->faces] : FACE_BLACK;
+            int oldedge = b->edges[j];
+            int newedge = (fc1==c) ^ (fc2==c);
+            if (oldedge != newedge) {
+                b->edges[j] = newedge;
+                tdq_add(b->vertextypes_todo, e->dot1 - g->dots);
+                tdq_add(b->vertextypes_todo, e->dot2 - g->dots);
+            }
+        }
+
+        /*
+         * Go through the to-do list of vertices whose types need
+         * refreshing. For each one, decide whether it's a corner, a
+         * straight, or a vertex not in the loop, and in the former
+         * two cases also work out the indices of its neighbour
+         * vertices along the loop. Any vertex that changes state must
+         * be put back on the to-do list for deciding if it's a black
+         * clue site, and so must its two new neighbours _and_ its two
+         * old neighbours.
+         */
+        while ((j = tdq_remove(b->vertextypes_todo)) >= 0) {
+            grid_dot *d = &g->dots[j];
+            int neighbours[2], type = 0, n = 0;
+            
+            for (k = 0; k < d->order; k++) {
+                grid_edge *e = d->edges[k];
+                grid_dot *d2 = (e->dot1 == d ? e->dot2 : e->dot1);
+                /* dir == 0,1,2,3 for an edge going L,U,R,D */
+                int dir = (d->y == d2->y) + 2*(d->x+d->y > d2->x+d2->y);
+                int ei = e - g->edges;
+                if (b->edges[ei]) {
+                    type |= 1 << dir;
+                    neighbours[n] = d2 - g->dots; 
+                    n++;
+                }
+            }
+
+            /*
+             * Decide if it's a corner, and set the corner flag if so.
+             */
+            if (type != 0 && type != 0x5 && type != 0xA)
+                type |= 0x10;
+
+            if (type != b->vertextypes[j]) {
+                /*
+                 * Recompute old neighbours, if any.
+                 */
+                if (b->vertextypes[j]) {
+                    tdq_add(b->blackclues_todo, b->neighbour[0][j]);
+                    tdq_add(b->blackclues_todo, b->neighbour[1][j]);
+                }
+                /*
+                 * Recompute this vertex.
+                 */
+                tdq_add(b->blackclues_todo, j);
+                b->vertextypes[j] = type;
+                /*
+                 * Recompute new neighbours, if any.
+                 */
+                if (b->vertextypes[j]) {
+                    b->neighbour[0][j] = neighbours[0];
+                    b->neighbour[1][j] = neighbours[1];
+                    tdq_add(b->blackclues_todo, b->neighbour[0][j]);
+                    tdq_add(b->blackclues_todo, b->neighbour[1][j]);
+                }
+            }
+        }
+
+        /*
+         * Go through the list of vertices which we must check to see
+         * if they're black clue sites. Each one is a black clue site
+         * iff it is a corner and its loop neighbours are non-corners.
+         * Adjust the running total of black clues we've counted.
+         */
+        while ((j = tdq_remove(b->blackclues_todo)) >= 0) {
+            ctx->score -= b->blackclues[j];
+            b->blackclues[j] = ((b->vertextypes[j] & 0x10) &&
+                                !((b->vertextypes[b->neighbour[0][j]] |
+                                   b->vertextypes[b->neighbour[1][j]])
+                                  & 0x10));
+            ctx->score += b->blackclues[j];
+        }
+    }
+
+    return ctx->score;
+}
+
+void pearl_loopgen(int w, int h, char *lines, random_state *rs)
+{
+    grid *g = grid_new(GRID_SQUARE, w-1, h-1, NULL);
+    char *board = snewn(g->num_faces, char);
+    int i, s = g->tilesize;
+    struct pearl_loopgen_bias_ctx biasctx;
+
+    memset(lines, 0, w*h);
+
+    /*
+     * Initialise the context for the bias function. Initially we fill
+     * all the to-do lists, so that the first call will scan
+     * everything; thereafter the lists stay empty so we make
+     * incremental changes.
+     */
+    biasctx.g = g;
+    biasctx.faces = snewn(g->num_faces, char);
+    biasctx.faces_todo = tdq_new(g->num_faces);
+    tdq_fill(biasctx.faces_todo);
+    biasctx.score = 0;
+    memset(biasctx.faces, FACE_GREY, g->num_faces);
+    for (i = 0; i < 2; i++) {
+        biasctx.boundaries[i].edges = snewn(g->num_edges, char);
+        memset(biasctx.boundaries[i].edges, 0, g->num_edges);
+        biasctx.boundaries[i].edges_todo = tdq_new(g->num_edges);
+        tdq_fill(biasctx.boundaries[i].edges_todo);
+        biasctx.boundaries[i].vertextypes = snewn(g->num_dots, char);
+        memset(biasctx.boundaries[i].vertextypes, 0, g->num_dots);
+        biasctx.boundaries[i].neighbour[0] = snewn(g->num_dots, int);
+        biasctx.boundaries[i].neighbour[1] = snewn(g->num_dots, int);
+        biasctx.boundaries[i].vertextypes_todo = tdq_new(g->num_dots);
+        tdq_fill(biasctx.boundaries[i].vertextypes_todo);
+        biasctx.boundaries[i].blackclues = snewn(g->num_dots, char);
+        memset(biasctx.boundaries[i].blackclues, 0, g->num_dots);
+        biasctx.boundaries[i].blackclues_todo = tdq_new(g->num_dots);
+        tdq_fill(biasctx.boundaries[i].blackclues_todo);
+    }
+    biasctx.boundaries[0].colour = FACE_WHITE;
+    biasctx.boundaries[1].colour = FACE_BLACK;
+    generate_loop(g, board, rs, pearl_loopgen_bias, &biasctx);
+    sfree(biasctx.faces);
+    tdq_free(biasctx.faces_todo);
+    for (i = 0; i < 2; i++) {
+        sfree(biasctx.boundaries[i].edges);
+        tdq_free(biasctx.boundaries[i].edges_todo);
+        sfree(biasctx.boundaries[i].vertextypes);
+        sfree(biasctx.boundaries[i].neighbour[0]);
+        sfree(biasctx.boundaries[i].neighbour[1]);
+        tdq_free(biasctx.boundaries[i].vertextypes_todo);
+        sfree(biasctx.boundaries[i].blackclues);
+        tdq_free(biasctx.boundaries[i].blackclues_todo);
+    }
+
+    for (i = 0; i < g->num_edges; i++) {
+        grid_edge *e = g->edges + i;
+        enum face_colour c1 = FACE_COLOUR(e->face1);
+        enum face_colour c2 = FACE_COLOUR(e->face2);
+        assert(c1 != FACE_GREY);
+        assert(c2 != FACE_GREY);
+        if (c1 != c2) {
+            /* This grid edge is on the loop: lay line along it */
+            int x1 = e->dot1->x/s, y1 = e->dot1->y/s;
+            int x2 = e->dot2->x/s, y2 = e->dot2->y/s;
+
+            /* (x1,y1) and (x2,y2) are now in our grid coords (0-w,0-h). */
+            if (x1 == x2) {
+                if (y1 > y2) SWAP(y1,y2);
+
+                assert(y1+1 == y2);
+                lines[y1*w+x1] |= D;
+                lines[y2*w+x1] |= U;
+            } else if (y1 == y2) {
+                if (x1 > x2) SWAP(x1,x2);
+
+                assert(x1+1 == x2);
+                lines[y1*w+x1] |= R;
+                lines[y1*w+x2] |= L;
+            } else
+                assert(!"grid with diagonal coords?!");
+        }
+    }
+
+    grid_free(g);
+    sfree(board);
+
+#if defined LOOPGEN_DIAGNOSTICS && !defined GENERATION_DIAGNOSTICS
+    printf("as returned:\n");
+    for (y = 0; y < h; y++) {
+       for (x = 0; x < w; x++) {
+           int type = lines[y*w+x];
+           char s[5], *p = s;
+           if (type & L) *p++ = 'L';
+           if (type & R) *p++ = 'R';
+           if (type & U) *p++ = 'U';
+           if (type & D) *p++ = 'D';
+           *p = '\0';
+           printf("%3s", s);
+       }
+       printf("\n");
+    }
+    printf("\n");
+#endif
+}
+
+static int new_clues(const game_params *params, random_state *rs,
+                     char *clues, char *grid)
+{
+    int w = params->w, h = params->h, diff = params->difficulty;
+    int ngen = 0, x, y, d, ret, i;
+
+
+    /*
+     * Difficulty exception: 5x5 Tricky is not generable (the
+     * generator will spin forever trying) and so we fudge it to Easy.
+     */
+    if (w == 5 && h == 5 && diff > DIFF_EASY)
+        diff = DIFF_EASY;
+
+    while (1) {
+        ngen++;
+       pearl_loopgen(w, h, grid, rs);
+
+#ifdef GENERATION_DIAGNOSTICS
+       printf("grid array:\n");
+       for (y = 0; y < h; y++) {
+           for (x = 0; x < w; x++) {
+               int type = grid[y*w+x];
+               char s[5], *p = s;
+               if (type & L) *p++ = 'L';
+               if (type & R) *p++ = 'R';
+               if (type & U) *p++ = 'U';
+               if (type & D) *p++ = 'D';
+               *p = '\0';
+               printf("%2s ", s);
+           }
+           printf("\n");
+       }
+       printf("\n");
+#endif
+
+       /*
+        * Set up the maximal clue array.
+        */
+       for (y = 0; y < h; y++)
+           for (x = 0; x < w; x++) {
+               int type = grid[y*w+x];
+
+               clues[y*w+x] = NOCLUE;
+
+               if ((bLR|bUD) & (1 << type)) {
+                   /*
+                    * This is a straight; see if it's a viable
+                    * candidate for a straight clue. It qualifies if
+                    * at least one of the squares it connects to is a
+                    * corner.
+                    */
+                   for (d = 1; d <= 8; d += d) if (type & d) {
+                       int xx = x + DX(d), yy = y + DY(d);
+                       assert(xx >= 0 && xx < w && yy >= 0 && yy < h);
+                       if ((bLU|bLD|bRU|bRD) & (1 << grid[yy*w+xx]))
+                           break;
+                   }
+                   if (d <= 8)        /* we found one */
+                       clues[y*w+x] = STRAIGHT;
+               } else if ((bLU|bLD|bRU|bRD) & (1 << type)) {
+                   /*
+                    * This is a corner; see if it's a viable candidate
+                    * for a corner clue. It qualifies if all the
+                    * squares it connects to are straights.
+                    */
+                   for (d = 1; d <= 8; d += d) if (type & d) {
+                       int xx = x + DX(d), yy = y + DY(d);
+                       assert(xx >= 0 && xx < w && yy >= 0 && yy < h);
+                       if (!((bLR|bUD) & (1 << grid[yy*w+xx])))
+                           break;
+                   }
+                   if (d > 8)         /* we didn't find a counterexample */
+                       clues[y*w+x] = CORNER;
+               }
+           }
+
+#ifdef GENERATION_DIAGNOSTICS
+       printf("clue array:\n");
+       for (y = 0; y < h; y++) {
+           for (x = 0; x < w; x++) {
+               printf("%c", " *O"[(unsigned char)clues[y*w+x]]);
+           }
+           printf("\n");
+       }
+       printf("\n");
+#endif
+
+        if (!params->nosolve) {
+            int *cluespace, *straights, *corners;
+            int nstraights, ncorners, nstraightpos, ncornerpos;
+
+            /*
+             * See if we can solve the puzzle just like this.
+             */
+            ret = pearl_solve(w, h, clues, grid, diff, FALSE);
+            assert(ret > 0);          /* shouldn't be inconsistent! */
+            if (ret != 1)
+                continue;                     /* go round and try again */
+
+            /*
+             * Check this puzzle isn't too easy.
+             */
+            if (diff > DIFF_EASY) {
+                ret = pearl_solve(w, h, clues, grid, diff-1, FALSE);
+                assert(ret > 0);
+                if (ret == 1)
+                    continue; /* too easy: try again */
+            }
+
+            /*
+             * Now shuffle the grid points and gradually remove the
+             * clues to find a minimal set which still leaves the
+             * puzzle soluble.
+             *
+             * We preferentially attempt to remove whichever type of
+             * clue is currently most numerous, to combat a general
+             * tendency of plain random generation to bias in favour
+             * of many white clues and few black.
+             *
+             * 'nstraights' and 'ncorners' count the number of clues
+             * of each type currently remaining in the grid;
+             * 'nstraightpos' and 'ncornerpos' count the clues of each
+             * type we have left to try to remove. (Clues which we
+             * have tried and failed to remove are counted by the
+             * former but not the latter.)
+             */
+            cluespace = snewn(w*h, int);
+            straights = cluespace;
+            nstraightpos = 0;
+            for (i = 0; i < w*h; i++)
+                if (clues[i] == STRAIGHT)
+                    straights[nstraightpos++] = i;
+            corners = straights + nstraightpos;
+            ncornerpos = 0;
+            for (i = 0; i < w*h; i++)
+                if (clues[i] == STRAIGHT)
+                    corners[ncornerpos++] = i;
+            nstraights = nstraightpos;
+            ncorners = ncornerpos;
+
+            shuffle(straights, nstraightpos, sizeof(*straights), rs);
+            shuffle(corners, ncornerpos, sizeof(*corners), rs);
+            while (nstraightpos > 0 || ncornerpos > 0) {
+                int cluepos;
+                int clue;
+
+                /*
+                 * Decide which clue to try to remove next. If both
+                 * types are available, we choose whichever kind is
+                 * currently overrepresented; otherwise we take
+                 * whatever we can get.
+                 */
+                if (nstraightpos > 0 && ncornerpos > 0) {
+                    if (nstraights >= ncorners)
+                        cluepos = straights[--nstraightpos];
+                    else
+                        cluepos = straights[--ncornerpos];
+                } else {
+                    if (nstraightpos > 0)
+                        cluepos = straights[--nstraightpos];
+                    else
+                        cluepos = straights[--ncornerpos];
+                }
+
+                y = cluepos / w;
+                x = cluepos % w;
+
+                clue = clues[y*w+x];
+                clues[y*w+x] = 0;             /* try removing this clue */
+
+                ret = pearl_solve(w, h, clues, grid, diff, FALSE);
+                assert(ret > 0);
+                if (ret != 1)
+                    clues[y*w+x] = clue;   /* oops, put it back again */
+            }
+            sfree(cluespace);
+        }
+
+#ifdef FINISHED_PUZZLE
+       printf("clue array:\n");
+       for (y = 0; y < h; y++) {
+           for (x = 0; x < w; x++) {
+               printf("%c", " *O"[(unsigned char)clues[y*w+x]]);
+           }
+           printf("\n");
+       }
+       printf("\n");
+#endif
+
+       break;                         /* got it */
+    }
+
+    debug(("%d %dx%d loops before finished puzzle.\n", ngen, w, h));
+
+    return ngen;
+}
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                          char **aux, int interactive)
+{
+    char *grid, *clues;
+    char *desc;
+    int w = params->w, h = params->h, i, j;
+
+    grid = snewn(w*h, char);
+    clues = snewn(w*h, char);
+
+    new_clues(params, rs, clues, grid);
+
+    desc = snewn(w * h + 1, char);
+    for (i = j = 0; i < w*h; i++) {
+        if (clues[i] == NOCLUE && j > 0 &&
+            desc[j-1] >= 'a' && desc[j-1] < 'z')
+            desc[j-1]++;
+        else if (clues[i] == NOCLUE)
+            desc[j++] = 'a';
+        else if (clues[i] == CORNER)
+            desc[j++] = 'B';
+        else if (clues[i] == STRAIGHT)
+            desc[j++] = 'W';
+    }
+    desc[j] = '\0';
+
+    *aux = snewn(w*h+1, char);
+    for (i = 0; i < w*h; i++)
+        (*aux)[i] = (grid[i] < 10) ? (grid[i] + '0') : (grid[i] + 'A' - 10);
+    (*aux)[w*h] = '\0';
+
+    sfree(grid);
+    sfree(clues);
+
+    return desc;
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    int i, sizesofar;
+    const int totalsize = params->w * params->h;
+
+    sizesofar = 0;
+    for (i = 0; desc[i]; i++) {
+        if (desc[i] >= 'a' && desc[i] <= 'z')
+            sizesofar += desc[i] - 'a' + 1;
+        else if (desc[i] == 'B' || desc[i] == 'W')
+            sizesofar++;
+        else
+            return "unrecognised character in string";
+    }
+
+    if (sizesofar > totalsize)
+        return "string too long";
+    else if (sizesofar < totalsize)
+        return "string too short";
+
+    return NULL;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    game_state *state = snew(game_state);
+    int i, j, sz = params->w*params->h;
+
+    state->completed = state->used_solve = FALSE;
+    state->shared = snew(struct shared_state);
+
+    state->shared->w = params->w;
+    state->shared->h = params->h;
+    state->shared->sz = sz;
+    state->shared->refcnt = 1;
+    state->shared->clues = snewn(sz, char);
+    for (i = j = 0; desc[i]; i++) {
+        assert(j < sz);
+        if (desc[i] >= 'a' && desc[i] <= 'z') {
+            int n = desc[i] - 'a' + 1;
+            assert(j + n <= sz);
+            while (n-- > 0)
+                state->shared->clues[j++] = NOCLUE;
+        } else if (desc[i] == 'B') {
+            state->shared->clues[j++] = CORNER;
+        } else if (desc[i] == 'W') {
+            state->shared->clues[j++] = STRAIGHT;
+        }
+    }
+
+    state->lines = snewn(sz, char);
+    state->errors = snewn(sz, char);
+    state->marks = snewn(sz, char);
+    for (i = 0; i < sz; i++)
+        state->lines[i] = state->errors[i] = state->marks[i] = BLANK;
+
+    return state;
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    game_state *ret = snew(game_state);
+    int sz = state->shared->sz, i;
+
+    ret->shared = state->shared;
+    ret->completed = state->completed;
+    ret->used_solve = state->used_solve;
+    ++ret->shared->refcnt;
+
+    ret->lines = snewn(sz, char);
+    ret->errors = snewn(sz, char);
+    ret->marks = snewn(sz, char);
+    for (i = 0; i < sz; i++) {
+        ret->lines[i] = state->lines[i];
+        ret->errors[i] = state->errors[i];
+        ret->marks[i] = state->marks[i];
+    }
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    assert(state);
+    if (--state->shared->refcnt == 0) {
+        sfree(state->shared->clues);
+        sfree(state->shared);
+    }
+    sfree(state->lines);
+    sfree(state->errors);
+    sfree(state->marks);
+    sfree(state);
+}
+
+static char nbits[16] = { 0, 1, 1, 2,
+                          1, 2, 2, 3,
+                          1, 2, 2, 3,
+                          2, 3, 3, 4 };
+#define NBITS(l) ( ((l) < 0 || (l) > 15) ? 4 : nbits[l] )
+
+#define ERROR_CLUE 16
+
+static void dsf_update_completion(game_state *state, int ax, int ay, char dir,
+                                 int *dsf)
+{
+    int w = state->shared->w /*, h = state->shared->h */;
+    int ac = ay*w+ax, bx, by, bc;
+
+    if (!(state->lines[ac] & dir)) return; /* no link */
+    bx = ax + DX(dir); by = ay + DY(dir);
+
+    assert(INGRID(state, bx, by)); /* should not have a link off grid */
+
+    bc = by*w+bx;
+    assert(state->lines[bc] & F(dir)); /* should have reciprocal link */
+    if (!(state->lines[bc] & F(dir))) return;
+
+    dsf_merge(dsf, ac, bc);
+}
+
+static void check_completion(game_state *state, int mark)
+{
+    int w = state->shared->w, h = state->shared->h, x, y, i, d;
+    int had_error = FALSE;
+    int *dsf, *component_state;
+    int nsilly, nloop, npath, largest_comp, largest_size, total_pathsize;
+    enum { COMP_NONE, COMP_LOOP, COMP_PATH, COMP_SILLY, COMP_EMPTY };
+
+    if (mark) {
+        for (i = 0; i < w*h; i++) {
+            state->errors[i] = 0;
+        }
+    }
+
+#define ERROR(x,y,e) do { had_error = TRUE; if (mark) state->errors[(y)*w+(x)] |= (e); } while(0)
+
+    /*
+     * Analyse the solution into loops, paths and stranger things.
+     * Basic strategy here is all the same as in Loopy - see the big
+     * comment in loopy.c's check_completion() - and for exactly the
+     * same reasons, since Loopy and Pearl have basically the same
+     * form of expected solution.
+     */
+    dsf = snew_dsf(w*h);
+
+    /* Build the dsf. */
+    for (x = 0; x < w; x++) {
+        for (y = 0; y < h; y++) {
+            dsf_update_completion(state, x, y, R, dsf);
+            dsf_update_completion(state, x, y, D, dsf);
+        }
+    }
+
+    /* Initialise a state variable for each connected component. */
+    component_state = snewn(w*h, int);
+    for (i = 0; i < w*h; i++) {
+        if (dsf_canonify(dsf, i) == i)
+            component_state[i] = COMP_LOOP;
+        else
+            component_state[i] = COMP_NONE;
+    }
+
+    /*
+     * Classify components, and mark errors where a square has more
+     * than two line segments.
+     */
+    for (x = 0; x < w; x++) {
+        for (y = 0; y < h; y++) {
+            int type = state->lines[y*w+x];
+            int degree = NBITS(type);
+            int comp = dsf_canonify(dsf, y*w+x);
+            if (degree > 2) {
+                ERROR(x,y,type);
+                component_state[comp] = COMP_SILLY;
+            } else if (degree == 0) {
+                component_state[comp] = COMP_EMPTY;
+            } else if (degree == 1) {
+                if (component_state[comp] != COMP_SILLY)
+                    component_state[comp] = COMP_PATH;
+            }
+        }
+    }
+
+    /* Count the components, and find the largest sensible one. */
+    nsilly = nloop = npath = 0;
+    total_pathsize = 0;
+    largest_comp = largest_size = -1;
+    for (i = 0; i < w*h; i++) {
+        if (component_state[i] == COMP_SILLY) {
+            nsilly++;
+        } else if (component_state[i] == COMP_PATH) {
+            total_pathsize += dsf_size(dsf, i);
+            npath = 1;
+        } else if (component_state[i] == COMP_LOOP) {
+            int this_size;
+
+            nloop++;
+
+            if ((this_size = dsf_size(dsf, i)) > largest_size) {
+                largest_comp = i;
+                largest_size = this_size;
+            }
+        }
+    }
+    if (largest_size < total_pathsize) {
+        largest_comp = -1;             /* means the paths */
+        largest_size = total_pathsize;
+    }
+
+    if (nloop > 0 && nloop + npath > 1) {
+        /*
+         * If there are at least two sensible components including at
+         * least one loop, highlight every sensible component that is
+         * not the largest one.
+         */
+        for (i = 0; i < w*h; i++) {
+            int comp = dsf_canonify(dsf, i);
+            if ((component_state[comp] == COMP_PATH &&
+                 -1 != largest_comp) ||
+                (component_state[comp] == COMP_LOOP &&
+                 comp != largest_comp))
+                ERROR(i%w, i/w, state->lines[i]);
+        }
+    }
+
+    /* Now we've finished with the dsf and component states. The only
+     * thing we'll need to remember later on is whether all edges were
+     * part of a single loop, for which our counter variables
+     * nsilly,nloop,npath are enough. */
+    sfree(component_state);
+    sfree(dsf);
+
+    /*
+     * Check that no clues are contradicted. This code is similar to
+     * the code that sets up the maximal clue array for any given
+     * loop.
+     */
+    for (x = 0; x < w; x++) {
+        for (y = 0; y < h; y++) {
+            int type = state->lines[y*w+x];
+            if (state->shared->clues[y*w+x] == CORNER) {
+                /* Supposed to be a corner: will find a contradiction if
+                 * it actually contains a straight line, or if it touches any
+                 * corners. */
+                if ((bLR|bUD) & (1 << type)) {
+                    ERROR(x,y,ERROR_CLUE); /* actually straight */
+                }
+                for (d = 1; d <= 8; d += d) if (type & d) {
+                    int xx = x + DX(d), yy = y + DY(d);
+                    if (!INGRID(state, xx, yy)) {
+                        ERROR(x,y,d); /* leads off grid */
+                    } else {
+                        if ((bLU|bLD|bRU|bRD) & (1 << state->lines[yy*w+xx])) {
+                            ERROR(x,y,ERROR_CLUE); /* touches corner */
+                        }
+                    }
+                }
+            } else if (state->shared->clues[y*w+x] == STRAIGHT) {
+                /* Supposed to be straight: will find a contradiction if
+                 * it actually contains a corner, or if it only touches
+                 * straight lines. */
+                if ((bLU|bLD|bRU|bRD) & (1 << type)) {
+                    ERROR(x,y,ERROR_CLUE); /* actually a corner */
+                }
+                i = 0;
+                for (d = 1; d <= 8; d += d) if (type & d) {
+                    int xx = x + DX(d), yy = y + DY(d);
+                    if (!INGRID(state, xx, yy)) {
+                        ERROR(x,y,d); /* leads off grid */
+                    } else {
+                        if ((bLR|bUD) & (1 << state->lines[yy*w+xx]))
+                            i++; /* a straight */
+                    }
+                }
+                if (i >= 2 && NBITS(type) >= 2) {
+                    ERROR(x,y,ERROR_CLUE); /* everything touched is straight */
+                }
+            }
+        }
+    }
+
+    if (nloop == 1 && nsilly == 0 && npath == 0) {
+        /*
+         * If there's exactly one loop (so that the puzzle is at least
+         * potentially complete), we need to ensure it hasn't left any
+         * clue out completely.
+         */
+        for (x = 0; x < w; x++) {
+            for (y = 0; y < h; y++) {
+                if (state->lines[y*w+x] == BLANK) {
+                    if (state->shared->clues[y*w+x] != NOCLUE) {
+                        /* the loop doesn't include this clue square! */
+                        ERROR(x, y, ERROR_CLUE);
+                    }
+                }
+            }
+        }
+
+        /*
+         * But if not, then we're done!
+         */
+        if (!had_error)
+            state->completed = TRUE;
+    }
+}
+
+/* completion check:
+ *
+ * - no clues must be contradicted (highlight clue itself in error if so)
+ * - if there is a closed loop it must include every line segment laid
+ *    - if there's a smaller closed loop then highlight whole loop as error
+ * - no square must have more than 2 lines radiating from centre point
+ *   (highlight all lines in that square as error if so)
+ */
+
+static char *solve_for_diff(game_state *state, char *old_lines, char *new_lines)
+{
+    int w = state->shared->w, h = state->shared->h, i;
+    char *move = snewn(w*h*40, char), *p = move;
+
+    *p++ = 'S';
+    for (i = 0; i < w*h; i++) {
+        if (old_lines[i] != new_lines[i]) {
+            p += sprintf(p, ";R%d,%d,%d", new_lines[i], i%w, i/w);
+        }
+    }
+    *p++ = '\0';
+    move = sresize(move, p - move, char);
+
+    return move;
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    game_state *solved = dup_game(state);
+    int i, ret, sz = state->shared->sz;
+    char *move;
+
+    if (aux) {
+        for (i = 0; i < sz; i++) {
+            if (aux[i] >= '0' && aux[i] <= '9')
+                solved->lines[i] = aux[i] - '0';
+            else if (aux[i] >= 'A' && aux[i] <= 'F')
+                solved->lines[i] = aux[i] - 'A' + 10;
+            else {
+                *error = "invalid char in aux";
+                move = NULL;
+                goto done;
+            }
+        }
+        ret = 1;
+    } else {
+        /* Try to solve with present (half-solved) state first: if there's no
+         * solution from there go back to original state. */
+        ret = pearl_solve(currstate->shared->w, currstate->shared->h,
+                          currstate->shared->clues, solved->lines,
+                          DIFFCOUNT, FALSE);
+        if (ret < 1)
+            ret = pearl_solve(state->shared->w, state->shared->h,
+                              state->shared->clues, solved->lines,
+                              DIFFCOUNT, FALSE);
+
+    }
+
+    if (ret < 1) {
+        *error = "Unable to find solution";
+        move = NULL;
+    } else {
+        move = solve_for_diff(solved, currstate->lines, solved->lines);
+    }
+
+done:
+    free_game(solved);
+    return move;
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    int w = state->shared->w, h = state->shared->h, cw = 4, ch = 2;
+    int gw = cw*(w-1) + 2, gh = ch*(h-1) + 1, len = gw * gh, r, c, j;
+    char *board = snewn(len + 1, char);
+
+    assert(board);
+    memset(board, ' ', len);
+
+    for (r = 0; r < h; ++r) {
+       for (c = 0; c < w; ++c) {
+           int i = r*w + c, cell = r*ch*gw + c*cw;
+           board[cell] = "+BW"[(unsigned char)state->shared->clues[i]];
+           if (c < w - 1 && (state->lines[i] & R || state->lines[i+1] & L))
+               memset(board + cell + 1, '-', cw - 1);
+           if (r < h - 1 && (state->lines[i] & D || state->lines[i+w] & U))
+               for (j = 1; j < ch; ++j) board[cell + j*gw] = '|';
+           if (c < w - 1 && (state->marks[i] & R || state->marks[i+1] & L))
+               board[cell + cw/2] = 'x';
+           if (r < h - 1 && (state->marks[i] & D || state->marks[i+w] & U))
+               board[cell + (ch/2 * gw)] = 'x';
+       }
+
+       for (j = 0; j < (r == h - 1 ? 1 : ch); ++j)
+           board[r*ch*gw + (gw - 1) + j*gw] = '\n';
+    }
+
+    board[len] = '\0';
+    return board;
+}
+
+struct game_ui {
+    int *dragcoords;       /* list of (y*w+x) coords in drag so far */
+    int ndragcoords;       /* number of entries in dragcoords.
+                            * 0 = click but no drag yet. -1 = no drag at all */
+    int clickx, clicky;    /* pixel position of initial click */
+
+    int curx, cury;        /* grid position of keyboard cursor */
+    int cursor_active;     /* TRUE iff cursor is shown */
+};
+
+static game_ui *new_ui(const game_state *state)
+{
+    game_ui *ui = snew(game_ui);
+    int sz = state->shared->sz;
+
+    ui->ndragcoords = -1;
+    ui->dragcoords = snewn(sz, int);
+    ui->cursor_active = FALSE;
+    ui->curx = ui->cury = 0;
+
+    return ui;
+}
+
+static void free_ui(game_ui *ui)
+{
+    sfree(ui->dragcoords);
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+}
+
+#define PREFERRED_TILE_SIZE 31
+#define HALFSZ (ds->halfsz)
+#define TILE_SIZE (ds->halfsz*2 + 1)
+
+#define BORDER ((get_gui_style() == GUI_LOOPY) ? (TILE_SIZE/8) : (TILE_SIZE/2))
+
+#define BORDER_WIDTH (max(TILE_SIZE / 32, 1))
+
+#define COORD(x) ( (x) * TILE_SIZE + BORDER )
+#define CENTERED_COORD(x) ( COORD(x) + TILE_SIZE/2 )
+#define FROMCOORD(x) ( ((x) < BORDER) ? -1 : ( ((x) - BORDER) / TILE_SIZE) )
+
+#define DS_ESHIFT 4     /* R/U/L/D shift, for error flags */
+#define DS_DSHIFT 8     /* R/U/L/D shift, for drag-in-progress flags */
+#define DS_MSHIFT 12    /* shift for no-line mark */
+
+#define DS_ERROR_CLUE (1 << 20)
+#define DS_FLASH (1 << 21)
+#define DS_CURSOR (1 << 22)
+
+enum { GUI_MASYU, GUI_LOOPY };
+
+static int get_gui_style(void)
+{
+    static int gui_style = -1;
+
+    if (gui_style == -1) {
+        char *env = getenv("PEARL_GUI_LOOPY");
+        if (env && (env[0] == 'y' || env[0] == 'Y'))
+            gui_style = GUI_LOOPY;
+        else
+            gui_style = GUI_MASYU;
+    }
+    return gui_style;
+}
+
+struct game_drawstate {
+    int halfsz;
+    int started;
+
+    int w, h, sz;
+    unsigned int *lflags;       /* size w*h */
+
+    char *draglines;            /* size w*h; lines flipped by current drag */
+};
+
+static void update_ui_drag(const game_state *state, game_ui *ui,
+                           int gx, int gy)
+{
+    int /* sz = state->shared->sz, */ w = state->shared->w;
+    int i, ox, oy, pos;
+    int lastpos;
+
+    if (!INGRID(state, gx, gy))
+        return;                        /* square is outside grid */
+
+    if (ui->ndragcoords < 0)
+        return;                        /* drag not in progress anyway */
+
+    pos = gy * w + gx;
+
+    lastpos = ui->dragcoords[ui->ndragcoords > 0 ? ui->ndragcoords-1 : 0];
+    if (pos == lastpos)
+        return;             /* same square as last visited one */
+
+    /* Drag confirmed, if it wasn't already. */
+    if (ui->ndragcoords == 0)
+        ui->ndragcoords = 1;
+
+    /*
+     * Dragging the mouse into a square that's already been visited by
+     * the drag path so far has the effect of truncating the path back
+     * to that square, so a player can back out part of an uncommitted
+     * drag without having to let go of the mouse.
+     */
+    for (i = 0; i < ui->ndragcoords; i++)
+        if (pos == ui->dragcoords[i]) {
+            ui->ndragcoords = i+1;
+            return;
+        }
+
+    /*
+     * Otherwise, dragging the mouse into a square that's a rook-move
+     * away from the last one on the path extends the path.
+     */
+    oy = ui->dragcoords[ui->ndragcoords-1] / w;
+    ox = ui->dragcoords[ui->ndragcoords-1] % w;
+    if (ox == gx || oy == gy) {
+        int dx = (gx < ox ? -1 : gx > ox ? +1 : 0);
+        int dy = (gy < oy ? -1 : gy > oy ? +1 : 0);
+        int dir = (dy>0 ? D : dy<0 ? U : dx>0 ? R : L);
+        while (ox != gx || oy != gy) {
+            /*
+             * If the drag attempts to cross a 'no line here' mark,
+             * stop there. We physically don't allow the user to drag
+             * over those marks.
+             */
+            if (state->marks[oy*w+ox] & dir)
+                break;
+            ox += dx;
+            oy += dy;
+            ui->dragcoords[ui->ndragcoords++] = oy * w + ox;
+        }
+    }
+
+    /*
+     * Failing that, we do nothing at all: if the user has dragged
+     * diagonally across the board, they'll just have to return the
+     * mouse to the last known position and do whatever they meant to
+     * do again, more slowly and clearly.
+     */
+}
+
+/*
+ * Routine shared between interpret_move and game_redraw to work out
+ * the intended effect of a drag path on the grid.
+ *
+ * Call it in a loop, like this:
+ *
+ *     int clearing = TRUE;
+ *     for (i = 0; i < ui->ndragcoords - 1; i++) {
+ *         int sx, sy, dx, dy, dir, oldstate, newstate;
+ *         interpret_ui_drag(state, ui, &clearing, i, &sx, &sy, &dx, &dy,
+ *                           &dir, &oldstate, &newstate);
+ *
+ *         [do whatever is needed to handle the fact that the drag
+ *         wants the edge from sx,sy to dx,dy (heading in direction
+ *         'dir' at the sx,sy end) to be changed from state oldstate
+ *         to state newstate, each of which equals either 0 or dir]
+ *     }
+ */
+static void interpret_ui_drag(const game_state *state, const game_ui *ui,
+                              int *clearing, int i, int *sx, int *sy,
+                              int *dx, int *dy, int *dir,
+                              int *oldstate, int *newstate)
+{
+    int w = state->shared->w;
+    int sp = ui->dragcoords[i], dp = ui->dragcoords[i+1];
+    *sy = sp/w;
+    *sx = sp%w;
+    *dy = dp/w;
+    *dx = dp%w;
+    *dir = (*dy>*sy ? D : *dy<*sy ? U : *dx>*sx ? R : L);
+    *oldstate = state->lines[sp] & *dir;
+    if (*oldstate) {
+        /*
+         * The edge we've dragged over was previously
+         * present. Set it to absent, unless we've already
+         * stopped doing that.
+         */
+        *newstate = *clearing ? 0 : *dir;
+    } else {
+        /*
+         * The edge we've dragged over was previously
+         * absent. Set it to present, and cancel the
+         * 'clearing' flag so that all subsequent edges in
+         * the drag are set rather than cleared.
+         */
+        *newstate = *dir;
+        *clearing = FALSE;
+    }
+}
+
+static char *mark_in_direction(const game_state *state, int x, int y, int dir,
+                              int primary, char *buf)
+{
+    int w = state->shared->w /*, h = state->shared->h, sz = state->shared->sz */;
+    int x2 = x + DX(dir);
+    int y2 = y + DY(dir);
+    int dir2 = F(dir);
+
+    char ch = primary ? 'F' : 'M', *other;
+
+    if (!INGRID(state, x, y) || !INGRID(state, x2, y2)) return "";
+
+    /* disallow laying a mark over a line, or vice versa. */
+    other = primary ? state->marks : state->lines;
+    if (other[y*w+x] & dir || other[y2*w+x2] & dir2) return "";
+    
+    sprintf(buf, "%c%d,%d,%d;%c%d,%d,%d", ch, dir, x, y, ch, dir2, x2, y2);
+    return dupstr(buf);
+}
+
+#define KEY_DIRECTION(btn) (\
+    (btn) == CURSOR_DOWN ? D : (btn) == CURSOR_UP ? U :\
+    (btn) == CURSOR_LEFT ? L : R)
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    int w = state->shared->w, h = state->shared->h /*, sz = state->shared->sz */;
+    int gx = FROMCOORD(x), gy = FROMCOORD(y), i;
+    int release = FALSE;
+    char tmpbuf[80];
+
+    int shift = button & MOD_SHFT, control = button & MOD_CTRL;
+    button &= ~MOD_MASK;
+
+    if (IS_MOUSE_DOWN(button)) {
+       ui->cursor_active = FALSE;
+
+        if (!INGRID(state, gx, gy)) {
+            ui->ndragcoords = -1;
+            return NULL;
+        }
+
+        ui->clickx = x; ui->clicky = y;
+        ui->dragcoords[0] = gy * w + gx;
+        ui->ndragcoords = 0;           /* will be 1 once drag is confirmed */
+
+        return "";
+    }
+
+    if (button == LEFT_DRAG && ui->ndragcoords >= 0) {
+        update_ui_drag(state, ui, gx, gy);
+        return "";
+    }
+
+    if (IS_MOUSE_RELEASE(button)) release = TRUE;
+
+    if (IS_CURSOR_MOVE(button)) {
+       if (!ui->cursor_active) {
+           ui->cursor_active = TRUE;
+       } else if (control | shift) {
+           char *move;
+           if (ui->ndragcoords > 0) return NULL;
+           ui->ndragcoords = -1;
+           move = mark_in_direction(state, ui->curx, ui->cury,
+                                    KEY_DIRECTION(button), control, tmpbuf);
+           if (control && !shift && *move)
+               move_cursor(button, &ui->curx, &ui->cury, w, h, FALSE);
+           return move;
+       } else {
+           move_cursor(button, &ui->curx, &ui->cury, w, h, FALSE);
+           if (ui->ndragcoords >= 0)
+               update_ui_drag(state, ui, ui->curx, ui->cury);
+       }
+       return "";
+    }
+
+    if (IS_CURSOR_SELECT(button)) {
+       if (!ui->cursor_active) {
+           ui->cursor_active = TRUE;
+           return "";
+       } else if (button == CURSOR_SELECT) {
+           if (ui->ndragcoords == -1) {
+               ui->ndragcoords = 0;
+               ui->dragcoords[0] = ui->cury * w + ui->curx;
+               ui->clickx = CENTERED_COORD(ui->curx);
+               ui->clicky = CENTERED_COORD(ui->cury);
+               return "";
+           } else release = TRUE;
+       } else if (button == CURSOR_SELECT2 && ui->ndragcoords >= 0) {
+           ui->ndragcoords = -1;
+           return "";
+       }
+    }
+
+    if (button == 27 || button == '\b') {
+        ui->ndragcoords = -1;
+        return "";
+    }
+
+    if (release) {
+        if (ui->ndragcoords > 0) {
+            /* End of a drag: process the cached line data. */
+            int buflen = 0, bufsize = 256, tmplen;
+            char *buf = NULL;
+            const char *sep = "";
+            int clearing = TRUE;
+
+            for (i = 0; i < ui->ndragcoords - 1; i++) {
+                int sx, sy, dx, dy, dir, oldstate, newstate;
+                interpret_ui_drag(state, ui, &clearing, i, &sx, &sy, &dx, &dy,
+                                  &dir, &oldstate, &newstate);
+
+                if (oldstate != newstate) {
+                    if (!buf) buf = snewn(bufsize, char);
+                    tmplen = sprintf(tmpbuf, "%sF%d,%d,%d;F%d,%d,%d", sep,
+                                     dir, sx, sy, F(dir), dx, dy);
+                    if (buflen + tmplen >= bufsize) {
+                        bufsize = (buflen + tmplen) * 5 / 4 + 256;
+                        buf = sresize(buf, bufsize, char);
+                    }
+                    strcpy(buf + buflen, tmpbuf);
+                    buflen += tmplen;
+                    sep = ";";
+                }
+            }
+
+            ui->ndragcoords = -1;
+
+            return buf ? buf : "";
+        } else if (ui->ndragcoords == 0) {
+            /* Click (or tiny drag). Work out which edge we were
+             * closest to. */
+            int cx, cy;
+
+            ui->ndragcoords = -1;
+
+            /*
+             * We process clicks based on the mouse-down location,
+             * because that's more natural for a user to carefully
+             * control than the mouse-up.
+             */
+            x = ui->clickx;
+            y = ui->clicky;
+
+            gx = FROMCOORD(x);
+            gy = FROMCOORD(y);
+            cx = CENTERED_COORD(gx);
+            cy = CENTERED_COORD(gy);
+
+            if (!INGRID(state, gx, gy)) return "";
+
+            if (max(abs(x-cx),abs(y-cy)) < TILE_SIZE/4) {
+                /* TODO closer to centre of grid: process as a cell click not an edge click. */
+
+                return "";
+            } else {
+               int direction;
+                if (abs(x-cx) < abs(y-cy)) {
+                    /* Closest to top/bottom edge. */
+                    direction = (y < cy) ? U : D;
+                } else {
+                    /* Closest to left/right edge. */
+                    direction = (x < cx) ? L : R;
+                }
+               return mark_in_direction(state, gx, gy, direction,
+                                        (button == LEFT_RELEASE), tmpbuf);
+            }
+        }
+    }
+
+    if (button == 'H' || button == 'h')
+        return dupstr("H");
+
+    return NULL;
+}
+
+static game_state *execute_move(const game_state *state, const char *move)
+{
+    int w = state->shared->w, h = state->shared->h;
+    char c;
+    int x, y, l, n;
+    game_state *ret = dup_game(state);
+
+    debug(("move: %s\n", move));
+
+    while (*move) {
+        c = *move;
+        if (c == 'S') {
+            ret->used_solve = TRUE;
+            move++;
+        } else if (c == 'L' || c == 'N' || c == 'R' || c == 'F' || c == 'M') {
+            /* 'line' or 'noline' or 'replace' or 'flip' or 'mark' */
+            move++;
+            if (sscanf(move, "%d,%d,%d%n", &l, &x, &y, &n) != 3)
+                goto badmove;
+            if (!INGRID(state, x, y)) goto badmove;
+            if (l < 0 || l > 15) goto badmove;
+
+            if (c == 'L')
+                ret->lines[y*w + x] |= (char)l;
+            else if (c == 'N')
+                ret->lines[y*w + x] &= ~((char)l);
+            else if (c == 'R') {
+                ret->lines[y*w + x] = (char)l;
+                ret->marks[y*w + x] &= ~((char)l); /* erase marks too */
+            } else if (c == 'F')
+                ret->lines[y*w + x] ^= (char)l;
+            else if (c == 'M')
+                ret->marks[y*w + x] ^= (char)l;
+
+            /*
+             * If we ended up trying to lay a line _over_ a mark,
+             * that's a failed move: interpret_move() should have
+             * ensured we never received a move string like that in
+             * the first place.
+             */
+            if ((ret->lines[y*w + x] & (char)l) &&
+                (ret->marks[y*w + x] & (char)l))
+                goto badmove;
+
+            move += n;
+        } else if (strcmp(move, "H") == 0) {
+            pearl_solve(ret->shared->w, ret->shared->h,
+                        ret->shared->clues, ret->lines, DIFFCOUNT, TRUE);
+            for (n = 0; n < w*h; n++)
+                ret->marks[n] &= ~ret->lines[n]; /* erase marks too */
+            move++;
+        } else {
+            goto badmove;
+        }
+        if (*move == ';')
+            move++;
+        else if (*move)
+            goto badmove;
+    }
+
+    check_completion(ret, TRUE);
+
+    return ret;
+
+badmove:
+    free_game(ret);
+    return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+#define FLASH_TIME 0.5F
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    struct { int halfsz; } ads, *ds = &ads;
+    ads.halfsz = (tilesize-1)/2;
+
+    *x = (params->w) * TILE_SIZE + 2 * BORDER;
+    *y = (params->h) * TILE_SIZE + 2 * BORDER;
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->halfsz = (tilesize-1)/2;
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+    int i;
+
+    game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT);
+
+    for (i = 0; i < 3; i++) {
+        ret[COL_BLACK * 3 + i] = 0.0F;
+        ret[COL_WHITE * 3 + i] = 1.0F;
+        ret[COL_GRID * 3 + i] = 0.4F;
+    }
+
+    ret[COL_ERROR * 3 + 0] = 1.0F;
+    ret[COL_ERROR * 3 + 1] = 0.0F;
+    ret[COL_ERROR * 3 + 2] = 0.0F;
+
+    ret[COL_DRAGON * 3 + 0] = 0.0F;
+    ret[COL_DRAGON * 3 + 1] = 0.0F;
+    ret[COL_DRAGON * 3 + 2] = 1.0F;
+
+    ret[COL_DRAGOFF * 3 + 0] = 0.8F;
+    ret[COL_DRAGOFF * 3 + 1] = 0.8F;
+    ret[COL_DRAGOFF * 3 + 2] = 1.0F;
+
+    ret[COL_FLASH * 3 + 0] = 1.0F;
+    ret[COL_FLASH * 3 + 1] = 1.0F;
+    ret[COL_FLASH * 3 + 2] = 1.0F;
+
+    *ncolours = NCOLOURS;
+
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    struct game_drawstate *ds = snew(struct game_drawstate);
+    int i;
+
+    ds->halfsz = 0;
+    ds->started = FALSE;
+
+    ds->w = state->shared->w;
+    ds->h = state->shared->h;
+    ds->sz = state->shared->sz;
+    ds->lflags = snewn(ds->sz, unsigned int);
+    for (i = 0; i < ds->sz; i++)
+        ds->lflags[i] = 0;
+
+    ds->draglines = snewn(ds->sz, char);
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds->draglines);
+    sfree(ds->lflags);
+    sfree(ds);
+}
+
+static void draw_lines_specific(drawing *dr, game_drawstate *ds,
+                                int x, int y, unsigned int lflags,
+                                unsigned int shift, int c)
+{
+    int ox = COORD(x), oy = COORD(y);
+    int t2 = HALFSZ, t16 = HALFSZ/4;
+    int cx = ox + t2, cy = oy + t2;
+    int d;
+
+    /* Draw each of the four directions, where laid (or error, or drag, etc.) */
+    for (d = 1; d < 16; d *= 2) {
+        int xoff = t2 * DX(d), yoff = t2 * DY(d);
+        int xnudge = abs(t16 * DX(C(d))), ynudge = abs(t16 * DY(C(d)));
+
+        if ((lflags >> shift) & d) {
+            int lx = cx + ((xoff < 0) ? xoff : 0) - xnudge;
+            int ly = cy + ((yoff < 0) ? yoff : 0) - ynudge;
+
+            if (c == COL_DRAGOFF && !(lflags & d))
+                continue;
+            if (c == COL_DRAGON && (lflags & d))
+                continue;
+
+            draw_rect(dr, lx, ly,
+                      abs(xoff)+2*xnudge+1,
+                      abs(yoff)+2*ynudge+1, c);
+            /* end cap */
+            draw_rect(dr, cx - t16, cy - t16, 2*t16+1, 2*t16+1, c);
+        }
+    }
+}
+
+static void draw_square(drawing *dr, game_drawstate *ds, const game_ui *ui,
+                        int x, int y, unsigned int lflags, char clue)
+{
+    int ox = COORD(x), oy = COORD(y);
+    int t2 = HALFSZ, t16 = HALFSZ/4;
+    int cx = ox + t2, cy = oy + t2;
+    int d;
+
+    assert(dr);
+
+    /* Clip to the grid square. */
+    clip(dr, ox, oy, TILE_SIZE, TILE_SIZE);
+
+    /* Clear the square. */
+    draw_rect(dr, ox, oy, TILE_SIZE, TILE_SIZE,
+             (lflags & DS_CURSOR) ?
+             COL_CURSOR_BACKGROUND : COL_BACKGROUND);
+             
+
+    if (get_gui_style() == GUI_LOOPY) {
+        /* Draw small dot, underneath any lines. */
+        draw_circle(dr, cx, cy, t16, COL_GRID, COL_GRID);
+    } else {
+        /* Draw outline of grid square */
+        draw_line(dr, ox, oy, COORD(x+1), oy, COL_GRID);
+        draw_line(dr, ox, oy, ox, COORD(y+1), COL_GRID);
+    }
+
+    /* Draw grid: either thin gridlines, or no-line marks.
+     * We draw these first because the thick laid lines should be on top. */
+    for (d = 1; d < 16; d *= 2) {
+        int xoff = t2 * DX(d), yoff = t2 * DY(d);
+
+        if ((x == 0 && d == L) ||
+            (y == 0 && d == U) ||
+            (x == ds->w-1 && d == R) ||
+            (y == ds->h-1 && d == D))
+            continue; /* no gridlines out to the border. */
+
+        if ((lflags >> DS_MSHIFT) & d) {
+            /* either a no-line mark ... */
+            int mx = cx + xoff, my = cy + yoff, msz = t16;
+
+            draw_line(dr, mx-msz, my-msz, mx+msz, my+msz, COL_BLACK);
+            draw_line(dr, mx-msz, my+msz, mx+msz, my-msz, COL_BLACK);
+        } else {
+            if (get_gui_style() == GUI_LOOPY) {
+                /* draw grid lines connecting centre of cells */
+                draw_line(dr, cx, cy, cx+xoff, cy+yoff, COL_GRID);
+            }
+        }
+    }
+
+    /* Draw each of the four directions, where laid (or error, or drag, etc.)
+     * Order is important here, specifically for the eventual colours of the
+     * exposed end caps. */
+    draw_lines_specific(dr, ds, x, y, lflags, 0,
+                        (lflags & DS_FLASH ? COL_FLASH : COL_BLACK));
+    draw_lines_specific(dr, ds, x, y, lflags, DS_ESHIFT, COL_ERROR);
+    draw_lines_specific(dr, ds, x, y, lflags, DS_DSHIFT, COL_DRAGOFF);
+    draw_lines_specific(dr, ds, x, y, lflags, DS_DSHIFT, COL_DRAGON);
+
+    /* Draw a clue, if present */
+    if (clue != NOCLUE) {
+        int c = (lflags & DS_FLASH) ? COL_FLASH :
+                (clue == STRAIGHT) ? COL_WHITE : COL_BLACK;
+
+        if (lflags & DS_ERROR_CLUE) /* draw a bigger 'error' clue circle. */
+            draw_circle(dr, cx, cy, TILE_SIZE*3/8, COL_ERROR, COL_ERROR);
+
+        draw_circle(dr, cx, cy, TILE_SIZE/4, c, COL_BLACK);
+    }
+
+    unclip(dr);
+    draw_update(dr, ox, oy, TILE_SIZE, TILE_SIZE);
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int w = state->shared->w, h = state->shared->h, sz = state->shared->sz;
+    int x, y, force = 0, flashing = 0;
+
+    if (!ds->started) {
+        /*
+         * The initial contents of the window are not guaranteed and
+         * can vary with front ends. To be on the safe side, all games
+         * should start by drawing a big background-colour rectangle
+         * covering the whole window.
+         */
+        draw_rect(dr, 0, 0, w*TILE_SIZE + 2*BORDER, h*TILE_SIZE + 2*BORDER,
+                  COL_BACKGROUND);
+
+        if (get_gui_style() == GUI_MASYU) {
+            /*
+             * Smaller black rectangle which is the main grid.
+             */
+            draw_rect(dr, BORDER - BORDER_WIDTH, BORDER - BORDER_WIDTH,
+                      w*TILE_SIZE + 2*BORDER_WIDTH + 1,
+                      h*TILE_SIZE + 2*BORDER_WIDTH + 1,
+                      COL_GRID);
+        }
+
+        draw_update(dr, 0, 0, w*TILE_SIZE + 2*BORDER, h*TILE_SIZE + 2*BORDER);
+
+        ds->started = TRUE;
+        force = 1;
+    }
+
+    if (flashtime > 0 &&
+        (flashtime <= FLASH_TIME/3 ||
+         flashtime >= FLASH_TIME*2/3))
+        flashing = DS_FLASH;
+
+    memset(ds->draglines, 0, sz);
+    if (ui->ndragcoords > 0) {
+        int i, clearing = TRUE;
+        for (i = 0; i < ui->ndragcoords - 1; i++) {
+            int sx, sy, dx, dy, dir, oldstate, newstate;
+            interpret_ui_drag(state, ui, &clearing, i, &sx, &sy, &dx, &dy,
+                              &dir, &oldstate, &newstate);
+            ds->draglines[sy*w+sx] ^= (oldstate ^ newstate);
+            ds->draglines[dy*w+dx] ^= (F(oldstate) ^ F(newstate));
+        }
+    }  
+
+    for (x = 0; x < w; x++) {
+        for (y = 0; y < h; y++) {
+            unsigned int f = (unsigned int)state->lines[y*w+x];
+            unsigned int eline = (unsigned int)(state->errors[y*w+x] & (R|U|L|D));
+
+            f |= eline << DS_ESHIFT;
+            f |= ((unsigned int)ds->draglines[y*w+x]) << DS_DSHIFT;
+            f |= ((unsigned int)state->marks[y*w+x]) << DS_MSHIFT;
+
+            if (state->errors[y*w+x] & ERROR_CLUE)
+                f |= DS_ERROR_CLUE;
+
+            f |= flashing;
+
+           if (ui->cursor_active && x == ui->curx && y == ui->cury)
+               f |= DS_CURSOR;
+
+            if (f != ds->lflags[y*w+x] || force) {
+                ds->lflags[y*w+x] = f;
+                draw_square(dr, ds, ui, x, y, f, state->shared->clues[y*w+x]);
+            }
+        }
+    }
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return 0.0F;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    if (!oldstate->completed && newstate->completed &&
+        !oldstate->used_solve && !newstate->used_solve)
+        return FLASH_TIME;
+    else
+        return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    return state->completed ? +1 : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+    int pw, ph;
+
+    /*
+     * I'll use 6mm squares by default.
+     */
+    game_compute_size(params, 600, &pw, &ph);
+    *x = pw / 100.0F;
+    *y = ph / 100.0F;
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+    int w = state->shared->w, h = state->shared->h, x, y;
+    int black = print_mono_colour(dr, 0);
+    int white = print_mono_colour(dr, 1);
+
+    /* No GUI_LOOPY here: only use the familiar masyu style. */
+
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    game_drawstate *ds = game_new_drawstate(dr, state);
+    game_set_size(dr, ds, NULL, tilesize);
+
+    /* Draw grid outlines (black). */
+    for (x = 0; x <= w; x++)
+        draw_line(dr, COORD(x), COORD(0), COORD(x), COORD(h), black);
+    for (y = 0; y <= h; y++)
+        draw_line(dr, COORD(0), COORD(y), COORD(w), COORD(y), black);
+
+    for (x = 0; x < w; x++) {
+        for (y = 0; y < h; y++) {
+            int cx = COORD(x) + HALFSZ, cy = COORD(y) + HALFSZ;
+            int clue = state->shared->clues[y*w+x];
+
+            draw_lines_specific(dr, ds, x, y, state->lines[y*w+x], 0, black);
+
+            if (clue != NOCLUE) {
+                int c = (clue == CORNER) ? black : white;
+                draw_circle(dr, cx, cy, TILE_SIZE/4, c, black);
+            }
+        }
+    }
+
+    game_free_drawstate(dr, ds);
+}
+
+#ifdef COMBINED
+#define thegame pearl
+#endif
+
+const struct game thegame = {
+    "Pearl", "games.pearl", "pearl",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    TRUE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    TRUE, FALSE, game_print_size, game_print,
+    FALSE,                            /* wants_statusbar */
+    FALSE, game_timing_state,
+    0,                                /* flags */
+};
+
+#ifdef STANDALONE_SOLVER
+
+#include <time.h>
+#include <stdarg.h>
+
+const char *quis = NULL;
+
+static void usage(FILE *out) {
+    fprintf(out, "usage: %s <params>\n", quis);
+}
+
+static void pnum(int n, int ntot, const char *desc)
+{
+    printf("%2.1f%% (%d) %s", (double)n*100.0 / (double)ntot, n, desc);
+}
+
+static void start_soak(game_params *p, random_state *rs, int nsecs)
+{
+    time_t tt_start, tt_now, tt_last;
+    int n = 0, nsolved = 0, nimpossible = 0, ret;
+    char *grid, *clues;
+
+    tt_start = tt_last = time(NULL);
+
+    /* Currently this generates puzzles of any difficulty (trying to solve it
+     * on the maximum difficulty level and not checking it's not too easy). */
+    printf("Soak-testing a %dx%d grid (any difficulty)", p->w, p->h);
+    if (nsecs > 0) printf(" for %d seconds", nsecs);
+    printf(".\n");
+
+    p->nosolve = TRUE;
+
+    grid = snewn(p->w*p->h, char);
+    clues = snewn(p->w*p->h, char);
+
+    while (1) {
+        n += new_clues(p, rs, clues, grid); /* should be 1, with nosolve */
+
+        ret = pearl_solve(p->w, p->h, clues, grid, DIFF_TRICKY, FALSE);
+        if (ret <= 0) nimpossible++;
+        if (ret == 1) nsolved++;
+
+        tt_now = time(NULL);
+        if (tt_now > tt_last) {
+            tt_last = tt_now;
+
+            printf("%d total, %3.1f/s, ",
+                   n, (double)n / ((double)tt_now - tt_start));
+            pnum(nsolved, n, "solved"); printf(", ");
+            printf("%3.1f/s", (double)nsolved / ((double)tt_now - tt_start));
+            if (nimpossible > 0)
+                pnum(nimpossible, n, "impossible");
+            printf("\n");
+        }
+        if (nsecs > 0 && (tt_now - tt_start) > nsecs) {
+            printf("\n");
+            break;
+        }
+    }
+
+    sfree(grid);
+    sfree(clues);
+}
+
+int main(int argc, const char *argv[])
+{
+    game_params *p = NULL;
+    random_state *rs = NULL;
+    time_t seed = time(NULL);
+    char *id = NULL, *err;
+
+    setvbuf(stdout, NULL, _IONBF, 0);
+
+    quis = argv[0];
+
+    while (--argc > 0) {
+        char *p = (char*)(*++argv);
+        if (!strcmp(p, "-e") || !strcmp(p, "--seed")) {
+            seed = atoi(*++argv);
+            argc--;
+        } else if (*p == '-') {
+            fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p);
+            usage(stderr);
+            exit(1);
+        } else {
+            id = p;
+        }
+    }
+
+    rs = random_new((void*)&seed, sizeof(time_t));
+    p = default_params();
+
+    if (id) {
+        if (strchr(id, ':')) {
+            fprintf(stderr, "soak takes params only.\n");
+            goto done;
+        }
+
+        decode_params(p, id);
+        err = validate_params(p, 1);
+        if (err) {
+            fprintf(stderr, "%s: %s", argv[0], err);
+            goto done;
+        }
+
+        start_soak(p, rs, 0); /* run forever */
+    } else {
+        int i;
+
+        for (i = 5; i <= 12; i++) {
+            p->w = p->h = i;
+            start_soak(p, rs, 5);
+        }
+    }
+
+done:
+    free_params(p);
+    random_free(rs);
+
+    return 0;
+}
+
+#endif
+
+/* vim: set shiftwidth=4 tabstop=8: */
diff --git a/pegs.R b/pegs.R
new file mode 100644 (file)
index 0000000..1e79e99
--- /dev/null
+++ b/pegs.R
@@ -0,0 +1,21 @@
+# -*- makefile -*-
+
+PEGS_EXTRA = tree234
+
+pegs     : [X] GTK COMMON pegs PEGS_EXTRA pegs-icon|no-icon
+
+pegs     : [G] WINDOWS COMMON pegs PEGS_EXTRA pegs.res|noicon.res
+
+ALL += pegs[COMBINED] PEGS_EXTRA
+
+!begin am gtk
+GAMES += pegs
+!end
+
+!begin >list.c
+    A(pegs) \
+!end
+
+!begin >gamedesc.txt
+pegs:pegs.exe:Pegs:Peg solitaire puzzle:Jump pegs over each other to remove all but one.
+!end
diff --git a/pegs.c b/pegs.c
new file mode 100644 (file)
index 0000000..1902e16
--- /dev/null
+++ b/pegs.c
@@ -0,0 +1,1340 @@
+/*
+ * pegs.c: the classic Peg Solitaire game.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+#include "tree234.h"
+
+#define GRID_HOLE 0
+#define GRID_PEG  1
+#define GRID_OBST 2
+
+#define GRID_CURSOR 10
+#define GRID_JUMPING 20
+
+enum {
+    COL_BACKGROUND,
+    COL_HIGHLIGHT,
+    COL_LOWLIGHT,
+    COL_PEG,
+    COL_CURSOR,
+    NCOLOURS
+};
+
+/*
+ * Grid shapes. I do some macro ickery here to ensure that my enum
+ * and the various forms of my name list always match up.
+ */
+#define TYPELIST(A) \
+    A(CROSS,Cross,cross) \
+    A(OCTAGON,Octagon,octagon) \
+    A(RANDOM,Random,random)
+#define ENUM(upper,title,lower) TYPE_ ## upper,
+#define TITLE(upper,title,lower) #title,
+#define LOWER(upper,title,lower) #lower,
+#define CONFIG(upper,title,lower) ":" #title
+
+enum { TYPELIST(ENUM) TYPECOUNT };
+static char const *const pegs_titletypes[] = { TYPELIST(TITLE) };
+static char const *const pegs_lowertypes[] = { TYPELIST(LOWER) };
+#define TYPECONFIG TYPELIST(CONFIG)
+
+#define FLASH_FRAME 0.13F
+
+struct game_params {
+    int w, h;
+    int type;
+};
+
+struct game_state {
+    int w, h;
+    int completed;
+    unsigned char *grid;
+};
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = ret->h = 7;
+    ret->type = TYPE_CROSS;
+
+    return ret;
+}
+
+static const struct game_params pegs_presets[] = {
+    {7, 7, TYPE_CROSS},
+    {7, 7, TYPE_OCTAGON},
+    {5, 5, TYPE_RANDOM},
+    {7, 7, TYPE_RANDOM},
+    {9, 9, TYPE_RANDOM},
+};
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params *ret;
+    char str[80];
+
+    if (i < 0 || i >= lenof(pegs_presets))
+        return FALSE;
+
+    ret = snew(game_params);
+    *ret = pegs_presets[i];
+
+    strcpy(str, pegs_titletypes[ret->type]);
+    if (ret->type == TYPE_RANDOM)
+       sprintf(str + strlen(str), " %dx%d", ret->w, ret->h);
+
+    *name = dupstr(str);
+    *params = ret;
+    return TRUE;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+static void decode_params(game_params *params, char const *string)
+{
+    char const *p = string;
+    int i;
+
+    params->w = atoi(p);
+    while (*p && isdigit((unsigned char)*p)) p++;
+    if (*p == 'x') {
+        p++;
+        params->h = atoi(p);
+        while (*p && isdigit((unsigned char)*p)) p++;
+    } else {
+        params->h = params->w;
+    }
+
+    for (i = 0; i < lenof(pegs_lowertypes); i++)
+       if (!strcmp(p, pegs_lowertypes[i]))
+           params->type = i;
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char str[80];
+
+    sprintf(str, "%dx%d", params->w, params->h);
+    if (full) {
+       assert(params->type >= 0 && params->type < lenof(pegs_lowertypes));
+       strcat(str, pegs_lowertypes[params->type]);
+    }
+    return dupstr(str);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret = snewn(4, config_item);
+    char buf[80];
+
+    ret[0].name = "Width";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->w);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Height";
+    ret[1].type = C_STRING;
+    sprintf(buf, "%d", params->h);
+    ret[1].sval = dupstr(buf);
+    ret[1].ival = 0;
+
+    ret[2].name = "Board type";
+    ret[2].type = C_CHOICES;
+    ret[2].sval = TYPECONFIG;
+    ret[2].ival = params->type;
+
+    ret[3].name = NULL;
+    ret[3].type = C_END;
+    ret[3].sval = NULL;
+    ret[3].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = atoi(cfg[0].sval);
+    ret->h = atoi(cfg[1].sval);
+    ret->type = cfg[2].ival;
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    if (full && (params->w <= 3 || params->h <= 3))
+       return "Width and height must both be greater than three";
+
+    /*
+     * It might be possible to implement generalisations of Cross
+     * and Octagon, but only if I can find a proof that they're all
+     * soluble. For the moment, therefore, I'm going to disallow
+     * them at any size other than the standard one.
+     */
+    if (full && (params->type == TYPE_CROSS || params->type == TYPE_OCTAGON)) {
+       if (params->w != 7 || params->h != 7)
+           return "This board type is only supported at 7x7";
+    }
+    return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Beginning of code to generate random Peg Solitaire boards.
+ * 
+ * This procedure is done with no aesthetic judgment, no effort at
+ * symmetry, no difficulty grading and generally no finesse
+ * whatsoever. We simply begin with an empty board containing a
+ * single peg, and repeatedly make random reverse moves until it's
+ * plausibly full. This typically yields a scrappy haphazard mess
+ * with several holes, an uneven shape, and no redeeming features
+ * except guaranteed solubility.
+ *
+ * My only concessions to sophistication are (a) to repeat the
+ * generation process until I at least get a grid that touches
+ * every edge of the specified board size, and (b) to try when
+ * selecting moves to reuse existing space rather than expanding
+ * into new space (so that non-rectangular board shape becomes a
+ * factor during play).
+ */
+
+struct move {
+    /*
+     * x,y are the start point of the move during generation (hence
+     * its endpoint during normal play).
+     * 
+     * dx,dy are the direction of the move during generation.
+     * Absolute value 1. Hence, for example, x=3,y=5,dx=1,dy=0
+     * means that the move during generation starts at (3,5) and
+     * ends at (5,5), and vice versa during normal play.
+     */
+    int x, y, dx, dy;
+    /*
+     * cost is 0, 1 or 2, depending on how many GRID_OBSTs we must
+     * turn into GRID_HOLEs to play this move.
+     */
+    int cost;
+};
+
+static int movecmp(void *av, void *bv)
+{
+    struct move *a = (struct move *)av;
+    struct move *b = (struct move *)bv;
+
+    if (a->y < b->y)
+       return -1;
+    else if (a->y > b->y)
+       return +1;
+
+    if (a->x < b->x)
+       return -1;
+    else if (a->x > b->x)
+       return +1;
+
+    if (a->dy < b->dy)
+       return -1;
+    else if (a->dy > b->dy)
+       return +1;
+
+    if (a->dx < b->dx)
+       return -1;
+    else if (a->dx > b->dx)
+       return +1;
+
+    return 0;
+}
+
+static int movecmpcost(void *av, void *bv)
+{
+    struct move *a = (struct move *)av;
+    struct move *b = (struct move *)bv;
+
+    if (a->cost < b->cost)
+       return -1;
+    else if (a->cost > b->cost)
+       return +1;
+
+    return movecmp(av, bv);
+}
+
+struct movetrees {
+    tree234 *bymove, *bycost;
+};
+
+static void update_moves(unsigned char *grid, int w, int h, int x, int y,
+                        struct movetrees *trees)
+{
+    struct move move;
+    int dir, pos;
+
+    /*
+     * There are twelve moves that can include (x,y): three in each
+     * of four directions. Check each one to see if it's possible.
+     */
+    for (dir = 0; dir < 4; dir++) {
+       int dx, dy;
+
+       if (dir & 1)
+           dx = 0, dy = dir - 2;
+       else
+           dy = 0, dx = dir - 1;
+
+       assert(abs(dx) + abs(dy) == 1);
+
+       for (pos = 0; pos < 3; pos++) {
+           int v1, v2, v3;
+
+           move.dx = dx;
+           move.dy = dy;
+           move.x = x - pos*dx;
+           move.y = y - pos*dy;
+
+           if (move.x < 0 || move.x >= w || move.y < 0 || move.y >= h)
+               continue;              /* completely invalid move */
+           if (move.x+2*move.dx < 0 || move.x+2*move.dx >= w ||
+               move.y+2*move.dy < 0 || move.y+2*move.dy >= h)
+               continue;              /* completely invalid move */
+
+           v1 = grid[move.y * w + move.x];
+           v2 = grid[(move.y+move.dy) * w + (move.x+move.dx)];
+           v3 = grid[(move.y+2*move.dy)*w + (move.x+2*move.dx)];
+           if (v1 == GRID_PEG && v2 != GRID_PEG && v3 != GRID_PEG) {
+               struct move *m;
+
+               move.cost = (v2 == GRID_OBST) + (v3 == GRID_OBST);
+
+               /*
+                * This move is possible. See if it's already in
+                * the tree.
+                */
+               m = find234(trees->bymove, &move, NULL);
+               if (m && m->cost != move.cost) {
+                   /*
+                    * It's in the tree but listed with the wrong
+                    * cost. Remove the old version.
+                    */
+#ifdef GENERATION_DIAGNOSTICS
+                   printf("correcting %d%+d,%d%+d at cost %d\n",
+                          m->x, m->dx, m->y, m->dy, m->cost);
+#endif
+                   del234(trees->bymove, m);
+                   del234(trees->bycost, m);
+                   sfree(m);
+                   m = NULL;
+               }
+               if (!m) {
+                   struct move *m, *m2;
+                   m = snew(struct move);
+                   *m = move;
+                   m2 = add234(trees->bymove, m);
+                   m2 = add234(trees->bycost, m);
+                   assert(m2 == m);
+#ifdef GENERATION_DIAGNOSTICS
+                   printf("adding %d%+d,%d%+d at cost %d\n",
+                          move.x, move.dx, move.y, move.dy, move.cost);
+#endif
+               } else {
+#ifdef GENERATION_DIAGNOSTICS
+                   printf("not adding %d%+d,%d%+d at cost %d\n",
+                          move.x, move.dx, move.y, move.dy, move.cost);
+#endif
+               }
+           } else {
+               /*
+                * This move is impossible. If it is already in the
+                * tree, delete it.
+                * 
+                * (We make use here of the fact that del234
+                * doesn't have to be passed a pointer to the
+                * _actual_ element it's deleting: it merely needs
+                * one that compares equal to it, and it will
+                * return the one it deletes.)
+                */
+               struct move *m = del234(trees->bymove, &move);
+#ifdef GENERATION_DIAGNOSTICS
+               printf("%sdeleting %d%+d,%d%+d\n", m ? "" : "not ",
+                      move.x, move.dx, move.y, move.dy);
+#endif
+               if (m) {
+                   del234(trees->bycost, m);
+                   sfree(m);
+               }
+           }
+       }
+    }
+}
+
+static void pegs_genmoves(unsigned char *grid, int w, int h, random_state *rs)
+{
+    struct movetrees atrees, *trees = &atrees;
+    struct move *m;
+    int x, y, i, nmoves;
+
+    trees->bymove = newtree234(movecmp);
+    trees->bycost = newtree234(movecmpcost);
+
+    for (y = 0; y < h; y++)
+       for (x = 0; x < w; x++)
+           if (grid[y*w+x] == GRID_PEG)
+               update_moves(grid, w, h, x, y, trees);
+
+    nmoves = 0;
+
+    while (1) {
+       int limit, maxcost, index;
+       struct move mtmp, move, *m;
+
+       /*
+        * See how many moves we can make at zero cost. Make one,
+        * if possible. Failing that, make a one-cost move, and
+        * then a two-cost one.
+        * 
+        * After filling at least half the input grid, we no longer
+        * accept cost-2 moves: if that's our only option, we give
+        * up and finish.
+        */
+       mtmp.y = h+1;
+       maxcost = (nmoves < w*h/2 ? 2 : 1);
+       m = NULL;                      /* placate optimiser */
+       for (mtmp.cost = 0; mtmp.cost <= maxcost; mtmp.cost++) {
+           limit = -1;
+           m = findrelpos234(trees->bycost, &mtmp, NULL, REL234_LT, &limit);
+#ifdef GENERATION_DIAGNOSTICS
+           printf("%d moves available with cost %d\n", limit+1, mtmp.cost);
+#endif
+           if (m)
+               break;
+       }
+       if (!m)
+           break;
+
+       index = random_upto(rs, limit+1);
+       move = *(struct move *)index234(trees->bycost, index);
+
+#ifdef GENERATION_DIAGNOSTICS
+       printf("selecting move %d%+d,%d%+d at cost %d\n",
+              move.x, move.dx, move.y, move.dy, move.cost);
+#endif
+
+       grid[move.y * w + move.x] = GRID_HOLE;
+       grid[(move.y+move.dy) * w + (move.x+move.dx)] = GRID_PEG;
+       grid[(move.y+2*move.dy)*w + (move.x+2*move.dx)] = GRID_PEG;
+
+       for (i = 0; i <= 2; i++) {
+           int tx = move.x + i*move.dx;
+           int ty = move.y + i*move.dy;
+           update_moves(grid, w, h, tx, ty, trees);
+       }
+
+       nmoves++;
+    }
+
+    while ((m = delpos234(trees->bymove, 0)) != NULL) {
+       del234(trees->bycost, m);
+       sfree(m);
+    }
+    freetree234(trees->bymove);
+    freetree234(trees->bycost);
+}
+
+static void pegs_generate(unsigned char *grid, int w, int h, random_state *rs)
+{
+    while (1) {
+       int x, y, extremes;
+
+       memset(grid, GRID_OBST, w*h);
+       grid[(h/2) * w + (w/2)] = GRID_PEG;
+#ifdef GENERATION_DIAGNOSTICS
+       printf("beginning move selection\n");
+#endif
+       pegs_genmoves(grid, w, h, rs);
+#ifdef GENERATION_DIAGNOSTICS
+       printf("finished move selection\n");
+#endif
+
+       extremes = 0;
+       for (y = 0; y < h; y++) {
+           if (grid[y*w+0] != GRID_OBST)
+               extremes |= 1;
+           if (grid[y*w+w-1] != GRID_OBST)
+               extremes |= 2;
+       }
+       for (x = 0; x < w; x++) {
+           if (grid[0*w+x] != GRID_OBST)
+               extremes |= 4;
+           if (grid[(h-1)*w+x] != GRID_OBST)
+               extremes |= 8;
+       }
+
+       if (extremes == 15)
+           break;
+#ifdef GENERATION_DIAGNOSTICS
+       printf("insufficient extent; trying again\n");
+#endif
+    }
+#ifdef GENERATION_DIAGNOSTICS
+    fflush(stdout);
+#endif
+}
+
+/* ----------------------------------------------------------------------
+ * End of board generation code. Now for the client code which uses
+ * it as part of the puzzle.
+ */
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                          char **aux, int interactive)
+{
+    int w = params->w, h = params->h;
+    unsigned char *grid;
+    char *ret;
+    int i;
+
+    grid = snewn(w*h, unsigned char);
+    if (params->type == TYPE_RANDOM) {
+       pegs_generate(grid, w, h, rs);
+    } else {
+       int x, y, cx, cy, v;
+
+       for (y = 0; y < h; y++)
+           for (x = 0; x < w; x++) {
+               v = GRID_OBST;         /* placate optimiser */
+               switch (params->type) {
+                 case TYPE_CROSS:
+                   cx = abs(x - w/2);
+                   cy = abs(y - h/2);
+                   if (cx == 0 && cy == 0)
+                       v = GRID_HOLE;
+                   else if (cx > 1 && cy > 1)
+                       v = GRID_OBST;
+                   else
+                       v = GRID_PEG;
+                   break;
+                 case TYPE_OCTAGON:
+                   cx = abs(x - w/2);
+                   cy = abs(y - h/2);
+                   if (cx + cy > 1 + max(w,h)/2)
+                       v = GRID_OBST;
+                   else
+                       v = GRID_PEG;
+                   break;
+               }
+               grid[y*w+x] = v;
+           }
+
+       if (params->type == TYPE_OCTAGON) {
+           /*
+            * The octagonal (European) solitaire layout is
+            * actually _insoluble_ with the starting hole at the
+            * centre. Here's a proof:
+            * 
+            * Colour the squares of the board diagonally in
+            * stripes of three different colours, which I'll call
+            * A, B and C. So the board looks like this:
+            * 
+            *     A B C
+            *   A B C A B
+            * A B C A B C A
+            * B C A B C A B
+            * C A B C A B C
+            *   B C A B C
+            *     A B C
+            * 
+            * Suppose we keep running track of the number of pegs
+            * occuping each colour of square. This colouring has
+            * the property that any valid move whatsoever changes
+            * all three of those counts by one (two of them go
+            * down and one goes up), which means that the _parity_
+            * of every count flips on every move.
+            * 
+            * If the centre square starts off unoccupied, then
+            * there are twelve pegs on each colour and all three
+            * counts start off even; therefore, after 35 moves all
+            * three counts would have to be odd, which isn't
+            * possible if there's only one peg left. []
+            * 
+            * This proof works just as well if the starting hole
+            * is _any_ of the thirteen positions labelled B. Also,
+            * we can stripe the board in the opposite direction
+            * and rule out any square labelled B in that colouring
+            * as well. This leaves:
+            * 
+            *     Y n Y
+            *   n n Y n n
+            * Y n n Y n n Y
+            * n Y Y n Y Y n
+            * Y n n Y n n Y
+            *   n n Y n n
+            *     Y n Y
+            * 
+            * where the ns are squares we've proved insoluble, and
+            * the Ys are the ones remaining.
+            * 
+            * That doesn't prove all those starting positions to
+            * be soluble, of course; they're merely the ones we
+            * _haven't_ proved to be impossible. Nevertheless, it
+            * turns out that they are all soluble, so when the
+            * user requests an Octagon board the simplest thing is
+            * to pick one of these at random.
+            * 
+            * Rather than picking equiprobably from those twelve
+            * positions, we'll pick equiprobably from the three
+            * equivalence classes
+            */
+           switch (random_upto(rs, 3)) {
+             case 0:
+               /* Remove a random corner piece. */
+               {
+                   int dx, dy;
+
+                   dx = random_upto(rs, 2) * 2 - 1;   /* +1 or -1 */
+                   dy = random_upto(rs, 2) * 2 - 1;   /* +1 or -1 */
+                   if (random_upto(rs, 2))
+                       dy *= 3;
+                   else
+                       dx *= 3;
+                   grid[(3+dy)*w+(3+dx)] = GRID_HOLE;
+               }
+               break;
+             case 1:
+               /* Remove a random piece two from the centre. */
+               {
+                   int dx, dy;
+                   dx = 2 * (random_upto(rs, 2) * 2 - 1);
+                   if (random_upto(rs, 2))
+                       dy = 0;
+                   else
+                       dy = dx, dx = 0;
+                   grid[(3+dy)*w+(3+dx)] = GRID_HOLE;
+               }
+               break;
+             default /* case 2 */:
+               /* Remove a random piece one from the centre. */
+               {
+                   int dx, dy;
+                   dx = random_upto(rs, 2) * 2 - 1;
+                   if (random_upto(rs, 2))
+                       dy = 0;
+                   else
+                       dy = dx, dx = 0;
+                   grid[(3+dy)*w+(3+dx)] = GRID_HOLE;
+               }
+               break;
+           }
+       }
+    }
+
+    /*
+     * Encode a game description which is simply a long list of P
+     * for peg, H for hole or O for obstacle.
+     */
+    ret = snewn(w*h+1, char);
+    for (i = 0; i < w*h; i++)
+       ret[i] = (grid[i] == GRID_PEG ? 'P' :
+                 grid[i] == GRID_HOLE ? 'H' : 'O');
+    ret[w*h] = '\0';
+
+    sfree(grid);
+
+    return ret;
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    int len = params->w * params->h;
+
+    if (len != strlen(desc))
+       return "Game description is wrong length";
+    if (len != strspn(desc, "PHO"))
+       return "Invalid character in game description";
+
+    return NULL;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    int w = params->w, h = params->h;
+    game_state *state = snew(game_state);
+    int i;
+
+    state->w = w;
+    state->h = h;
+    state->completed = 0;
+    state->grid = snewn(w*h, unsigned char);
+    for (i = 0; i < w*h; i++)
+       state->grid[i] = (desc[i] == 'P' ? GRID_PEG :
+                         desc[i] == 'H' ? GRID_HOLE : GRID_OBST);
+
+    return state;
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    int w = state->w, h = state->h;
+    game_state *ret = snew(game_state);
+
+    ret->w = state->w;
+    ret->h = state->h;
+    ret->completed = state->completed;
+    ret->grid = snewn(w*h, unsigned char);
+    memcpy(ret->grid, state->grid, w*h);
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    sfree(state->grid);
+    sfree(state);
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    return NULL;
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    int w = state->w, h = state->h;
+    int x, y;
+    char *ret;
+
+    ret = snewn((w+1)*h + 1, char);
+
+    for (y = 0; y < h; y++) {
+       for (x = 0; x < w; x++)
+           ret[y*(w+1)+x] = (state->grid[y*w+x] == GRID_HOLE ? '-' :
+                             state->grid[y*w+x] == GRID_PEG ? '*' : ' ');
+       ret[y*(w+1)+w] = '\n';
+    }
+    ret[h*(w+1)] = '\0';
+
+    return ret;
+}
+
+struct game_ui {
+    int dragging;                     /* boolean: is a drag in progress? */
+    int sx, sy;                               /* grid coords of drag start cell */
+    int dx, dy;                               /* pixel coords of current drag posn */
+    int cur_x, cur_y, cur_visible, cur_jumping;
+};
+
+static game_ui *new_ui(const game_state *state)
+{
+    game_ui *ui = snew(game_ui);
+    int x, y, v;
+
+    ui->sx = ui->sy = ui->dx = ui->dy = 0;
+    ui->dragging = FALSE;
+    ui->cur_visible = ui->cur_jumping = 0;
+
+    /* make sure we start the cursor somewhere on the grid. */
+    for (x = 0; x < state->w; x++) {
+        for (y = 0; y < state->h; y++) {
+            v = state->grid[y*state->w+x];
+            if (v == GRID_PEG || v == GRID_HOLE) {
+                ui->cur_x = x; ui->cur_y = y;
+                goto found;
+            }
+        }
+    }
+    assert(!"new_ui found nowhere for cursor");
+found:
+
+    return ui;
+}
+
+static void free_ui(game_ui *ui)
+{
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+    /*
+     * Cancel a drag, in case the source square has become
+     * unoccupied.
+     */
+    ui->dragging = FALSE;
+}
+
+#define PREFERRED_TILE_SIZE 33
+#define TILESIZE (ds->tilesize)
+#define BORDER (TILESIZE / 2)
+
+#define HIGHLIGHT_WIDTH (TILESIZE / 16)
+
+#define COORD(x)     ( BORDER + (x) * TILESIZE )
+#define FROMCOORD(x) ( ((x) + TILESIZE - BORDER) / TILESIZE - 1 )
+
+struct game_drawstate {
+    int tilesize;
+    blitter *drag_background;
+    int dragging, dragx, dragy;
+    int w, h;
+    unsigned char *grid;
+    int started;
+    int bgcolour;
+};
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    int w = state->w, h = state->h;
+    char buf[80];
+
+    if (button == LEFT_BUTTON) {
+       int tx, ty;
+
+       /*
+        * Left button down: we attempt to start a drag.
+        */
+       
+       /*
+        * There certainly shouldn't be a current drag in progress,
+        * unless the midend failed to send us button events in
+        * order; it has a responsibility to always get that right,
+        * so we can legitimately punish it by failing an
+        * assertion.
+        */
+       assert(!ui->dragging);
+
+       tx = FROMCOORD(x);
+       ty = FROMCOORD(y);
+       if (tx >= 0 && tx < w && ty >= 0 && ty < h &&
+           state->grid[ty*w+tx] == GRID_PEG) {
+           ui->dragging = TRUE;
+           ui->sx = tx;
+           ui->sy = ty;
+           ui->dx = x;
+           ui->dy = y;
+            ui->cur_visible = ui->cur_jumping = 0;
+           return "";                 /* ui modified */
+       }
+    } else if (button == LEFT_DRAG && ui->dragging) {
+       /*
+        * Mouse moved; just move the peg being dragged.
+        */
+       ui->dx = x;
+       ui->dy = y;
+       return "";                     /* ui modified */
+    } else if (button == LEFT_RELEASE && ui->dragging) {
+       int tx, ty, dx, dy;
+
+       /*
+        * Button released. Identify the target square of the drag,
+        * see if it represents a valid move, and if so make it.
+        */
+       ui->dragging = FALSE;          /* cancel the drag no matter what */
+       tx = FROMCOORD(x);
+       ty = FROMCOORD(y);
+       if (tx < 0 || tx >= w || ty < 0 || ty >= h)
+           return "";                 /* target out of range */
+       dx = tx - ui->sx;
+       dy = ty - ui->sy;
+       if (max(abs(dx),abs(dy)) != 2 || min(abs(dx),abs(dy)) != 0)
+           return "";                 /* move length was wrong */
+       dx /= 2;
+       dy /= 2;
+
+       if (state->grid[ty*w+tx] != GRID_HOLE ||
+           state->grid[(ty-dy)*w+(tx-dx)] != GRID_PEG ||
+           state->grid[ui->sy*w+ui->sx] != GRID_PEG)
+           return "";                 /* grid contents were invalid */
+
+       /*
+        * We have a valid move. Encode it simply as source and
+        * destination coordinate pairs.
+        */
+       sprintf(buf, "%d,%d-%d,%d", ui->sx, ui->sy, tx, ty);
+       return dupstr(buf);
+    } else if (IS_CURSOR_MOVE(button)) {
+        if (!ui->cur_jumping) {
+            /* Not jumping; move cursor as usual, making sure we don't
+             * leave the gameboard (which may be an irregular shape) */
+            int cx = ui->cur_x, cy = ui->cur_y;
+            move_cursor(button, &cx, &cy, w, h, 0);
+            ui->cur_visible = 1;
+            if (state->grid[cy*w+cx] == GRID_HOLE ||
+                state->grid[cy*w+cx] == GRID_PEG) {
+                ui->cur_x = cx;
+                ui->cur_y = cy;
+            }
+            return "";
+        } else {
+            int dx, dy, mx, my, jx, jy;
+
+            /* We're jumping; if the requested direction has a hole, and
+             * there's a peg in the way, */
+            assert(state->grid[ui->cur_y*w+ui->cur_x] == GRID_PEG);
+            dx = (button == CURSOR_RIGHT) ? 1 : (button == CURSOR_LEFT) ? -1 : 0;
+            dy = (button == CURSOR_DOWN) ? 1 : (button == CURSOR_UP) ? -1 : 0;
+
+            mx = ui->cur_x+dx; my = ui->cur_y+dy;
+            jx = mx+dx; jy = my+dy;
+
+            ui->cur_jumping = 0; /* reset, whatever. */
+            if (jx >= 0 && jy >= 0 && jx < w && jy < h &&
+                state->grid[my*w+mx] == GRID_PEG &&
+                state->grid[jy*w+jx] == GRID_HOLE) {
+                /* Move cursor to the jumped-to location (this felt more
+                 * natural while playtesting) */
+                sprintf(buf, "%d,%d-%d,%d", ui->cur_x, ui->cur_y, jx, jy);
+                ui->cur_x = jx; ui->cur_y = jy;
+                return dupstr(buf);
+            }
+            return "";
+        }
+    } else if (IS_CURSOR_SELECT(button)) {
+        if (!ui->cur_visible) {
+            ui->cur_visible = 1;
+            return "";
+        }
+        if (ui->cur_jumping) {
+            ui->cur_jumping = 0;
+            return "";
+        }
+        if (state->grid[ui->cur_y*w+ui->cur_x] == GRID_PEG) {
+            /* cursor is on peg: next arrow-move wil jump. */
+            ui->cur_jumping = 1;
+            return "";
+        }
+        return NULL;
+    }
+
+    return NULL;
+}
+
+static game_state *execute_move(const game_state *state, const char *move)
+{
+    int w = state->w, h = state->h;
+    int sx, sy, tx, ty;
+    game_state *ret;
+
+    if (sscanf(move, "%d,%d-%d,%d", &sx, &sy, &tx, &ty) == 4) {
+       int mx, my, dx, dy;
+
+       if (sx < 0 || sx >= w || sy < 0 || sy >= h)
+           return NULL;               /* source out of range */
+       if (tx < 0 || tx >= w || ty < 0 || ty >= h)
+           return NULL;               /* target out of range */
+
+       dx = tx - sx;
+       dy = ty - sy;
+       if (max(abs(dx),abs(dy)) != 2 || min(abs(dx),abs(dy)) != 0)
+           return NULL;               /* move length was wrong */
+       mx = sx + dx/2;
+       my = sy + dy/2;
+
+       if (state->grid[sy*w+sx] != GRID_PEG ||
+           state->grid[my*w+mx] != GRID_PEG ||
+           state->grid[ty*w+tx] != GRID_HOLE)
+           return NULL;               /* grid contents were invalid */
+
+       ret = dup_game(state);
+       ret->grid[sy*w+sx] = GRID_HOLE;
+       ret->grid[my*w+mx] = GRID_HOLE;
+       ret->grid[ty*w+tx] = GRID_PEG;
+
+        /*
+         * Opinion varies on whether getting to a single peg counts as
+         * completing the game, or whether that peg has to be at a
+         * specific location (central in the classic cross game, for
+         * instance). For now we take the former, rather lax position.
+         */
+        if (!ret->completed) {
+            int count = 0, i;
+            for (i = 0; i < w*h; i++)
+                if (ret->grid[i] == GRID_PEG)
+                    count++;
+            if (count == 1)
+                ret->completed = 1;
+        }
+
+       return ret;
+    }
+    return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    struct { int tilesize; } ads, *ds = &ads;
+    ads.tilesize = tilesize;
+
+    *x = TILESIZE * params->w + 2 * BORDER;
+    *y = TILESIZE * params->h + 2 * BORDER;
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+
+    assert(TILESIZE > 0);
+
+    assert(!ds->drag_background);      /* set_size is never called twice */
+    ds->drag_background = blitter_new(dr, TILESIZE, TILESIZE);
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+
+    game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT);
+
+    ret[COL_PEG * 3 + 0] = 0.0F;
+    ret[COL_PEG * 3 + 1] = 0.0F;
+    ret[COL_PEG * 3 + 2] = 1.0F;
+
+    ret[COL_CURSOR * 3 + 0] = 0.5F;
+    ret[COL_CURSOR * 3 + 1] = 0.5F;
+    ret[COL_CURSOR * 3 + 2] = 1.0F;
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    int w = state->w, h = state->h;
+    struct game_drawstate *ds = snew(struct game_drawstate);
+
+    ds->tilesize = 0;                 /* not decided yet */
+
+    /* We can't allocate the blitter rectangle for the drag background
+     * until we know what size to make it. */
+    ds->drag_background = NULL;
+    ds->dragging = FALSE;
+
+    ds->w = w;
+    ds->h = h;
+    ds->grid = snewn(w*h, unsigned char);
+    memset(ds->grid, 255, w*h);
+
+    ds->started = FALSE;
+    ds->bgcolour = -1;
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    if (ds->drag_background)
+       blitter_free(dr, ds->drag_background);
+    sfree(ds->grid);
+    sfree(ds);
+}
+
+static void draw_tile(drawing *dr, game_drawstate *ds,
+                     int x, int y, int v, int bgcolour)
+{
+    int cursor = 0, jumping = 0, bg;
+
+    if (bgcolour >= 0) {
+       draw_rect(dr, x, y, TILESIZE, TILESIZE, bgcolour);
+    }
+    if (v >= GRID_JUMPING) {
+        jumping = 1; v -= GRID_JUMPING;
+    }
+    if (v >= GRID_CURSOR) {
+        cursor = 1; v -= GRID_CURSOR;
+    }
+
+    if (v == GRID_HOLE) {
+        bg = cursor ? COL_HIGHLIGHT : COL_LOWLIGHT;
+        assert(!jumping); /* can't jump from a hole! */
+       draw_circle(dr, x+TILESIZE/2, y+TILESIZE/2, TILESIZE/4,
+                    bg, bg);
+    } else if (v == GRID_PEG) {
+        bg = (cursor || jumping) ? COL_CURSOR : COL_PEG;
+       draw_circle(dr, x+TILESIZE/2, y+TILESIZE/2, TILESIZE/3,
+                   bg, bg);
+        bg = (!cursor || jumping) ? COL_PEG : COL_CURSOR;
+        draw_circle(dr, x+TILESIZE/2, y+TILESIZE/2, TILESIZE/4,
+                    bg, bg);
+    }
+
+    draw_update(dr, x, y, TILESIZE, TILESIZE);
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int w = state->w, h = state->h;
+    int x, y;
+    int bgcolour;
+
+    if (flashtime > 0) {
+        int frame = (int)(flashtime / FLASH_FRAME);
+        bgcolour = (frame % 2 ? COL_LOWLIGHT : COL_HIGHLIGHT);
+    } else
+        bgcolour = COL_BACKGROUND;
+
+    /*
+     * Erase the sprite currently being dragged, if any.
+     */
+    if (ds->dragging) {
+       assert(ds->drag_background);
+        blitter_load(dr, ds->drag_background, ds->dragx, ds->dragy);
+        draw_update(dr, ds->dragx, ds->dragy, TILESIZE, TILESIZE);
+       ds->dragging = FALSE;
+    }
+
+    if (!ds->started) {
+       draw_rect(dr, 0, 0,
+                 TILESIZE * state->w + 2 * BORDER,
+                 TILESIZE * state->h + 2 * BORDER, COL_BACKGROUND);
+
+       /*
+        * Draw relief marks around all the squares that aren't
+        * GRID_OBST.
+        */
+       for (y = 0; y < h; y++)
+           for (x = 0; x < w; x++)
+               if (state->grid[y*w+x] != GRID_OBST) {
+                   /*
+                    * First pass: draw the full relief square.
+                    */
+                   int coords[6];
+                   coords[0] = COORD(x+1) + HIGHLIGHT_WIDTH - 1;
+                   coords[1] = COORD(y) - HIGHLIGHT_WIDTH;
+                   coords[2] = COORD(x) - HIGHLIGHT_WIDTH;
+                   coords[3] = COORD(y+1) + HIGHLIGHT_WIDTH - 1;
+                   coords[4] = COORD(x) - HIGHLIGHT_WIDTH;
+                   coords[5] = COORD(y) - HIGHLIGHT_WIDTH;
+                   draw_polygon(dr, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT);
+                   coords[4] = COORD(x+1) + HIGHLIGHT_WIDTH - 1;
+                   coords[5] = COORD(y+1) + HIGHLIGHT_WIDTH - 1;
+                   draw_polygon(dr, coords, 3, COL_LOWLIGHT, COL_LOWLIGHT);
+               }
+       for (y = 0; y < h; y++)
+           for (x = 0; x < w; x++)
+               if (state->grid[y*w+x] != GRID_OBST) {
+                   /*
+                    * Second pass: draw everything but the two
+                    * diagonal corners.
+                    */
+                   draw_rect(dr, COORD(x) - HIGHLIGHT_WIDTH,
+                             COORD(y) - HIGHLIGHT_WIDTH,
+                             TILESIZE + HIGHLIGHT_WIDTH,
+                             TILESIZE + HIGHLIGHT_WIDTH, COL_HIGHLIGHT);
+                   draw_rect(dr, COORD(x),
+                             COORD(y),
+                             TILESIZE + HIGHLIGHT_WIDTH,
+                             TILESIZE + HIGHLIGHT_WIDTH, COL_LOWLIGHT);
+               }
+       for (y = 0; y < h; y++)
+           for (x = 0; x < w; x++)
+               if (state->grid[y*w+x] != GRID_OBST) {
+                   /*
+                    * Third pass: draw a trapezium on each edge.
+                    */
+                   int coords[8];
+                   int dx, dy, s, sn, c;
+
+                   for (dx = 0; dx < 2; dx++) {
+                       dy = 1 - dx;
+                       for (s = 0; s < 2; s++) {
+                           sn = 2*s - 1;
+                           c = s ? COL_LOWLIGHT : COL_HIGHLIGHT;
+
+                           coords[0] = COORD(x) + (s*dx)*(TILESIZE-1);
+                           coords[1] = COORD(y) + (s*dy)*(TILESIZE-1);
+                           coords[2] = COORD(x) + (s*dx+dy)*(TILESIZE-1);
+                           coords[3] = COORD(y) + (s*dy+dx)*(TILESIZE-1);
+                           coords[4] = coords[2] - HIGHLIGHT_WIDTH * (dy-sn*dx);
+                           coords[5] = coords[3] - HIGHLIGHT_WIDTH * (dx-sn*dy);
+                           coords[6] = coords[0] + HIGHLIGHT_WIDTH * (dy+sn*dx);
+                           coords[7] = coords[1] + HIGHLIGHT_WIDTH * (dx+sn*dy);
+                           draw_polygon(dr, coords, 4, c, c);
+                       }
+                   }
+               }
+       for (y = 0; y < h; y++)
+           for (x = 0; x < w; x++)
+               if (state->grid[y*w+x] != GRID_OBST) {
+                   /*
+                    * Second pass: draw everything but the two
+                    * diagonal corners.
+                    */
+                   draw_rect(dr, COORD(x),
+                             COORD(y),
+                             TILESIZE,
+                             TILESIZE, COL_BACKGROUND);
+               }
+
+       ds->started = TRUE;
+
+       draw_update(dr, 0, 0,
+                   TILESIZE * state->w + 2 * BORDER,
+                   TILESIZE * state->h + 2 * BORDER);
+    }
+
+    /*
+     * Loop over the grid redrawing anything that looks as if it
+     * needs it.
+     */
+    for (y = 0; y < h; y++)
+       for (x = 0; x < w; x++) {
+           int v;
+
+           v = state->grid[y*w+x];
+           /*
+            * Blank the source of a drag so it looks as if the
+            * user picked the peg up physically.
+            */
+           if (ui->dragging && ui->sx == x && ui->sy == y && v == GRID_PEG)
+               v = GRID_HOLE;
+
+            if (ui->cur_visible && ui->cur_x == x && ui->cur_y == y)
+                v += ui->cur_jumping ? GRID_JUMPING : GRID_CURSOR;
+
+           if (v != GRID_OBST &&
+                (bgcolour != ds->bgcolour || /* always redraw when flashing */
+                 v != ds->grid[y*w+x])) {
+               draw_tile(dr, ds, COORD(x), COORD(y), v, bgcolour);
+                ds->grid[y*w+x] = v;
+           }
+       }
+
+    /*
+     * Draw the dragging sprite if any.
+     */
+    if (ui->dragging) {
+       ds->dragging = TRUE;
+       ds->dragx = ui->dx - TILESIZE/2;
+       ds->dragy = ui->dy - TILESIZE/2;
+       blitter_save(dr, ds->drag_background, ds->dragx, ds->dragy);
+       draw_tile(dr, ds, ds->dragx, ds->dragy, GRID_PEG, -1);
+    }
+
+    ds->bgcolour = bgcolour;
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return 0.0F;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    if (!oldstate->completed && newstate->completed)
+        return 2 * FLASH_FRAME;
+    else
+        return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    /*
+     * Dead-end situations are assumed to be rescuable by Undo, so we
+     * don't bother to identify them and return -1.
+     */
+    return state->completed ? +1 : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+}
+
+#ifdef COMBINED
+#define thegame pegs
+#endif
+
+const struct game thegame = {
+    "Pegs", "games.pegs", "pegs",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    FALSE, solve_game,
+    TRUE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    FALSE, FALSE, game_print_size, game_print,
+    FALSE,                            /* wants_statusbar */
+    FALSE, game_timing_state,
+    0,                                /* flags */
+};
+
+/* vim: set shiftwidth=4 tabstop=8: */
diff --git a/penrose.c b/penrose.c
new file mode 100644 (file)
index 0000000..ccde30d
--- /dev/null
+++ b/penrose.c
@@ -0,0 +1,629 @@
+/* penrose.c
+ *
+ * Penrose tile generator.
+ *
+ * Uses half-tile technique outlined on:
+ *
+ * http://tartarus.org/simon/20110412-penrose/penrose.xhtml
+ */
+
+#include <assert.h>
+#include <string.h>
+#include <math.h>
+#include <stdio.h>
+
+#include "puzzles.h" /* for malloc routines, and PI */
+#include "penrose.h"
+
+/* -------------------------------------------------------
+ * 36-degree basis vector arithmetic routines.
+ */
+
+/* Imagine drawing a
+ * ten-point 'clock face' like this:
+ *
+ *                     -E
+ *                 -D   |   A
+ *                   \  |  /
+ *              -C.   \ | /   ,B
+ *                 `-._\|/_,-'
+ *                 ,-' /|\ `-.
+ *              -B'   / | \   `C
+ *                   /  |  \
+ *                 -A   |   D
+ *                      E
+ *
+ * In case the ASCII art isn't clear, those are supposed to be ten
+ * vectors of length 1, all sticking out from the origin at equal
+ * angular spacing (hence 36 degrees). Our basis vectors are A,B,C,D (I
+ * choose them to be symmetric about the x-axis so that the final
+ * translation into 2d coordinates will also be symmetric, which I
+ * think will avoid minor rounding uglinesses), so our vector
+ * representation sets
+ *
+ *   A = (1,0,0,0)
+ *   B = (0,1,0,0)
+ *   C = (0,0,1,0)
+ *   D = (0,0,0,1)
+ *
+ * The fifth vector E looks at first glance as if it needs to be
+ * another basis vector, but in fact it doesn't, because it can be
+ * represented in terms of the other four. Imagine starting from the
+ * origin and following the path -A, +B, -C, +D: you'll find you've
+ * traced four sides of a pentagram, and ended up one E-vector away
+ * from the origin. So we have
+ *
+ *   E = (-1,1,-1,1)
+ *
+ * This tells us that we can rotate any vector in this system by 36
+ * degrees: if we start with a*A + b*B + c*C + d*D, we want to end up
+ * with a*B + b*C + c*D + d*E, and we substitute our identity for E to
+ * turn that into a*B + b*C + c*D + d*(-A+B-C+D). In other words,
+ *
+ *   rotate_one_notch_clockwise(a,b,c,d) = (-d, d+a, -d+b, d+c)
+ *
+ * and you can verify for yourself that applying that operation
+ * repeatedly starting with (1,0,0,0) cycles round ten vectors and
+ * comes back to where it started.
+ *
+ * The other operation that may be required is to construct vectors
+ * with lengths that are multiples of phi. That can be done by
+ * observing that the vector C-B is parallel to E and has length 1/phi,
+ * and the vector D-A is parallel to E and has length phi. So this
+ * tells us that given any vector, we can construct one which points in
+ * the same direction and is 1/phi or phi times its length, like this:
+ *
+ *   divide_by_phi(vector) = rotate(vector, 2) - rotate(vector, 3)
+ *   multiply_by_phi(vector) = rotate(vector, 1) - rotate(vector, 4)
+ *
+ * where rotate(vector, n) means applying the above
+ * rotate_one_notch_clockwise primitive n times. Expanding out the
+ * applications of rotate gives the following direct representation in
+ * terms of the vector coordinates:
+ *
+ *   divide_by_phi(a,b,c,d) = (b-d, c+d-b, a+b-c, c-a)
+ *   multiply_by_phi(a,b,c,d) = (a+b-d, c+d, a+b, c+d-a)
+ *
+ * and you can verify for yourself that those two operations are
+ * inverses of each other (as you'd hope!).
+ *
+ * Having done all of this, testing for equality between two vectors is
+ * a trivial matter of comparing the four integer coordinates. (Which
+ * it _wouldn't_ have been if we'd kept E as a fifth basis vector,
+ * because then (-1,1,-1,1,0) and (0,0,0,0,1) would have had to be
+ * considered identical. So leaving E out is vital.)
+ */
+
+struct vector { int a, b, c, d; };
+
+static vector v_origin(void)
+{
+    vector v;
+    v.a = v.b = v.c = v.d = 0;
+    return v;
+}
+
+/* We start with a unit vector of B: this means we can easily
+ * draw an isoceles triangle centred on the X axis. */
+#ifdef TEST_VECTORS
+
+static vector v_unit(void)
+{
+    vector v;
+
+    v.b = 1;
+    v.a = v.c = v.d = 0;
+    return v;
+}
+
+#endif
+
+#define COS54 0.5877852
+#define SIN54 0.8090169
+#define COS18 0.9510565
+#define SIN18 0.3090169
+
+/* These two are a bit rough-and-ready for now. Note that B/C are
+ * 18 degrees from the x-axis, and A/D are 54 degrees. */
+double v_x(vector *vs, int i)
+{
+    return (vs[i].a + vs[i].d) * COS54 +
+           (vs[i].b + vs[i].c) * COS18;
+}
+
+double v_y(vector *vs, int i)
+{
+    return (vs[i].a - vs[i].d) * SIN54 +
+           (vs[i].b - vs[i].c) * SIN18;
+
+}
+
+static vector v_trans(vector v, vector trans)
+{
+    v.a += trans.a;
+    v.b += trans.b;
+    v.c += trans.c;
+    v.d += trans.d;
+    return v;
+}
+
+static vector v_rotate_36(vector v)
+{
+    vector vv;
+    vv.a = -v.d;
+    vv.b =  v.d + v.a;
+    vv.c = -v.d + v.b;
+    vv.d =  v.d + v.c;
+    return vv;
+}
+
+static vector v_rotate(vector v, int ang)
+{
+    int i;
+
+    assert((ang % 36) == 0);
+    while (ang < 0) ang += 360;
+    ang = 360-ang;
+    for (i = 0; i < (ang/36); i++)
+        v = v_rotate_36(v);
+    return v;
+}
+
+#ifdef TEST_VECTORS
+
+static vector v_scale(vector v, int sc)
+{
+    v.a *= sc;
+    v.b *= sc;
+    v.c *= sc;
+    v.d *= sc;
+    return v;
+}
+
+#endif
+
+static vector v_growphi(vector v)
+{
+    vector vv;
+    vv.a = v.a + v.b - v.d;
+    vv.b = v.c + v.d;
+    vv.c = v.a + v.b;
+    vv.d = v.c + v.d - v.a;
+    return vv;
+}
+
+static vector v_shrinkphi(vector v)
+{
+    vector vv;
+    vv.a = v.b - v.d;
+    vv.b = v.c + v.d - v.b;
+    vv.c = v.a + v.b - v.c;
+    vv.d = v.c - v.a;
+    return vv;
+}
+
+#ifdef TEST_VECTORS
+
+static const char *v_debug(vector v)
+{
+    static char buf[255];
+    sprintf(buf,
+             "(%d,%d,%d,%d)[%2.2f,%2.2f]",
+             v.a, v.b, v.c, v.d, v_x(&v,0), v_y(&v,0));
+    return buf;
+}
+
+#endif
+
+/* -------------------------------------------------------
+ * Tiling routines.
+ */
+
+static vector xform_coord(vector vo, int shrink, vector vtrans, int ang)
+{
+    if (shrink < 0)
+        vo = v_shrinkphi(vo);
+    else if (shrink > 0)
+        vo = v_growphi(vo);
+
+    vo = v_rotate(vo, ang);
+    vo = v_trans(vo, vtrans);
+
+    return vo;
+}
+
+
+#define XFORM(n,o,s,a) vs[(n)] = xform_coord(v_edge, (s), vs[(o)], (a))
+
+static int penrose_p2_small(penrose_state *state, int depth, int flip,
+                            vector v_orig, vector v_edge);
+
+static int penrose_p2_large(penrose_state *state, int depth, int flip,
+                            vector v_orig, vector v_edge)
+{
+    vector vv_orig, vv_edge;
+
+#ifdef DEBUG_PENROSE
+    {
+        vector vs[3];
+        vs[0] = v_orig;
+        XFORM(1, 0, 0, 0);
+        XFORM(2, 0, 0, -36*flip);
+
+        state->new_tile(state, vs, 3, depth);
+    }
+#endif
+
+    if (flip > 0) {
+        vector vs[4];
+
+        vs[0] = v_orig;
+        XFORM(1, 0, 0, -36);
+        XFORM(2, 0, 0, 0);
+        XFORM(3, 0, 0, 36);
+
+        state->new_tile(state, vs, 4, depth);
+    }
+    if (depth >= state->max_depth) return 0;
+
+    vv_orig = v_trans(v_orig, v_rotate(v_edge, -36*flip));
+    vv_edge = v_rotate(v_edge, 108*flip);
+
+    penrose_p2_small(state, depth+1, flip,
+                     v_orig, v_shrinkphi(v_edge));
+
+    penrose_p2_large(state, depth+1, flip,
+                     vv_orig, v_shrinkphi(vv_edge));
+    penrose_p2_large(state, depth+1, -flip,
+                     vv_orig, v_shrinkphi(vv_edge));
+
+    return 0;
+}
+
+static int penrose_p2_small(penrose_state *state, int depth, int flip,
+                            vector v_orig, vector v_edge)
+{
+    vector vv_orig;
+
+#ifdef DEBUG_PENROSE
+    {
+        vector vs[3];
+        vs[0] = v_orig;
+        XFORM(1, 0, 0, 0);
+        XFORM(2, 0, -1, -36*flip);
+
+        state->new_tile(state, vs, 3, depth);
+    }
+#endif
+
+    if (flip > 0) {
+        vector vs[4];
+
+        vs[0] = v_orig;
+        XFORM(1, 0, 0, -72);
+        XFORM(2, 0, -1, -36);
+        XFORM(3, 0, 0, 0);
+
+        state->new_tile(state, vs, 4, depth);
+    }
+
+    if (depth >= state->max_depth) return 0;
+
+    vv_orig = v_trans(v_orig, v_edge);
+
+    penrose_p2_large(state, depth+1, -flip,
+                     v_orig, v_shrinkphi(v_rotate(v_edge, -36*flip)));
+
+    penrose_p2_small(state, depth+1, flip,
+                     vv_orig, v_shrinkphi(v_rotate(v_edge, -144*flip)));
+
+    return 0;
+}
+
+static int penrose_p3_small(penrose_state *state, int depth, int flip,
+                            vector v_orig, vector v_edge);
+
+static int penrose_p3_large(penrose_state *state, int depth, int flip,
+                            vector v_orig, vector v_edge)
+{
+    vector vv_orig;
+
+#ifdef DEBUG_PENROSE
+    {
+        vector vs[3];
+        vs[0] = v_orig;
+        XFORM(1, 0, 1, 0);
+        XFORM(2, 0, 0, -36*flip);
+
+        state->new_tile(state, vs, 3, depth);
+    }
+#endif
+
+    if (flip > 0) {
+        vector vs[4];
+
+        vs[0] = v_orig;
+        XFORM(1, 0, 0, -36);
+        XFORM(2, 0, 1, 0);
+        XFORM(3, 0, 0, 36);
+
+        state->new_tile(state, vs, 4, depth);
+    }
+    if (depth >= state->max_depth) return 0;
+
+    vv_orig = v_trans(v_orig, v_edge);
+
+    penrose_p3_large(state, depth+1, -flip,
+                     vv_orig, v_shrinkphi(v_rotate(v_edge, 180)));
+
+    penrose_p3_small(state, depth+1, flip,
+                     vv_orig, v_shrinkphi(v_rotate(v_edge, -108*flip)));
+
+    vv_orig = v_trans(v_orig, v_growphi(v_edge));
+
+    penrose_p3_large(state, depth+1, flip,
+                     vv_orig, v_shrinkphi(v_rotate(v_edge, -144*flip)));
+
+
+    return 0;
+}
+
+static int penrose_p3_small(penrose_state *state, int depth, int flip,
+                            vector v_orig, vector v_edge)
+{
+    vector vv_orig;
+
+#ifdef DEBUG_PENROSE
+    {
+        vector vs[3];
+        vs[0] = v_orig;
+        XFORM(1, 0, 0, 0);
+        XFORM(2, 0, 0, -36*flip);
+
+        state->new_tile(state, vs, 3, depth);
+    }
+#endif
+
+    if (flip > 0) {
+        vector vs[4];
+
+        vs[0] = v_orig;
+        XFORM(1, 0, 0, -36);
+        XFORM(3, 0, 0, 0);
+        XFORM(2, 3, 0, -36);
+
+        state->new_tile(state, vs, 4, depth);
+    }
+    if (depth >= state->max_depth) return 0;
+
+    /* NB these two are identical to the first two of p3_large. */
+    vv_orig = v_trans(v_orig, v_edge);
+
+    penrose_p3_large(state, depth+1, -flip,
+                     vv_orig, v_shrinkphi(v_rotate(v_edge, 180)));
+
+    penrose_p3_small(state, depth+1, flip,
+                     vv_orig, v_shrinkphi(v_rotate(v_edge, -108*flip)));
+
+    return 0;
+}
+
+/* -------------------------------------------------------
+ * Utility routines.
+ */
+
+double penrose_side_length(double start_size, int depth)
+{
+  return start_size / pow(PHI, depth);
+}
+
+void penrose_count_tiles(int depth, int *nlarge, int *nsmall)
+{
+  /* Steal sgt's fibonacci thingummy. */
+}
+
+/*
+ * It turns out that an acute isosceles triangle with sides in ratio 1:phi:phi
+ * has an incentre which is conveniently 2*phi^-2 of the way from the apex to
+ * the base. Why's that convenient? Because: if we situate the incentre of the
+ * triangle at the origin, then we can place the apex at phi^-2 * (B+C), and
+ * the other two vertices at apex-B and apex-C respectively. So that's an acute
+ * triangle with its long sides of unit length, covering a circle about the
+ * origin of radius 1-(2*phi^-2), which is conveniently enough phi^-3.
+ *
+ * (later mail: this is an overestimate by about 5%)
+ */
+
+int penrose(penrose_state *state, int which, int angle)
+{
+    vector vo = v_origin();
+    vector vb = v_origin();
+
+    vo.b = vo.c = -state->start_size;
+    vo = v_shrinkphi(v_shrinkphi(vo));
+
+    vb.b = state->start_size;
+
+    vo = v_rotate(vo, angle);
+    vb = v_rotate(vb, angle);
+
+    if (which == PENROSE_P2)
+        return penrose_p2_large(state, 0, 1, vo, vb);
+    else
+        return penrose_p3_small(state, 0, 1, vo, vb);
+}
+
+/*
+ * We're asked for a MxN grid, which just means a tiling fitting into roughly
+ * an MxN space in some kind of reasonable unit - say, the side length of the
+ * two-arrow edges of the tiles. By some reasoning in a previous email, that
+ * means we want to pick some subarea of a circle of radius 3.11*sqrt(M^2+N^2).
+ * To cover that circle, we need to subdivide a triangle large enough that it
+ * contains a circle of that radius.
+ *
+ * Hence: start with those three vectors marking triangle vertices, scale them
+ * all up by phi repeatedly until the radius of the inscribed circle gets
+ * bigger than the target, and then recurse into that triangle with the same
+ * recursion depth as the number of times you scaled up. That will give you
+ * tiles of unit side length, covering a circle big enough that if you randomly
+ * choose an orientation and coordinates within the circle, you'll be able to
+ * get any valid piece of Penrose tiling of size MxN.
+ */
+#define INCIRCLE_RADIUS 0.22426 /* phi^-3 less 5%: see above */
+
+void penrose_calculate_size(int which, int tilesize, int w, int h,
+                            double *required_radius, int *start_size, int *depth)
+{
+    double rradius, size;
+    int n = 0;
+
+    /*
+     * Fudge factor to scale P2 and P3 tilings differently. This
+     * doesn't seem to have much relevance to questions like the
+     * average number of tiles per unit area; it's just aesthetic.
+     */
+    if (which == PENROSE_P2)
+        tilesize = tilesize * 3 / 2;
+    else
+        tilesize = tilesize * 5 / 4;
+
+    rradius = tilesize * 3.11 * sqrt((double)(w*w + h*h));
+    size = tilesize;
+
+    while ((size * INCIRCLE_RADIUS) < rradius) {
+        n++;
+        size = size * PHI;
+    }
+
+    *start_size = (int)size;
+    *depth = n;
+    *required_radius = rradius;
+}
+
+/* -------------------------------------------------------
+ * Test code.
+ */
+
+#ifdef TEST_PENROSE
+
+#include <stdio.h>
+#include <string.h>
+
+int show_recursion = 0;
+int ntiles, nfinal;
+
+int test_cb(penrose_state *state, vector *vs, int n, int depth)
+{
+    int i, xoff = 0, yoff = 0;
+    double l = penrose_side_length(state->start_size, depth);
+    double rball = l / 10.0;
+    const char *col;
+
+    ntiles++;
+    if (state->max_depth == depth) {
+        col = n == 4 ? "black" : "green";
+        nfinal++;
+    } else {
+        if (!show_recursion)
+            return 0;
+        col = n == 4 ? "red" : "blue";
+    }
+    if (n != 4) yoff = state->start_size;
+
+    printf("<polygon points=\"");
+    for (i = 0; i < n; i++) {
+        printf("%s%f,%f", (i == 0) ? "" : " ",
+               v_x(vs, i) + xoff, v_y(vs, i) + yoff);
+    }
+    printf("\" style=\"fill: %s; fill-opacity: 0.2; stroke: %s\" />\n", col, col);
+    printf("<ellipse cx=\"%f\" cy=\"%f\" rx=\"%f\" ry=\"%f\" fill=\"%s\" />",
+           v_x(vs, 0) + xoff, v_y(vs, 0) + yoff, rball, rball, col);
+
+    return 0;
+}
+
+void usage_exit(void)
+{
+    fprintf(stderr, "Usage: penrose-test [--recursion] P2|P3 SIZE DEPTH\n");
+    exit(1);
+}
+
+int main(int argc, char *argv[])
+{
+    penrose_state ps;
+    int which = 0;
+
+    while (--argc > 0) {
+        char *p = *++argv;
+        if (!strcmp(p, "-h") || !strcmp(p, "--help")) {
+            usage_exit();
+        } else if (!strcmp(p, "--recursion")) {
+            show_recursion = 1;
+        } else if (*p == '-') {
+            fprintf(stderr, "Unrecognised option '%s'\n", p);
+            exit(1);
+        } else {
+            break;
+        }
+    }
+
+    if (argc < 3) usage_exit();
+
+    if (strcmp(argv[0], "P2") == 0) which = PENROSE_P2;
+    else if (strcmp(argv[0], "P3") == 0) which = PENROSE_P3;
+    else usage_exit();
+
+    ps.start_size = atoi(argv[1]);
+    ps.max_depth = atoi(argv[2]);
+    ps.new_tile = test_cb;
+
+    ntiles = nfinal = 0;
+
+    printf("\
+<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n\
+<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 20010904//EN\"\n\
+\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n\
+\n\
+<svg xmlns=\"http://www.w3.org/2000/svg\"\n\
+xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n\n");
+
+    printf("<g>\n");
+    penrose(&ps, which);
+    printf("</g>\n");
+
+    printf("<!-- %d tiles and %d leaf tiles total -->\n",
+           ntiles, nfinal);
+
+    printf("</svg>");
+
+    return 0;
+}
+
+#endif
+
+#ifdef TEST_VECTORS
+
+static void dbgv(const char *msg, vector v)
+{
+    printf("%s: %s\n", msg, v_debug(v));
+}
+
+int main(int argc, const char *argv[])
+{
+   vector v = v_unit();
+
+   dbgv("unit vector", v);
+   v = v_rotate(v, 36);
+   dbgv("rotated 36", v);
+   v = v_scale(v, 2);
+   dbgv("scaled x2", v);
+   v = v_shrinkphi(v);
+   dbgv("shrunk phi", v);
+   v = v_rotate(v, -36);
+   dbgv("rotated -36", v);
+
+   return 0;
+}
+
+#endif
+/* vim: set shiftwidth=4 tabstop=8: */
diff --git a/penrose.h b/penrose.h
new file mode 100644 (file)
index 0000000..ba5ae16
--- /dev/null
+++ b/penrose.h
@@ -0,0 +1,59 @@
+/* penrose.h
+ *
+ * Penrose tiling functions.
+ *
+ * Provides an interface with which to generate Penrose tilings
+ * by recursive subdivision of an initial tile of choice (one of the
+ * four sets of two pairs kite/dart, or thin/thick rhombus).
+ *
+ * You supply a callback function and a context pointer, which is
+ * called with each tile in turn: you choose how many times to recurse.
+ */
+
+#ifndef _PENROSE_H
+#define _PENROSE_H
+
+#ifndef PHI
+#define PHI 1.6180339887
+#endif
+
+typedef struct vector vector;
+
+double v_x(vector *vs, int i);
+double v_y(vector *vs, int i);
+
+typedef struct penrose_state penrose_state;
+
+/* Return non-zero to clip the tree here (i.e. not recurse
+ * below this tile).
+ *
+ * Parameters are state, vector array, npoints, depth.
+ * ctx is inside state.
+ */
+typedef int (*tile_callback)(penrose_state *, vector *, int, int);
+
+struct penrose_state {
+    int start_size;  /* initial side length */
+    int max_depth;      /* Recursion depth */
+
+    tile_callback new_tile;
+    void *ctx;          /* for callback */
+};
+
+enum { PENROSE_P2, PENROSE_P3 };
+
+extern int penrose(penrose_state *state, int which, int angle);
+
+/* Returns the side-length of a penrose tile at recursion level
+ * gen, given a starting side length. */
+extern double penrose_side_length(double start_size, int depth);
+
+/* Returns the count of each type of tile at a given recursion depth. */
+extern void penrose_count_tiles(int gen, int *nlarge, int *nsmall);
+
+/* Calculate start size and recursion depth required to produce a
+ * width-by-height sized patch of penrose tiles with the given tilesize */
+extern void penrose_calculate_size(int which, int tilesize, int w, int h,
+                                   double *required_radius, int *start_size, int *depth);
+
+#endif
diff --git a/preprocessed.but b/preprocessed.but
new file mode 100644 (file)
index 0000000..0bc62ea
--- /dev/null
@@ -0,0 +1,3403 @@
+\title Simon Tatham's Portable Puzzle Collection
+
+\cfg{winhelp-filename}{puzzles.hlp}
+\cfg{winhelp-contents-titlepage}{Contents}
+
+\cfg{text-filename}{puzzles.txt}
+
+\cfg{html-contents-filename}{index.html}
+\cfg{html-template-filename}{%k.html}
+\cfg{html-index-filename}{docindex.html}
+\cfg{html-leaf-level}{1}
+\cfg{html-contents-depth-0}{1}
+\cfg{html-contents-depth-1}{2}
+\cfg{html-leaf-contains-contents}{true}
+
+\cfg{info-filename}{puzzles.info}
+
+\cfg{ps-filename}{puzzles.ps}
+\cfg{pdf-filename}{puzzles.pdf}
+
+\define{by} \u00D7{x}
+
+\define{dash} \u2013{-}
+
+\define{times} \u00D7{*}
+
+\define{divide} \u00F7{/}
+
+\define{minus} \u2212{-}
+
+This is a collection of small one-player puzzle games.
+
+\copyright This manual is copyright 2004-2014 Simon Tatham. All rights
+reserved. You may distribute this documentation under the MIT licence.
+See \k{licence} for the licence text in full.
+
+\cfg{html-local-head}{<meta name="AppleTitle" content="Puzzles Help">}
+
+\C{intro} Introduction
+
+I wrote this collection because I thought there should be more small
+desktop toys available: little games you can pop up in a window and
+play for two or three minutes while you take a break from whatever
+else you were doing. And I was also annoyed that every time I found
+a good game on (say) \i{Unix}, it wasn't available the next time I
+was sitting at a \i{Windows} machine, or vice versa; so I arranged
+that everything in my personal puzzle collection will happily run on
+both, and have more recently done a port to \i{Mac OS X} as well. When I
+find (or perhaps invent) further puzzle games that I like, they'll
+be added to this collection and will immediately be available on
+both platforms. And if anyone feels like writing any other front
+ends \dash PocketPC, Mac OS pre-10, or whatever it might be \dash
+then all the games in this framework will immediately become
+available on another platform as well.
+
+The actual games in this collection were mostly not my invention; they
+are re-implementations of existing game concepts within my portable
+puzzle framework. I do not claim credit, in general, for inventing the
+rules of any of these puzzles. (I don't even claim authorship of all
+the code; some of the puzzles have been submitted by other authors.)
+
+This collection is distributed under the \i{MIT licence} (see
+\k{licence}). This means that you can do pretty much anything you like
+with the game binaries or the code, except pretending you wrote them
+yourself, or suing me if anything goes wrong. 
+
+The most recent versions, and \i{source code}, can be found at
+\I{website}\W{http://www.chiark.greenend.org.uk/~sgtatham/puzzles/}\cw{http://www.chiark.greenend.org.uk/~sgtatham/puzzles/}.
+
+Please report \I{feedback}\i{bugs} to
+\W{mailto:anakin@pobox.com}\cw{anakin@pobox.com}.
+You might find it helpful to read this article before reporting a bug:
+
+\W{http://www.chiark.greenend.org.uk/~sgtatham/bugs.html}\cw{http://www.chiark.greenend.org.uk/~sgtatham/bugs.html}
+
+\ii{Patches} are welcome. Especially if they provide a new front end
+(to make all these games run on another platform), or a new game.
+
+
+\C{common} \ii{Common features}
+
+This chapter describes features that are common to all the games.
+
+\H{common-actions} \I{controls}Common actions
+
+These actions are all available from the \I{Game menu}\q{Game} menu
+and via \I{keys}keyboard shortcuts, in addition to any game-specific
+actions.
+
+(On \i{Mac OS X}, to conform with local user interface standards, these
+actions are situated on the \I{File menu}\q{File} and \I{Edit
+menu}\q{Edit} menus instead.)
+
+\dt \ii\e{New game} (\q{N}, Ctrl+\q{N})
+
+\dd Starts a new game, with a random initial state.
+
+\dt \ii\e{Restart game}
+
+\dd Resets the current game to its initial state. (This can be undone.)
+
+\dt \ii\e{Load}
+
+\dd Loads a saved game from a file on disk.
+
+\dt \ii\e{Save}
+
+\dd Saves the current state of your game to a file on disk.
+
+\lcont{
+
+The Load and Save operations preserve your entire game
+history (so you can save, reload, and still Undo and Redo things you
+had done before saving).
+
+}
+
+\dt \I{printing, on Windows}\e{Print}
+
+\dd Where supported (currently only on Windows), brings up a dialog
+allowing you to print an arbitrary number of puzzles randomly
+generated from the current parameters, optionally including the
+current puzzle. (Only for puzzles which make sense to print, of
+course \dash it's hard to think of a sensible printable representation
+of Fifteen!)
+
+\dt \ii\e{Undo} (\q{U}, Ctrl+\q{Z}, Ctrl+\q{_})
+
+\dd Undoes a single move. (You can undo moves back to the start of the
+session.)
+
+\dt \ii\e{Redo} (\q{R}, Ctrl+\q{R})
+
+\dd Redoes a previously undone move.
+
+\dt \ii\e{Copy}
+
+\dd Copies the current state of your game to the clipboard in text
+format, so that you can paste it into (say) an e-mail client or a
+web message board if you're discussing the game with someone else.
+(Not all games support this feature.)
+
+\dt \ii\e{Solve}
+
+\dd Transforms the puzzle instantly into its solved state. For some
+games (Cube) this feature is not supported at all because it is of
+no particular use. For other games (such as Pattern), the solved
+state can be used to give you information, if you can't see how a
+solution can exist at all or you want to know where you made a
+mistake. For still other games (such as Sixteen), automatic solution
+tells you nothing about how to \e{get} to the solution, but it does
+provide a useful way to get there quickly so that you can experiment
+with set-piece moves and transformations.
+
+\lcont{
+
+Some games (such as Solo) are capable of solving a game ID you have
+typed in from elsewhere. Other games (such as Rectangles) cannot
+solve a game ID they didn't invent themself, but when they did
+invent the game ID they know what the solution is already. Still
+other games (Pattern) can solve \e{some} external game IDs, but only
+if they aren't too difficult.
+
+The \q{Solve} command adds the solved state to the end of the undo
+chain for the puzzle. In other words, if you want to go back to
+solving it yourself after seeing the answer, you can just press Undo.
+
+}
+
+\dt \I{exit}\ii\e{Quit} (\q{Q}, Ctrl+\q{Q})
+
+\dd Closes the application entirely.
+
+\H{common-id} Specifying games with the \ii{game ID}
+
+There are two ways to save a game specification out of a puzzle and
+recreate it later, or recreate it in somebody else's copy of the
+same puzzle.
+
+The \q{\i{Specific}} and \q{\i{Random Seed}} options from the
+\I{Game menu}\q{Game} menu (or the \q{File} menu, on \i{Mac OS X}) each
+show a piece of text (a \q{game ID}) which is sufficient to
+reconstruct precisely the same game at a later date.
+
+You can enter either of these pieces of text back into the program
+(via the same \q{Specific} or \q{Random Seed} menu options) at a
+later point, and it will recreate the same game. You can also use
+either one as a \i{command line} argument (on Windows or Unix); see
+\k{common-cmdline} for more detail.
+
+The difference between the two forms is that a descriptive game ID
+is a literal \e{description} of the \i{initial state} of the game,
+whereas a random seed is just a piece of arbitrary text which was
+provided as input to the random number generator used to create the
+puzzle. This means that:
+
+\b Descriptive game IDs tend to be longer in many puzzles (although
+some, such as Cube (\k{cube}), only need very short descriptions).
+So a random seed is often a \e{quicker} way to note down the puzzle
+you're currently playing, or to tell it to somebody else so they can
+play the same one as you.
+
+\b Any text at all is a valid random seed. The automatically
+generated ones are fifteen-digit numbers, but anything will do; you
+can type in your full name, or a word you just made up, and a valid
+puzzle will be generated from it. This provides a way for two or
+more people to race to complete the same puzzle: you think of a
+random seed, then everybody types it in at the same time, and nobody
+has an advantage due to having seen the generated puzzle before
+anybody else.
+
+\b It is often possible to convert puzzles from other sources (such
+as \q{nonograms} or \q{sudoku} from newspapers) into descriptive
+game IDs suitable for use with these programs.
+
+\b Random seeds are not guaranteed to produce the same result if you
+use them with a different \i\e{version} of the puzzle program. This
+is because the generation algorithm might have been improved or
+modified in later versions of the code, and will therefore produce a
+different result when given the same sequence of random numbers. Use
+a descriptive game ID if you aren't sure that it will be used on the
+same version of the program as yours.
+
+\lcont{(Use the \q{About} menu option to find out the version number
+of the program. Programs with the same version number running on
+different platforms should still be random-seed compatible.)}
+
+\I{ID format}A descriptive game ID starts with a piece of text which
+encodes the \i\e{parameters} of the current game (such as grid
+size). Then there is a colon, and after that is the description of
+the game's initial state. A random seed starts with a similar string
+of parameters, but then it contains a hash sign followed by
+arbitrary data.
+
+If you enter a descriptive game ID, the program will not be able to
+show you the random seed which generated it, since it wasn't
+generated \e{from} a random seed. If you \e{enter} a random seed,
+however, the program will be able to show you the descriptive game
+ID derived from that random seed.
+
+Note that the game parameter strings are not always identical
+between the two forms. For some games, there will be parameter data
+provided with the random seed which is not included in the
+descriptive game ID. This is because that parameter information is
+only relevant when \e{generating} puzzle grids, and is not important
+when playing them. Thus, for example, the difficulty level in Solo
+(\k{solo}) is not mentioned in the descriptive game ID.
+
+These additional parameters are also not set permanently if you type
+in a game ID. For example, suppose you have Solo set to \q{Advanced}
+difficulty level, and then a friend wants your help with a
+\q{Trivial} puzzle; so the friend reads out a random seed specifying
+\q{Trivial} difficulty, and you type it in. The program will
+generate you the same \q{Trivial} grid which your friend was having
+trouble with, but once you have finished playing it, when you ask
+for a new game it will automatically go back to the \q{Advanced}
+difficulty which it was previously set on.
+
+\H{common-type} The \q{Type} menu
+
+The \I{Type menu}\q{Type} menu, if present, may contain a list of
+\i{preset} game settings. Selecting one of these will start a new
+random game with the parameters specified.
+
+The \q{Type} menu may also contain a \q{\i{Custom}} option which
+allows you to fine-tune game \i{parameters}. The parameters
+available are specific to each game and are described in the
+following sections.
+
+\H{common-cmdline} Specifying game parameters on the \i{command line}
+
+(This section does not apply to the \i{Mac OS X} version.)
+
+The games in this collection deliberately do not ever save
+information on to the computer they run on: they have no high score
+tables and no saved preferences. (This is because I expect at least
+some people to play them at work, and those people will probably
+appreciate leaving as little evidence as possible!)
+
+However, if you do want to arrange for one of these games to
+\I{default parameters, specifying}default to a particular set of
+parameters, you can specify them on the command line.
+
+The easiest way to do this is to set up the parameters you want
+using the \q{Type} menu (see \k{common-type}), and then to select
+\q{Random Seed} from the \q{Game} or \q{File} menu (see
+\k{common-id}). The text in the \q{Game ID} box will be composed of
+two parts, separated by a hash. The first of these parts represents
+the game parameters (the size of the playing area, for example, and
+anything else you set using the \q{Type} menu).
+
+If you run the game with just that parameter text on the command
+line, it will start up with the settings you specified.
+
+For example: if you run Cube (see \k{cube}), select \q{Octahedron}
+from the \q{Type} menu, and then go to the game ID selection, you
+will see a string of the form \cq{o2x2#338686542711620}. Take only
+the part before the hash (\cq{o2x2}), and start Cube with that text
+on the command line: \cq{cube o2x2}.
+
+If you copy the \e{entire} game ID on to the command line, the game
+will start up in the specific game that was described. This is
+occasionally a more convenient way to start a particular game ID
+than by pasting it into the game ID selection box.
+
+(You could also retrieve the encoded game parameters using the
+\q{Specific} menu option instead of \q{Random Seed}, but if you do
+then some options, such as the difficulty level in Solo, will be
+missing. See \k{common-id} for more details on this.)
+
+\H{common-unix-cmdline} \i{Unix} \i{command-line} options
+
+(This section only applies to the Unix port.)
+
+In addition to being able to specify game parameters on the command
+line (see \k{common-cmdline}), there are various other options:
+
+\dt \cw{--game}
+
+\dt \cw{--load}
+
+\dd These options respectively determine whether the command-line
+argument is treated as specifying game parameters or a \i{save} file
+to \i{load}. Only one should be specified. If neither of these options
+is specified, a guess is made based on the format of the argument.
+
+\dt \cw{--generate }\e{n}
+
+\dd If this option is specified, instead of a puzzle being displayed,
+a number of descriptive game IDs will be \I{generating game IDs}invented
+and printed on standard output. This is useful for gaining access to
+the game generation algorithms without necessarily using the frontend.
+
+\lcont{
+
+If game parameters are specified on the command-line, they will be
+used to generate the game IDs; otherwise a default set of parameters
+will be used.
+
+The most common use of this option is in conjunction with \c{--print},
+in which case its behaviour is slightly different; see below.
+
+}
+
+\dt \I{printing, on Unix}\cw{--print }\e{w}\cw{x}\e{h}
+
+\dd If this option is specified, instead of a puzzle being displayed,
+a printed representation of one or more unsolved puzzles is sent to
+standard output, in \i{PostScript} format.
+
+\lcont{
+
+On each page of puzzles, there will be \e{w} across and \e{h} down. If
+there are more puzzles than \e{w}\by\e{h}, more than one page will be
+printed.
+
+If \c{--generate} has also been specified, the invented game IDs will
+be used to generate the printed output. Otherwise, a list of game IDs
+is expected on standard input (which can be descriptive or random
+seeds; see \k{common-id}), in the same format produced by
+\c{--generate}.
+
+For example:
+
+\c net --generate 12 --print 2x3 7x7w | lpr
+
+will generate two pages of printed Net puzzles (each of which will
+have a 7\by\.7 wrapping grid), and pipe the output to the \c{lpr}
+command, which on many systems will send them to an actual printer.
+
+There are various other options which affect printing; see below.
+
+}
+
+\dt \cw{--save }\e{file-prefix} [ \cw{--save-suffix }\e{file-suffix} ]
+
+\dd If this option is specified, instead of a puzzle being
+displayed, saved-game files for one or more unsolved puzzles are
+written to files constructed from the supplied prefix and/or suffix.
+
+\lcont{
+
+If \c{--generate} has also been specified, the invented game IDs will
+be used to generate the printed output. Otherwise, a list of game IDs
+is expected on standard input (which can be descriptive or random
+seeds; see \k{common-id}), in the same format produced by
+\c{--generate}.
+
+For example:
+
+\c net --generate 12 --save game --save-suffix .sav
+
+will generate twelve Net saved-game files with the names
+\cw{game0.sav} to \cw{game11.sav}.
+
+}
+
+\dt \cw{--version}
+
+\dd Prints version information about the game, and then quits.
+
+The following options are only meaningful if \c{--print} is also
+specified:
+
+\dt \cw{--with-solutions}
+
+\dd The set of pages filled with unsolved puzzles will be followed by
+the solutions to those puzzles.
+
+\dt \cw{--scale }\e{n}
+
+\dd Adjusts how big each puzzle is when printed. Larger numbers make
+puzzles bigger; the default is 1.0.
+
+\dt \cw{--colour}
+
+\dd Puzzles will be printed in colour, rather than in black and white
+(if supported by the puzzle).
+
+
+\C{net} \i{Net}
+
+\cfg{winhelp-topic}{games.net}
+
+(\e{Note:} the \i{Windows} version of this game is called
+\i\cw{NETGAME.EXE} to avoid clashing with Windows's own \cw{NET.EXE}.)
+
+I originally saw this in the form of a Flash game called \i{FreeNet}
+\k{FreeNet}, written by Pavils Jurjans; there are several other
+implementations under the name \i{NetWalk}. The computer prepares a
+network by connecting up the centres of squares in a grid, and then
+shuffles the network by rotating every tile randomly. Your job is to
+rotate it all back into place. The successful solution will be an
+entirely connected network, with no closed loops. \#{The latter
+clause means that there are no closed paths within the network.
+Could this be clearer? "No closed paths"?} As a visual aid,
+all tiles which are connected to the one in the middle are
+highlighted. 
+
+\B{FreeNet} \W{http://www.jurjans.lv/stuff/net/FreeNet.htm}\cw{http://www.jurjans.lv/stuff/net/FreeNet.htm}
+
+\H{net-controls} \i{Net controls}
+
+\IM{Net controls} controls, for Net
+\IM{Net controls} keys, for Net
+\IM{Net controls} shortcuts (keyboard), for Net
+
+This game can be played with either the keyboard or the mouse. The
+controls are:
+
+\dt \e{Select tile}: mouse pointer, arrow keys
+
+\dt \e{Rotate tile anticlockwise}: left mouse button, \q{A} key
+
+\dt \e{Rotate tile clockwise}: right mouse button, \q{D} key
+
+\dt \e{Rotate tile by 180 degrees}: \q{F} key
+
+\dt \e{Lock (or unlock) tile}: middle mouse button, shift-click, \q{S} key
+
+\dd You can lock a tile once you're sure of its orientation. You can
+also unlock it again, but while it's locked you can't accidentally
+turn it.
+
+The following controls are not necessary to complete the game, but may
+be useful:
+
+\dt \e{Shift grid}: Shift + arrow keys
+
+\dd On grids that wrap, you can move the origin of the grid, so that
+tiles that were on opposite sides of the grid can be seen together.
+
+\dt \e{Move centre}: Ctrl + arrow keys
+
+\dd You can change which tile is used as the source of highlighting.
+(It doesn't ultimately matter which tile this is, as every tile will
+be connected to every other tile in a correct solution, but it may be
+helpful in the intermediate stages of solving the puzzle.)
+
+\dt \e{Jumble tiles}: \q{J} key
+
+\dd This key turns all tiles that are not locked to random
+orientations.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{net-params} \I{parameters, for Net}Net parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in tiles.
+
+\dt \e{Walls wrap around}
+
+\dd If checked, flow can pass from the left edge to the right edge,
+and from top to bottom, and vice versa.
+
+\dt \e{Barrier probability}
+
+\dd A number between 0.0 and 1.0 controlling whether an immovable
+barrier is placed between two tiles to prevent flow between them (a
+higher number gives more barriers). Since barriers are immovable, they
+act as constraints on the solution (i.e., hints).
+
+\lcont{
+
+The grid generation in Net has been carefully arranged so that the
+barriers are independent of the rest of the grid. This means that if
+you note down the random seed used to generate the current puzzle
+(see \k{common-id}), change the \e{Barrier probability} parameter,
+and then re-enter the same random seed, you should see exactly the
+same starting grid, with the only change being the number of
+barriers. So if you're stuck on a particular grid and need a hint,
+you could start up another instance of Net, set up the same
+parameters but a higher barrier probability, and enter the game seed
+from the original Net window.
+
+}
+
+\dt \e{Ensure unique solution}
+
+\dd Normally, Net will make sure that the puzzles it presents have
+only one solution. Puzzles with ambiguous sections can be more
+difficult and more subtle, so if you like you can turn off this
+feature and risk having ambiguous puzzles. (Also, finding \e{all}
+the possible solutions can be an additional challenge for an
+advanced player.)
+
+
+\C{cube} \i{Cube}
+
+\cfg{winhelp-topic}{games.cube}
+
+This is another one I originally saw as a web game. This one was a
+Java game \k{cube-java-game}, by Paul Scott. You have a grid of 16
+squares, six of which are blue; on one square rests a cube. Your move
+is to use the arrow keys to roll the cube through 90 degrees so that
+it moves to an adjacent square. If you roll the cube on to a blue
+square, the blue square is picked up on one face of the cube; if you
+roll a blue face of the cube on to a non-blue square, the blueness is
+put down again. (In general, whenever you roll the cube, the two faces
+that come into contact swap colours.) Your job is to get all six blue
+squares on to the six faces of the cube at the same time. Count your
+moves and try to do it in as few as possible. 
+
+Unlike the original Java game, my version has an additional feature:
+once you've mastered the game with a cube rolling on a square grid,
+you can change to a triangular grid and roll any of a tetrahedron, an
+octahedron or an icosahedron. 
+
+\B{cube-java-game} \W{http://www3.sympatico.ca/paulscott/cube/cube.htm}\cw{http://www3.sympatico.ca/paulscott/cube/cube.htm}
+
+\H{cube-controls} \i{Cube controls}
+
+\IM{Cube controls} controls, for Cube
+\IM{Cube controls} keys, for Cube
+\IM{Cube controls} shortcuts (keyboard), for Cube
+
+This game can be played with either the keyboard or the mouse.
+
+Left-clicking anywhere on the window will move the cube (or other
+solid) towards the mouse pointer.
+
+The arrow keys can also used to roll the cube on its square grid in
+the four cardinal directions.
+On the triangular grids, the mapping of arrow keys to directions is
+more approximate. Vertical movement is disallowed where it doesn't
+make sense. The four keys surrounding the arrow keys on the numeric
+keypad (\q{7}, \q{9}, \q{1}, \q{3}) can be used for diagonal movement.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{cube-params} \I{parameters, for Cube}Cube parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Type of solid}
+
+\dd Selects the solid to roll (and hence the shape of the grid):
+tetrahedron, cube, octahedron, or icosahedron.
+
+\dt \e{Width / top}, \e{Height / bottom}
+
+\dd On a square grid, horizontal and vertical dimensions. On a
+triangular grid, the number of triangles on the top and bottom rows
+respectively.
+
+
+\C{fifteen} \i{Fifteen}
+
+\cfg{winhelp-topic}{games.fifteen}
+
+The old ones are the best: this is the good old \q{\i{15-puzzle}}
+with sliding tiles. You have a 4\by\.4 square grid; 15 squares
+contain numbered tiles, and the sixteenth is empty. Your move is to
+choose a tile next to the empty space, and slide it into the space.
+The aim is to end up with the tiles in numerical order, with the
+space in the bottom right (so that the top row reads 1,2,3,4 and the
+bottom row reads 13,14,15,\e{space}).
+
+\H{fifteen-controls} \i{Fifteen controls}
+
+\IM{Fifteen controls} controls, for Fifteen
+\IM{Fifteen controls} keys, for Fifteen
+\IM{Fifteen controls} shortcuts (keyboard), for Fifteen
+
+This game can be controlled with the mouse or the keyboard.
+
+A left-click with the mouse in the row or column containing the empty
+space will move as many tiles as necessary to move the space to the
+mouse pointer.
+
+The arrow keys will move a tile adjacent to the space in the direction
+indicated (moving the space in the \e{opposite} direction).
+
+Pressing \q{h} will make a suggested move.  Pressing \q{h} enough
+times will solve the game, but it may scramble your progress while
+doing so.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{fifteen-params} \I{parameters, for Fifteen}Fifteen parameters
+
+The only options available from the \q{Custom...} option on the \q{Type}
+menu are \e{Width} and \e{Height}, which are self-explanatory. (Once
+you've changed these, it's not a \q{15-puzzle} any more, of course!)
+
+
+\C{sixteen} \i{Sixteen}
+
+\cfg{winhelp-topic}{games.sixteen}
+
+Another sliding tile puzzle, visually similar to Fifteen (see
+\k{fifteen}) but with a different type of move. This time, there is no
+hole: all 16 squares on the grid contain numbered squares. Your move
+is to shift an entire row left or right, or shift an entire column up
+or down; every time you do that, the tile you shift off the grid
+re-appears at the other end of the same row, in the space you just
+vacated. To win, arrange the tiles into numerical order (1,2,3,4 on
+the top row, 13,14,15,16 on the bottom). When you've done that, try
+playing on different sizes of grid. 
+
+I \e{might} have invented this game myself, though only by accident if
+so (and I'm sure other people have independently invented it). I
+thought I was imitating a screensaver I'd seen, but I have a feeling
+that the screensaver might actually have been a Fifteen-type puzzle
+rather than this slightly different kind. So this might be the one
+thing in my puzzle collection which represents creativity on my part
+rather than just engineering.
+
+\H{sixteen-controls} \I{controls, for Sixteen}Sixteen controls
+
+Left-clicking on an arrow will move the appropriate row or column in
+the direction indicated.  Right-clicking will move it in the opposite
+direction.
+
+Alternatively, use the cursor keys to move the position indicator
+around the edge of the grid, and use the return key to move the
+row/column in the direction indicated. 
+
+You can also move the tiles directly.  Move the cursor onto a tile,
+hold Control and press an arrow key to move the tile under the
+cursor and move the cursor along with the tile.  Or, hold Shift to
+move only the tile.  Pressing Enter simulates holding down Control
+(press Enter again to release), while pressing Space simulates
+holding down shift.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{sixteen-params} \I{parameters, for Sixteen}Sixteen parameters
+
+The parameters available from the \q{Custom...} option on the
+\q{Type} menu are:
+
+\b \e{Width} and \e{Height}, which are self-explanatory.
+
+\b You can ask for a limited shuffling operation to be performed on
+the grid. By default, Sixteen will shuffle the grid in such a way
+that any arrangement is about as probable as any other. You can
+override this by requesting a precise number of shuffling moves to
+be performed. Typically your aim is then to determine the precise
+set of shuffling moves and invert them exactly, so that you answer
+(say) a four-move shuffle with a four-move solution. Note that the
+more moves you ask for, the more likely it is that solutions shorter
+than the target length will turn out to be possible.
+
+
+\C{twiddle} \i{Twiddle}
+
+\cfg{winhelp-topic}{games.twiddle}
+
+Twiddle is a tile-rearrangement puzzle, visually similar to Sixteen
+(see \k{sixteen}): you are given a grid of square tiles, each
+containing a number, and your aim is to arrange the numbers into
+ascending order.
+
+In basic Twiddle, your move is to rotate a square group of four
+tiles about their common centre. (Orientation is not significant in
+the basic puzzle, although you can select it.) On more advanced
+settings, you can rotate a larger square group of tiles.
+
+I first saw this type of puzzle in the GameCube game \q{Metroid
+Prime 2}. In the Main Gyro Chamber in that game, there is a puzzle
+you solve to unlock a door, which is a special case of Twiddle. I
+developed this game as a generalisation of that puzzle.
+
+\H{twiddle-controls} \I{controls, for Twiddle}Twiddle controls
+
+To play Twiddle, click the mouse in the centre of the square group
+you wish to rotate. In the basic mode, you rotate a 2\by\.2 square,
+which means you have to click at a corner point where four tiles
+meet.
+
+In more advanced modes you might be rotating 3\by\.3 or even more at
+a time; if the size of the square is odd then you simply click in
+the centre tile of the square you want to rotate.
+
+Clicking with the left mouse button rotates the group anticlockwise.
+Clicking with the right button rotates it clockwise.
+
+You can also move an outline square around the grid with the cursor
+keys; the square is the size above (2\by\.2 by default, or larger).
+Pressing the return key or space bar will rotate the current square
+anticlockwise or clockwise respectively.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{twiddle-parameters} \I{parameters, for Twiddle}Twiddle parameters
+
+Twiddle provides several configuration options via the \q{Custom}
+option on the \q{Type} menu:
+
+\b You can configure the width and height of the puzzle grid.
+
+\b You can configure the size of square block that rotates at a time.
+
+\b You can ask for every square in the grid to be distinguishable
+(the default), or you can ask for a simplified puzzle in which there
+are groups of identical numbers. In the simplified puzzle your aim
+is just to arrange all the 1s into the first row, all the 2s into
+the second row, and so on.
+
+\b You can configure whether the orientation of tiles matters. If
+you ask for an orientable puzzle, each tile will have a triangle
+drawn in it. All the triangles must be pointing upwards to complete
+the puzzle.
+
+\b You can ask for a limited shuffling operation to be performed on
+the grid. By default, Twiddle will shuffle the grid so much that any
+arrangement is about as probable as any other. You can override this
+by requesting a precise number of shuffling moves to be performed.
+Typically your aim is then to determine the precise set of shuffling
+moves and invert them exactly, so that you answer (say) a four-move
+shuffle with a four-move solution. Note that the more moves you ask
+for, the more likely it is that solutions shorter than the target
+length will turn out to be possible.
+
+
+\C{rect} \i{Rectangles}
+
+\cfg{winhelp-topic}{games.rectangles}
+
+You have a grid of squares, with numbers written in some (but not all)
+of the squares. Your task is to subdivide the grid into rectangles of
+various sizes, such that (a) every rectangle contains exactly one
+numbered square, and (b) the area of each rectangle is equal to the
+number written in its numbered square.
+
+Credit for this game goes to the Japanese puzzle magazine \i{Nikoli}
+\k{nikoli-rect}; I've also seen a Palm implementation at \i{Puzzle
+Palace} \k{puzzle-palace-rect}. Unlike Puzzle Palace's
+implementation, my version automatically generates random grids of
+any size you like. The quality of puzzle design is therefore not
+quite as good as hand-crafted puzzles would be, but on the plus side
+you get an inexhaustible supply of puzzles tailored to your own
+specification.
+
+\B{nikoli-rect} \W{http://www.nikoli.co.jp/en/puzzles/shikaku.html}\cw{http://www.nikoli.co.jp/en/puzzles/shikaku.html}
+(beware of Flash)
+
+\B{puzzle-palace-rect} \W{https://web.archive.org/web/20041024001459/http://www.puzzle.gr.jp/puzzle/sikaku/palm/index.html.en}\cw{https://web.archive.org/web/20041024001459/http://www.puzzle.gr.jp/puzzle/sikaku/palm/index.html.en}
+
+\H{rectangles-controls} \I{controls, for Rectangles}Rectangles controls
+
+This game is played with the mouse or cursor keys.
+
+Left-click any edge to toggle it on or off, or left-click and drag to draw
+an entire rectangle (or line) on the grid in one go (removing any existing
+edges within that rectangle). Right-clicking and dragging will allow you
+to erase the contents of a rectangle without affecting its edges. 
+
+Alternatively, use the cursor keys to move the position indicator
+around the board. Pressing the return key then allows you to use the
+cursor keys to drag a rectangle out from that position, and pressing
+the return key again completes the rectangle. Using the space bar
+instead of the return key allows you to erase the contents of a
+rectangle without affecting its edges, as above. Pressing escape
+cancels a drag.
+
+When a rectangle of the correct size is completed, it will be shaded.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{rectangles-params} \I{parameters, for Rectangles}Rectangles parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid, in squares.
+
+\dt \e{Expansion factor}
+
+\dd This is a mechanism for changing the type of grids generated by
+the program. Some people prefer a grid containing a few large
+rectangles to one containing many small ones. So you can ask
+Rectangles to essentially generate a \e{smaller} grid than the size
+you specified, and then to expand it by adding rows and columns.
+
+\lcont{
+
+The default expansion factor of zero means that Rectangles will
+simply generate a grid of the size you ask for, and do nothing
+further. If you set an expansion factor of (say) 0.5, it means that
+each dimension of the grid will be expanded to half again as big
+after generation. In other words, the initial grid will be 2/3 the
+size in each dimension, and will be expanded to its full size
+without adding any more rectangles.
+
+Setting an expansion factor of around 0.5 tends to make the game
+more difficult, and also (in my experience) rewards a less deductive
+and more intuitive playing style. If you set it \e{too} high,
+though, the game simply cannot generate more than a few rectangles
+to cover the entire grid, and the game becomes trivial.
+
+}
+
+\dt \e{Ensure unique solution}
+
+\dd Normally, Rectangles will make sure that the puzzles it presents
+have only one solution. Puzzles with ambiguous sections can be more
+difficult and more subtle, so if you like you can turn off this
+feature and risk having ambiguous puzzles. Also, finding \e{all} the
+possible solutions can be an additional challenge for an advanced
+player. Turning off this option can also speed up puzzle generation.
+
+
+\C{netslide} \i{Netslide}
+
+\cfg{winhelp-topic}{games.netslide}
+
+This game combines the grid generation of Net (see \k{net}) with the
+movement of Sixteen (see \k{sixteen}): you have a Net grid, but
+instead of rotating tiles back into place you have to slide them
+into place by moving a whole row at a time. 
+
+As in Sixteen, \I{controls, for Netslide}control is with the mouse or
+cursor keys. See \k{sixteen-controls}.
+
+\I{parameters, for Netslide}The available game parameters have similar
+meanings to those in Net (see \k{net-params}) and Sixteen (see
+\k{sixteen-params}).
+
+Netslide was contributed to this collection by Richard Boulton.
+
+
+\C{pattern} \i{Pattern}
+
+\cfg{winhelp-topic}{games.pattern}
+
+You have a grid of squares, which must all be filled in either black
+or white. Beside each row of the grid are listed the lengths of the
+runs of black squares on that row; above each column are listed the
+lengths of the runs of black squares in that column. Your aim is to
+fill in the entire grid black or white.
+
+I first saw this puzzle form around 1995, under the name
+\q{\i{nonograms}}. I've seen it in various places since then, under
+different names.
+
+Normally, puzzles of this type turn out to be a meaningful picture
+of something once you've solved them. However, since this version
+generates the puzzles automatically, they will just look like random
+groupings of squares. (One user has suggested that this is actually
+a \e{good} thing, since it prevents you from guessing the colour of
+squares based on the picture, and forces you to use logic instead.)
+The advantage, though, is that you never run out of them.
+
+\H{pattern-controls} \I{controls, for Pattern}Pattern controls
+
+This game is played with the mouse.
+
+Left-click in a square to colour it black. Right-click to colour it
+white. If you make a mistake, you can middle-click, or hold down
+Shift while clicking with any button, to colour the square in the
+default grey (meaning \q{undecided}) again.
+
+You can click and drag with the left or right mouse button to colour
+a vertical or horizontal line of squares black or white at a time
+(respectively). If you click and drag with the middle button, or
+with Shift held down, you can colour a whole rectangle of squares
+grey.
+
+You can also move around the grid with the cursor keys. Pressing the
+return key will cycle the current cell through empty, then black, then
+white, then empty, and the space bar does the same cycle in reverse.
+
+Moving the cursor while holding Control will colour the moved-over
+squares black.  Holding Shift will colour the moved-over squares
+white, and holding both will colour them grey.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{pattern-parameters} \I{parameters, for Pattern}Pattern parameters
+
+The only options available from the \q{Custom...} option on the \q{Type}
+menu are \e{Width} and \e{Height}, which are self-explanatory.
+
+
+\C{solo} \i{Solo}
+
+\cfg{winhelp-topic}{games.solo}
+
+You have a square grid, which is divided into as many equally sized
+sub-blocks as the grid has rows. Each square must be filled in with
+a digit from 1 to the size of the grid, in such a way that
+
+\b every row contains only one occurrence of each digit
+
+\b every column contains only one occurrence of each digit
+
+\b every block contains only one occurrence of each digit.
+
+\b (optionally, by default off) each of the square's two main
+diagonals contains only one occurrence of each digit.
+
+You are given some of the numbers as clues; your aim is to place the
+rest of the numbers correctly.
+
+Under the default settings, the sub-blocks are square or
+rectangular. The default puzzle size is 3\by\.3 (a 9\by\.9 actual
+grid, divided into nine 3\by\.3 blocks). You can also select sizes
+with rectangular blocks instead of square ones, such as 2\by\.3 (a
+6\by\.6 grid divided into six 3\by\.2 blocks). Alternatively, you
+can select \q{jigsaw} mode, in which the sub-blocks are arbitrary
+shapes which differ between individual puzzles.
+
+Another available mode is \q{killer}. In this mode, clues are not
+given in the form of filled-in squares; instead, the grid is divided
+into \q{cages} by coloured lines, and for each cage the game tells
+you what the sum of all the digits in that cage should be. Also, no
+digit may appear more than once within a cage, even if the cage
+crosses the boundaries of existing regions.
+
+If you select a puzzle size which requires more than 9 digits, the
+additional digits will be letters of the alphabet. For example, if
+you select 3\by\.4 then the digits which go in your grid will be 1
+to 9, plus \cq{a}, \cq{b} and \cq{c}. This cannot be selected for
+killer puzzles.
+
+I first saw this puzzle in \i{Nikoli} \k{nikoli-solo}, although it's
+also been popularised by various newspapers under the name
+\q{Sudoku} or \q{Su Doku}.  Howard Garns is considered the inventor
+of the modern form of the puzzle, and it was first published in
+\e{Dell Pencil Puzzles and Word Games}.  A more elaborate treatment
+of the history of the puzzle can be found on Wikipedia
+\k{wikipedia-solo}.
+
+\B{nikoli-solo} \W{http://www.nikoli.co.jp/en/puzzles/sudoku.html}\cw{http://www.nikoli.co.jp/en/puzzles/sudoku.html}
+(beware of Flash)
+
+\B{wikipedia-solo} \W{http://en.wikipedia.org/wiki/Sudoku}\cw{http://en.wikipedia.org/wiki/Sudoku}
+
+\H{solo-controls} \I{controls, for Solo}Solo controls
+
+To play Solo, simply click the mouse in any empty square and then
+type a digit or letter on the keyboard to fill that square. If you
+make a mistake, click the mouse in the incorrect square and press
+Space to clear it again (or use the Undo feature).
+
+If you \e{right}-click in a square and then type a number, that
+number will be entered in the square as a \q{pencil mark}. You can
+have pencil marks for multiple numbers in the same square. Squares
+containing filled-in numbers cannot also contain pencil marks.
+
+The game pays no attention to pencil marks, so exactly what you use
+them for is up to you: you can use them as reminders that a
+particular square needs to be re-examined once you know more about a
+particular number, or you can use them as lists of the possible
+numbers in a given square, or anything else you feel like.
+
+To erase a single pencil mark, right-click in the square and type
+the same number again.
+
+All pencil marks in a square are erased when you left-click and type
+a number, or when you left-click and press space. Right-clicking and
+pressing space will also erase pencil marks.
+
+Alternatively, use the cursor keys to move the mark around the grid.
+Pressing the return key toggles the mark (from a normal mark to a
+pencil mark), and typing a number in is entered in the square in the
+appropriate way; typing in a 0 or using the space bar will clear a
+filled square. 
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{solo-parameters} \I{parameters, for Solo}Solo parameters
+
+Solo allows you to configure two separate dimensions of the puzzle
+grid on the \q{Type} menu: the number of columns, and the number of
+rows, into which the main grid is divided. (The size of a block is
+the inverse of this: for example, if you select 2 columns and 3 rows,
+each actual block will have 3 columns and 2 rows.)
+
+If you tick the \q{X} checkbox, Solo will apply the optional extra
+constraint that the two main diagonals of the grid also contain one
+of every digit. (This is sometimes known as \q{Sudoku-X} in
+newspapers.) In this mode, the squares on the two main diagonals
+will be shaded slightly so that you know it's enabled.
+
+If you tick the \q{Jigsaw} checkbox, Solo will generate randomly
+shaped sub-blocks. In this mode, the actual grid size will be taken
+to be the product of the numbers entered in the \q{Columns} and
+\q{Rows} boxes. There is no reason why you have to enter a number
+greater than 1 in both boxes; Jigsaw mode has no constraint on the
+grid size, and it can even be a prime number if you feel like it.
+
+If you tick the \q{Killer} checkbox, Solo will generate a set of
+of cages, which are randomly shaped and drawn in an outline of a
+different colour.  Each of these regions contains a smaller clue
+which shows the digit sum of all the squares in this region.
+
+You can also configure the type of symmetry shown in the generated
+puzzles. More symmetry makes the puzzles look prettier but may also
+make them easier, since the symmetry constraints can force more
+clues than necessary to be present. Completely asymmetric puzzles
+have the freedom to contain as few clues as possible.
+
+Finally, you can configure the difficulty of the generated puzzles.
+Difficulty levels are judged by the complexity of the techniques of
+deduction required to solve the puzzle: each level requires a mode
+of reasoning which was not necessary in the previous one. In
+particular, on difficulty levels \q{Trivial} and \q{Basic} there
+will be a square you can fill in with a single number at all times,
+whereas at \q{Intermediate} level and beyond you will have to make
+partial deductions about the \e{set} of squares a number could be in
+(or the set of numbers that could be in a square).
+\#{Advanced, Extreme?}
+At \q{Unreasonable} level, even this is not enough, and you will
+eventually have to make a guess, and then backtrack if it turns out
+to be wrong.
+
+Generating difficult puzzles is itself difficult: if you select one
+of the higher difficulty levels, Solo may have to make many attempts
+at generating a puzzle before it finds one hard enough for you. Be
+prepared to wait, especially if you have also configured a large
+puzzle size.
+
+
+\C{mines} \i{Mines}
+
+\cfg{winhelp-topic}{games.mines}
+
+You have a grid of covered squares, some of which contain mines, but
+you don't know which. Your job is to uncover every square which does
+\e{not} contain a mine. If you uncover a square containing a mine,
+you lose. If you uncover a square which does not contain a mine, you
+are told how many mines are contained within the eight surrounding
+squares.
+
+This game needs no introduction; popularised by Windows, it is
+perhaps the single best known desktop puzzle game in existence.
+
+This version of it has an unusual property. By default, it will
+generate its mine positions in such a way as to ensure that you
+never need to \e{guess} where a mine is: you will always be able to
+deduce it somehow. So you will never, as can happen in other
+versions, get to the last four squares and discover that there are
+two mines left but you have no way of knowing for sure where they
+are.
+
+\H{mines-controls} \I{controls, for Mines}Mines controls
+
+This game is played with the mouse.
+
+If you left-click in a covered square, it will be uncovered.
+
+If you right-click in a covered square, it will place a flag which
+indicates that the square is believed to be a mine. Left-clicking in
+a marked square will not uncover it, for safety. You can right-click
+again to remove a mark placed in error.
+
+If you left-click in an \e{uncovered} square, it will \q{clear
+around} the square. This means: if the square has exactly as many
+flags surrounding it as it should have mines, then all the covered
+squares next to it which are \e{not} flagged will be uncovered. So
+once you think you know the location of all the mines around a
+square, you can use this function as a shortcut to avoid having to
+click on each of the remaining squares one by one.
+
+If you uncover a square which has \e{no} mines in the surrounding
+eight squares, then it is obviously safe to uncover those squares in
+turn, and so on if any of them also has no surrounding mines. This
+will be done for you automatically; so sometimes when you uncover a
+square, a whole new area will open up to be explored.
+
+You can also use the cursor keys to move around the minefield.
+Pressing the return key in a covered square uncovers it, and in an
+uncovered square will clear around it (so it acts as the left button),
+pressing the space bar in a covered square will place a flag
+(similarly, it acts as the right button).
+
+All the actions described in \k{common-actions} are also available.
+
+Even Undo is available, although you might consider it cheating to
+use it. If you step on a mine, the program will only reveal the mine
+in question (unlike most other implementations, which reveal all of
+them). You can then Undo your fatal move and continue playing if you
+like. The program will track the number of times you died (and Undo
+will not reduce that counter), so when you get to the end of the
+game you know whether or not you did it without making any errors.
+
+(If you really want to know the full layout of the grid, which other
+implementations will show you after you die, you can always use the
+Solve menu option.)
+
+\H{mines-parameters} \I{parameters, for Mines}Mines parameters
+
+The options available from the \q{Custom...} option on the \q{Type}
+menu are:
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares.
+
+\dt \e{Mines}
+
+\dd Number of mines in the grid. You can enter this as an absolute
+mine count, or alternatively you can put a \cw{%} sign on the end in
+which case the game will arrange for that proportion of the squares
+in the grid to be mines.
+
+\lcont{
+
+Beware of setting the mine count too high. At very high densities,
+the program may spend forever searching for a solvable grid.
+
+}
+
+\dt \e{Ensure solubility}
+
+\dd When this option is enabled (as it is by default), Mines will
+ensure that the entire grid can be fully deduced starting from the
+initial open space. If you prefer the riskier grids generated by
+other implementations, you can switch off this option.
+
+
+\C{samegame} \i{Same Game}
+
+\cfg{winhelp-topic}{games.samegame}
+
+You have a grid of coloured squares, which you have to clear by 
+highlighting contiguous regions of more than one coloured square;
+the larger the region you highlight, the more points you get (and
+the faster you clear the arena).
+
+If you clear the grid you win. If you end up with nothing but 
+single squares (i.e., there are no more clickable regions left) you
+lose.
+
+Removing a region causes the rest of the grid to shuffle up:
+blocks that are suspended will fall down (first), and then empty
+columns are filled from the right. 
+
+Same Game was contributed to this collection by James Harvey.
+
+\H{samegame-controls} \i{Same Game controls}
+
+\IM{Same Game controls} controls, for Same Game
+\IM{Same Game controls} keys, for Same Game
+\IM{Same Game controls} shortcuts (keyboard), for Same Game
+
+This game can be played with either the keyboard or the mouse.
+
+If you left-click an unselected region, it becomes selected (possibly
+clearing the current selection). 
+
+If you left-click the selected region, it will be removed (and the
+rest of the grid shuffled immediately).
+
+If you right-click the selected region, it will be unselected. 
+
+The cursor keys move a cursor around the grid. Pressing the Space or
+Enter keys while the cursor is in an unselected region selects it;
+pressing Space or Enter again removes it as above.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{samegame-parameters} \I{parameters, for Same Game}Same Game parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares.
+
+\dt \e{No. of colours}
+
+\dd Number of different colours used to fill the grid; the more colours,
+the fewer large regions of colour and thus the more difficult it is to
+successfully clear the grid.
+
+\dt \e{Scoring system}
+
+\dd Controls the precise mechanism used for scoring. With the default
+system, \q{(n-2)^2}, only regions of three squares or more will score
+any points at all. With the alternative \q{(n-1)^2} system, regions of
+two squares score a point each, and larger regions score relatively
+more points.
+
+\dt \e{Ensure solubility}
+
+\dd If this option is ticked (the default state), generated grids
+will be guaranteed to have at least one solution.
+
+\lcont{
+
+If you turn it off, the game generator will not try to guarantee
+soluble grids; it will, however, still ensure that there are at
+least 2 squares of each colour on the grid at the start (since a
+grid with exactly one square of a given colour is \e{definitely}
+insoluble). Grids generated with this option disabled may contain
+more large areas of contiguous colour, leading to opportunities for
+higher scores; they can also take less time to generate.
+
+}
+
+
+\C{flip} \i{Flip}
+
+\cfg{winhelp-topic}{games.flip}
+
+You have a grid of squares, some light and some dark. Your aim is to
+light all the squares up at the same time. You can choose any square
+and flip its state from light to dark or dark to light, but when you
+do so, other squares around it change state as well.
+
+Each square contains a small diagram showing which other squares
+change when you flip it.
+
+\H{flip-controls} \i{Flip controls}
+
+\IM{Flip controls} controls, for Flip
+\IM{Flip controls} keys, for Flip
+\IM{Flip controls} shortcuts (keyboard), for Flip
+
+This game can be played with either the keyboard or the mouse.
+
+Left-click in a square to flip it and its associated squares, or
+use the cursor keys to choose a square and the space bar or Enter
+key to flip.
+
+If you use the \q{Solve} function on this game, it will mark some of
+the squares in red. If you click once in every square with a red
+mark, the game should be solved. (If you click in a square
+\e{without} a red mark, a red mark will appear in it to indicate
+that you will need to reverse that operation to reach the solution.)
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{flip-parameters} \I{parameters, for flip}Flip parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares.
+
+\dt \e{Shape type}
+
+\dd This control determines the shape of the region which is flipped
+by clicking in any given square. The default setting, \q{Crosses},
+causes every square to flip itself and its four immediate neighbours
+(or three or two if it's at an edge or corner). The other setting,
+\q{Random}, causes a random shape to be chosen for every square, so
+the game is different every time.
+
+
+\C{guess} \i{Guess}
+
+\cfg{winhelp-topic}{games.guess}
+
+You have a set of coloured pegs, and have to reproduce a
+predetermined sequence of them (chosen by the computer) within a
+certain number of guesses. 
+
+Each guess gets marked with the number of correctly-coloured pegs
+in the correct places (in black), and also the number of
+correctly-coloured pegs in the wrong places (in white). 
+
+This game is also known (and marketed, by Hasbro, mainly) as
+a board game \q{\i{Mastermind}}, with 6 colours, 4 pegs per row,
+and 10 guesses. However, this version allows custom settings of number
+of colours (up to 10), number of pegs per row, and number of guesses. 
+
+Guess was contributed to this collection by James Harvey.
+
+\H{guess-controls} \i{Guess controls}
+
+\IM{Guess controls} controls, for Guess
+\IM{Guess controls} keys, for Guess
+\IM{Guess controls} shortcuts (keyboard), for Guess
+
+This game can be played with either the keyboard or the mouse.
+
+With the mouse, drag a coloured peg from the tray on the left-hand
+side to its required position in the current guess; pegs may also be
+dragged from current and past guesses to copy them elsewhere. To
+remove a peg, drag it off its current position to somewhere invalid.
+
+Right-clicking in the current guess adds a \q{hold} marker; pegs
+that have hold markers will be automatically added to the next guess
+after marking.
+
+Alternatively, with the keyboard, the up and down cursor keys can be
+used to select a peg colour, the left and right keys to select a
+peg position, and the space bar or Enter key to place a peg of the
+selected colour in the chosen position. \q{D} or Backspace removes a
+peg, and Space adds a hold marker.
+
+Pressing \q{h} or \q{?} will fill the current guess with a suggested
+guess.  Using this is not recommended for 10 or more pegs as it is
+slow.
+
+When the guess is complete, the smaller feedback pegs will be highlighted;
+clicking on these (or moving the peg cursor to them with the arrow keys
+and pressing the space bar or Enter key) will mark the current guess,
+copy any held pegs to the next guess, and move the \q{current guess}
+marker.
+
+If you correctly position all the pegs the solution will be displayed
+below; if you run out of guesses (or select \q{Solve...}) the solution
+will also be revealed.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{guess-parameters} \I{parameters, for Guess}Guess parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu. The default game matches the parameters for the 
+board game \q{Mastermind}. 
+
+\dt \e{Colours}
+
+\dd Number of colours the solution is chosen from; from 2 to 10
+(more is harder).
+
+\dt \e{Pegs per guess}
+
+\dd Number of pegs per guess (more is harder).
+
+\dt \e{Guesses}
+
+\dd Number of guesses you have to find the solution in (fewer is harder).
+
+\dt \e{Allow blanks}
+
+\dd Allows blank pegs to be given as part of a guess (makes it easier, because
+you know that those will never be counted as part of the solution). This
+is turned off by default. 
+
+\lcont{
+
+Note that this doesn't allow blank pegs in the solution; if you really wanted
+that, use one extra colour.
+
+}
+
+\dt \e{Allow duplicates}
+
+\dd Allows the solution (and the guesses) to contain colours more than once;
+this increases the search space (making things harder), and is turned on by
+default.
+
+
+\C{pegs} \i{Pegs}
+
+\cfg{winhelp-topic}{games.pegs}
+
+A number of pegs are placed in holes on a board. You can remove a
+peg by jumping an adjacent peg over it (horizontally or vertically)
+to a vacant hole on the other side. Your aim is to remove all but one
+of the pegs initially present.
+
+This game, best known as \I{Solitaire, Peg}\q{Peg Solitaire}, is
+possibly one of the oldest puzzle games still commonly known.
+
+\H{pegs-controls} \i{Pegs controls}
+
+\IM{Pegs controls} controls, for Pegs
+
+To move a peg, drag it with the mouse from its current position to
+its final position. If the final position is exactly two holes away
+from the initial position, is currently unoccupied by a peg, and
+there is a peg in the intervening square, the move will be permitted
+and the intervening peg will be removed.
+
+Vacant spaces which you can move a peg into are marked with holes. A
+space with no peg and no hole is not available for moving at all: it
+is an obstacle which you must work around.
+
+You can also use the cursor keys to move a position indicator around
+the board. Pressing the return key while over a peg, followed by a
+cursor key, will jump the peg in that direction (if that is a legal
+move).
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{pegs-parameters} \I{parameters, for Pegs}Pegs parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in holes.
+
+\dt \e{Board type}
+
+\dd Controls whether you are given a board of a standard shape or a
+randomly generated shape. The two standard shapes currently
+supported are \q{Cross} and \q{Octagon} (also commonly known as the
+English and European traditional board layouts respectively).
+Selecting \q{Random} will give you a different board shape every
+time (but always one that is known to have a solution).
+
+
+\C{dominosa} \i{Dominosa}
+
+\cfg{winhelp-topic}{games.dominosa}
+
+A normal set of dominoes \dash that is, one instance of every
+(unordered) pair of numbers from 0 to 6 \dash has been arranged
+irregularly into a rectangle; then the number in each square has
+been written down and the dominoes themselves removed. Your task is
+to reconstruct the pattern by arranging the set of dominoes to match
+the provided array of numbers.
+
+This puzzle is widely credited to O. S. Adler, and takes part of its
+name from those initials.
+
+\H{dominosa-controls} \i{Dominosa controls}
+
+\IM{Dominosa controls} controls, for Dominosa
+
+Left-clicking between any two adjacent numbers places a domino
+covering them, or removes one if it is already present. Trying to
+place a domino which overlaps existing dominoes will remove the ones
+it overlaps.
+
+Right-clicking between two adjacent numbers draws a line between
+them, which you can use to remind yourself that you know those two
+numbers are \e{not} covered by a single domino. Right-clicking again
+removes the line.
+
+You can also use the cursor keys to move a cursor around the grid.
+When the cursor is half way between two adjacent numbers, pressing
+the return key will place a domino covering those numbers, or
+pressing the space bar will lay a line between the two squares.
+Repeating either action removes the domino or line.
+
+Pressing a number key will highlight all occurrences of that
+number. Pressing that number again will clear the highlighting. Up to two
+different numbers can be highlighted at any given time.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{dominosa-parameters} \I{parameters, for Dominosa}Dominosa parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Maximum number on dominoes}
+
+\dd Controls the size of the puzzle, by controlling the size of the
+set of dominoes used to make it. Dominoes with numbers going up to N
+will give rise to an (N+2) \by (N+1) rectangle; so, in particular,
+the default value of 6 gives an 8\by\.7 grid.
+
+\dt \e{Ensure unique solution}
+
+\dd Normally, Dominosa will make sure that the puzzles it presents
+have only one solution. Puzzles with ambiguous sections can be more
+difficult and sometimes more subtle, so if you like you can turn off
+this feature. Also, finding \e{all} the possible solutions can be an
+additional challenge for an advanced player. Turning off this option
+can also speed up puzzle generation.
+
+
+\C{untangle} \i{Untangle}
+
+\cfg{winhelp-topic}{games.untangle}
+
+You are given a number of points, some of which have lines drawn
+between them. You can move the points about arbitrarily; your aim is
+to position the points so that no line crosses another.
+
+I originally saw this in the form of a Flash game called \i{Planarity}
+\k{Planarity}, written by John Tantalo.
+
+\B{Planarity} \W{http://planarity.net}\cw{http://planarity.net}
+
+\H{untangle-controls} \i{Untangle controls}
+
+\IM{Untangle controls} controls, for Untangle
+
+To move a point, click on it with the left mouse button and drag it
+into a new position.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{untangle-parameters} \I{parameters, for Untangle}Untangle parameters
+
+There is only one parameter available from the \q{Custom...} option
+on the \q{Type} menu:
+
+\dt \e{Number of points}
+
+\dd Controls the size of the puzzle, by specifying the number of
+points in the generated graph.
+
+
+\C{blackbox} \i{Black Box}
+
+\cfg{winhelp-topic}{games.blackbox}
+
+A number of balls are hidden in a rectangular arena. You have to
+deduce the positions of the balls by firing lasers positioned at
+the edges of the arena and observing how their beams are deflected. 
+
+Beams will travel straight from their origin until they hit the
+opposite side of the arena (at which point they emerge), unless
+affected by balls in one of the following ways:
+
+\b A beam that hits a ball head-on is absorbed and will never
+   re-emerge. This includes beams that meet a ball on the first rank
+   of the arena.
+
+\b A beam with a ball in its front-left square and no ball ahead of it
+   gets deflected 90 degrees to the right.
+
+\b A beam with a ball in its front-right square and no ball ahead of
+   it gets similarly deflected to the left.
+
+\b A beam that would re-emerge from its entry location is considered to be
+   \q{reflected}. 
+
+\b A beam which would get deflected before entering the arena by a
+   ball to the front-left or front-right of its entry point is also
+   considered to be \q{reflected}.
+
+Beams that are reflected appear as a \q{R}; beams that hit balls
+head-on appear as \q{H}. Otherwise, a number appears at the firing
+point and the location where the beam emerges (this number is unique
+to that shot).
+
+You can place guesses as to the location of the balls, based on the
+entry and exit patterns of the beams; once you have placed enough
+balls a button appears enabling you to have your guesses checked. 
+
+Here is a diagram showing how the positions of balls can create each
+of the beam behaviours shown above:
+
+\c  1RHR---- 
+\c |..O.O...|
+\c 2........3
+\c |........|
+\c |........|
+\c 3........|
+\c |......O.|
+\c H........|
+\c |.....O..|
+\c  12-RR---
+
+As shown, it is possible for a beam to receive multiple reflections
+before re-emerging (see turn 3). Similarly, a beam may be reflected
+(possibly more than once) before receiving a hit (the \q{H} on the
+left side of the example).
+
+Note that any layout with more than 4 balls may have a non-unique
+solution.  The following diagram illustrates this; if you know the
+board contains 5 balls, it is impossible to determine where the fifth
+ball is (possible positions marked with an \cw{x}):
+
+\c  -------- 
+\c |........|
+\c |........|
+\c |..O..O..|
+\c |...xx...|
+\c |...xx...|
+\c |..O..O..|
+\c |........|
+\c |........|
+\c  --------
+
+For this reason, when you have your guesses checked, the game will
+check that your solution \e{produces the same results} as the
+computer's, rather than that your solution is identical to the
+computer's. So in the above example, you could put the fifth ball at
+\e{any} of the locations marked with an \cw{x}, and you would still
+win.
+
+Black Box was contributed to this collection by James Harvey.
+
+\H{blackbox-controls} \i{Black Box controls}
+
+\IM{Black Box controls} controls, for Black Box
+\IM{Black Box controls} keys, for Black Box
+\IM{Black Box controls} shortcuts (keyboard), for Black Box
+
+To fire a laser beam, left-click in a square around the edge of the
+arena. The results will be displayed immediately. Clicking or holding
+the left button on one of these squares will highlight the current go
+(or a previous go) to confirm the exit point for that laser, if
+applicable.
+
+To guess the location of a ball, left-click within the arena and a
+black circle will appear marking the guess; click again to remove the
+guessed ball.
+
+Locations in the arena may be locked against modification by
+right-clicking; whole rows and columns may be similarly locked by
+right-clicking in the laser square above/below that column, or to the
+left/right of that row.
+
+The cursor keys may also be used to move around the grid. Pressing the
+Enter key will fire a laser or add a new ball-location guess, and
+pressing Space will lock a cell, row, or column.
+
+When an appropriate number of balls have been guessed, a button will
+appear at the top-left corner of the grid; clicking that (with mouse
+or cursor) will check your guesses. 
+
+If you click the \q{check} button and your guesses are not correct,
+the game will show you the minimum information necessary to
+demonstrate this to you, so you can try again. If your ball
+positions are not consistent with the beam paths you already know
+about, one beam path will be circled to indicate that it proves you
+wrong. If your positions match all the existing beam paths but are
+still wrong, one new beam path will be revealed (written in red)
+which is not consistent with your current guesses.
+
+If you decide to give up completely, you can select Solve to reveal
+the actual ball positions. At this point, correctly-placed balls
+will be displayed as filled black circles, incorrectly-placed balls
+as filled black circles with red crosses, and missing balls as filled
+red circles. In addition, a red circle marks any laser you had already
+fired which is not consistent with your ball layout (just as when you
+press the \q{check} button), and red text marks any laser you
+\e{could} have fired in order to distinguish your ball layout from the
+correct one.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{blackbox-parameters} \I{parameters, for Black Box}Black Box parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares. There are 2 \by \e{Width} \by \e{Height} lasers 
+per grid, two per row and two per column. 
+
+\dt \e{No. of balls}
+
+\dd Number of balls to place in the grid. This can be a single number,
+or a range (separated with a hyphen, like \q{2-6}), and determines the
+number of balls to place on the grid. The \q{reveal} button is only
+enabled if you have guessed an appropriate number of balls; a guess
+using a different number to the original solution is still acceptable,
+if all the beam inputs and outputs match.
+
+
+\C{slant} \i{Slant}
+
+\cfg{winhelp-topic}{games.slant}
+
+You have a grid of squares. Your aim is to draw a diagonal line
+through each square, and choose which way each line slants so that
+the following conditions are met:
+
+\b The diagonal lines never form a loop.
+
+\b Any point with a circled number has precisely that many lines
+meeting at it. (Thus, a 4 is the centre of a cross shape, whereas a
+zero is the centre of a diamond shape \dash or rather, a partial
+diamond shape, because a zero can never appear in the middle of the
+grid because that would immediately cause a loop.)
+
+Credit for this puzzle goes to \i{Nikoli} \k{nikoli-slant}.
+
+\B{nikoli-slant}
+\W{http://www.nikoli.co.jp/ja/puzzles/gokigen_naname}\cw{http://www.nikoli.co.jp/ja/puzzles/gokigen_naname}
+(in Japanese)
+
+\H{slant-controls} \i{Slant controls}
+
+\IM{Slant controls} controls, for Slant
+
+Left-clicking in a blank square will place a \cw{\\} in it (a line
+leaning to the left, i.e. running from the top left of the square to
+the bottom right). Right-clicking in a blank square will place a
+\cw{/} in it (leaning to the right, running from top right to bottom
+left).
+
+Continuing to click either button will cycle between the three
+possible square contents. Thus, if you left-click repeatedly in a
+blank square it will change from blank to \cw{\\} to \cw{/} back to
+blank, and if you right-click repeatedly the square will change from
+blank to \cw{/} to \cw{\\} back to blank. (Therefore, you can play
+the game entirely with one button if you need to.)
+
+You can also use the cursor keys to move around the grid. Pressing the
+return or space keys will place a \cw{\\} or a \cw{/}, respectively,
+and will then cycle them as above.  You can also press \cw{/} or
+\cw{\\} to place a \cw{/} or \cw{\\}, respectively, independent of
+what is already in the cursor square.  Backspace removes any line from
+the cursor square.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{slant-parameters} \I{parameters, for Slant}Slant parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares.
+
+\dt \e{Difficulty}
+
+\dd Controls the difficulty of the generated puzzle. At Hard level,
+you are required to do deductions based on knowledge of
+\e{relationships} between squares rather than always being able to
+deduce the exact contents of one square at a time. (For example, you
+might know that two squares slant in the same direction, even if you
+don't yet know what that direction is, and this might enable you to
+deduce something about still other squares.) Even at Hard level,
+guesswork and backtracking should never be necessary.
+
+
+\C{lightup} \i{Light Up}
+
+\cfg{winhelp-topic}{games.lightup}
+
+You have a grid of squares. Some are filled in black; some of the
+black squares are numbered. Your aim is to \q{light up} all the
+empty squares by placing light bulbs in some of them.
+
+Each light bulb illuminates the square it is on, plus all squares in
+line with it horizontally or vertically unless a black square is
+blocking the way.
+
+To win the game, you must satisfy the following conditions:
+
+\b All non-black squares are lit.
+
+\b No light is lit by another light.
+
+\b All numbered black squares have exactly that number of lights adjacent to
+   them (in the four squares above, below, and to the side).
+
+Non-numbered black squares may have any number of lights adjacent to them. 
+
+Credit for this puzzle goes to \i{Nikoli} \k{nikoli-lightup}.
+
+Light Up was contributed to this collection by James Harvey.
+
+\B{nikoli-lightup}
+\W{http://www.nikoli.co.jp/en/puzzles/akari.html}\cw{http://www.nikoli.co.jp/en/puzzles/akari.html}
+(beware of Flash)
+
+\H{lightup-controls} \i{Light Up controls}
+
+\IM{Light Up controls} controls, for Light Up
+
+Left-clicking in a non-black square will toggle the presence of a light
+in that square. Right-clicking in a non-black square toggles a mark there to aid
+solving; it can be used to highlight squares that cannot be lit, for example. 
+
+You may not place a light in a marked square, nor place a mark in a lit square.
+
+The game will highlight obvious errors in red. Lights lit by other
+lights are highlighted in this way, as are numbered squares which
+do not (or cannot) have the right number of lights next to them.
+  
+Thus, the grid is solved when all non-black squares have yellow
+highlights and there are no red lights.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{lightup-parameters} \I{parameters, for Light Up}Light Up parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares.
+
+\dt \e{%age of black squares}
+
+\dd Rough percentage of black squares in the grid.
+
+\lcont{
+
+This is a hint rather than an instruction. If the grid generator is
+unable to generate a puzzle to this precise specification, it will
+increase the proportion of black squares until it can.
+
+}
+
+\dt \e{Symmetry}
+
+\dd Allows you to specify the required symmetry of the black squares
+in the grid. (This does not affect the difficulty of the puzzles
+noticeably.)
+
+\dt \e{Difficulty}
+
+\dd \q{Easy} means that the puzzles should be soluble without
+backtracking or guessing, \q{Hard} means that some guesses will
+probably be necessary.
+
+
+\C{map} \i{Map}
+
+\cfg{winhelp-topic}{games.map}
+
+You are given a map consisting of a number of regions. Your task is
+to colour each region with one of four colours, in such a way that
+no two regions sharing a boundary have the same colour. You are
+provided with some regions already coloured, sufficient to make the
+remainder of the solution unique.
+
+Only regions which share a length of border are required to be
+different colours. Two regions which meet at only one \e{point}
+(i.e. are diagonally separated) may be the same colour.
+
+I believe this puzzle is original; I've never seen an implementation
+of it anywhere else. The concept of a \i{four-colouring} puzzle was
+suggested by Owen Dunn; credit must also go to Nikoli and to Verity
+Allan for inspiring the train of thought that led to me realising
+Owen's suggestion was a viable puzzle. Thanks also to Gareth Taylor
+for many detailed suggestions.
+
+\H{map-controls} \i{Map controls}
+
+\IM{Map controls} controls, for Map
+
+To colour a region, click the left mouse button on an existing
+region of the desired colour and drag that colour into the new
+region.
+
+(The program will always ensure the starting puzzle has at least one
+region of each colour, so that this is always possible!)
+
+If you need to clear a region, you can drag from an empty region, or
+from the puzzle boundary if there are no empty regions left.
+
+Dragging a colour using the \e{right} mouse button will stipple the
+region in that colour, which you can use as a note to yourself that
+you think the region \e{might} be that colour. A region can contain
+stipples in multiple colours at once. (This is often useful at the
+harder difficulty levels.)
+
+You can also use the cursor keys to move around the map: the colour of
+the cursor indicates the position of the colour you would drag (which
+is not obvious if you're on a region's boundary, since it depends on the
+direction from which you approached the boundary). Pressing the return
+key starts a drag of that colour, as above, which you control with the
+cursor keys; pressing the return key again finishes the drag. The
+space bar can be used similarly to create a stippled region. 
+Double-pressing the return key (without moving the cursor) will clear
+the region, as a drag from an empty region does: this is useful with
+the cursor mode if you have filled the entire map in but need to 
+correct the layout.
+
+If you press L during play, the game will toggle display of a number
+in each region of the map. This is useful if you want to discuss a
+particular puzzle instance with a friend \dash having an unambiguous
+name for each region is much easier than trying to refer to them all
+by names such as \q{the one down and right of the brown one on the
+top border}.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{map-parameters} \I{parameters, for Map}Map parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares.
+
+\dt \e{Regions}
+
+\dd Number of regions in the generated map.
+
+\dt \e{Difficulty}
+
+\dd In \q{Easy} mode, there should always be at least one region
+whose colour can be determined trivially. In \q{Normal} and \q{Hard}
+modes, you will have to use increasingly complex logic to deduce the
+colour of some regions. However, it will always be possible without
+having to guess or backtrack.
+
+\lcont{
+
+In \q{Unreasonable} mode, the program will feel free to generate
+puzzles which are as hard as it can possibly make them: the only
+constraint is that they should still have a unique solution. Solving
+Unreasonable puzzles may require guessing and backtracking.
+
+}
+
+
+\C{loopy} \i{Loopy}
+
+\cfg{winhelp-topic}{games.loopy}
+
+You are given a grid of dots, marked with yellow lines to indicate
+which dots you are allowed to connect directly together. Your aim is
+to use some subset of those yellow lines to draw a single unbroken
+loop from dot to dot within the grid.
+
+Some of the spaces between the lines contain numbers. These numbers
+indicate how many of the lines around that space form part of the
+loop. The loop you draw must correctly satisfy all of these clues to
+be considered a correct solution.
+
+In the default mode, the dots are arranged in a grid of squares;
+however, you can also play on triangular or hexagonal grids, or even
+more exotic ones.
+
+Credit for the basic puzzle idea goes to \i{Nikoli}
+\k{nikoli-loopy}.
+
+Loopy was originally contributed to this collection by Mike Pinna,
+and subsequently enhanced to handle various types of non-square grid
+by Lambros Lambrou.
+
+\B{nikoli-loopy}
+\W{http://www.nikoli.co.jp/en/puzzles/slitherlink.html}\cw{http://www.nikoli.co.jp/en/puzzles/slitherlink.html}
+(beware of Flash)
+
+\H{loopy-controls} \i{Loopy controls}
+
+\IM{Loopy controls} controls, for Loopy
+
+Click the left mouse button on a yellow line to turn it black,
+indicating that you think it is part of the loop. Click again to
+turn the line yellow again (meaning you aren't sure yet).
+
+If you are sure that a particular line segment is \e{not} part of
+the loop, you can click the right mouse button to remove it
+completely. Again, clicking a second time will turn the line back to
+yellow.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{loopy-parameters} \I{parameters, for Loopy}Loopy parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid, measured in number of regions across and down. For
+square grids, it's clear how this is counted; for other types of
+grid you may have to think a bit to see how the dimensions are
+measured.
+
+\dt \e{Grid type}
+
+\dd Allows you to choose between a selection of types of tiling.
+Some have all the faces the same but may have multiple different
+types of vertex (e.g. the \e{Cairo} or \e{Kites} mode); others have
+all the vertices the same but may have different types of face (e.g.
+the \e{Great Hexagonal}). The square, triangular and honeycomb grids
+are fully regular, and have all their vertices \e{and} faces the
+same; this makes them the least confusing to play.
+
+\dt \e{Difficulty}
+
+\dd Controls the difficulty of the generated puzzle.
+\#{FIXME: what distinguishes Easy, Medium, and Hard? In particular,
+when are backtracking/guesswork required, if ever?}
+
+
+\C{inertia} \i{Inertia}
+
+\cfg{winhelp-topic}{games.inertia}
+
+You are a small green ball sitting in a grid full of obstacles. Your
+aim is to collect all the gems without running into any mines.
+
+You can move the ball in any orthogonal \e{or diagonal} direction.
+Once the ball starts moving, it will continue until something stops
+it. A wall directly in its path will stop it (but if it is moving
+diagonally, it will move through a diagonal gap between two other
+walls without stopping). Also, some of the squares are \q{stops};
+when the ball moves on to a stop, it will stop moving no matter what
+direction it was going in. Gems do \e{not} stop the ball; it picks
+them up and keeps on going.
+
+Running into a mine is fatal. Even if you picked up the last gem in
+the same move which then hit a mine, the game will count you as dead
+rather than victorious.
+
+This game was originally implemented for Windows by Ben Olmstead
+\k{bem}, who was kind enough to release his source code on request
+so that it could be re-implemented for this collection.
+
+\B{bem} \W{http://xn13.com/}\cw{http://xn13.com/}
+
+\H{inertia-controls} \i{Inertia controls}
+
+\IM{Inertia controls} controls, for Inertia
+\IM{Inertia controls} keys, for Inertia
+\IM{Inertia controls} shortcuts (keyboard), for Inertia
+
+You can move the ball in any of the eight directions using the
+numeric keypad. Alternatively, if you click the left mouse button on
+the grid, the ball will begin a move in the general direction of
+where you clicked.
+
+If you use the \q{Solve} function on this game, the program will
+compute a path through the grid which collects all the remaining
+gems and returns to the current position. A hint arrow will appear
+on the ball indicating the direction in which you should move to
+begin on this path. If you then move in that direction, the arrow
+will update to indicate the next direction on the path. You can also
+press Space to automatically move in the direction of the hint
+arrow. If you move in a different direction from the one shown by
+the arrow, arrows will be shown only if the puzzle is still solvable.
+
+All the actions described in \k{common-actions} are also available.
+In particular, if you do run into a mine and die, you can use the
+Undo function and resume playing from before the fatal move. The
+game will keep track of the number of times you have done this.
+
+\H{inertia-parameters} \I{parameters, for Inertia}Inertia parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares.
+
+
+\C{tents} \i{Tents}
+
+\cfg{winhelp-topic}{games.tents}
+
+You have a grid of squares, some of which contain trees. Your aim is
+to place tents in some of the remaining squares, in such a way that
+the following conditions are met:
+
+\b There are exactly as many tents as trees.
+
+\b The tents and trees can be matched up in such a way that each
+tent is directly adjacent (horizontally or vertically, but not
+diagonally) to its own tree. However, a tent may be adjacent to
+other trees as well as its own.
+
+\b No two tents are adjacent horizontally, vertically \e{or
+diagonally}.
+
+\b The number of tents in each row, and in each column, matches the
+numbers given round the sides of the grid.
+
+This puzzle can be found in several places on the Internet, and was
+brought to my attention by e-mail. I don't know who I should credit
+for inventing it.
+
+\H{tents-controls} \i{Tents controls}
+
+\IM{Tents controls} controls, for Tents
+
+Left-clicking in a blank square will place a tent in it.
+Right-clicking in a blank square will colour it green, indicating
+that you are sure it \e{isn't} a tent. Clicking either button in an
+occupied square will clear it.
+
+If you \e{drag} with the right button along a row or column, every
+blank square in the region you cover will be turned green, and no
+other squares will be affected. (This is useful for clearing the
+remainder of a row once you have placed all its tents.)
+
+You can also use the cursor keys to move around the grid. Pressing the
+return key over an empty square will place a tent, and pressing the
+space bar over an empty square will colour it green; either key will
+clear an occupied square.  Holding Shift and pressing the cursor keys
+will colour empty squares green.  Holding Control and pressing the
+cursor keys will colour green both empty squares and squares with tents.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{tents-parameters} \I{parameters, for Tents}Tents parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares.
+
+\dt \e{Difficulty}
+
+\dd Controls the difficulty of the generated puzzle. More difficult
+puzzles require more complex deductions, but at present none of the
+available difficulty levels requires guesswork or backtracking.
+
+
+\C{bridges} \i{Bridges}
+
+\cfg{winhelp-topic}{games.bridges}
+
+You have a set of islands distributed across the playing area. Each
+island contains a number. Your aim is to connect the islands
+together with bridges, in such a way that:
+
+\b Bridges run horizontally or vertically.
+
+\b The number of bridges terminating at any island is equal to the
+number written in that island.
+
+\b Two bridges may run in parallel between the same two islands, but
+no more than two may do so.
+
+\b No bridge crosses another bridge.
+
+\b All the islands are connected together.
+
+There are some configurable alternative modes, which involve
+changing the parallel-bridge limit to something other than 2, and
+introducing the additional constraint that no sequence of bridges
+may form a loop from one island back to the same island. The rules
+stated above are the default ones.
+
+Credit for this puzzle goes to \i{Nikoli} \k{nikoli-bridges}.
+
+Bridges was contributed to this collection by James Harvey.
+
+\B{nikoli-bridges}
+\W{http://www.nikoli.co.jp/en/puzzles/hashiwokakero.html}\cw{http://www.nikoli.co.jp/en/puzzles/hashiwokakero.html}
+(beware of Flash)
+
+\H{bridges-controls} \i{Bridges controls}
+
+\IM{Bridges controls} controls, for Bridges
+
+To place a bridge between two islands, click the mouse down on one
+island and drag it towards the other. You do not need to drag all
+the way to the other island; you only need to move the mouse far
+enough for the intended bridge direction to be unambiguous. (So you
+can keep the mouse near the starting island and conveniently throw
+bridges out from it in many directions.)
+
+Doing this again when a bridge is already present will add another
+parallel bridge. If there are already as many bridges between the
+two islands as permitted by the current game rules (i.e. two by
+default), the same dragging action will remove all of them.
+
+If you want to remind yourself that two islands definitely \e{do
+not} have a bridge between them, you can right-drag between them in
+the same way to draw a \q{non-bridge} marker.
+
+If you think you have finished with an island (i.e. you have placed
+all its bridges and are confident that they are in the right
+places), you can mark the island as finished by left-clicking on it.
+This will highlight it and all the bridges connected to it, and you
+will be prevented from accidentally modifying any of those bridges
+in future. Left-clicking again on a highlighted island will unmark
+it and restore your ability to modify it.
+
+You can also use the cursor keys to move around the grid: if possible
+the cursor will always move orthogonally, otherwise it will move
+towards the nearest island to the indicated direction. Holding Control
+and pressing a cursor key will lay a bridge in that direction (if
+available); Shift and a cursor key will lay a \q{non-bridge} marker.
+Pressing the return key followed by a cursor key will also lay a
+bridge in that direction.
+
+You can mark an island as finished by pressing the space bar or by
+pressing the return key twice.
+
+By pressing a number key, you can jump to the nearest island with that
+number.  Letters \q{a}, ..., \q{f} count as 10, ..., 15 and \q{0} as
+16.
+
+Violations of the puzzle rules will be marked in red:
+
+\b An island with too many bridges will be highlighted in red.
+
+\b An island with too few bridges will be highlighted in red if it
+is definitely an error (as opposed to merely not being finished
+yet): if adding enough bridges would involve having to cross another
+bridge or remove a non-bridge marker, or if the island has been
+highlighted as complete.
+
+\b A group of islands and bridges may be highlighted in red if it is
+a closed subset of the puzzle with no way to connect it to the rest
+of the islands. For example, if you directly connect two 1s together
+with a bridge and they are not the only two islands on the grid,
+they will light up red to indicate that such a group cannot be
+contained in any valid solution.
+
+\b If you have selected the (non-default) option to disallow loops
+in the solution, a group of bridges which forms a loop will be
+highlighted.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{bridges-parameters} \I{parameters, for Bridges}Bridges parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares.
+
+\dt \e{Difficulty}
+
+\dd Difficulty level of puzzle.
+
+\dt \e{Allow loops}
+
+\dd This is set by default. If cleared, puzzles will be generated in
+such a way that they are always soluble without creating a loop, and
+solutions which do involve a loop will be disallowed.
+
+\dt \e{Max. bridges per direction}
+
+\dd Maximum number of bridges in any particular direction. The
+default is 2, but you can change it to 1, 3 or 4. In general, fewer
+is easier.
+
+\dt \e{%age of island squares}
+
+\dd Gives a rough percentage of islands the generator will try and
+lay before finishing the puzzle. Certain layouts will not manage to
+lay enough islands; this is an upper bound.
+
+\dt \e{Expansion factor (%age)}
+
+\dd The grid generator works by picking an existing island at random
+(after first creating an initial island somewhere). It then decides
+on a direction (at random), and then works out how far it could
+extend before creating another island. This parameter determines how
+likely it is to extend as far as it can, rather than choosing
+somewhere closer.
+
+\lcont{
+
+High expansion factors usually mean easier puzzles with fewer
+possible islands; low expansion factors can create lots of
+tightly-packed islands.
+
+}
+
+
+\C{unequal} \i{Unequal}
+
+\cfg{winhelp-topic}{games.unequal}
+
+You have a square grid; each square may contain a digit from 1 to
+the size of the grid, and some squares have clue signs between
+them. Your aim is to fully populate the grid with numbers such that:
+
+\b Each row contains only one occurrence of each digit
+
+\b Each column contains only one occurrence of each digit
+
+\b All the clue signs are satisfied. 
+
+There are two modes for this game, \q{Unequal} and \q{Adjacent}.
+
+In \q{Unequal} mode, the clue signs are greater-than symbols indicating one
+square's value is greater than its neighbour's. In this mode not all clues
+may be visible, particularly at higher difficulty levels. 
+
+In \q{Adjacent} mode, the clue signs are bars indicating
+one square's value is numerically adjacent (i.e. one higher or one lower)
+than its neighbour. In this mode all clues are always visible: absence of
+a bar thus means that a square's value is definitely not numerically adjacent
+to that neighbour's.  
+
+In \q{Trivial} difficulty level (available via the \q{Custom} game type
+selector), there are no greater-than signs in \q{Unequal} mode; the puzzle is
+to solve the \i{Latin square} only.
+
+At the time of writing, the \q{Unequal} mode of this puzzle is appearing in the
+Guardian weekly under the name \q{\i{Futoshiki}}.
+
+Unequal was contributed to this collection by James Harvey.
+
+\H{unequal-controls} \i{Unequal controls}
+
+\IM{Unequal controls} controls, for Unequal
+
+Unequal shares much of its control system with Solo.
+
+To play Unequal, simply click the mouse in any empty square and then
+type a digit or letter on the keyboard to fill that square. If you
+make a mistake, click the mouse in the incorrect square and press
+Space to clear it again (or use the Undo feature).
+
+If you \e{right}-click in a square and then type a number, that
+number will be entered in the square as a \q{pencil mark}. You can
+have pencil marks for multiple numbers in the same square. Squares
+containing filled-in numbers cannot also contain pencil marks.
+
+The game pays no attention to pencil marks, so exactly what you use
+them for is up to you: you can use them as reminders that a
+particular square needs to be re-examined once you know more about a
+particular number, or you can use them as lists of the possible
+numbers in a given square, or anything else you feel like.
+
+To erase a single pencil mark, right-click in the square and type
+the same number again.
+
+All pencil marks in a square are erased when you left-click and type
+a number, or when you left-click and press space. Right-clicking and
+pressing space will also erase pencil marks.
+
+As for Solo, the cursor keys can be used in conjunction with the digit
+keys to set numbers or pencil marks. You can also use the \q{M} key to
+auto-fill every numeric hint, ready for removal as required, or the \q{H}
+key to do the same but also to remove all obvious hints. 
+
+Alternatively, use the cursor keys to move the mark around the grid.
+Pressing the return key toggles the mark (from a normal mark to a
+pencil mark), and typing a number in is entered in the square in the
+appropriate way; typing in a 0 or using the space bar will clear a
+filled square. 
+
+Left-clicking a clue will mark it as done (grey it out), or unmark it
+if it is already marked.  Holding Control or Shift and pressing an
+arrow key likewise marks any clue adjacent to the cursor in the given
+direction.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{unequal-parameters} \I{parameters, for Unequal}Unequal parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Mode}
+
+\dd Mode of the puzzle (\q{Unequal} or \q{Adjacent})
+
+\dt \e{Size (s*s)}
+
+\dd Size of grid.
+
+\dt \e{Difficulty}
+
+\dd Controls the difficulty of the generated puzzle. At Trivial
+level, there are no greater-than signs; the puzzle is to solve the
+Latin square only. At Recursive level (only available via the
+\q{Custom} game type selector) backtracking will be required, but
+the solution should still be unique. The levels in between require
+increasingly complex reasoning to avoid having to backtrack.
+
+
+
+\C{galaxies} \i{Galaxies}
+
+\cfg{winhelp-topic}{games.galaxies}
+
+You have a rectangular grid containing a number of dots. Your aim is
+to draw edges along the grid lines which divide the rectangle into
+regions in such a way that every region is 180\u00b0{-degree}
+rotationally symmetric, and contains exactly one dot which is
+located at its centre of symmetry.
+
+This puzzle was invented by \i{Nikoli} \k{nikoli-galaxies}, under
+the name \q{Tentai Show}; its name is commonly translated into
+English as \q{Spiral Galaxies}.
+
+Galaxies was contributed to this collection by James Harvey.
+
+\B{nikoli-galaxies} \W{http://www.nikoli.co.jp/en/puzzles/astronomical_show.html}\cw{http://www.nikoli.co.jp/en/puzzles/astronomical_show.html}
+
+\H{galaxies-controls} \i{Galaxies controls}
+
+\IM{Galaxies controls} controls, for Galaxies
+
+Left-click on any grid line to draw an edge if there isn't one
+already, or to remove one if there is. When you create a valid
+region (one which is closed, contains exactly one dot, is
+180\u00b0{-degree} symmetric about that dot, and contains no
+extraneous edges inside it) it will be highlighted automatically; so
+your aim is to have the whole grid highlighted in that way.
+
+During solving, you might know that a particular grid square belongs
+to a specific dot, but not be sure of where the edges go and which
+other squares are connected to the dot. In order to mark this so you
+don't forget, you can right-click on the dot and drag, which will
+create an arrow marker pointing at the dot. Drop that in a square of
+your choice and it will remind you which dot it's associated with.
+You can also right-click on existing arrows to pick them up and move
+them, or destroy them by dropping them off the edge of the grid.
+(Also, if you're not sure which dot an arrow is pointing at, you can
+pick it up and move it around to make it clearer. It will swivel
+constantly as you drag it, to stay pointed at its parent dot.)
+
+You can also use the cursor keys to move around the grid squares and
+lines.  Pressing the return key when over a grid line will draw or
+clear its edge, as above. Pressing the return key when over a dot will
+pick up an arrow, to be dropped the next time the return key is
+pressed; this can also be used to move existing arrows around, removing
+them by dropping them on a dot or another arrow.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{galaxies-parameters} \I{parameters, for Galaxies}Galaxies parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares.
+
+\dt \e{Difficulty}
+
+\dd Controls the difficulty of the generated puzzle. More difficult
+puzzles require more complex deductions, and the \q{Unreasonable}
+difficulty level may require backtracking.
+
+
+
+\C{filling} \i{Filling}
+
+\cfg{winhelp-topic}{games.filling}
+
+You have a grid of squares, some of which contain digits, and the
+rest of which are empty. Your job is to fill in digits in the empty
+squares, in such a way that each connected region of squares all
+containing the same digit has an area equal to that digit.
+
+(\q{Connected region}, for the purposes of this game, does not count
+diagonally separated squares as adjacent.)
+
+For example, it follows that no square can contain a zero, and that
+two adjacent squares can not both contain a one.  No region has an
+area greater than 9 (because then its area would not be a single
+digit).
+
+Credit for this puzzle goes to \i{Nikoli} \k{nikoli-fillomino}.
+
+Filling was contributed to this collection by Jonas K\u00F6{oe}lker.
+
+\B{nikoli-fillomino}
+\W{http://www.nikoli.co.jp/en/puzzles/fillomino.html}\cw{http://www.nikoli.co.jp/en/puzzles/fillomino.html}
+
+\H{filling-controls} \I{controls, for Filling}Filling controls
+
+To play Filling, simply click the mouse in any empty square and then
+type a digit on the keyboard to fill that square. By dragging the
+mouse, you can select multiple squares to fill with a single keypress.
+If you make a mistake, click the mouse in the incorrect square and
+press 0, Space, Backspace or Enter to clear it again (or use the Undo
+feature).
+
+You can also move around the grid with the cursor keys; typing a digit will
+fill the square containing the cursor with that number; typing 0 will clear
+it.  You can also select multiple squares for numbering or clearing with the
+return and arrow keys, before typing a digit to fill or clear the highlighted
+squares (as above).  The space bar adds and removes single squares to and from
+the selection.  Backspace and escape remove all squares from the selection.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{filling-parameters} \I{parameters, for Filling}Filling parameters
+
+Filling allows you to configure the number of rows and columns of the
+grid, through the \q{Type} menu.
+
+
+\C{keen} \i{Keen}
+
+\cfg{winhelp-topic}{games.keen}
+
+You have a square grid; each square may contain a digit from 1 to
+the size of the grid. The grid is divided into blocks of varying
+shape and size, with arithmetic clues written in them. Your aim is
+to fully populate the grid with digits such that:
+
+\b Each row contains only one occurrence of each digit
+
+\b Each column contains only one occurrence of each digit
+
+\b The digits in each block can be combined to form the number
+stated in the clue, using the arithmetic operation given in the
+clue. That is:
+
+\lcont{
+
+\b An addition clue means that the sum of the digits in the block
+must be the given number. For example, \q{15+} means the contents of
+the block adds up to fifteen.
+
+\b A multiplication clue (e.g. \q{60\times}), similarly, means that
+the product of the digits in the block must be the given number.
+
+\b A subtraction clue will always be written in a block of size two,
+and it means that one of the digits in the block is greater than the
+other by the given amount. For example, \q{2\minus} means that one
+of the digits in the block is 2 more than the other, or equivalently
+that one digit minus the other one is 2. The two digits could be
+either way round, though.
+
+\b A division clue (e.g. \q{3\divide}), similarly, is always in a
+block of size two and means that one digit divided by the other is
+equal to the given amount.
+
+Note that a block may contain the same digit more than once
+(provided the identical ones are not in the same row and column).
+This rule is precisely the opposite of the rule in Solo's \q{Killer}
+mode (see \k{solo}).
+
+}
+
+This puzzle appears in the Times under the name \q{\i{KenKen}}.
+
+
+\H{keen-controls} \i{Keen controls}
+
+\IM{Keen controls} controls, for Keen
+
+Keen shares much of its control system with Solo (and Unequal).
+
+To play Keen, simply click the mouse in any empty square and then
+type a digit on the keyboard to fill that square. If you make a
+mistake, click the mouse in the incorrect square and press Space to
+clear it again (or use the Undo feature).
+
+If you \e{right}-click in a square and then type a number, that
+number will be entered in the square as a \q{pencil mark}. You can
+have pencil marks for multiple numbers in the same square. Squares
+containing filled-in numbers cannot also contain pencil marks.
+
+The game pays no attention to pencil marks, so exactly what you use
+them for is up to you: you can use them as reminders that a
+particular square needs to be re-examined once you know more about a
+particular number, or you can use them as lists of the possible
+numbers in a given square, or anything else you feel like.
+
+To erase a single pencil mark, right-click in the square and type
+the same number again.
+
+All pencil marks in a square are erased when you left-click and type
+a number, or when you left-click and press space. Right-clicking and
+pressing space will also erase pencil marks.
+
+As for Solo, the cursor keys can be used in conjunction with the
+digit keys to set numbers or pencil marks. Use the cursor keys to
+move a highlight around the grid, and type a digit to enter it in
+the highlighted square. Pressing return toggles the highlight into a
+mode in which you can enter or remove pencil marks.
+
+Pressing M will fill in a full set of pencil marks in every square
+that does not have a main digit in it.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{keen-parameters} \I{parameters, for Keen}Keen parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Grid size}
+
+\dd Specifies the size of the grid. Lower limit is 3; upper limit is
+9 (because the user interface would become more difficult with
+\q{digits} bigger than 9!).
+
+\dt \e{Difficulty}
+
+\dd Controls the difficulty of the generated puzzle. At Unreasonable
+level, some backtracking will be required, but the solution should
+still be unique. The remaining levels require increasingly complex
+reasoning to avoid having to backtrack.
+
+\dt \e{Multiplication only}
+
+\dd If this is enabled, all boxes will be multiplication boxes.
+With this rule, the puzzle is known as \q{Inshi No Heya}.
+
+\C{towers} \i{Towers}
+
+\cfg{winhelp-topic}{games.towers}
+
+You have a square grid. On each square of the grid you can build a
+tower, with its height ranging from 1 to the size of the grid.
+Around the edge of the grid are some numeric clues.
+
+Your task is to build a tower on every square, in such a way that:
+
+\b Each row contains every possible height of tower once
+
+\b Each column contains every possible height of tower once
+
+\b Each numeric clue describes the number of towers that can be seen
+if you look into the square from that direction, assuming that
+shorter towers are hidden behind taller ones. For example, in a
+5\by\.5 grid, a clue marked \q{5} indicates that the five tower
+heights must appear in increasing order (otherwise you would not be
+able to see all five towers), whereas a clue marked \q{1} indicates
+that the tallest tower (the one marked 5) must come first.
+
+In harder or larger puzzles, some towers will be specified for you
+as well as the clues round the edge, and some edge clues may be
+missing.
+
+This puzzle appears on the web under various names, particularly
+\q{\i{Skyscrapers}}, but I don't know who first invented it.
+
+
+\H{towers-controls} \i{Towers controls}
+
+\IM{Towers controls} controls, for Towers
+
+Towers shares much of its control system with Solo, Unequal and Keen.
+
+To play Towers, simply click the mouse in any empty square and then
+type a digit on the keyboard to fill that square with a tower of the
+given height. If you make a mistake, click the mouse in the
+incorrect square and press Space to clear it again (or use the Undo
+feature).
+
+If you \e{right}-click in a square and then type a number, that
+number will be entered in the square as a \q{pencil mark}. You can
+have pencil marks for multiple numbers in the same square. A square
+containing a tower cannot also contain pencil marks.
+
+The game pays no attention to pencil marks, so exactly what you use
+them for is up to you: you can use them as reminders that a
+particular square needs to be re-examined once you know more about a
+particular number, or you can use them as lists of the possible
+numbers in a given square, or anything else you feel like.
+
+To erase a single pencil mark, right-click in the square and type
+the same number again.
+
+All pencil marks in a square are erased when you left-click and type
+a number, or when you left-click and press space. Right-clicking and
+pressing space will also erase pencil marks.
+
+As for Solo, the cursor keys can be used in conjunction with the
+digit keys to set numbers or pencil marks. Use the cursor keys to
+move a highlight around the grid, and type a digit to enter it in
+the highlighted square. Pressing return toggles the highlight into a
+mode in which you can enter or remove pencil marks.
+
+Pressing M will fill in a full set of pencil marks in every square
+that does not have a main digit in it.
+
+Left-clicking a clue will mark it as done (grey it out), or unmark it
+if it is already marked.  Holding Control or Shift and pressing an
+arrow key likewise marks any clue in the given direction.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{towers-parameters} \I{parameters, for Towers}Towers parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Grid size}
+
+\dd Specifies the size of the grid. Lower limit is 3; upper limit is
+9 (because the user interface would become more difficult with
+\q{digits} bigger than 9!).
+
+\dt \e{Difficulty}
+
+\dd Controls the difficulty of the generated puzzle. At Unreasonable
+level, some backtracking will be required, but the solution should
+still be unique. The remaining levels require increasingly complex
+reasoning to avoid having to backtrack.
+
+
+\C{singles} \i{Singles}
+
+\cfg{winhelp-topic}{games.singles}
+
+You have a grid of white squares, all of which contain numbers. Your task
+is to colour some of the squares black (removing the number) so as to satisfy
+all of the following conditions:
+
+\b No number occurs more than once in any row or column.
+
+\b No black square is horizontally or vertically adjacent to any other black
+square.
+
+\b The remaining white squares must all form one contiguous region
+(connected by edges, not just touching at corners).
+
+Credit for this puzzle goes to \i{Nikoli} \k{nikoli-hitori} who call it
+\i{Hitori}. 
+
+Singles was contributed to this collection by James Harvey.
+
+\B{nikoli-hitori}
+\W{http://www.nikoli.com/en/puzzles/hitori.html}\cw{http://www.nikoli.com/en/puzzles/hitori.html}
+(beware of Flash)
+
+\H{singles-controls} \i{Singles controls}
+
+\IM{Singles controls} controls, for Singles
+
+Left-clicking on an empty square will colour it black; left-clicking again 
+will restore the number. Right-clicking will add a circle (useful for 
+indicating that a cell is definitely not black). 
+
+You can also use the cursor keys to move around the grid. Pressing the
+return or space keys will turn a square black or add a circle respectively,
+and pressing the key again will restore the number or remove the circle.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{singles-parameters} \I{parameters, for Singles}Singles parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares.
+
+\dt \e{Difficulty}
+
+\dd Controls the difficulty of the generated puzzle.
+
+
+\C{magnets} \i{Magnets}
+
+\cfg{winhelp-topic}{games.magnets}
+
+A rectangular grid has been filled with a mixture of magnets (that is,
+dominoes with one positive end and one negative end) and blank dominoes
+(that is, dominoes with two neutral poles).
+These dominoes are initially only seen in silhouette. Around the grid
+are placed a number of clues indicating the number of positive and
+negative poles contained in certain columns and rows.
+
+Your aim is to correctly place the magnets and blank dominoes such that
+all the clues are satisfied, with the additional constraint that no two
+similar magnetic poles may be orthogonally adjacent (since they repel).
+Neutral poles do not repel, and can be adjacent to any other pole. 
+
+Credit for this puzzle goes to \i{Janko} \k{janko-magnets}.
+
+Magnets was contributed to this collection by James Harvey.
+
+\B{janko-magnets}
+\W{http://www.janko.at/Raetsel/Magnete/index.htm}\cw{http://www.janko.at/Raetsel/Magnete/index.htm}
+
+\H{magnets-controls} \i{Magnets controls}
+
+\IM{Magnets controls} controls, for Magnets
+
+Left-clicking on an empty square places a magnet at that position with
+the positive pole on the square and the negative pole on the other half
+of the magnet; left-clicking again reverses the polarity, and a third
+click removes the magnet.
+
+Right-clicking on an empty square places a blank domino there.
+Right-clicking again places two question marks on the domino, signifying
+\q{this cannot be blank} (which can be useful to note deductions while
+solving), and right-clicking again empties the domino. 
+
+Left-clicking a clue will mark it as done (grey it out), or unmark it if
+it is already marked.
+
+You can also use the cursor keys to move a cursor around the grid. 
+Pressing the return key will lay a domino with a positive pole at that
+position; pressing again reverses the polarity and then removes the
+domino, as with left-clicking. Using the space bar allows placement
+of blank dominoes and cannot-be-blank hints, as for right-clicking. 
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{magnets-parameters} \I{parameters, for Magnets}Magnets parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares. There will be half \e{Width} \by \e{Height}
+dominoes in the grid: if this number is odd then one square will be blank.
+
+\lcont{
+
+(Grids with at least one odd dimension tend to be easier to solve.)
+
+}
+
+\dt \e{Difficulty}
+
+\dd Controls the difficulty of the generated puzzle. At Tricky level,
+you are required to make more deductions about empty dominoes and
+row/column counts. 
+
+\dt \e{Strip clues}
+
+\dd If true, some of the clues around the grid are removed at generation
+time, making the puzzle more difficult.
+
+
+\C{signpost} \i{Signpost}
+
+\cfg{winhelp-topic}{games.signpost}
+
+You have a grid of squares; each square (except the last one)
+contains an arrow, and some squares also contain numbers. Your job
+is to connect the squares to form a continuous list of numbers
+starting at 1 and linked in the direction of the arrows \dash so the
+arrow inside the square with the number 1 will point to the square
+containing the number 2, which will point to the square containing
+the number 3, etc. Each square can be any distance away from the
+previous one, as long as it is somewhere in the direction of the
+arrow.
+
+By convention the first and last numbers are shown; one or more
+interim numbers may also appear at the beginning. 
+
+Credit for this puzzle goes to \i{Janko} \k{janko-arrowpath}, who call it
+\q{Pfeilpfad} (\q{arrow path}).
+
+Signpost was contributed to this collection by James Harvey.
+
+\B{janko-arrowpath}
+\W{http://janko.at/Raetsel/Pfeilpfad/index.htm}\cw{http://janko.at/Raetsel/Pfeilpfad/index.htm}
+
+\H{signpost-controls} \I{controls, for Signpost}Signpost controls
+
+To play Signpost, you connect squares together by dragging from one
+square to another, indicating that they are adjacent in the
+sequence. Drag with the left button from a square to its successor,
+or with the right button from a square to its predecessor.
+
+If you connect together two squares in this way and one of them has
+a number in it, the appropriate number will appear in the other
+square. If you connect two non-numbered squares, they will be
+assigned temporary algebraic labels: on the first occasion, they
+will be labelled \cq{a} and \cq{a+1}, and then \cq{b} and \cq{b+1},
+and so on. Connecting more squares on to the ends of such a chain
+will cause them all to be labelled with the same letter.
+
+When you left-click or right-click in a square, the legal squares to
+connect it to will be shown.
+
+The arrow in each square starts off black, and goes grey once you
+connect the square to its successor. Also, each square which needs a
+predecessor has a small dot in the bottom left corner, which
+vanishes once you link a square to it. So your aim is always to
+connect a square with a black arrow to a square with a dot.
+
+To remove any links for a particular square (both incoming and
+outgoing), left-drag it off the grid. To remove a whole chain,
+right-drag any square in the chain off the grid.
+
+You can also use the cursor keys to move around the grid squares and
+lines. Pressing the return key when over a square starts a link
+operation, and pressing the return key again over a square will
+finish the link, if allowable. Pressing the space bar over a square
+will show the other squares pointing to it, and allow you to form a
+backward link, and pressing the space bar again cancels this.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{signpost-parameters} \I{parameters, for Signpost}Signpost parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares.
+
+\dt \e{Force start/end to corners}
+
+\dd If true, the start and end squares are always placed in opposite corners
+(the start at the top left, and the end at the bottom right). If false the start
+and end squares are placed randomly (although always both shown). 
+
+\C{range} \i{Range}
+
+\cfg{winhelp-topic}{games.range}
+
+You have a grid of squares; some squares contain numbers.  Your job is
+to colour some of the squares black, such that several criteria are
+satisfied:
+
+\b no square with a number is coloured black.
+
+\b no two black squares are adjacent (horizontally or vertically).
+
+\b for any two white squares, there is a path between them using only
+white squares.
+
+\b for each square with a number, that number denotes the total number
+of white squares reachable from that square going in a straight line
+in any horizontal or vertical direction until hitting a wall or a
+black square; the square with the number is included in the total
+(once).
+
+For instance, a square containing the number one must have four black
+squares as its neighbours by the last criterion; but then it's
+impossible for it to be connected to any outside white square, which
+violates the second to last criterion.  So no square will contain the
+number one.
+
+Credit for this puzzle goes to \i{Nikoli}, who have variously called
+it \q{Kurodoko}, \q{Kuromasu} or \q{Where is Black Cells}.
+\k{nikoli-range}.
+
+Range was contributed to this collection by Jonas K\u00F6{oe}lker.
+
+\B{nikoli-range}
+\W{http://www.nikoli.co.jp/en/puzzles/where_is_black_cells.html}\cw{http://www.nikoli.co.jp/en/puzzles/where_is_black_cells.html}
+
+\H{range-controls} \I{controls, for Range}Range controls
+
+Click with the left button to paint a square black, or with the right
+button to mark a square with a dot to indicate that you are sure it
+should \e{not} be painted black. Repeated clicking with either button
+will cycle the square through the three possible states (filled,
+dotted or empty) in opposite directions.
+
+You can also use the cursor keys to move around the grid squares.
+Pressing Return does the same as clicking with the left button, while
+pressing Space does the same as a right button click.  Moving with the
+cursor keys while holding Shift will place dots in all squares that
+are moved through.
+
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{range-parameters} \I{parameters, for Range}Range parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares.
+
+\C{pearl} \i{Pearl}
+
+\cfg{winhelp-topic}{games.pearl}
+
+You have a grid of squares. Your job is to draw lines between the
+centres of horizontally or vertically adjacent squares, so that the
+lines form a single closed loop. In the resulting grid, some of the
+squares that the loop passes through will contain corners, and some
+will be straight horizontal or vertical lines. (And some squares can
+be completely empty \dash the loop doesn't have to pass through every
+square.)
+
+Some of the squares contain black and white circles, which are clues
+that the loop must satisfy.
+
+A black circle in a square indicates that that square is a corner, but
+neither of the squares adjacent to it in the loop is also a corner.
+
+A white circle indicates that the square is a straight edge, but \e{at
+least one} of the squares adjacent to it in the loop is a corner.
+
+(In both cases, the clue only constrains the two squares adjacent
+\e{in the loop}, that is, the squares that the loop passes into after
+leaving the clue square. The squares that are only adjacent \e{in the
+grid} are not constrained.)
+
+Credit for this puzzle goes to \i{Nikoli}, who call it \q{Masyu}.
+\k{nikoli-pearl}
+
+Thanks to James Harvey for assistance with the implementation.
+
+\B{nikoli-pearl}
+\W{http://www.nikoli.co.jp/en/puzzles/masyu.html}\cw{http://www.nikoli.co.jp/en/puzzles/masyu.html}
+(beware of Flash)
+
+\H{pearl-controls} \I{controls, for Pearl}Pearl controls
+
+Click with the left button on a grid edge to draw a segment of the
+loop through that edge, or to remove a segment once it is drawn.
+
+Drag with the left button through a series of squares to draw more
+than one segment of the loop in one go. Alternatively, drag over an
+existing part of the loop to undraw it, or to undraw part of it and
+then go in a different direction.
+
+Click with the right button on a grid edge to mark it with a cross,
+indicating that you are sure the loop does not go through that edge.
+(For instance, if you have decided which of the squares adjacent to a
+white clue has to be a corner, but don't yet know which way the corner
+turns, you might mark the one way it \e{can't} go with a cross.)
+
+Alternatively, use the cursor keys to move the cursor.  Use the Enter
+key to begin and end keyboard \q{drag} operations.  Use the Space,
+Escape or Backspace keys to cancel the drag.  Or, hold Control while
+dragging with the cursor keys to toggle segments as you move between
+squares.
+
+Pressing Control-Shift-arrowkey or Shift-arrowkey simulates a left or
+right click, respectively, on the edge in the direction of the key.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{pearl-parameters} \I{parameters, for Pearl}Pearl parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\C{undead} \i{Undead}
+
+\cfg{winhelp-topic}{games.undead}
+
+You are given a grid of squares, some of which contain diagonal
+mirrors. Every square which is not a mirror must be filled with one of
+three types of undead monster: a ghost, a vampire, or a zombie.
+
+Vampires can be seen directly, but are invisible when reflected in
+mirrors. Ghosts are the opposite way round: they can be seen in
+mirrors, but are invisible when looked at directly. Zombies are
+visible by any means.
+
+You are also told the total number of each type of monster in the
+grid. Also around the edge of the grid are written numbers, which
+indicate how many monsters can be seen if you look into the grid along
+a row or column starting from that position. (The diagonal mirrors are
+reflective on both sides. If your reflected line of sight crosses the
+same monster more than once, the number will count it each time it is
+visible, not just once.)
+
+This puzzle type was invented by David Millar, under the name
+\q{Haunted Mirror Maze}. See \k{janko-undead} for more details.
+
+Undead was contributed to this collection by Steffen Bauer.
+
+\B{janko-undead}
+\W{http://www.janko.at/Raetsel/Spukschloss/index.htm}\cw{http://www.janko.at/Raetsel/Spukschloss/index.htm}
+
+\H{undead-controls} \I{controls, for Undead}Undead controls
+
+Undead has a similar control system to Solo, Unequal and Keen.
+
+To play Undead, click the mouse in any empty square and then type a
+letter on the keyboard indicating the type of monster: \q{G} for a
+ghost, \q{V} for a vampire, or \q{Z} for a zombie. If you make a
+mistake, click the mouse in the incorrect square and press Space to
+clear it again (or use the Undo feature).
+
+If you \e{right}-click in a square and then type a letter, the
+corresponding monster will be shown in reduced size in that square, as
+a \q{pencil mark}. You can have pencil marks for multiple monsters in
+the same square. A square containing a full-size monster cannot also
+contain pencil marks.
+
+The game pays no attention to pencil marks, so exactly what you use
+them for is up to you: you can use them as reminders that a particular
+square needs to be re-examined once you know more about a particular
+monster, or you can use them as lists of the possible monster in a
+given square, or anything else you feel like.
+
+To erase a single pencil mark, right-click in the square and type
+the same letter again.
+
+All pencil marks in a square are erased when you left-click and type a
+monster letter, or when you left-click and press Space. Right-clicking
+and pressing space will also erase pencil marks.
+
+As for Solo, the cursor keys can be used in conjunction with the letter
+keys to place monsters or pencil marks. Use the cursor keys to move a
+highlight around the grid, and type a monster letter to enter it in
+the highlighted square. Pressing return toggles the highlight into a
+mode in which you can enter or remove pencil marks.
+
+If you prefer plain letters of the alphabet to cute monster pictures,
+you can press \q{A} to toggle between showing the monsters as monsters or
+showing them as letters.
+
+Left-clicking a clue will mark it as done (grey it out), or unmark it
+if it is already marked.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{undead-parameters} \I{parameters, for Undead}Undead parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares.
+
+\dt \e{Difficulty}
+
+\dd Controls the difficulty of the generated puzzle.
+
+\C{unruly} \i{Unruly}
+
+\cfg{winhelp-topic}{games.unruly}
+
+You are given a grid of squares, which you must colour either black or
+white. Some squares are provided as clues; the rest are left for you
+to fill in. Each row and column must contain the same number of black
+and white squares, and no row or column may contain three consecutive
+squares of the same colour.
+
+This puzzle type was invented by Adolfo Zanellati, under the name
+\q{Tohu wa Vohu}. See \k{janko-unruly} for more details.
+
+Unruly was contributed to this collection by Lennard Sprong.
+
+\B{janko-unruly}
+\W{http://www.janko.at/Raetsel/Tohu-Wa-Vohu/index.htm}\cw{http://www.janko.at/Raetsel/Tohu-Wa-Vohu/index.htm}
+
+\H{unruly-controls} \I{controls, for Unruly}Unruly controls
+
+To play Unruly, click the mouse in a square to change its colour.
+Left-clicking an empty square will turn it black, and right-clicking
+will turn it white. Keep clicking the same button to cycle through the
+three possible states for the square. If you middle-click in a square
+it will be reset to empty.
+
+You can also use the cursor keys to move around the grid. Pressing the
+return or space keys will turn an empty square black or white
+respectively (and then cycle the colours in the same way as the mouse
+buttons), and pressing Backspace will reset a square to empty.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{unruly-parameters} \I{parameters, for Unruly}Unruly parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares. (Note that the rules of the game require
+both the width and height to be even numbers.)
+
+\dt \e{Difficulty}
+
+\dd Controls the difficulty of the generated puzzle.
+
+\dt \e{Unique rows and columns}
+
+\dd If enabled, no two rows are permitted to have exactly the same
+pattern, and likewise columns. (A row and a column can match, though.)
+
+\C{flood} \i{Flood}
+
+\cfg{winhelp-topic}{games.flood}
+
+You are given a grid of squares, coloured at random in multiple
+colours. In each move, you can flood-fill the top left square in a
+colour of your choice (i.e. every square reachable from the starting
+square by an orthogonally connected path of squares all the same
+colour will be filled in the new colour). As you do this, more and
+more of the grid becomes connected to the starting square.
+
+Your aim is to make the whole grid the same colour, in as few moves as
+possible. The game will set a limit on the number of moves, based on
+running its own internal solver. You win if you can make the whole
+grid the same colour in that many moves or fewer.
+
+I saw this game (with a fixed grid size, fixed number of colours, and
+fixed move limit) at http://floodit.appspot.com (no longer accessible).
+
+\H{flood-controls} \I{controls, for Flood}Flood controls
+
+To play Flood, click the mouse in a square. The top left corner and
+everything connected to it will be flood-filled with the colour of the
+square you clicked. Clicking a square the same colour as the top left
+corner has no effect, and therefore does not count as a move.
+
+You can also use the cursor keys to move a cursor (outline black
+square) around the grid. Pressing the return key will fill the top
+left corner in the colour of the square under the cursor.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{flood-parameters} \I{parameters, for Flood}Flood parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of the grid, in squares.
+
+\dt \e{Colours}
+
+\dd Number of colours used to fill the grid. Must be at least 3 (with
+two colours there would only be one legal move at any stage, hence no
+choice to make at all), and at most 10.
+
+\dt \e{Extra moves permitted}
+
+\dd Controls the difficulty of the puzzle, by increasing the move
+limit. In each new grid, Flood will run an internal solver to generate
+its own solution, and then the value in this field will be added to
+the length of Flood's solution to generate the game's move limit. So a
+value of 0 requires you to be just as efficient as Flood's automated
+solver, and a larger value makes it easier.
+
+\lcont{
+
+(Note that Flood's internal solver will not necessarily find the
+shortest possible solution, though I believe it's pretty close. For a
+real challenge, set this value to 0 and then try to solve a grid in
+\e{strictly fewer} moves than the limit you're given!)
+
+}
+
+\C{tracks} \i{Tracks}
+
+\cfg{winhelp-topic}{games.tracks}
+
+You are given a grid of squares, some of which are filled with train
+tracks. You need to complete the track from A to B so that the rows and
+columns contain the same number of track segments as are indicated in the
+clues to the top and right of the grid.
+
+There are only straight and 90 degree curved rails, and the track may not
+cross itself.
+
+Tracks was contributed to this collection by James Harvey.
+
+\H{tracks-controls} \I{controls, for Tracks}Tracks controls
+
+Left-clicking on an edge between two squares adds a track segment between
+the two squares. Right-clicking on an edge adds a cross on the edge,
+indicating no track is possible there.
+
+Left-clicking in a square adds a colour indicator showing that you know the
+square must contain a track, even if you don't know which edges it crosses
+yet. Right-clicking in a square adds a cross indicating it contains no
+track segment.
+
+Left- or right-dragging between squares allows you to lay a straight line
+of is-track or is-not-track indicators, useful for filling in rows or
+columns to match the clue.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{tracks-parameters} \I{parameters, for Tracks}Tracks parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of the grid, in squares.
+
+\dt \e{Difficulty}
+
+\dd Controls the difficulty of the generated puzzle: at Tricky level,
+you are required to make more deductions regarding disregarding moves
+that would lead to impossible crossings later.
+
+\dt \e{Disallow consecutive 1 clues}
+
+\dd Controls whether the Tracks game generation permits two adjacent
+rows or columns to have a 1 clue, or permits the row or column of the
+track's endpoint to have a 1 clue. By default this is not permitted,
+to avoid long straight boring segments of track and make the games
+more twiddly and interesting. If you want to restore the possibility,
+turn this option off.
+
+
+\C{palisade} \i{Palisade}
+
+\cfg{winhelp-topic}{games.palisade}
+
+You're given a grid of squares, some of which contain numbers.  Your
+goal is to subdivide the grid into contiguous regions, all of the same
+(given) size, such that each square containing a number is adjacent to
+exactly that many edges (including those between the inside and the
+outside of the grid).
+
+Credit for this puzzle goes to \i{Nikoli}, who call it \q{Five Cells}.
+\k{nikoli-palisade}.
+
+Palisade was contributed to this collection by Jonas K\u00F6{oe}lker.
+
+\B{nikoli-palisade}
+\W{http://nikoli.co.jp/en/puzzles/five_cells.html}\cw{http://nikoli.co.jp/en/puzzles/five_cells.html}
+
+\H{palisade-controls} \I{controls, for Palisade}Palisade controls
+
+Left-click to place an edge.  Right-click to indicate \q{no edge}.
+Alternatively, the arrow keys will move a keyboard cursor.  Holding
+Control while pressing an arrow key will place an edge.  Press
+Shift-arrowkey to switch off an edge.  Repeat an action to perform
+its inverse.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{Palisade-parameters} \I{parameters, for Palisade}Palisade parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares.
+
+\dt \e{Region size}
+
+\dd The size of the regions into which the grid must be subdivided.
+
+\A{licence} \I{MIT licence}\ii{Licence}
+
+This software is \i{copyright} 2004-2014 Simon Tatham.
+
+Portions copyright Richard Boulton, James Harvey, Mike Pinna, Jonas
+K\u00F6{oe}lker, Dariusz Olszewski, Michael Schierl, Lambros Lambrou,
+Bernd Schmidt, Steffen Bauer, Lennard Sprong and Rogier Goossens.
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation files
+(the \q{Software}), to deal in the Software without restriction,
+including without limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of the Software,
+and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED \q{AS IS}, WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+\IM{command-line}{command line} command line
+
+\IM{default parameters, specifying} default parameters, specifying
+\IM{default parameters, specifying} preferences, specifying default
+
+\IM{Unix} Unix
+\IM{Unix} Linux
+
+\IM{generating game IDs} generating game IDs
+\IM{generating game IDs} game ID, generating
+
+\IM{specific} \q{Specific}, menu option
+\IM{custom} \q{Custom}, menu option
+
+\IM{game ID} game ID
+\IM{game ID} ID, game
+\IM{ID format} ID format
+\IM{ID format} format, ID
+\IM{ID format} game ID, format
+
+\IM{keys} keys
+\IM{keys} shortcuts (keyboard)
+
+\IM{initial state} initial state
+\IM{initial state} state, initial
+
+\IM{MIT licence} MIT licence
+\IM{MIT licence} licence, MIT
+
+\versionid Simon Tatham's Portable Puzzle Collection, version 20161228.7cae89f
diff --git a/printing.c b/printing.c
new file mode 100644 (file)
index 0000000..e921a4d
--- /dev/null
@@ -0,0 +1,247 @@
+/*
+ * printing.c: Cross-platform printing manager. Handles document
+ * setup and layout.
+ */
+
+#include "puzzles.h"
+
+struct puzzle {
+    const game *game;
+    game_params *par;
+    game_state *st;
+    game_state *st2;
+};
+
+struct document {
+    int pw, ph;
+    int npuzzles;
+    struct puzzle *puzzles;
+    int puzzlesize;
+    int got_solns;
+    float *colwid, *rowht;
+    float userscale;
+};
+
+/*
+ * Create a new print document. pw and ph are the layout
+ * parameters: they state how many puzzles will be printed across
+ * the page, and down the page.
+ */
+document *document_new(int pw, int ph, float userscale)
+{
+    document *doc = snew(document);
+
+    doc->pw = pw;
+    doc->ph = ph;
+    doc->puzzles = NULL;
+    doc->puzzlesize = doc->npuzzles = 0;
+    doc->got_solns = FALSE;
+
+    doc->colwid = snewn(pw, float);
+    doc->rowht = snewn(ph, float);
+
+    doc->userscale = userscale;
+
+    return doc;
+}
+
+/*
+ * Free a document structure, whether it's been printed or not.
+ */
+void document_free(document *doc)
+{
+    int i;
+
+    for (i = 0; i < doc->npuzzles; i++) {
+       doc->puzzles[i].game->free_params(doc->puzzles[i].par);
+       doc->puzzles[i].game->free_game(doc->puzzles[i].st);
+       if (doc->puzzles[i].st2)
+           doc->puzzles[i].game->free_game(doc->puzzles[i].st2);
+    }
+
+    sfree(doc->colwid);
+    sfree(doc->rowht);
+
+    sfree(doc->puzzles);
+    sfree(doc);
+}
+
+/*
+ * Called from midend.c to add a puzzle to be printed. Provides a
+ * game_params (for initial layout computation), a game_state, and
+ * optionally a second game_state to be printed in parallel on
+ * another sheet (typically the solution to the first game_state).
+ */
+void document_add_puzzle(document *doc, const game *game, game_params *par,
+                        game_state *st, game_state *st2)
+{
+    if (doc->npuzzles >= doc->puzzlesize) {
+       doc->puzzlesize += 32;
+       doc->puzzles = sresize(doc->puzzles, doc->puzzlesize, struct puzzle);
+    }
+    doc->puzzles[doc->npuzzles].game = game;
+    doc->puzzles[doc->npuzzles].par = par;
+    doc->puzzles[doc->npuzzles].st = st;
+    doc->puzzles[doc->npuzzles].st2 = st2;
+    doc->npuzzles++;
+    if (st2)
+       doc->got_solns = TRUE;
+}
+
+static void get_puzzle_size(document *doc, struct puzzle *pz,
+                           float *w, float *h, float *scale)
+{
+    float ww, hh, ourscale;
+
+    /* Get the preferred size of the game, in mm. */
+    pz->game->print_size(pz->par, &ww, &hh);
+
+    /* Adjust for user-supplied scale factor. */
+    ourscale = doc->userscale;
+
+    /*
+     * FIXME: scale it down here if it's too big for the page size.
+     * Rather than do complicated things involving scaling all
+     * columns down in proportion, the simplest approach seems to
+     * me to be to scale down until the game fits within one evenly
+     * divided cell of the page (i.e. width/pw by height/ph).
+     * 
+     * In order to do this step we need the page size available.
+     */
+
+    *scale = ourscale;
+    *w = ww * ourscale;
+    *h = hh * ourscale;
+}
+
+/*
+ * Having accumulated a load of puzzles, actually do the printing.
+ */
+void document_print(document *doc, drawing *dr)
+{
+    int ppp;                          /* puzzles per page */
+    int pages, passes;
+    int page, pass;
+    int pageno;
+
+    ppp = doc->pw * doc->ph;
+    pages = (doc->npuzzles + ppp - 1) / ppp;
+    passes = (doc->got_solns ? 2 : 1);
+
+    print_begin_doc(dr, pages * passes);
+
+    pageno = 1;
+    for (pass = 0; pass < passes; pass++) {
+       for (page = 0; page < pages; page++) {
+           int i, n, offset;
+           float colsum, rowsum;
+
+           print_begin_page(dr, pageno);
+
+           offset = page * ppp;
+           n = min(ppp, doc->npuzzles - offset);
+
+           for (i = 0; i < doc->pw; i++)
+               doc->colwid[i] = 0;
+           for (i = 0; i < doc->ph; i++)
+               doc->rowht[i] = 0;
+
+           /*
+            * Lay the page out by computing all the puzzle sizes.
+            */
+           for (i = 0; i < n; i++) {
+               struct puzzle *pz = doc->puzzles + offset + i;
+               int x = i % doc->pw, y = i / doc->pw;
+               float w, h, scale;
+
+               get_puzzle_size(doc, pz, &w, &h, &scale);
+
+               /* Update the maximum width/height of this column. */
+               doc->colwid[x] = max(doc->colwid[x], w);
+               doc->rowht[y] = max(doc->rowht[y], h);
+           }
+
+           /*
+            * Add up the maximum column/row widths to get the
+            * total amount of space used up by puzzles on the
+            * page. We will use this to compute gutter widths.
+            */
+           colsum = 0.0;
+           for (i = 0; i < doc->pw; i++)
+               colsum += doc->colwid[i];
+           rowsum = 0.0;
+           for (i = 0; i < doc->ph; i++)
+               rowsum += doc->rowht[i];
+
+           /*
+            * Now do the printing.
+            */
+           for (i = 0; i < n; i++) {
+               struct puzzle *pz = doc->puzzles + offset + i;
+               int x = i % doc->pw, y = i / doc->pw, j;
+               float w, h, scale, xm, xc, ym, yc;
+               int pixw, pixh, tilesize;
+
+               if (pass == 1 && !pz->st2)
+                   continue;          /* nothing to do */
+
+               /*
+                * The total amount of gutter space is the page
+                * width minus colsum. This is divided into pw+1
+                * gutters, so the amount of horizontal gutter
+                * space appearing to the left of this puzzle
+                * column is
+                * 
+                *   (width-colsum) * (x+1)/(pw+1)
+                * = width * (x+1)/(pw+1) - (colsum * (x+1)/(pw+1))
+                */
+               xm = (float)(x+1) / (doc->pw + 1);
+               xc = -xm * colsum;
+               /* And similarly for y. */
+               ym = (float)(y+1) / (doc->ph + 1);
+               yc = -ym * rowsum;
+
+               /*
+                * However, the amount of space to the left of this
+                * puzzle isn't just gutter space: we must also
+                * count the widths of all the previous columns.
+                */
+               for (j = 0; j < x; j++)
+                   xc += doc->colwid[j];
+               /* And similarly for rows. */
+               for (j = 0; j < y; j++)
+                   yc += doc->rowht[j];
+
+               /*
+                * Now we adjust for this _specific_ puzzle, which
+                * means centring it within the cell we've just
+                * computed.
+                */
+               get_puzzle_size(doc, pz, &w, &h, &scale);
+               xc += (doc->colwid[x] - w) / 2;
+               yc += (doc->rowht[y] - h) / 2;
+
+               /*
+                * And now we know where and how big we want to
+                * print the puzzle, just go ahead and do so. For
+                * the moment I'll pick a standard pixel tile size
+                * of 512.
+                * 
+                * (FIXME: would it be better to pick this value
+                * with reference to the printer resolution? Or
+                * permit each game to choose its own?)
+                */
+               tilesize = 512;
+               pz->game->compute_size(pz->par, tilesize, &pixw, &pixh);
+               print_begin_puzzle(dr, xm, xc, ym, yc, pixw, pixh, w, scale);
+               pz->game->print(dr, pass == 0 ? pz->st : pz->st2, tilesize);
+               print_end_puzzle(dr);
+           }
+
+           print_end_page(dr, pageno);
+           pageno++;
+       }
+    }
+
+    print_end_doc(dr);
+}
diff --git a/ps.c b/ps.c
new file mode 100644 (file)
index 0000000..2394cc5
--- /dev/null
+++ b/ps.c
@@ -0,0 +1,433 @@
+/*
+ * ps.c: PostScript printing functions.
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <assert.h>
+
+#include "puzzles.h"
+
+#define ROOT2 1.414213562
+
+struct psdata {
+    FILE *fp;
+    int colour;
+    int ytop;
+    int clipped;
+    float hatchthick, hatchspace;
+    int gamewidth, gameheight;
+    drawing *drawing;
+};
+
+static void ps_printf(psdata *ps, char *fmt, ...)
+{
+    va_list ap;
+
+    va_start(ap, fmt);
+    vfprintf(ps->fp, fmt, ap);
+    va_end(ap);
+}
+
+static void ps_fill(psdata *ps, int colour)
+{
+    int hatch;
+    float r, g, b;
+
+    print_get_colour(ps->drawing, colour, ps->colour, &hatch, &r, &g, &b);
+
+    if (hatch < 0) {
+       if (ps->colour)
+           ps_printf(ps, "%g %g %g setrgbcolor fill\n", r, g, b);
+       else
+           ps_printf(ps, "%g setgray fill\n", r);
+    } else {
+       /* Clip to the region. */
+       ps_printf(ps, "gsave clip\n");
+       /* Hatch the entire game printing area. */
+       ps_printf(ps, "newpath\n");
+       if (hatch == HATCH_VERT || hatch == HATCH_PLUS)
+           ps_printf(ps, "0 %g %d {\n"
+                     "  0 moveto 0 %d rlineto\n"
+                     "} for\n", ps->hatchspace, ps->gamewidth,
+                     ps->gameheight);
+       if (hatch == HATCH_HORIZ || hatch == HATCH_PLUS)
+           ps_printf(ps, "0 %g %d {\n"
+                     "  0 exch moveto %d 0 rlineto\n"
+                     "} for\n", ps->hatchspace, ps->gameheight,
+                     ps->gamewidth);
+       if (hatch == HATCH_SLASH || hatch == HATCH_X)
+           ps_printf(ps, "%d %g %d {\n"
+                     "  0 moveto %d dup rlineto\n"
+                     "} for\n", -ps->gameheight, ps->hatchspace * ROOT2,
+                     ps->gamewidth, max(ps->gamewidth, ps->gameheight));
+       if (hatch == HATCH_BACKSLASH || hatch == HATCH_X)
+           ps_printf(ps, "0 %g %d {\n"
+                     "  0 moveto %d neg dup neg rlineto\n"
+                     "} for\n", ps->hatchspace * ROOT2,
+                     ps->gamewidth+ps->gameheight,
+                     max(ps->gamewidth, ps->gameheight));
+       ps_printf(ps, "0 setgray %g setlinewidth stroke grestore\n",
+                 ps->hatchthick);
+    }
+}
+
+static void ps_setcolour_internal(psdata *ps, int colour, char *suffix)
+{
+    int hatch;
+    float r, g, b;
+
+    print_get_colour(ps->drawing, colour, ps->colour, &hatch, &r, &g, &b);
+
+    /*
+     * Stroking in hatched colours is not permitted.
+     */
+    assert(hatch < 0);
+    
+    if (ps->colour)
+       ps_printf(ps, "%g %g %g setrgbcolor%s\n", r, g, b, suffix);
+    else
+       ps_printf(ps, "%g setgray%s\n", r, suffix);
+}
+
+static void ps_setcolour(psdata *ps, int colour)
+{
+    ps_setcolour_internal(ps, colour, "");
+}
+
+static void ps_stroke(psdata *ps, int colour)
+{
+    ps_setcolour_internal(ps, colour, " stroke");
+}
+
+static void ps_draw_text(void *handle, int x, int y, int fonttype,
+                        int fontsize, int align, int colour, char *text)
+{
+    psdata *ps = (psdata *)handle;
+
+    y = ps->ytop - y;
+    ps_setcolour(ps, colour);
+    ps_printf(ps, "/%s findfont %d scalefont setfont\n",
+             fonttype == FONT_FIXED ? "Courier-L1" : "Helvetica-L1",
+             fontsize);
+    if (align & ALIGN_VCENTRE) {
+       ps_printf(ps, "newpath 0 0 moveto (X) true charpath flattenpath"
+                 " pathbbox\n"
+                 "3 -1 roll add 2 div %d exch sub %d exch moveto pop pop\n",
+                 y, x);
+    } else {
+       ps_printf(ps, "%d %d moveto\n", x, y);
+    }
+    ps_printf(ps, "(");
+    while (*text) {
+       if (*text == '\\' || *text == '(' || *text == ')')
+           ps_printf(ps, "\\");
+       ps_printf(ps, "%c", *text);
+       text++;
+    }
+    ps_printf(ps, ") ");
+    if (align & (ALIGN_HCENTRE | ALIGN_HRIGHT))
+       ps_printf(ps, "dup stringwidth pop %sneg 0 rmoveto show\n",
+                 (align & ALIGN_HCENTRE) ? "2 div " : "");
+    else
+       ps_printf(ps, "show\n");
+}
+
+static void ps_draw_rect(void *handle, int x, int y, int w, int h, int colour)
+{
+    psdata *ps = (psdata *)handle;
+
+    y = ps->ytop - y;
+    /*
+     * Offset by half a pixel for the exactness requirement.
+     */
+    ps_printf(ps, "newpath %g %g moveto %d 0 rlineto 0 %d rlineto"
+             " %d 0 rlineto closepath\n", x - 0.5, y + 0.5, w, -h, -w);
+    ps_fill(ps, colour);
+}
+
+static void ps_draw_line(void *handle, int x1, int y1, int x2, int y2,
+                        int colour)
+{
+    psdata *ps = (psdata *)handle;
+
+    y1 = ps->ytop - y1;
+    y2 = ps->ytop - y2;
+    ps_printf(ps, "newpath %d %d moveto %d %d lineto\n", x1, y1, x2, y2);
+    ps_stroke(ps, colour);
+}
+
+static void ps_draw_polygon(void *handle, int *coords, int npoints,
+                           int fillcolour, int outlinecolour)
+{
+    psdata *ps = (psdata *)handle;
+
+    int i;
+
+    ps_printf(ps, "newpath %d %d moveto\n", coords[0], ps->ytop - coords[1]);
+
+    for (i = 1; i < npoints; i++)
+       ps_printf(ps, "%d %d lineto\n", coords[i*2], ps->ytop - coords[i*2+1]);
+
+    ps_printf(ps, "closepath\n");
+
+    if (fillcolour >= 0) {
+       ps_printf(ps, "gsave\n");
+       ps_fill(ps, fillcolour);
+       ps_printf(ps, "grestore\n");
+    }
+    ps_stroke(ps, outlinecolour);
+}
+
+static void ps_draw_circle(void *handle, int cx, int cy, int radius,
+                          int fillcolour, int outlinecolour)
+{
+    psdata *ps = (psdata *)handle;
+
+    cy = ps->ytop - cy;
+
+    ps_printf(ps, "newpath %d %d %d 0 360 arc closepath\n", cx, cy, radius);
+
+    if (fillcolour >= 0) {
+       ps_printf(ps, "gsave\n");
+       ps_fill(ps, fillcolour);
+       ps_printf(ps, "grestore\n");
+    }
+    ps_stroke(ps, outlinecolour);
+}
+
+static void ps_unclip(void *handle)
+{
+    psdata *ps = (psdata *)handle;
+
+    assert(ps->clipped);
+    ps_printf(ps, "grestore\n");
+    ps->clipped = FALSE;
+}
+static void ps_clip(void *handle, int x, int y, int w, int h)
+{
+    psdata *ps = (psdata *)handle;
+
+    if (ps->clipped)
+       ps_unclip(ps);
+
+    y = ps->ytop - y;
+    /*
+     * Offset by half a pixel for the exactness requirement.
+     */
+    ps_printf(ps, "gsave\n");
+    ps_printf(ps, "newpath %g %g moveto %d 0 rlineto 0 %d rlineto"
+             " %d 0 rlineto closepath\n", x - 0.5, y + 0.5, w, -h, -w);
+    ps_printf(ps, "clip\n");
+    ps->clipped = TRUE;
+}
+
+static void ps_line_width(void *handle, float width)
+{
+    psdata *ps = (psdata *)handle;
+
+    ps_printf(ps, "%g setlinewidth\n", width);
+}
+
+static void ps_line_dotted(void *handle, int dotted)
+{
+    psdata *ps = (psdata *)handle;
+
+    if (dotted) {
+       ps_printf(ps, "[ currentlinewidth 3 mul ] 0 setdash\n");
+    } else {
+       ps_printf(ps, "[ ] 0 setdash\n");
+    }
+}
+
+static char *ps_text_fallback(void *handle, const char *const *strings,
+                             int nstrings)
+{
+    /*
+     * We can handle anything in ISO 8859-1, and we'll manually
+     * translate it out of UTF-8 for the purpose.
+     */
+    int i, maxlen;
+    char *ret;
+
+    maxlen = 0;
+    for (i = 0; i < nstrings; i++) {
+       int len = strlen(strings[i]);
+       if (maxlen < len) maxlen = len;
+    }
+
+    ret = snewn(maxlen + 1, char);
+
+    for (i = 0; i < nstrings; i++) {
+       const char *p = strings[i];
+       char *q = ret;
+
+       while (*p) {
+           int c = (unsigned char)*p++;
+           if (c < 0x80) {
+               *q++ = c;              /* ASCII */
+           } else if ((c == 0xC2 || c == 0xC3) && (*p & 0xC0) == 0x80) {
+               *q++ = (c << 6) | (*p++ & 0x3F);   /* top half of 8859-1 */
+           } else {
+               break;
+           }
+       }
+
+       if (!*p) {
+           *q = '\0';
+           return ret;
+       }
+    }
+
+    assert(!"Should never reach here");
+    return NULL;
+}
+
+static void ps_begin_doc(void *handle, int pages)
+{
+    psdata *ps = (psdata *)handle;
+
+    fputs("%!PS-Adobe-3.0\n", ps->fp);
+    fputs("%%Creator: Simon Tatham's Portable Puzzle Collection\n", ps->fp);
+    fputs("%%DocumentData: Clean7Bit\n", ps->fp);
+    fputs("%%LanguageLevel: 1\n", ps->fp);
+    fprintf(ps->fp, "%%%%Pages: %d\n", pages);
+    fputs("%%DocumentNeededResources:\n", ps->fp);
+    fputs("%%+ font Helvetica\n", ps->fp);
+    fputs("%%+ font Courier\n", ps->fp);
+    fputs("%%EndComments\n", ps->fp);
+    fputs("%%BeginSetup\n", ps->fp);
+    fputs("%%IncludeResource: font Helvetica\n", ps->fp);
+    fputs("%%IncludeResource: font Courier\n", ps->fp);
+    fputs("%%EndSetup\n", ps->fp);
+    fputs("%%BeginProlog\n", ps->fp);
+    /*
+     * Re-encode Helvetica and Courier into ISO-8859-1, which gives
+     * us times and divide signs - and also (according to the
+     * Language Reference Manual) a bonus in that the ASCII '-' code
+     * point now points to a minus sign instead of a hyphen.
+     */
+    fputs("/Helvetica findfont " /* get the font dictionary */
+         "dup maxlength dict dup begin " /* create and open a new dict */
+         "exch " /* move the original font to top of stack */
+         "{1 index /FID ne {def} {pop pop} ifelse} forall "
+                                      /* copy everything except FID */
+         "/Encoding ISOLatin1Encoding def "
+                             /* set the thing we actually wanted to change */
+         "/FontName /Helvetica-L1 def " /* set a new font name */
+         "FontName end exch definefont" /* and define the font */
+         "\n", ps->fp);
+    fputs("/Courier findfont " /* get the font dictionary */
+         "dup maxlength dict dup begin " /* create and open a new dict */
+         "exch " /* move the original font to top of stack */
+         "{1 index /FID ne {def} {pop pop} ifelse} forall "
+                                      /* copy everything except FID */
+         "/Encoding ISOLatin1Encoding def "
+                             /* set the thing we actually wanted to change */
+         "/FontName /Courier-L1 def " /* set a new font name */
+         "FontName end exch definefont" /* and define the font */
+         "\n", ps->fp);
+    fputs("%%EndProlog\n", ps->fp);
+}
+
+static void ps_begin_page(void *handle, int number)
+{
+    psdata *ps = (psdata *)handle;
+
+    fprintf(ps->fp, "%%%%Page: %d %d\ngsave save\n%g dup scale\n",
+           number, number, 72.0 / 25.4);
+}
+
+static void ps_begin_puzzle(void *handle, float xm, float xc,
+                           float ym, float yc, int pw, int ph, float wmm)
+{
+    psdata *ps = (psdata *)handle;
+
+    fprintf(ps->fp, "gsave\n"
+           "clippath flattenpath pathbbox pop pop translate\n"
+           "clippath flattenpath pathbbox 4 2 roll pop pop\n"
+           "exch %g mul %g add exch dup %g mul %g add sub translate\n"
+           "%g dup scale\n"
+           "0 -%d translate\n", xm, xc, ym, yc, wmm/pw, ph);
+    ps->ytop = ph;
+    ps->clipped = FALSE;
+    ps->gamewidth = pw;
+    ps->gameheight = ph;
+    ps->hatchthick = 0.2 * pw / wmm;
+    ps->hatchspace = 1.0 * pw / wmm;
+}
+
+static void ps_end_puzzle(void *handle)
+{
+    psdata *ps = (psdata *)handle;
+
+    fputs("grestore\n", ps->fp);
+}
+
+static void ps_end_page(void *handle, int number)
+{
+    psdata *ps = (psdata *)handle;
+
+    fputs("restore grestore showpage\n", ps->fp);
+}
+
+static void ps_end_doc(void *handle)
+{
+    psdata *ps = (psdata *)handle;
+
+    fputs("%%EOF\n", ps->fp);
+}
+
+static const struct drawing_api ps_drawing = {
+    ps_draw_text,
+    ps_draw_rect,
+    ps_draw_line,
+    ps_draw_polygon,
+    ps_draw_circle,
+    NULL /* draw_update */,
+    ps_clip,
+    ps_unclip,
+    NULL /* start_draw */,
+    NULL /* end_draw */,
+    NULL /* status_bar */,
+    NULL /* blitter_new */,
+    NULL /* blitter_free */,
+    NULL /* blitter_save */,
+    NULL /* blitter_load */,
+    ps_begin_doc,
+    ps_begin_page,
+    ps_begin_puzzle,
+    ps_end_puzzle,
+    ps_end_page,
+    ps_end_doc,
+    ps_line_width,
+    ps_line_dotted,
+    ps_text_fallback,
+};
+
+psdata *ps_init(FILE *outfile, int colour)
+{
+    psdata *ps = snew(psdata);
+
+    ps->fp = outfile;
+    ps->colour = colour;
+    ps->ytop = 0;
+    ps->clipped = FALSE;
+    ps->hatchthick = ps->hatchspace = ps->gamewidth = ps->gameheight = 0;
+    ps->drawing = drawing_new(&ps_drawing, NULL, ps);
+
+    return ps;
+}
+
+void ps_free(psdata *ps)
+{
+    drawing_free(ps->drawing);
+    sfree(ps);
+}
+
+drawing *ps_drawing_api(psdata *ps)
+{
+    return ps->drawing;
+}
diff --git a/puzzles.but b/puzzles.but
new file mode 100644 (file)
index 0000000..2ae6ffe
--- /dev/null
@@ -0,0 +1,3403 @@
+\title Simon Tatham's Portable Puzzle Collection
+
+\cfg{winhelp-filename}{puzzles.hlp}
+\cfg{winhelp-contents-titlepage}{Contents}
+
+\cfg{text-filename}{puzzles.txt}
+
+\cfg{html-contents-filename}{index.html}
+\cfg{html-template-filename}{%k.html}
+\cfg{html-index-filename}{docindex.html}
+\cfg{html-leaf-level}{1}
+\cfg{html-contents-depth-0}{1}
+\cfg{html-contents-depth-1}{2}
+\cfg{html-leaf-contains-contents}{true}
+
+\cfg{info-filename}{puzzles.info}
+
+\cfg{ps-filename}{puzzles.ps}
+\cfg{pdf-filename}{puzzles.pdf}
+
+\define{by} \u00D7{x}
+
+\define{dash} \u2013{-}
+
+\define{times} \u00D7{*}
+
+\define{divide} \u00F7{/}
+
+\define{minus} \u2212{-}
+
+This is a collection of small one-player puzzle games.
+
+\copyright This manual is copyright 2004-2014 Simon Tatham. All rights
+reserved. You may distribute this documentation under the MIT licence.
+See \k{licence} for the licence text in full.
+
+\cfg{html-local-head}{<meta name="AppleTitle" content="Puzzles Help">}
+
+\C{intro} Introduction
+
+I wrote this collection because I thought there should be more small
+desktop toys available: little games you can pop up in a window and
+play for two or three minutes while you take a break from whatever
+else you were doing. And I was also annoyed that every time I found
+a good game on (say) \i{Unix}, it wasn't available the next time I
+was sitting at a \i{Windows} machine, or vice versa; so I arranged
+that everything in my personal puzzle collection will happily run on
+both, and have more recently done a port to \i{Mac OS X} as well. When I
+find (or perhaps invent) further puzzle games that I like, they'll
+be added to this collection and will immediately be available on
+both platforms. And if anyone feels like writing any other front
+ends \dash PocketPC, Mac OS pre-10, or whatever it might be \dash
+then all the games in this framework will immediately become
+available on another platform as well.
+
+The actual games in this collection were mostly not my invention; they
+are re-implementations of existing game concepts within my portable
+puzzle framework. I do not claim credit, in general, for inventing the
+rules of any of these puzzles. (I don't even claim authorship of all
+the code; some of the puzzles have been submitted by other authors.)
+
+This collection is distributed under the \i{MIT licence} (see
+\k{licence}). This means that you can do pretty much anything you like
+with the game binaries or the code, except pretending you wrote them
+yourself, or suing me if anything goes wrong. 
+
+The most recent versions, and \i{source code}, can be found at
+\I{website}\W{http://www.chiark.greenend.org.uk/~sgtatham/puzzles/}\cw{http://www.chiark.greenend.org.uk/~sgtatham/puzzles/}.
+
+Please report \I{feedback}\i{bugs} to
+\W{mailto:anakin@pobox.com}\cw{anakin@pobox.com}.
+You might find it helpful to read this article before reporting a bug:
+
+\W{http://www.chiark.greenend.org.uk/~sgtatham/bugs.html}\cw{http://www.chiark.greenend.org.uk/~sgtatham/bugs.html}
+
+\ii{Patches} are welcome. Especially if they provide a new front end
+(to make all these games run on another platform), or a new game.
+
+
+\C{common} \ii{Common features}
+
+This chapter describes features that are common to all the games.
+
+\H{common-actions} \I{controls}Common actions
+
+These actions are all available from the \I{Game menu}\q{Game} menu
+and via \I{keys}keyboard shortcuts, in addition to any game-specific
+actions.
+
+(On \i{Mac OS X}, to conform with local user interface standards, these
+actions are situated on the \I{File menu}\q{File} and \I{Edit
+menu}\q{Edit} menus instead.)
+
+\dt \ii\e{New game} (\q{N}, Ctrl+\q{N})
+
+\dd Starts a new game, with a random initial state.
+
+\dt \ii\e{Restart game}
+
+\dd Resets the current game to its initial state. (This can be undone.)
+
+\dt \ii\e{Load}
+
+\dd Loads a saved game from a file on disk.
+
+\dt \ii\e{Save}
+
+\dd Saves the current state of your game to a file on disk.
+
+\lcont{
+
+The Load and Save operations preserve your entire game
+history (so you can save, reload, and still Undo and Redo things you
+had done before saving).
+
+}
+
+\dt \I{printing, on Windows}\e{Print}
+
+\dd Where supported (currently only on Windows), brings up a dialog
+allowing you to print an arbitrary number of puzzles randomly
+generated from the current parameters, optionally including the
+current puzzle. (Only for puzzles which make sense to print, of
+course \dash it's hard to think of a sensible printable representation
+of Fifteen!)
+
+\dt \ii\e{Undo} (\q{U}, Ctrl+\q{Z}, Ctrl+\q{_})
+
+\dd Undoes a single move. (You can undo moves back to the start of the
+session.)
+
+\dt \ii\e{Redo} (\q{R}, Ctrl+\q{R})
+
+\dd Redoes a previously undone move.
+
+\dt \ii\e{Copy}
+
+\dd Copies the current state of your game to the clipboard in text
+format, so that you can paste it into (say) an e-mail client or a
+web message board if you're discussing the game with someone else.
+(Not all games support this feature.)
+
+\dt \ii\e{Solve}
+
+\dd Transforms the puzzle instantly into its solved state. For some
+games (Cube) this feature is not supported at all because it is of
+no particular use. For other games (such as Pattern), the solved
+state can be used to give you information, if you can't see how a
+solution can exist at all or you want to know where you made a
+mistake. For still other games (such as Sixteen), automatic solution
+tells you nothing about how to \e{get} to the solution, but it does
+provide a useful way to get there quickly so that you can experiment
+with set-piece moves and transformations.
+
+\lcont{
+
+Some games (such as Solo) are capable of solving a game ID you have
+typed in from elsewhere. Other games (such as Rectangles) cannot
+solve a game ID they didn't invent themself, but when they did
+invent the game ID they know what the solution is already. Still
+other games (Pattern) can solve \e{some} external game IDs, but only
+if they aren't too difficult.
+
+The \q{Solve} command adds the solved state to the end of the undo
+chain for the puzzle. In other words, if you want to go back to
+solving it yourself after seeing the answer, you can just press Undo.
+
+}
+
+\dt \I{exit}\ii\e{Quit} (\q{Q}, Ctrl+\q{Q})
+
+\dd Closes the application entirely.
+
+\H{common-id} Specifying games with the \ii{game ID}
+
+There are two ways to save a game specification out of a puzzle and
+recreate it later, or recreate it in somebody else's copy of the
+same puzzle.
+
+The \q{\i{Specific}} and \q{\i{Random Seed}} options from the
+\I{Game menu}\q{Game} menu (or the \q{File} menu, on \i{Mac OS X}) each
+show a piece of text (a \q{game ID}) which is sufficient to
+reconstruct precisely the same game at a later date.
+
+You can enter either of these pieces of text back into the program
+(via the same \q{Specific} or \q{Random Seed} menu options) at a
+later point, and it will recreate the same game. You can also use
+either one as a \i{command line} argument (on Windows or Unix); see
+\k{common-cmdline} for more detail.
+
+The difference between the two forms is that a descriptive game ID
+is a literal \e{description} of the \i{initial state} of the game,
+whereas a random seed is just a piece of arbitrary text which was
+provided as input to the random number generator used to create the
+puzzle. This means that:
+
+\b Descriptive game IDs tend to be longer in many puzzles (although
+some, such as Cube (\k{cube}), only need very short descriptions).
+So a random seed is often a \e{quicker} way to note down the puzzle
+you're currently playing, or to tell it to somebody else so they can
+play the same one as you.
+
+\b Any text at all is a valid random seed. The automatically
+generated ones are fifteen-digit numbers, but anything will do; you
+can type in your full name, or a word you just made up, and a valid
+puzzle will be generated from it. This provides a way for two or
+more people to race to complete the same puzzle: you think of a
+random seed, then everybody types it in at the same time, and nobody
+has an advantage due to having seen the generated puzzle before
+anybody else.
+
+\b It is often possible to convert puzzles from other sources (such
+as \q{nonograms} or \q{sudoku} from newspapers) into descriptive
+game IDs suitable for use with these programs.
+
+\b Random seeds are not guaranteed to produce the same result if you
+use them with a different \i\e{version} of the puzzle program. This
+is because the generation algorithm might have been improved or
+modified in later versions of the code, and will therefore produce a
+different result when given the same sequence of random numbers. Use
+a descriptive game ID if you aren't sure that it will be used on the
+same version of the program as yours.
+
+\lcont{(Use the \q{About} menu option to find out the version number
+of the program. Programs with the same version number running on
+different platforms should still be random-seed compatible.)}
+
+\I{ID format}A descriptive game ID starts with a piece of text which
+encodes the \i\e{parameters} of the current game (such as grid
+size). Then there is a colon, and after that is the description of
+the game's initial state. A random seed starts with a similar string
+of parameters, but then it contains a hash sign followed by
+arbitrary data.
+
+If you enter a descriptive game ID, the program will not be able to
+show you the random seed which generated it, since it wasn't
+generated \e{from} a random seed. If you \e{enter} a random seed,
+however, the program will be able to show you the descriptive game
+ID derived from that random seed.
+
+Note that the game parameter strings are not always identical
+between the two forms. For some games, there will be parameter data
+provided with the random seed which is not included in the
+descriptive game ID. This is because that parameter information is
+only relevant when \e{generating} puzzle grids, and is not important
+when playing them. Thus, for example, the difficulty level in Solo
+(\k{solo}) is not mentioned in the descriptive game ID.
+
+These additional parameters are also not set permanently if you type
+in a game ID. For example, suppose you have Solo set to \q{Advanced}
+difficulty level, and then a friend wants your help with a
+\q{Trivial} puzzle; so the friend reads out a random seed specifying
+\q{Trivial} difficulty, and you type it in. The program will
+generate you the same \q{Trivial} grid which your friend was having
+trouble with, but once you have finished playing it, when you ask
+for a new game it will automatically go back to the \q{Advanced}
+difficulty which it was previously set on.
+
+\H{common-type} The \q{Type} menu
+
+The \I{Type menu}\q{Type} menu, if present, may contain a list of
+\i{preset} game settings. Selecting one of these will start a new
+random game with the parameters specified.
+
+The \q{Type} menu may also contain a \q{\i{Custom}} option which
+allows you to fine-tune game \i{parameters}. The parameters
+available are specific to each game and are described in the
+following sections.
+
+\H{common-cmdline} Specifying game parameters on the \i{command line}
+
+(This section does not apply to the \i{Mac OS X} version.)
+
+The games in this collection deliberately do not ever save
+information on to the computer they run on: they have no high score
+tables and no saved preferences. (This is because I expect at least
+some people to play them at work, and those people will probably
+appreciate leaving as little evidence as possible!)
+
+However, if you do want to arrange for one of these games to
+\I{default parameters, specifying}default to a particular set of
+parameters, you can specify them on the command line.
+
+The easiest way to do this is to set up the parameters you want
+using the \q{Type} menu (see \k{common-type}), and then to select
+\q{Random Seed} from the \q{Game} or \q{File} menu (see
+\k{common-id}). The text in the \q{Game ID} box will be composed of
+two parts, separated by a hash. The first of these parts represents
+the game parameters (the size of the playing area, for example, and
+anything else you set using the \q{Type} menu).
+
+If you run the game with just that parameter text on the command
+line, it will start up with the settings you specified.
+
+For example: if you run Cube (see \k{cube}), select \q{Octahedron}
+from the \q{Type} menu, and then go to the game ID selection, you
+will see a string of the form \cq{o2x2#338686542711620}. Take only
+the part before the hash (\cq{o2x2}), and start Cube with that text
+on the command line: \cq{PREFIX-cube o2x2}.
+
+If you copy the \e{entire} game ID on to the command line, the game
+will start up in the specific game that was described. This is
+occasionally a more convenient way to start a particular game ID
+than by pasting it into the game ID selection box.
+
+(You could also retrieve the encoded game parameters using the
+\q{Specific} menu option instead of \q{Random Seed}, but if you do
+then some options, such as the difficulty level in Solo, will be
+missing. See \k{common-id} for more details on this.)
+
+\H{common-unix-cmdline} \i{Unix} \i{command-line} options
+
+(This section only applies to the Unix port.)
+
+In addition to being able to specify game parameters on the command
+line (see \k{common-cmdline}), there are various other options:
+
+\dt \cw{--game}
+
+\dt \cw{--load}
+
+\dd These options respectively determine whether the command-line
+argument is treated as specifying game parameters or a \i{save} file
+to \i{load}. Only one should be specified. If neither of these options
+is specified, a guess is made based on the format of the argument.
+
+\dt \cw{--generate }\e{n}
+
+\dd If this option is specified, instead of a puzzle being displayed,
+a number of descriptive game IDs will be \I{generating game IDs}invented
+and printed on standard output. This is useful for gaining access to
+the game generation algorithms without necessarily using the frontend.
+
+\lcont{
+
+If game parameters are specified on the command-line, they will be
+used to generate the game IDs; otherwise a default set of parameters
+will be used.
+
+The most common use of this option is in conjunction with \c{--print},
+in which case its behaviour is slightly different; see below.
+
+}
+
+\dt \I{printing, on Unix}\cw{--print }\e{w}\cw{x}\e{h}
+
+\dd If this option is specified, instead of a puzzle being displayed,
+a printed representation of one or more unsolved puzzles is sent to
+standard output, in \i{PostScript} format.
+
+\lcont{
+
+On each page of puzzles, there will be \e{w} across and \e{h} down. If
+there are more puzzles than \e{w}\by\e{h}, more than one page will be
+printed.
+
+If \c{--generate} has also been specified, the invented game IDs will
+be used to generate the printed output. Otherwise, a list of game IDs
+is expected on standard input (which can be descriptive or random
+seeds; see \k{common-id}), in the same format produced by
+\c{--generate}.
+
+For example:
+
+\c PREFIX-net --generate 12 --print 2x3 7x7w | lpr
+
+will generate two pages of printed Net puzzles (each of which will
+have a 7\by\.7 wrapping grid), and pipe the output to the \c{lpr}
+command, which on many systems will send them to an actual printer.
+
+There are various other options which affect printing; see below.
+
+}
+
+\dt \cw{--save }\e{file-prefix} [ \cw{--save-suffix }\e{file-suffix} ]
+
+\dd If this option is specified, instead of a puzzle being
+displayed, saved-game files for one or more unsolved puzzles are
+written to files constructed from the supplied prefix and/or suffix.
+
+\lcont{
+
+If \c{--generate} has also been specified, the invented game IDs will
+be used to generate the printed output. Otherwise, a list of game IDs
+is expected on standard input (which can be descriptive or random
+seeds; see \k{common-id}), in the same format produced by
+\c{--generate}.
+
+For example:
+
+\c PREFIX-net --generate 12 --save game --save-suffix .sav
+
+will generate twelve Net saved-game files with the names
+\cw{game0.sav} to \cw{game11.sav}.
+
+}
+
+\dt \cw{--version}
+
+\dd Prints version information about the game, and then quits.
+
+The following options are only meaningful if \c{--print} is also
+specified:
+
+\dt \cw{--with-solutions}
+
+\dd The set of pages filled with unsolved puzzles will be followed by
+the solutions to those puzzles.
+
+\dt \cw{--scale }\e{n}
+
+\dd Adjusts how big each puzzle is when printed. Larger numbers make
+puzzles bigger; the default is 1.0.
+
+\dt \cw{--colour}
+
+\dd Puzzles will be printed in colour, rather than in black and white
+(if supported by the puzzle).
+
+
+\C{net} \i{Net}
+
+\cfg{winhelp-topic}{games.net}
+
+(\e{Note:} the \i{Windows} version of this game is called
+\i\cw{NETGAME.EXE} to avoid clashing with Windows's own \cw{NET.EXE}.)
+
+I originally saw this in the form of a Flash game called \i{FreeNet}
+\k{FreeNet}, written by Pavils Jurjans; there are several other
+implementations under the name \i{NetWalk}. The computer prepares a
+network by connecting up the centres of squares in a grid, and then
+shuffles the network by rotating every tile randomly. Your job is to
+rotate it all back into place. The successful solution will be an
+entirely connected network, with no closed loops. \#{The latter
+clause means that there are no closed paths within the network.
+Could this be clearer? "No closed paths"?} As a visual aid,
+all tiles which are connected to the one in the middle are
+highlighted. 
+
+\B{FreeNet} \W{http://www.jurjans.lv/stuff/net/FreeNet.htm}\cw{http://www.jurjans.lv/stuff/net/FreeNet.htm}
+
+\H{net-controls} \i{Net controls}
+
+\IM{Net controls} controls, for Net
+\IM{Net controls} keys, for Net
+\IM{Net controls} shortcuts (keyboard), for Net
+
+This game can be played with either the keyboard or the mouse. The
+controls are:
+
+\dt \e{Select tile}: mouse pointer, arrow keys
+
+\dt \e{Rotate tile anticlockwise}: left mouse button, \q{A} key
+
+\dt \e{Rotate tile clockwise}: right mouse button, \q{D} key
+
+\dt \e{Rotate tile by 180 degrees}: \q{F} key
+
+\dt \e{Lock (or unlock) tile}: middle mouse button, shift-click, \q{S} key
+
+\dd You can lock a tile once you're sure of its orientation. You can
+also unlock it again, but while it's locked you can't accidentally
+turn it.
+
+The following controls are not necessary to complete the game, but may
+be useful:
+
+\dt \e{Shift grid}: Shift + arrow keys
+
+\dd On grids that wrap, you can move the origin of the grid, so that
+tiles that were on opposite sides of the grid can be seen together.
+
+\dt \e{Move centre}: Ctrl + arrow keys
+
+\dd You can change which tile is used as the source of highlighting.
+(It doesn't ultimately matter which tile this is, as every tile will
+be connected to every other tile in a correct solution, but it may be
+helpful in the intermediate stages of solving the puzzle.)
+
+\dt \e{Jumble tiles}: \q{J} key
+
+\dd This key turns all tiles that are not locked to random
+orientations.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{net-params} \I{parameters, for Net}Net parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in tiles.
+
+\dt \e{Walls wrap around}
+
+\dd If checked, flow can pass from the left edge to the right edge,
+and from top to bottom, and vice versa.
+
+\dt \e{Barrier probability}
+
+\dd A number between 0.0 and 1.0 controlling whether an immovable
+barrier is placed between two tiles to prevent flow between them (a
+higher number gives more barriers). Since barriers are immovable, they
+act as constraints on the solution (i.e., hints).
+
+\lcont{
+
+The grid generation in Net has been carefully arranged so that the
+barriers are independent of the rest of the grid. This means that if
+you note down the random seed used to generate the current puzzle
+(see \k{common-id}), change the \e{Barrier probability} parameter,
+and then re-enter the same random seed, you should see exactly the
+same starting grid, with the only change being the number of
+barriers. So if you're stuck on a particular grid and need a hint,
+you could start up another instance of Net, set up the same
+parameters but a higher barrier probability, and enter the game seed
+from the original Net window.
+
+}
+
+\dt \e{Ensure unique solution}
+
+\dd Normally, Net will make sure that the puzzles it presents have
+only one solution. Puzzles with ambiguous sections can be more
+difficult and more subtle, so if you like you can turn off this
+feature and risk having ambiguous puzzles. (Also, finding \e{all}
+the possible solutions can be an additional challenge for an
+advanced player.)
+
+
+\C{cube} \i{Cube}
+
+\cfg{winhelp-topic}{games.cube}
+
+This is another one I originally saw as a web game. This one was a
+Java game \k{cube-java-game}, by Paul Scott. You have a grid of 16
+squares, six of which are blue; on one square rests a cube. Your move
+is to use the arrow keys to roll the cube through 90 degrees so that
+it moves to an adjacent square. If you roll the cube on to a blue
+square, the blue square is picked up on one face of the cube; if you
+roll a blue face of the cube on to a non-blue square, the blueness is
+put down again. (In general, whenever you roll the cube, the two faces
+that come into contact swap colours.) Your job is to get all six blue
+squares on to the six faces of the cube at the same time. Count your
+moves and try to do it in as few as possible. 
+
+Unlike the original Java game, my version has an additional feature:
+once you've mastered the game with a cube rolling on a square grid,
+you can change to a triangular grid and roll any of a tetrahedron, an
+octahedron or an icosahedron. 
+
+\B{cube-java-game} \W{http://www3.sympatico.ca/paulscott/cube/cube.htm}\cw{http://www3.sympatico.ca/paulscott/cube/cube.htm}
+
+\H{cube-controls} \i{Cube controls}
+
+\IM{Cube controls} controls, for Cube
+\IM{Cube controls} keys, for Cube
+\IM{Cube controls} shortcuts (keyboard), for Cube
+
+This game can be played with either the keyboard or the mouse.
+
+Left-clicking anywhere on the window will move the cube (or other
+solid) towards the mouse pointer.
+
+The arrow keys can also used to roll the cube on its square grid in
+the four cardinal directions.
+On the triangular grids, the mapping of arrow keys to directions is
+more approximate. Vertical movement is disallowed where it doesn't
+make sense. The four keys surrounding the arrow keys on the numeric
+keypad (\q{7}, \q{9}, \q{1}, \q{3}) can be used for diagonal movement.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{cube-params} \I{parameters, for Cube}Cube parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Type of solid}
+
+\dd Selects the solid to roll (and hence the shape of the grid):
+tetrahedron, cube, octahedron, or icosahedron.
+
+\dt \e{Width / top}, \e{Height / bottom}
+
+\dd On a square grid, horizontal and vertical dimensions. On a
+triangular grid, the number of triangles on the top and bottom rows
+respectively.
+
+
+\C{fifteen} \i{Fifteen}
+
+\cfg{winhelp-topic}{games.fifteen}
+
+The old ones are the best: this is the good old \q{\i{15-puzzle}}
+with sliding tiles. You have a 4\by\.4 square grid; 15 squares
+contain numbered tiles, and the sixteenth is empty. Your move is to
+choose a tile next to the empty space, and slide it into the space.
+The aim is to end up with the tiles in numerical order, with the
+space in the bottom right (so that the top row reads 1,2,3,4 and the
+bottom row reads 13,14,15,\e{space}).
+
+\H{fifteen-controls} \i{Fifteen controls}
+
+\IM{Fifteen controls} controls, for Fifteen
+\IM{Fifteen controls} keys, for Fifteen
+\IM{Fifteen controls} shortcuts (keyboard), for Fifteen
+
+This game can be controlled with the mouse or the keyboard.
+
+A left-click with the mouse in the row or column containing the empty
+space will move as many tiles as necessary to move the space to the
+mouse pointer.
+
+The arrow keys will move a tile adjacent to the space in the direction
+indicated (moving the space in the \e{opposite} direction).
+
+Pressing \q{h} will make a suggested move.  Pressing \q{h} enough
+times will solve the game, but it may scramble your progress while
+doing so.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{fifteen-params} \I{parameters, for Fifteen}Fifteen parameters
+
+The only options available from the \q{Custom...} option on the \q{Type}
+menu are \e{Width} and \e{Height}, which are self-explanatory. (Once
+you've changed these, it's not a \q{15-puzzle} any more, of course!)
+
+
+\C{sixteen} \i{Sixteen}
+
+\cfg{winhelp-topic}{games.sixteen}
+
+Another sliding tile puzzle, visually similar to Fifteen (see
+\k{fifteen}) but with a different type of move. This time, there is no
+hole: all 16 squares on the grid contain numbered squares. Your move
+is to shift an entire row left or right, or shift an entire column up
+or down; every time you do that, the tile you shift off the grid
+re-appears at the other end of the same row, in the space you just
+vacated. To win, arrange the tiles into numerical order (1,2,3,4 on
+the top row, 13,14,15,16 on the bottom). When you've done that, try
+playing on different sizes of grid. 
+
+I \e{might} have invented this game myself, though only by accident if
+so (and I'm sure other people have independently invented it). I
+thought I was imitating a screensaver I'd seen, but I have a feeling
+that the screensaver might actually have been a Fifteen-type puzzle
+rather than this slightly different kind. So this might be the one
+thing in my puzzle collection which represents creativity on my part
+rather than just engineering.
+
+\H{sixteen-controls} \I{controls, for Sixteen}Sixteen controls
+
+Left-clicking on an arrow will move the appropriate row or column in
+the direction indicated.  Right-clicking will move it in the opposite
+direction.
+
+Alternatively, use the cursor keys to move the position indicator
+around the edge of the grid, and use the return key to move the
+row/column in the direction indicated. 
+
+You can also move the tiles directly.  Move the cursor onto a tile,
+hold Control and press an arrow key to move the tile under the
+cursor and move the cursor along with the tile.  Or, hold Shift to
+move only the tile.  Pressing Enter simulates holding down Control
+(press Enter again to release), while pressing Space simulates
+holding down shift.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{sixteen-params} \I{parameters, for Sixteen}Sixteen parameters
+
+The parameters available from the \q{Custom...} option on the
+\q{Type} menu are:
+
+\b \e{Width} and \e{Height}, which are self-explanatory.
+
+\b You can ask for a limited shuffling operation to be performed on
+the grid. By default, Sixteen will shuffle the grid in such a way
+that any arrangement is about as probable as any other. You can
+override this by requesting a precise number of shuffling moves to
+be performed. Typically your aim is then to determine the precise
+set of shuffling moves and invert them exactly, so that you answer
+(say) a four-move shuffle with a four-move solution. Note that the
+more moves you ask for, the more likely it is that solutions shorter
+than the target length will turn out to be possible.
+
+
+\C{twiddle} \i{Twiddle}
+
+\cfg{winhelp-topic}{games.twiddle}
+
+Twiddle is a tile-rearrangement puzzle, visually similar to Sixteen
+(see \k{sixteen}): you are given a grid of square tiles, each
+containing a number, and your aim is to arrange the numbers into
+ascending order.
+
+In basic Twiddle, your move is to rotate a square group of four
+tiles about their common centre. (Orientation is not significant in
+the basic puzzle, although you can select it.) On more advanced
+settings, you can rotate a larger square group of tiles.
+
+I first saw this type of puzzle in the GameCube game \q{Metroid
+Prime 2}. In the Main Gyro Chamber in that game, there is a puzzle
+you solve to unlock a door, which is a special case of Twiddle. I
+developed this game as a generalisation of that puzzle.
+
+\H{twiddle-controls} \I{controls, for Twiddle}Twiddle controls
+
+To play Twiddle, click the mouse in the centre of the square group
+you wish to rotate. In the basic mode, you rotate a 2\by\.2 square,
+which means you have to click at a corner point where four tiles
+meet.
+
+In more advanced modes you might be rotating 3\by\.3 or even more at
+a time; if the size of the square is odd then you simply click in
+the centre tile of the square you want to rotate.
+
+Clicking with the left mouse button rotates the group anticlockwise.
+Clicking with the right button rotates it clockwise.
+
+You can also move an outline square around the grid with the cursor
+keys; the square is the size above (2\by\.2 by default, or larger).
+Pressing the return key or space bar will rotate the current square
+anticlockwise or clockwise respectively.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{twiddle-parameters} \I{parameters, for Twiddle}Twiddle parameters
+
+Twiddle provides several configuration options via the \q{Custom}
+option on the \q{Type} menu:
+
+\b You can configure the width and height of the puzzle grid.
+
+\b You can configure the size of square block that rotates at a time.
+
+\b You can ask for every square in the grid to be distinguishable
+(the default), or you can ask for a simplified puzzle in which there
+are groups of identical numbers. In the simplified puzzle your aim
+is just to arrange all the 1s into the first row, all the 2s into
+the second row, and so on.
+
+\b You can configure whether the orientation of tiles matters. If
+you ask for an orientable puzzle, each tile will have a triangle
+drawn in it. All the triangles must be pointing upwards to complete
+the puzzle.
+
+\b You can ask for a limited shuffling operation to be performed on
+the grid. By default, Twiddle will shuffle the grid so much that any
+arrangement is about as probable as any other. You can override this
+by requesting a precise number of shuffling moves to be performed.
+Typically your aim is then to determine the precise set of shuffling
+moves and invert them exactly, so that you answer (say) a four-move
+shuffle with a four-move solution. Note that the more moves you ask
+for, the more likely it is that solutions shorter than the target
+length will turn out to be possible.
+
+
+\C{rect} \i{Rectangles}
+
+\cfg{winhelp-topic}{games.rectangles}
+
+You have a grid of squares, with numbers written in some (but not all)
+of the squares. Your task is to subdivide the grid into rectangles of
+various sizes, such that (a) every rectangle contains exactly one
+numbered square, and (b) the area of each rectangle is equal to the
+number written in its numbered square.
+
+Credit for this game goes to the Japanese puzzle magazine \i{Nikoli}
+\k{nikoli-rect}; I've also seen a Palm implementation at \i{Puzzle
+Palace} \k{puzzle-palace-rect}. Unlike Puzzle Palace's
+implementation, my version automatically generates random grids of
+any size you like. The quality of puzzle design is therefore not
+quite as good as hand-crafted puzzles would be, but on the plus side
+you get an inexhaustible supply of puzzles tailored to your own
+specification.
+
+\B{nikoli-rect} \W{http://www.nikoli.co.jp/en/puzzles/shikaku.html}\cw{http://www.nikoli.co.jp/en/puzzles/shikaku.html}
+(beware of Flash)
+
+\B{puzzle-palace-rect} \W{https://web.archive.org/web/20041024001459/http://www.puzzle.gr.jp/puzzle/sikaku/palm/index.html.en}\cw{https://web.archive.org/web/20041024001459/http://www.puzzle.gr.jp/puzzle/sikaku/palm/index.html.en}
+
+\H{rectangles-controls} \I{controls, for Rectangles}Rectangles controls
+
+This game is played with the mouse or cursor keys.
+
+Left-click any edge to toggle it on or off, or left-click and drag to draw
+an entire rectangle (or line) on the grid in one go (removing any existing
+edges within that rectangle). Right-clicking and dragging will allow you
+to erase the contents of a rectangle without affecting its edges. 
+
+Alternatively, use the cursor keys to move the position indicator
+around the board. Pressing the return key then allows you to use the
+cursor keys to drag a rectangle out from that position, and pressing
+the return key again completes the rectangle. Using the space bar
+instead of the return key allows you to erase the contents of a
+rectangle without affecting its edges, as above. Pressing escape
+cancels a drag.
+
+When a rectangle of the correct size is completed, it will be shaded.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{rectangles-params} \I{parameters, for Rectangles}Rectangles parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid, in squares.
+
+\dt \e{Expansion factor}
+
+\dd This is a mechanism for changing the type of grids generated by
+the program. Some people prefer a grid containing a few large
+rectangles to one containing many small ones. So you can ask
+Rectangles to essentially generate a \e{smaller} grid than the size
+you specified, and then to expand it by adding rows and columns.
+
+\lcont{
+
+The default expansion factor of zero means that Rectangles will
+simply generate a grid of the size you ask for, and do nothing
+further. If you set an expansion factor of (say) 0.5, it means that
+each dimension of the grid will be expanded to half again as big
+after generation. In other words, the initial grid will be 2/3 the
+size in each dimension, and will be expanded to its full size
+without adding any more rectangles.
+
+Setting an expansion factor of around 0.5 tends to make the game
+more difficult, and also (in my experience) rewards a less deductive
+and more intuitive playing style. If you set it \e{too} high,
+though, the game simply cannot generate more than a few rectangles
+to cover the entire grid, and the game becomes trivial.
+
+}
+
+\dt \e{Ensure unique solution}
+
+\dd Normally, Rectangles will make sure that the puzzles it presents
+have only one solution. Puzzles with ambiguous sections can be more
+difficult and more subtle, so if you like you can turn off this
+feature and risk having ambiguous puzzles. Also, finding \e{all} the
+possible solutions can be an additional challenge for an advanced
+player. Turning off this option can also speed up puzzle generation.
+
+
+\C{netslide} \i{Netslide}
+
+\cfg{winhelp-topic}{games.netslide}
+
+This game combines the grid generation of Net (see \k{net}) with the
+movement of Sixteen (see \k{sixteen}): you have a Net grid, but
+instead of rotating tiles back into place you have to slide them
+into place by moving a whole row at a time. 
+
+As in Sixteen, \I{controls, for Netslide}control is with the mouse or
+cursor keys. See \k{sixteen-controls}.
+
+\I{parameters, for Netslide}The available game parameters have similar
+meanings to those in Net (see \k{net-params}) and Sixteen (see
+\k{sixteen-params}).
+
+Netslide was contributed to this collection by Richard Boulton.
+
+
+\C{pattern} \i{Pattern}
+
+\cfg{winhelp-topic}{games.pattern}
+
+You have a grid of squares, which must all be filled in either black
+or white. Beside each row of the grid are listed the lengths of the
+runs of black squares on that row; above each column are listed the
+lengths of the runs of black squares in that column. Your aim is to
+fill in the entire grid black or white.
+
+I first saw this puzzle form around 1995, under the name
+\q{\i{nonograms}}. I've seen it in various places since then, under
+different names.
+
+Normally, puzzles of this type turn out to be a meaningful picture
+of something once you've solved them. However, since this version
+generates the puzzles automatically, they will just look like random
+groupings of squares. (One user has suggested that this is actually
+a \e{good} thing, since it prevents you from guessing the colour of
+squares based on the picture, and forces you to use logic instead.)
+The advantage, though, is that you never run out of them.
+
+\H{pattern-controls} \I{controls, for Pattern}Pattern controls
+
+This game is played with the mouse.
+
+Left-click in a square to colour it black. Right-click to colour it
+white. If you make a mistake, you can middle-click, or hold down
+Shift while clicking with any button, to colour the square in the
+default grey (meaning \q{undecided}) again.
+
+You can click and drag with the left or right mouse button to colour
+a vertical or horizontal line of squares black or white at a time
+(respectively). If you click and drag with the middle button, or
+with Shift held down, you can colour a whole rectangle of squares
+grey.
+
+You can also move around the grid with the cursor keys. Pressing the
+return key will cycle the current cell through empty, then black, then
+white, then empty, and the space bar does the same cycle in reverse.
+
+Moving the cursor while holding Control will colour the moved-over
+squares black.  Holding Shift will colour the moved-over squares
+white, and holding both will colour them grey.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{pattern-parameters} \I{parameters, for Pattern}Pattern parameters
+
+The only options available from the \q{Custom...} option on the \q{Type}
+menu are \e{Width} and \e{Height}, which are self-explanatory.
+
+
+\C{solo} \i{Solo}
+
+\cfg{winhelp-topic}{games.solo}
+
+You have a square grid, which is divided into as many equally sized
+sub-blocks as the grid has rows. Each square must be filled in with
+a digit from 1 to the size of the grid, in such a way that
+
+\b every row contains only one occurrence of each digit
+
+\b every column contains only one occurrence of each digit
+
+\b every block contains only one occurrence of each digit.
+
+\b (optionally, by default off) each of the square's two main
+diagonals contains only one occurrence of each digit.
+
+You are given some of the numbers as clues; your aim is to place the
+rest of the numbers correctly.
+
+Under the default settings, the sub-blocks are square or
+rectangular. The default puzzle size is 3\by\.3 (a 9\by\.9 actual
+grid, divided into nine 3\by\.3 blocks). You can also select sizes
+with rectangular blocks instead of square ones, such as 2\by\.3 (a
+6\by\.6 grid divided into six 3\by\.2 blocks). Alternatively, you
+can select \q{jigsaw} mode, in which the sub-blocks are arbitrary
+shapes which differ between individual puzzles.
+
+Another available mode is \q{killer}. In this mode, clues are not
+given in the form of filled-in squares; instead, the grid is divided
+into \q{cages} by coloured lines, and for each cage the game tells
+you what the sum of all the digits in that cage should be. Also, no
+digit may appear more than once within a cage, even if the cage
+crosses the boundaries of existing regions.
+
+If you select a puzzle size which requires more than 9 digits, the
+additional digits will be letters of the alphabet. For example, if
+you select 3\by\.4 then the digits which go in your grid will be 1
+to 9, plus \cq{a}, \cq{b} and \cq{c}. This cannot be selected for
+killer puzzles.
+
+I first saw this puzzle in \i{Nikoli} \k{nikoli-solo}, although it's
+also been popularised by various newspapers under the name
+\q{Sudoku} or \q{Su Doku}.  Howard Garns is considered the inventor
+of the modern form of the puzzle, and it was first published in
+\e{Dell Pencil Puzzles and Word Games}.  A more elaborate treatment
+of the history of the puzzle can be found on Wikipedia
+\k{wikipedia-solo}.
+
+\B{nikoli-solo} \W{http://www.nikoli.co.jp/en/puzzles/sudoku.html}\cw{http://www.nikoli.co.jp/en/puzzles/sudoku.html}
+(beware of Flash)
+
+\B{wikipedia-solo} \W{http://en.wikipedia.org/wiki/Sudoku}\cw{http://en.wikipedia.org/wiki/Sudoku}
+
+\H{solo-controls} \I{controls, for Solo}Solo controls
+
+To play Solo, simply click the mouse in any empty square and then
+type a digit or letter on the keyboard to fill that square. If you
+make a mistake, click the mouse in the incorrect square and press
+Space to clear it again (or use the Undo feature).
+
+If you \e{right}-click in a square and then type a number, that
+number will be entered in the square as a \q{pencil mark}. You can
+have pencil marks for multiple numbers in the same square. Squares
+containing filled-in numbers cannot also contain pencil marks.
+
+The game pays no attention to pencil marks, so exactly what you use
+them for is up to you: you can use them as reminders that a
+particular square needs to be re-examined once you know more about a
+particular number, or you can use them as lists of the possible
+numbers in a given square, or anything else you feel like.
+
+To erase a single pencil mark, right-click in the square and type
+the same number again.
+
+All pencil marks in a square are erased when you left-click and type
+a number, or when you left-click and press space. Right-clicking and
+pressing space will also erase pencil marks.
+
+Alternatively, use the cursor keys to move the mark around the grid.
+Pressing the return key toggles the mark (from a normal mark to a
+pencil mark), and typing a number in is entered in the square in the
+appropriate way; typing in a 0 or using the space bar will clear a
+filled square. 
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{solo-parameters} \I{parameters, for Solo}Solo parameters
+
+Solo allows you to configure two separate dimensions of the puzzle
+grid on the \q{Type} menu: the number of columns, and the number of
+rows, into which the main grid is divided. (The size of a block is
+the inverse of this: for example, if you select 2 columns and 3 rows,
+each actual block will have 3 columns and 2 rows.)
+
+If you tick the \q{X} checkbox, Solo will apply the optional extra
+constraint that the two main diagonals of the grid also contain one
+of every digit. (This is sometimes known as \q{Sudoku-X} in
+newspapers.) In this mode, the squares on the two main diagonals
+will be shaded slightly so that you know it's enabled.
+
+If you tick the \q{Jigsaw} checkbox, Solo will generate randomly
+shaped sub-blocks. In this mode, the actual grid size will be taken
+to be the product of the numbers entered in the \q{Columns} and
+\q{Rows} boxes. There is no reason why you have to enter a number
+greater than 1 in both boxes; Jigsaw mode has no constraint on the
+grid size, and it can even be a prime number if you feel like it.
+
+If you tick the \q{Killer} checkbox, Solo will generate a set of
+of cages, which are randomly shaped and drawn in an outline of a
+different colour.  Each of these regions contains a smaller clue
+which shows the digit sum of all the squares in this region.
+
+You can also configure the type of symmetry shown in the generated
+puzzles. More symmetry makes the puzzles look prettier but may also
+make them easier, since the symmetry constraints can force more
+clues than necessary to be present. Completely asymmetric puzzles
+have the freedom to contain as few clues as possible.
+
+Finally, you can configure the difficulty of the generated puzzles.
+Difficulty levels are judged by the complexity of the techniques of
+deduction required to solve the puzzle: each level requires a mode
+of reasoning which was not necessary in the previous one. In
+particular, on difficulty levels \q{Trivial} and \q{Basic} there
+will be a square you can fill in with a single number at all times,
+whereas at \q{Intermediate} level and beyond you will have to make
+partial deductions about the \e{set} of squares a number could be in
+(or the set of numbers that could be in a square).
+\#{Advanced, Extreme?}
+At \q{Unreasonable} level, even this is not enough, and you will
+eventually have to make a guess, and then backtrack if it turns out
+to be wrong.
+
+Generating difficult puzzles is itself difficult: if you select one
+of the higher difficulty levels, Solo may have to make many attempts
+at generating a puzzle before it finds one hard enough for you. Be
+prepared to wait, especially if you have also configured a large
+puzzle size.
+
+
+\C{mines} \i{Mines}
+
+\cfg{winhelp-topic}{games.mines}
+
+You have a grid of covered squares, some of which contain mines, but
+you don't know which. Your job is to uncover every square which does
+\e{not} contain a mine. If you uncover a square containing a mine,
+you lose. If you uncover a square which does not contain a mine, you
+are told how many mines are contained within the eight surrounding
+squares.
+
+This game needs no introduction; popularised by Windows, it is
+perhaps the single best known desktop puzzle game in existence.
+
+This version of it has an unusual property. By default, it will
+generate its mine positions in such a way as to ensure that you
+never need to \e{guess} where a mine is: you will always be able to
+deduce it somehow. So you will never, as can happen in other
+versions, get to the last four squares and discover that there are
+two mines left but you have no way of knowing for sure where they
+are.
+
+\H{mines-controls} \I{controls, for Mines}Mines controls
+
+This game is played with the mouse.
+
+If you left-click in a covered square, it will be uncovered.
+
+If you right-click in a covered square, it will place a flag which
+indicates that the square is believed to be a mine. Left-clicking in
+a marked square will not uncover it, for safety. You can right-click
+again to remove a mark placed in error.
+
+If you left-click in an \e{uncovered} square, it will \q{clear
+around} the square. This means: if the square has exactly as many
+flags surrounding it as it should have mines, then all the covered
+squares next to it which are \e{not} flagged will be uncovered. So
+once you think you know the location of all the mines around a
+square, you can use this function as a shortcut to avoid having to
+click on each of the remaining squares one by one.
+
+If you uncover a square which has \e{no} mines in the surrounding
+eight squares, then it is obviously safe to uncover those squares in
+turn, and so on if any of them also has no surrounding mines. This
+will be done for you automatically; so sometimes when you uncover a
+square, a whole new area will open up to be explored.
+
+You can also use the cursor keys to move around the minefield.
+Pressing the return key in a covered square uncovers it, and in an
+uncovered square will clear around it (so it acts as the left button),
+pressing the space bar in a covered square will place a flag
+(similarly, it acts as the right button).
+
+All the actions described in \k{common-actions} are also available.
+
+Even Undo is available, although you might consider it cheating to
+use it. If you step on a mine, the program will only reveal the mine
+in question (unlike most other implementations, which reveal all of
+them). You can then Undo your fatal move and continue playing if you
+like. The program will track the number of times you died (and Undo
+will not reduce that counter), so when you get to the end of the
+game you know whether or not you did it without making any errors.
+
+(If you really want to know the full layout of the grid, which other
+implementations will show you after you die, you can always use the
+Solve menu option.)
+
+\H{mines-parameters} \I{parameters, for Mines}Mines parameters
+
+The options available from the \q{Custom...} option on the \q{Type}
+menu are:
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares.
+
+\dt \e{Mines}
+
+\dd Number of mines in the grid. You can enter this as an absolute
+mine count, or alternatively you can put a \cw{%} sign on the end in
+which case the game will arrange for that proportion of the squares
+in the grid to be mines.
+
+\lcont{
+
+Beware of setting the mine count too high. At very high densities,
+the program may spend forever searching for a solvable grid.
+
+}
+
+\dt \e{Ensure solubility}
+
+\dd When this option is enabled (as it is by default), Mines will
+ensure that the entire grid can be fully deduced starting from the
+initial open space. If you prefer the riskier grids generated by
+other implementations, you can switch off this option.
+
+
+\C{samegame} \i{Same Game}
+
+\cfg{winhelp-topic}{games.samegame}
+
+You have a grid of coloured squares, which you have to clear by 
+highlighting contiguous regions of more than one coloured square;
+the larger the region you highlight, the more points you get (and
+the faster you clear the arena).
+
+If you clear the grid you win. If you end up with nothing but 
+single squares (i.e., there are no more clickable regions left) you
+lose.
+
+Removing a region causes the rest of the grid to shuffle up:
+blocks that are suspended will fall down (first), and then empty
+columns are filled from the right. 
+
+Same Game was contributed to this collection by James Harvey.
+
+\H{samegame-controls} \i{Same Game controls}
+
+\IM{Same Game controls} controls, for Same Game
+\IM{Same Game controls} keys, for Same Game
+\IM{Same Game controls} shortcuts (keyboard), for Same Game
+
+This game can be played with either the keyboard or the mouse.
+
+If you left-click an unselected region, it becomes selected (possibly
+clearing the current selection). 
+
+If you left-click the selected region, it will be removed (and the
+rest of the grid shuffled immediately).
+
+If you right-click the selected region, it will be unselected. 
+
+The cursor keys move a cursor around the grid. Pressing the Space or
+Enter keys while the cursor is in an unselected region selects it;
+pressing Space or Enter again removes it as above.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{samegame-parameters} \I{parameters, for Same Game}Same Game parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares.
+
+\dt \e{No. of colours}
+
+\dd Number of different colours used to fill the grid; the more colours,
+the fewer large regions of colour and thus the more difficult it is to
+successfully clear the grid.
+
+\dt \e{Scoring system}
+
+\dd Controls the precise mechanism used for scoring. With the default
+system, \q{(n-2)^2}, only regions of three squares or more will score
+any points at all. With the alternative \q{(n-1)^2} system, regions of
+two squares score a point each, and larger regions score relatively
+more points.
+
+\dt \e{Ensure solubility}
+
+\dd If this option is ticked (the default state), generated grids
+will be guaranteed to have at least one solution.
+
+\lcont{
+
+If you turn it off, the game generator will not try to guarantee
+soluble grids; it will, however, still ensure that there are at
+least 2 squares of each colour on the grid at the start (since a
+grid with exactly one square of a given colour is \e{definitely}
+insoluble). Grids generated with this option disabled may contain
+more large areas of contiguous colour, leading to opportunities for
+higher scores; they can also take less time to generate.
+
+}
+
+
+\C{flip} \i{Flip}
+
+\cfg{winhelp-topic}{games.flip}
+
+You have a grid of squares, some light and some dark. Your aim is to
+light all the squares up at the same time. You can choose any square
+and flip its state from light to dark or dark to light, but when you
+do so, other squares around it change state as well.
+
+Each square contains a small diagram showing which other squares
+change when you flip it.
+
+\H{flip-controls} \i{Flip controls}
+
+\IM{Flip controls} controls, for Flip
+\IM{Flip controls} keys, for Flip
+\IM{Flip controls} shortcuts (keyboard), for Flip
+
+This game can be played with either the keyboard or the mouse.
+
+Left-click in a square to flip it and its associated squares, or
+use the cursor keys to choose a square and the space bar or Enter
+key to flip.
+
+If you use the \q{Solve} function on this game, it will mark some of
+the squares in red. If you click once in every square with a red
+mark, the game should be solved. (If you click in a square
+\e{without} a red mark, a red mark will appear in it to indicate
+that you will need to reverse that operation to reach the solution.)
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{flip-parameters} \I{parameters, for flip}Flip parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares.
+
+\dt \e{Shape type}
+
+\dd This control determines the shape of the region which is flipped
+by clicking in any given square. The default setting, \q{Crosses},
+causes every square to flip itself and its four immediate neighbours
+(or three or two if it's at an edge or corner). The other setting,
+\q{Random}, causes a random shape to be chosen for every square, so
+the game is different every time.
+
+
+\C{guess} \i{Guess}
+
+\cfg{winhelp-topic}{games.guess}
+
+You have a set of coloured pegs, and have to reproduce a
+predetermined sequence of them (chosen by the computer) within a
+certain number of guesses. 
+
+Each guess gets marked with the number of correctly-coloured pegs
+in the correct places (in black), and also the number of
+correctly-coloured pegs in the wrong places (in white). 
+
+This game is also known (and marketed, by Hasbro, mainly) as
+a board game \q{\i{Mastermind}}, with 6 colours, 4 pegs per row,
+and 10 guesses. However, this version allows custom settings of number
+of colours (up to 10), number of pegs per row, and number of guesses. 
+
+Guess was contributed to this collection by James Harvey.
+
+\H{guess-controls} \i{Guess controls}
+
+\IM{Guess controls} controls, for Guess
+\IM{Guess controls} keys, for Guess
+\IM{Guess controls} shortcuts (keyboard), for Guess
+
+This game can be played with either the keyboard or the mouse.
+
+With the mouse, drag a coloured peg from the tray on the left-hand
+side to its required position in the current guess; pegs may also be
+dragged from current and past guesses to copy them elsewhere. To
+remove a peg, drag it off its current position to somewhere invalid.
+
+Right-clicking in the current guess adds a \q{hold} marker; pegs
+that have hold markers will be automatically added to the next guess
+after marking.
+
+Alternatively, with the keyboard, the up and down cursor keys can be
+used to select a peg colour, the left and right keys to select a
+peg position, and the space bar or Enter key to place a peg of the
+selected colour in the chosen position. \q{D} or Backspace removes a
+peg, and Space adds a hold marker.
+
+Pressing \q{h} or \q{?} will fill the current guess with a suggested
+guess.  Using this is not recommended for 10 or more pegs as it is
+slow.
+
+When the guess is complete, the smaller feedback pegs will be highlighted;
+clicking on these (or moving the peg cursor to them with the arrow keys
+and pressing the space bar or Enter key) will mark the current guess,
+copy any held pegs to the next guess, and move the \q{current guess}
+marker.
+
+If you correctly position all the pegs the solution will be displayed
+below; if you run out of guesses (or select \q{Solve...}) the solution
+will also be revealed.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{guess-parameters} \I{parameters, for Guess}Guess parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu. The default game matches the parameters for the 
+board game \q{Mastermind}. 
+
+\dt \e{Colours}
+
+\dd Number of colours the solution is chosen from; from 2 to 10
+(more is harder).
+
+\dt \e{Pegs per guess}
+
+\dd Number of pegs per guess (more is harder).
+
+\dt \e{Guesses}
+
+\dd Number of guesses you have to find the solution in (fewer is harder).
+
+\dt \e{Allow blanks}
+
+\dd Allows blank pegs to be given as part of a guess (makes it easier, because
+you know that those will never be counted as part of the solution). This
+is turned off by default. 
+
+\lcont{
+
+Note that this doesn't allow blank pegs in the solution; if you really wanted
+that, use one extra colour.
+
+}
+
+\dt \e{Allow duplicates}
+
+\dd Allows the solution (and the guesses) to contain colours more than once;
+this increases the search space (making things harder), and is turned on by
+default.
+
+
+\C{pegs} \i{Pegs}
+
+\cfg{winhelp-topic}{games.pegs}
+
+A number of pegs are placed in holes on a board. You can remove a
+peg by jumping an adjacent peg over it (horizontally or vertically)
+to a vacant hole on the other side. Your aim is to remove all but one
+of the pegs initially present.
+
+This game, best known as \I{Solitaire, Peg}\q{Peg Solitaire}, is
+possibly one of the oldest puzzle games still commonly known.
+
+\H{pegs-controls} \i{Pegs controls}
+
+\IM{Pegs controls} controls, for Pegs
+
+To move a peg, drag it with the mouse from its current position to
+its final position. If the final position is exactly two holes away
+from the initial position, is currently unoccupied by a peg, and
+there is a peg in the intervening square, the move will be permitted
+and the intervening peg will be removed.
+
+Vacant spaces which you can move a peg into are marked with holes. A
+space with no peg and no hole is not available for moving at all: it
+is an obstacle which you must work around.
+
+You can also use the cursor keys to move a position indicator around
+the board. Pressing the return key while over a peg, followed by a
+cursor key, will jump the peg in that direction (if that is a legal
+move).
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{pegs-parameters} \I{parameters, for Pegs}Pegs parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in holes.
+
+\dt \e{Board type}
+
+\dd Controls whether you are given a board of a standard shape or a
+randomly generated shape. The two standard shapes currently
+supported are \q{Cross} and \q{Octagon} (also commonly known as the
+English and European traditional board layouts respectively).
+Selecting \q{Random} will give you a different board shape every
+time (but always one that is known to have a solution).
+
+
+\C{dominosa} \i{Dominosa}
+
+\cfg{winhelp-topic}{games.dominosa}
+
+A normal set of dominoes \dash that is, one instance of every
+(unordered) pair of numbers from 0 to 6 \dash has been arranged
+irregularly into a rectangle; then the number in each square has
+been written down and the dominoes themselves removed. Your task is
+to reconstruct the pattern by arranging the set of dominoes to match
+the provided array of numbers.
+
+This puzzle is widely credited to O. S. Adler, and takes part of its
+name from those initials.
+
+\H{dominosa-controls} \i{Dominosa controls}
+
+\IM{Dominosa controls} controls, for Dominosa
+
+Left-clicking between any two adjacent numbers places a domino
+covering them, or removes one if it is already present. Trying to
+place a domino which overlaps existing dominoes will remove the ones
+it overlaps.
+
+Right-clicking between two adjacent numbers draws a line between
+them, which you can use to remind yourself that you know those two
+numbers are \e{not} covered by a single domino. Right-clicking again
+removes the line.
+
+You can also use the cursor keys to move a cursor around the grid.
+When the cursor is half way between two adjacent numbers, pressing
+the return key will place a domino covering those numbers, or
+pressing the space bar will lay a line between the two squares.
+Repeating either action removes the domino or line.
+
+Pressing a number key will highlight all occurrences of that
+number. Pressing that number again will clear the highlighting. Up to two
+different numbers can be highlighted at any given time.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{dominosa-parameters} \I{parameters, for Dominosa}Dominosa parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Maximum number on dominoes}
+
+\dd Controls the size of the puzzle, by controlling the size of the
+set of dominoes used to make it. Dominoes with numbers going up to N
+will give rise to an (N+2) \by (N+1) rectangle; so, in particular,
+the default value of 6 gives an 8\by\.7 grid.
+
+\dt \e{Ensure unique solution}
+
+\dd Normally, Dominosa will make sure that the puzzles it presents
+have only one solution. Puzzles with ambiguous sections can be more
+difficult and sometimes more subtle, so if you like you can turn off
+this feature. Also, finding \e{all} the possible solutions can be an
+additional challenge for an advanced player. Turning off this option
+can also speed up puzzle generation.
+
+
+\C{untangle} \i{Untangle}
+
+\cfg{winhelp-topic}{games.untangle}
+
+You are given a number of points, some of which have lines drawn
+between them. You can move the points about arbitrarily; your aim is
+to position the points so that no line crosses another.
+
+I originally saw this in the form of a Flash game called \i{Planarity}
+\k{Planarity}, written by John Tantalo.
+
+\B{Planarity} \W{http://planarity.net}\cw{http://planarity.net}
+
+\H{untangle-controls} \i{Untangle controls}
+
+\IM{Untangle controls} controls, for Untangle
+
+To move a point, click on it with the left mouse button and drag it
+into a new position.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{untangle-parameters} \I{parameters, for Untangle}Untangle parameters
+
+There is only one parameter available from the \q{Custom...} option
+on the \q{Type} menu:
+
+\dt \e{Number of points}
+
+\dd Controls the size of the puzzle, by specifying the number of
+points in the generated graph.
+
+
+\C{blackbox} \i{Black Box}
+
+\cfg{winhelp-topic}{games.blackbox}
+
+A number of balls are hidden in a rectangular arena. You have to
+deduce the positions of the balls by firing lasers positioned at
+the edges of the arena and observing how their beams are deflected. 
+
+Beams will travel straight from their origin until they hit the
+opposite side of the arena (at which point they emerge), unless
+affected by balls in one of the following ways:
+
+\b A beam that hits a ball head-on is absorbed and will never
+   re-emerge. This includes beams that meet a ball on the first rank
+   of the arena.
+
+\b A beam with a ball in its front-left square and no ball ahead of it
+   gets deflected 90 degrees to the right.
+
+\b A beam with a ball in its front-right square and no ball ahead of
+   it gets similarly deflected to the left.
+
+\b A beam that would re-emerge from its entry location is considered to be
+   \q{reflected}. 
+
+\b A beam which would get deflected before entering the arena by a
+   ball to the front-left or front-right of its entry point is also
+   considered to be \q{reflected}.
+
+Beams that are reflected appear as a \q{R}; beams that hit balls
+head-on appear as \q{H}. Otherwise, a number appears at the firing
+point and the location where the beam emerges (this number is unique
+to that shot).
+
+You can place guesses as to the location of the balls, based on the
+entry and exit patterns of the beams; once you have placed enough
+balls a button appears enabling you to have your guesses checked. 
+
+Here is a diagram showing how the positions of balls can create each
+of the beam behaviours shown above:
+
+\c  1RHR---- 
+\c |..O.O...|
+\c 2........3
+\c |........|
+\c |........|
+\c 3........|
+\c |......O.|
+\c H........|
+\c |.....O..|
+\c  12-RR---
+
+As shown, it is possible for a beam to receive multiple reflections
+before re-emerging (see turn 3). Similarly, a beam may be reflected
+(possibly more than once) before receiving a hit (the \q{H} on the
+left side of the example).
+
+Note that any layout with more than 4 balls may have a non-unique
+solution.  The following diagram illustrates this; if you know the
+board contains 5 balls, it is impossible to determine where the fifth
+ball is (possible positions marked with an \cw{x}):
+
+\c  -------- 
+\c |........|
+\c |........|
+\c |..O..O..|
+\c |...xx...|
+\c |...xx...|
+\c |..O..O..|
+\c |........|
+\c |........|
+\c  --------
+
+For this reason, when you have your guesses checked, the game will
+check that your solution \e{produces the same results} as the
+computer's, rather than that your solution is identical to the
+computer's. So in the above example, you could put the fifth ball at
+\e{any} of the locations marked with an \cw{x}, and you would still
+win.
+
+Black Box was contributed to this collection by James Harvey.
+
+\H{blackbox-controls} \i{Black Box controls}
+
+\IM{Black Box controls} controls, for Black Box
+\IM{Black Box controls} keys, for Black Box
+\IM{Black Box controls} shortcuts (keyboard), for Black Box
+
+To fire a laser beam, left-click in a square around the edge of the
+arena. The results will be displayed immediately. Clicking or holding
+the left button on one of these squares will highlight the current go
+(or a previous go) to confirm the exit point for that laser, if
+applicable.
+
+To guess the location of a ball, left-click within the arena and a
+black circle will appear marking the guess; click again to remove the
+guessed ball.
+
+Locations in the arena may be locked against modification by
+right-clicking; whole rows and columns may be similarly locked by
+right-clicking in the laser square above/below that column, or to the
+left/right of that row.
+
+The cursor keys may also be used to move around the grid. Pressing the
+Enter key will fire a laser or add a new ball-location guess, and
+pressing Space will lock a cell, row, or column.
+
+When an appropriate number of balls have been guessed, a button will
+appear at the top-left corner of the grid; clicking that (with mouse
+or cursor) will check your guesses. 
+
+If you click the \q{check} button and your guesses are not correct,
+the game will show you the minimum information necessary to
+demonstrate this to you, so you can try again. If your ball
+positions are not consistent with the beam paths you already know
+about, one beam path will be circled to indicate that it proves you
+wrong. If your positions match all the existing beam paths but are
+still wrong, one new beam path will be revealed (written in red)
+which is not consistent with your current guesses.
+
+If you decide to give up completely, you can select Solve to reveal
+the actual ball positions. At this point, correctly-placed balls
+will be displayed as filled black circles, incorrectly-placed balls
+as filled black circles with red crosses, and missing balls as filled
+red circles. In addition, a red circle marks any laser you had already
+fired which is not consistent with your ball layout (just as when you
+press the \q{check} button), and red text marks any laser you
+\e{could} have fired in order to distinguish your ball layout from the
+correct one.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{blackbox-parameters} \I{parameters, for Black Box}Black Box parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares. There are 2 \by \e{Width} \by \e{Height} lasers 
+per grid, two per row and two per column. 
+
+\dt \e{No. of balls}
+
+\dd Number of balls to place in the grid. This can be a single number,
+or a range (separated with a hyphen, like \q{2-6}), and determines the
+number of balls to place on the grid. The \q{reveal} button is only
+enabled if you have guessed an appropriate number of balls; a guess
+using a different number to the original solution is still acceptable,
+if all the beam inputs and outputs match.
+
+
+\C{slant} \i{Slant}
+
+\cfg{winhelp-topic}{games.slant}
+
+You have a grid of squares. Your aim is to draw a diagonal line
+through each square, and choose which way each line slants so that
+the following conditions are met:
+
+\b The diagonal lines never form a loop.
+
+\b Any point with a circled number has precisely that many lines
+meeting at it. (Thus, a 4 is the centre of a cross shape, whereas a
+zero is the centre of a diamond shape \dash or rather, a partial
+diamond shape, because a zero can never appear in the middle of the
+grid because that would immediately cause a loop.)
+
+Credit for this puzzle goes to \i{Nikoli} \k{nikoli-slant}.
+
+\B{nikoli-slant}
+\W{http://www.nikoli.co.jp/ja/puzzles/gokigen_naname}\cw{http://www.nikoli.co.jp/ja/puzzles/gokigen_naname}
+(in Japanese)
+
+\H{slant-controls} \i{Slant controls}
+
+\IM{Slant controls} controls, for Slant
+
+Left-clicking in a blank square will place a \cw{\\} in it (a line
+leaning to the left, i.e. running from the top left of the square to
+the bottom right). Right-clicking in a blank square will place a
+\cw{/} in it (leaning to the right, running from top right to bottom
+left).
+
+Continuing to click either button will cycle between the three
+possible square contents. Thus, if you left-click repeatedly in a
+blank square it will change from blank to \cw{\\} to \cw{/} back to
+blank, and if you right-click repeatedly the square will change from
+blank to \cw{/} to \cw{\\} back to blank. (Therefore, you can play
+the game entirely with one button if you need to.)
+
+You can also use the cursor keys to move around the grid. Pressing the
+return or space keys will place a \cw{\\} or a \cw{/}, respectively,
+and will then cycle them as above.  You can also press \cw{/} or
+\cw{\\} to place a \cw{/} or \cw{\\}, respectively, independent of
+what is already in the cursor square.  Backspace removes any line from
+the cursor square.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{slant-parameters} \I{parameters, for Slant}Slant parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares.
+
+\dt \e{Difficulty}
+
+\dd Controls the difficulty of the generated puzzle. At Hard level,
+you are required to do deductions based on knowledge of
+\e{relationships} between squares rather than always being able to
+deduce the exact contents of one square at a time. (For example, you
+might know that two squares slant in the same direction, even if you
+don't yet know what that direction is, and this might enable you to
+deduce something about still other squares.) Even at Hard level,
+guesswork and backtracking should never be necessary.
+
+
+\C{lightup} \i{Light Up}
+
+\cfg{winhelp-topic}{games.lightup}
+
+You have a grid of squares. Some are filled in black; some of the
+black squares are numbered. Your aim is to \q{light up} all the
+empty squares by placing light bulbs in some of them.
+
+Each light bulb illuminates the square it is on, plus all squares in
+line with it horizontally or vertically unless a black square is
+blocking the way.
+
+To win the game, you must satisfy the following conditions:
+
+\b All non-black squares are lit.
+
+\b No light is lit by another light.
+
+\b All numbered black squares have exactly that number of lights adjacent to
+   them (in the four squares above, below, and to the side).
+
+Non-numbered black squares may have any number of lights adjacent to them. 
+
+Credit for this puzzle goes to \i{Nikoli} \k{nikoli-lightup}.
+
+Light Up was contributed to this collection by James Harvey.
+
+\B{nikoli-lightup}
+\W{http://www.nikoli.co.jp/en/puzzles/akari.html}\cw{http://www.nikoli.co.jp/en/puzzles/akari.html}
+(beware of Flash)
+
+\H{lightup-controls} \i{Light Up controls}
+
+\IM{Light Up controls} controls, for Light Up
+
+Left-clicking in a non-black square will toggle the presence of a light
+in that square. Right-clicking in a non-black square toggles a mark there to aid
+solving; it can be used to highlight squares that cannot be lit, for example. 
+
+You may not place a light in a marked square, nor place a mark in a lit square.
+
+The game will highlight obvious errors in red. Lights lit by other
+lights are highlighted in this way, as are numbered squares which
+do not (or cannot) have the right number of lights next to them.
+  
+Thus, the grid is solved when all non-black squares have yellow
+highlights and there are no red lights.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{lightup-parameters} \I{parameters, for Light Up}Light Up parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares.
+
+\dt \e{%age of black squares}
+
+\dd Rough percentage of black squares in the grid.
+
+\lcont{
+
+This is a hint rather than an instruction. If the grid generator is
+unable to generate a puzzle to this precise specification, it will
+increase the proportion of black squares until it can.
+
+}
+
+\dt \e{Symmetry}
+
+\dd Allows you to specify the required symmetry of the black squares
+in the grid. (This does not affect the difficulty of the puzzles
+noticeably.)
+
+\dt \e{Difficulty}
+
+\dd \q{Easy} means that the puzzles should be soluble without
+backtracking or guessing, \q{Hard} means that some guesses will
+probably be necessary.
+
+
+\C{map} \i{Map}
+
+\cfg{winhelp-topic}{games.map}
+
+You are given a map consisting of a number of regions. Your task is
+to colour each region with one of four colours, in such a way that
+no two regions sharing a boundary have the same colour. You are
+provided with some regions already coloured, sufficient to make the
+remainder of the solution unique.
+
+Only regions which share a length of border are required to be
+different colours. Two regions which meet at only one \e{point}
+(i.e. are diagonally separated) may be the same colour.
+
+I believe this puzzle is original; I've never seen an implementation
+of it anywhere else. The concept of a \i{four-colouring} puzzle was
+suggested by Owen Dunn; credit must also go to Nikoli and to Verity
+Allan for inspiring the train of thought that led to me realising
+Owen's suggestion was a viable puzzle. Thanks also to Gareth Taylor
+for many detailed suggestions.
+
+\H{map-controls} \i{Map controls}
+
+\IM{Map controls} controls, for Map
+
+To colour a region, click the left mouse button on an existing
+region of the desired colour and drag that colour into the new
+region.
+
+(The program will always ensure the starting puzzle has at least one
+region of each colour, so that this is always possible!)
+
+If you need to clear a region, you can drag from an empty region, or
+from the puzzle boundary if there are no empty regions left.
+
+Dragging a colour using the \e{right} mouse button will stipple the
+region in that colour, which you can use as a note to yourself that
+you think the region \e{might} be that colour. A region can contain
+stipples in multiple colours at once. (This is often useful at the
+harder difficulty levels.)
+
+You can also use the cursor keys to move around the map: the colour of
+the cursor indicates the position of the colour you would drag (which
+is not obvious if you're on a region's boundary, since it depends on the
+direction from which you approached the boundary). Pressing the return
+key starts a drag of that colour, as above, which you control with the
+cursor keys; pressing the return key again finishes the drag. The
+space bar can be used similarly to create a stippled region. 
+Double-pressing the return key (without moving the cursor) will clear
+the region, as a drag from an empty region does: this is useful with
+the cursor mode if you have filled the entire map in but need to 
+correct the layout.
+
+If you press L during play, the game will toggle display of a number
+in each region of the map. This is useful if you want to discuss a
+particular puzzle instance with a friend \dash having an unambiguous
+name for each region is much easier than trying to refer to them all
+by names such as \q{the one down and right of the brown one on the
+top border}.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{map-parameters} \I{parameters, for Map}Map parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares.
+
+\dt \e{Regions}
+
+\dd Number of regions in the generated map.
+
+\dt \e{Difficulty}
+
+\dd In \q{Easy} mode, there should always be at least one region
+whose colour can be determined trivially. In \q{Normal} and \q{Hard}
+modes, you will have to use increasingly complex logic to deduce the
+colour of some regions. However, it will always be possible without
+having to guess or backtrack.
+
+\lcont{
+
+In \q{Unreasonable} mode, the program will feel free to generate
+puzzles which are as hard as it can possibly make them: the only
+constraint is that they should still have a unique solution. Solving
+Unreasonable puzzles may require guessing and backtracking.
+
+}
+
+
+\C{loopy} \i{Loopy}
+
+\cfg{winhelp-topic}{games.loopy}
+
+You are given a grid of dots, marked with yellow lines to indicate
+which dots you are allowed to connect directly together. Your aim is
+to use some subset of those yellow lines to draw a single unbroken
+loop from dot to dot within the grid.
+
+Some of the spaces between the lines contain numbers. These numbers
+indicate how many of the lines around that space form part of the
+loop. The loop you draw must correctly satisfy all of these clues to
+be considered a correct solution.
+
+In the default mode, the dots are arranged in a grid of squares;
+however, you can also play on triangular or hexagonal grids, or even
+more exotic ones.
+
+Credit for the basic puzzle idea goes to \i{Nikoli}
+\k{nikoli-loopy}.
+
+Loopy was originally contributed to this collection by Mike Pinna,
+and subsequently enhanced to handle various types of non-square grid
+by Lambros Lambrou.
+
+\B{nikoli-loopy}
+\W{http://www.nikoli.co.jp/en/puzzles/slitherlink.html}\cw{http://www.nikoli.co.jp/en/puzzles/slitherlink.html}
+(beware of Flash)
+
+\H{loopy-controls} \i{Loopy controls}
+
+\IM{Loopy controls} controls, for Loopy
+
+Click the left mouse button on a yellow line to turn it black,
+indicating that you think it is part of the loop. Click again to
+turn the line yellow again (meaning you aren't sure yet).
+
+If you are sure that a particular line segment is \e{not} part of
+the loop, you can click the right mouse button to remove it
+completely. Again, clicking a second time will turn the line back to
+yellow.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{loopy-parameters} \I{parameters, for Loopy}Loopy parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid, measured in number of regions across and down. For
+square grids, it's clear how this is counted; for other types of
+grid you may have to think a bit to see how the dimensions are
+measured.
+
+\dt \e{Grid type}
+
+\dd Allows you to choose between a selection of types of tiling.
+Some have all the faces the same but may have multiple different
+types of vertex (e.g. the \e{Cairo} or \e{Kites} mode); others have
+all the vertices the same but may have different types of face (e.g.
+the \e{Great Hexagonal}). The square, triangular and honeycomb grids
+are fully regular, and have all their vertices \e{and} faces the
+same; this makes them the least confusing to play.
+
+\dt \e{Difficulty}
+
+\dd Controls the difficulty of the generated puzzle.
+\#{FIXME: what distinguishes Easy, Medium, and Hard? In particular,
+when are backtracking/guesswork required, if ever?}
+
+
+\C{inertia} \i{Inertia}
+
+\cfg{winhelp-topic}{games.inertia}
+
+You are a small green ball sitting in a grid full of obstacles. Your
+aim is to collect all the gems without running into any mines.
+
+You can move the ball in any orthogonal \e{or diagonal} direction.
+Once the ball starts moving, it will continue until something stops
+it. A wall directly in its path will stop it (but if it is moving
+diagonally, it will move through a diagonal gap between two other
+walls without stopping). Also, some of the squares are \q{stops};
+when the ball moves on to a stop, it will stop moving no matter what
+direction it was going in. Gems do \e{not} stop the ball; it picks
+them up and keeps on going.
+
+Running into a mine is fatal. Even if you picked up the last gem in
+the same move which then hit a mine, the game will count you as dead
+rather than victorious.
+
+This game was originally implemented for Windows by Ben Olmstead
+\k{bem}, who was kind enough to release his source code on request
+so that it could be re-implemented for this collection.
+
+\B{bem} \W{http://xn13.com/}\cw{http://xn13.com/}
+
+\H{inertia-controls} \i{Inertia controls}
+
+\IM{Inertia controls} controls, for Inertia
+\IM{Inertia controls} keys, for Inertia
+\IM{Inertia controls} shortcuts (keyboard), for Inertia
+
+You can move the ball in any of the eight directions using the
+numeric keypad. Alternatively, if you click the left mouse button on
+the grid, the ball will begin a move in the general direction of
+where you clicked.
+
+If you use the \q{Solve} function on this game, the program will
+compute a path through the grid which collects all the remaining
+gems and returns to the current position. A hint arrow will appear
+on the ball indicating the direction in which you should move to
+begin on this path. If you then move in that direction, the arrow
+will update to indicate the next direction on the path. You can also
+press Space to automatically move in the direction of the hint
+arrow. If you move in a different direction from the one shown by
+the arrow, arrows will be shown only if the puzzle is still solvable.
+
+All the actions described in \k{common-actions} are also available.
+In particular, if you do run into a mine and die, you can use the
+Undo function and resume playing from before the fatal move. The
+game will keep track of the number of times you have done this.
+
+\H{inertia-parameters} \I{parameters, for Inertia}Inertia parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares.
+
+
+\C{tents} \i{Tents}
+
+\cfg{winhelp-topic}{games.tents}
+
+You have a grid of squares, some of which contain trees. Your aim is
+to place tents in some of the remaining squares, in such a way that
+the following conditions are met:
+
+\b There are exactly as many tents as trees.
+
+\b The tents and trees can be matched up in such a way that each
+tent is directly adjacent (horizontally or vertically, but not
+diagonally) to its own tree. However, a tent may be adjacent to
+other trees as well as its own.
+
+\b No two tents are adjacent horizontally, vertically \e{or
+diagonally}.
+
+\b The number of tents in each row, and in each column, matches the
+numbers given round the sides of the grid.
+
+This puzzle can be found in several places on the Internet, and was
+brought to my attention by e-mail. I don't know who I should credit
+for inventing it.
+
+\H{tents-controls} \i{Tents controls}
+
+\IM{Tents controls} controls, for Tents
+
+Left-clicking in a blank square will place a tent in it.
+Right-clicking in a blank square will colour it green, indicating
+that you are sure it \e{isn't} a tent. Clicking either button in an
+occupied square will clear it.
+
+If you \e{drag} with the right button along a row or column, every
+blank square in the region you cover will be turned green, and no
+other squares will be affected. (This is useful for clearing the
+remainder of a row once you have placed all its tents.)
+
+You can also use the cursor keys to move around the grid. Pressing the
+return key over an empty square will place a tent, and pressing the
+space bar over an empty square will colour it green; either key will
+clear an occupied square.  Holding Shift and pressing the cursor keys
+will colour empty squares green.  Holding Control and pressing the
+cursor keys will colour green both empty squares and squares with tents.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{tents-parameters} \I{parameters, for Tents}Tents parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares.
+
+\dt \e{Difficulty}
+
+\dd Controls the difficulty of the generated puzzle. More difficult
+puzzles require more complex deductions, but at present none of the
+available difficulty levels requires guesswork or backtracking.
+
+
+\C{bridges} \i{Bridges}
+
+\cfg{winhelp-topic}{games.bridges}
+
+You have a set of islands distributed across the playing area. Each
+island contains a number. Your aim is to connect the islands
+together with bridges, in such a way that:
+
+\b Bridges run horizontally or vertically.
+
+\b The number of bridges terminating at any island is equal to the
+number written in that island.
+
+\b Two bridges may run in parallel between the same two islands, but
+no more than two may do so.
+
+\b No bridge crosses another bridge.
+
+\b All the islands are connected together.
+
+There are some configurable alternative modes, which involve
+changing the parallel-bridge limit to something other than 2, and
+introducing the additional constraint that no sequence of bridges
+may form a loop from one island back to the same island. The rules
+stated above are the default ones.
+
+Credit for this puzzle goes to \i{Nikoli} \k{nikoli-bridges}.
+
+Bridges was contributed to this collection by James Harvey.
+
+\B{nikoli-bridges}
+\W{http://www.nikoli.co.jp/en/puzzles/hashiwokakero.html}\cw{http://www.nikoli.co.jp/en/puzzles/hashiwokakero.html}
+(beware of Flash)
+
+\H{bridges-controls} \i{Bridges controls}
+
+\IM{Bridges controls} controls, for Bridges
+
+To place a bridge between two islands, click the mouse down on one
+island and drag it towards the other. You do not need to drag all
+the way to the other island; you only need to move the mouse far
+enough for the intended bridge direction to be unambiguous. (So you
+can keep the mouse near the starting island and conveniently throw
+bridges out from it in many directions.)
+
+Doing this again when a bridge is already present will add another
+parallel bridge. If there are already as many bridges between the
+two islands as permitted by the current game rules (i.e. two by
+default), the same dragging action will remove all of them.
+
+If you want to remind yourself that two islands definitely \e{do
+not} have a bridge between them, you can right-drag between them in
+the same way to draw a \q{non-bridge} marker.
+
+If you think you have finished with an island (i.e. you have placed
+all its bridges and are confident that they are in the right
+places), you can mark the island as finished by left-clicking on it.
+This will highlight it and all the bridges connected to it, and you
+will be prevented from accidentally modifying any of those bridges
+in future. Left-clicking again on a highlighted island will unmark
+it and restore your ability to modify it.
+
+You can also use the cursor keys to move around the grid: if possible
+the cursor will always move orthogonally, otherwise it will move
+towards the nearest island to the indicated direction. Holding Control
+and pressing a cursor key will lay a bridge in that direction (if
+available); Shift and a cursor key will lay a \q{non-bridge} marker.
+Pressing the return key followed by a cursor key will also lay a
+bridge in that direction.
+
+You can mark an island as finished by pressing the space bar or by
+pressing the return key twice.
+
+By pressing a number key, you can jump to the nearest island with that
+number.  Letters \q{a}, ..., \q{f} count as 10, ..., 15 and \q{0} as
+16.
+
+Violations of the puzzle rules will be marked in red:
+
+\b An island with too many bridges will be highlighted in red.
+
+\b An island with too few bridges will be highlighted in red if it
+is definitely an error (as opposed to merely not being finished
+yet): if adding enough bridges would involve having to cross another
+bridge or remove a non-bridge marker, or if the island has been
+highlighted as complete.
+
+\b A group of islands and bridges may be highlighted in red if it is
+a closed subset of the puzzle with no way to connect it to the rest
+of the islands. For example, if you directly connect two 1s together
+with a bridge and they are not the only two islands on the grid,
+they will light up red to indicate that such a group cannot be
+contained in any valid solution.
+
+\b If you have selected the (non-default) option to disallow loops
+in the solution, a group of bridges which forms a loop will be
+highlighted.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{bridges-parameters} \I{parameters, for Bridges}Bridges parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares.
+
+\dt \e{Difficulty}
+
+\dd Difficulty level of puzzle.
+
+\dt \e{Allow loops}
+
+\dd This is set by default. If cleared, puzzles will be generated in
+such a way that they are always soluble without creating a loop, and
+solutions which do involve a loop will be disallowed.
+
+\dt \e{Max. bridges per direction}
+
+\dd Maximum number of bridges in any particular direction. The
+default is 2, but you can change it to 1, 3 or 4. In general, fewer
+is easier.
+
+\dt \e{%age of island squares}
+
+\dd Gives a rough percentage of islands the generator will try and
+lay before finishing the puzzle. Certain layouts will not manage to
+lay enough islands; this is an upper bound.
+
+\dt \e{Expansion factor (%age)}
+
+\dd The grid generator works by picking an existing island at random
+(after first creating an initial island somewhere). It then decides
+on a direction (at random), and then works out how far it could
+extend before creating another island. This parameter determines how
+likely it is to extend as far as it can, rather than choosing
+somewhere closer.
+
+\lcont{
+
+High expansion factors usually mean easier puzzles with fewer
+possible islands; low expansion factors can create lots of
+tightly-packed islands.
+
+}
+
+
+\C{unequal} \i{Unequal}
+
+\cfg{winhelp-topic}{games.unequal}
+
+You have a square grid; each square may contain a digit from 1 to
+the size of the grid, and some squares have clue signs between
+them. Your aim is to fully populate the grid with numbers such that:
+
+\b Each row contains only one occurrence of each digit
+
+\b Each column contains only one occurrence of each digit
+
+\b All the clue signs are satisfied. 
+
+There are two modes for this game, \q{Unequal} and \q{Adjacent}.
+
+In \q{Unequal} mode, the clue signs are greater-than symbols indicating one
+square's value is greater than its neighbour's. In this mode not all clues
+may be visible, particularly at higher difficulty levels. 
+
+In \q{Adjacent} mode, the clue signs are bars indicating
+one square's value is numerically adjacent (i.e. one higher or one lower)
+than its neighbour. In this mode all clues are always visible: absence of
+a bar thus means that a square's value is definitely not numerically adjacent
+to that neighbour's.  
+
+In \q{Trivial} difficulty level (available via the \q{Custom} game type
+selector), there are no greater-than signs in \q{Unequal} mode; the puzzle is
+to solve the \i{Latin square} only.
+
+At the time of writing, the \q{Unequal} mode of this puzzle is appearing in the
+Guardian weekly under the name \q{\i{Futoshiki}}.
+
+Unequal was contributed to this collection by James Harvey.
+
+\H{unequal-controls} \i{Unequal controls}
+
+\IM{Unequal controls} controls, for Unequal
+
+Unequal shares much of its control system with Solo.
+
+To play Unequal, simply click the mouse in any empty square and then
+type a digit or letter on the keyboard to fill that square. If you
+make a mistake, click the mouse in the incorrect square and press
+Space to clear it again (or use the Undo feature).
+
+If you \e{right}-click in a square and then type a number, that
+number will be entered in the square as a \q{pencil mark}. You can
+have pencil marks for multiple numbers in the same square. Squares
+containing filled-in numbers cannot also contain pencil marks.
+
+The game pays no attention to pencil marks, so exactly what you use
+them for is up to you: you can use them as reminders that a
+particular square needs to be re-examined once you know more about a
+particular number, or you can use them as lists of the possible
+numbers in a given square, or anything else you feel like.
+
+To erase a single pencil mark, right-click in the square and type
+the same number again.
+
+All pencil marks in a square are erased when you left-click and type
+a number, or when you left-click and press space. Right-clicking and
+pressing space will also erase pencil marks.
+
+As for Solo, the cursor keys can be used in conjunction with the digit
+keys to set numbers or pencil marks. You can also use the \q{M} key to
+auto-fill every numeric hint, ready for removal as required, or the \q{H}
+key to do the same but also to remove all obvious hints. 
+
+Alternatively, use the cursor keys to move the mark around the grid.
+Pressing the return key toggles the mark (from a normal mark to a
+pencil mark), and typing a number in is entered in the square in the
+appropriate way; typing in a 0 or using the space bar will clear a
+filled square. 
+
+Left-clicking a clue will mark it as done (grey it out), or unmark it
+if it is already marked.  Holding Control or Shift and pressing an
+arrow key likewise marks any clue adjacent to the cursor in the given
+direction.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{unequal-parameters} \I{parameters, for Unequal}Unequal parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Mode}
+
+\dd Mode of the puzzle (\q{Unequal} or \q{Adjacent})
+
+\dt \e{Size (s*s)}
+
+\dd Size of grid.
+
+\dt \e{Difficulty}
+
+\dd Controls the difficulty of the generated puzzle. At Trivial
+level, there are no greater-than signs; the puzzle is to solve the
+Latin square only. At Recursive level (only available via the
+\q{Custom} game type selector) backtracking will be required, but
+the solution should still be unique. The levels in between require
+increasingly complex reasoning to avoid having to backtrack.
+
+
+
+\C{galaxies} \i{Galaxies}
+
+\cfg{winhelp-topic}{games.galaxies}
+
+You have a rectangular grid containing a number of dots. Your aim is
+to draw edges along the grid lines which divide the rectangle into
+regions in such a way that every region is 180\u00b0{-degree}
+rotationally symmetric, and contains exactly one dot which is
+located at its centre of symmetry.
+
+This puzzle was invented by \i{Nikoli} \k{nikoli-galaxies}, under
+the name \q{Tentai Show}; its name is commonly translated into
+English as \q{Spiral Galaxies}.
+
+Galaxies was contributed to this collection by James Harvey.
+
+\B{nikoli-galaxies} \W{http://www.nikoli.co.jp/en/puzzles/astronomical_show.html}\cw{http://www.nikoli.co.jp/en/puzzles/astronomical_show.html}
+
+\H{galaxies-controls} \i{Galaxies controls}
+
+\IM{Galaxies controls} controls, for Galaxies
+
+Left-click on any grid line to draw an edge if there isn't one
+already, or to remove one if there is. When you create a valid
+region (one which is closed, contains exactly one dot, is
+180\u00b0{-degree} symmetric about that dot, and contains no
+extraneous edges inside it) it will be highlighted automatically; so
+your aim is to have the whole grid highlighted in that way.
+
+During solving, you might know that a particular grid square belongs
+to a specific dot, but not be sure of where the edges go and which
+other squares are connected to the dot. In order to mark this so you
+don't forget, you can right-click on the dot and drag, which will
+create an arrow marker pointing at the dot. Drop that in a square of
+your choice and it will remind you which dot it's associated with.
+You can also right-click on existing arrows to pick them up and move
+them, or destroy them by dropping them off the edge of the grid.
+(Also, if you're not sure which dot an arrow is pointing at, you can
+pick it up and move it around to make it clearer. It will swivel
+constantly as you drag it, to stay pointed at its parent dot.)
+
+You can also use the cursor keys to move around the grid squares and
+lines.  Pressing the return key when over a grid line will draw or
+clear its edge, as above. Pressing the return key when over a dot will
+pick up an arrow, to be dropped the next time the return key is
+pressed; this can also be used to move existing arrows around, removing
+them by dropping them on a dot or another arrow.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{galaxies-parameters} \I{parameters, for Galaxies}Galaxies parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares.
+
+\dt \e{Difficulty}
+
+\dd Controls the difficulty of the generated puzzle. More difficult
+puzzles require more complex deductions, and the \q{Unreasonable}
+difficulty level may require backtracking.
+
+
+
+\C{filling} \i{Filling}
+
+\cfg{winhelp-topic}{games.filling}
+
+You have a grid of squares, some of which contain digits, and the
+rest of which are empty. Your job is to fill in digits in the empty
+squares, in such a way that each connected region of squares all
+containing the same digit has an area equal to that digit.
+
+(\q{Connected region}, for the purposes of this game, does not count
+diagonally separated squares as adjacent.)
+
+For example, it follows that no square can contain a zero, and that
+two adjacent squares can not both contain a one.  No region has an
+area greater than 9 (because then its area would not be a single
+digit).
+
+Credit for this puzzle goes to \i{Nikoli} \k{nikoli-fillomino}.
+
+Filling was contributed to this collection by Jonas K\u00F6{oe}lker.
+
+\B{nikoli-fillomino}
+\W{http://www.nikoli.co.jp/en/puzzles/fillomino.html}\cw{http://www.nikoli.co.jp/en/puzzles/fillomino.html}
+
+\H{filling-controls} \I{controls, for Filling}Filling controls
+
+To play Filling, simply click the mouse in any empty square and then
+type a digit on the keyboard to fill that square. By dragging the
+mouse, you can select multiple squares to fill with a single keypress.
+If you make a mistake, click the mouse in the incorrect square and
+press 0, Space, Backspace or Enter to clear it again (or use the Undo
+feature).
+
+You can also move around the grid with the cursor keys; typing a digit will
+fill the square containing the cursor with that number; typing 0 will clear
+it.  You can also select multiple squares for numbering or clearing with the
+return and arrow keys, before typing a digit to fill or clear the highlighted
+squares (as above).  The space bar adds and removes single squares to and from
+the selection.  Backspace and escape remove all squares from the selection.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{filling-parameters} \I{parameters, for Filling}Filling parameters
+
+Filling allows you to configure the number of rows and columns of the
+grid, through the \q{Type} menu.
+
+
+\C{keen} \i{Keen}
+
+\cfg{winhelp-topic}{games.keen}
+
+You have a square grid; each square may contain a digit from 1 to
+the size of the grid. The grid is divided into blocks of varying
+shape and size, with arithmetic clues written in them. Your aim is
+to fully populate the grid with digits such that:
+
+\b Each row contains only one occurrence of each digit
+
+\b Each column contains only one occurrence of each digit
+
+\b The digits in each block can be combined to form the number
+stated in the clue, using the arithmetic operation given in the
+clue. That is:
+
+\lcont{
+
+\b An addition clue means that the sum of the digits in the block
+must be the given number. For example, \q{15+} means the contents of
+the block adds up to fifteen.
+
+\b A multiplication clue (e.g. \q{60\times}), similarly, means that
+the product of the digits in the block must be the given number.
+
+\b A subtraction clue will always be written in a block of size two,
+and it means that one of the digits in the block is greater than the
+other by the given amount. For example, \q{2\minus} means that one
+of the digits in the block is 2 more than the other, or equivalently
+that one digit minus the other one is 2. The two digits could be
+either way round, though.
+
+\b A division clue (e.g. \q{3\divide}), similarly, is always in a
+block of size two and means that one digit divided by the other is
+equal to the given amount.
+
+Note that a block may contain the same digit more than once
+(provided the identical ones are not in the same row and column).
+This rule is precisely the opposite of the rule in Solo's \q{Killer}
+mode (see \k{solo}).
+
+}
+
+This puzzle appears in the Times under the name \q{\i{KenKen}}.
+
+
+\H{keen-controls} \i{Keen controls}
+
+\IM{Keen controls} controls, for Keen
+
+Keen shares much of its control system with Solo (and Unequal).
+
+To play Keen, simply click the mouse in any empty square and then
+type a digit on the keyboard to fill that square. If you make a
+mistake, click the mouse in the incorrect square and press Space to
+clear it again (or use the Undo feature).
+
+If you \e{right}-click in a square and then type a number, that
+number will be entered in the square as a \q{pencil mark}. You can
+have pencil marks for multiple numbers in the same square. Squares
+containing filled-in numbers cannot also contain pencil marks.
+
+The game pays no attention to pencil marks, so exactly what you use
+them for is up to you: you can use them as reminders that a
+particular square needs to be re-examined once you know more about a
+particular number, or you can use them as lists of the possible
+numbers in a given square, or anything else you feel like.
+
+To erase a single pencil mark, right-click in the square and type
+the same number again.
+
+All pencil marks in a square are erased when you left-click and type
+a number, or when you left-click and press space. Right-clicking and
+pressing space will also erase pencil marks.
+
+As for Solo, the cursor keys can be used in conjunction with the
+digit keys to set numbers or pencil marks. Use the cursor keys to
+move a highlight around the grid, and type a digit to enter it in
+the highlighted square. Pressing return toggles the highlight into a
+mode in which you can enter or remove pencil marks.
+
+Pressing M will fill in a full set of pencil marks in every square
+that does not have a main digit in it.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{keen-parameters} \I{parameters, for Keen}Keen parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Grid size}
+
+\dd Specifies the size of the grid. Lower limit is 3; upper limit is
+9 (because the user interface would become more difficult with
+\q{digits} bigger than 9!).
+
+\dt \e{Difficulty}
+
+\dd Controls the difficulty of the generated puzzle. At Unreasonable
+level, some backtracking will be required, but the solution should
+still be unique. The remaining levels require increasingly complex
+reasoning to avoid having to backtrack.
+
+\dt \e{Multiplication only}
+
+\dd If this is enabled, all boxes will be multiplication boxes.
+With this rule, the puzzle is known as \q{Inshi No Heya}.
+
+\C{towers} \i{Towers}
+
+\cfg{winhelp-topic}{games.towers}
+
+You have a square grid. On each square of the grid you can build a
+tower, with its height ranging from 1 to the size of the grid.
+Around the edge of the grid are some numeric clues.
+
+Your task is to build a tower on every square, in such a way that:
+
+\b Each row contains every possible height of tower once
+
+\b Each column contains every possible height of tower once
+
+\b Each numeric clue describes the number of towers that can be seen
+if you look into the square from that direction, assuming that
+shorter towers are hidden behind taller ones. For example, in a
+5\by\.5 grid, a clue marked \q{5} indicates that the five tower
+heights must appear in increasing order (otherwise you would not be
+able to see all five towers), whereas a clue marked \q{1} indicates
+that the tallest tower (the one marked 5) must come first.
+
+In harder or larger puzzles, some towers will be specified for you
+as well as the clues round the edge, and some edge clues may be
+missing.
+
+This puzzle appears on the web under various names, particularly
+\q{\i{Skyscrapers}}, but I don't know who first invented it.
+
+
+\H{towers-controls} \i{Towers controls}
+
+\IM{Towers controls} controls, for Towers
+
+Towers shares much of its control system with Solo, Unequal and Keen.
+
+To play Towers, simply click the mouse in any empty square and then
+type a digit on the keyboard to fill that square with a tower of the
+given height. If you make a mistake, click the mouse in the
+incorrect square and press Space to clear it again (or use the Undo
+feature).
+
+If you \e{right}-click in a square and then type a number, that
+number will be entered in the square as a \q{pencil mark}. You can
+have pencil marks for multiple numbers in the same square. A square
+containing a tower cannot also contain pencil marks.
+
+The game pays no attention to pencil marks, so exactly what you use
+them for is up to you: you can use them as reminders that a
+particular square needs to be re-examined once you know more about a
+particular number, or you can use them as lists of the possible
+numbers in a given square, or anything else you feel like.
+
+To erase a single pencil mark, right-click in the square and type
+the same number again.
+
+All pencil marks in a square are erased when you left-click and type
+a number, or when you left-click and press space. Right-clicking and
+pressing space will also erase pencil marks.
+
+As for Solo, the cursor keys can be used in conjunction with the
+digit keys to set numbers or pencil marks. Use the cursor keys to
+move a highlight around the grid, and type a digit to enter it in
+the highlighted square. Pressing return toggles the highlight into a
+mode in which you can enter or remove pencil marks.
+
+Pressing M will fill in a full set of pencil marks in every square
+that does not have a main digit in it.
+
+Left-clicking a clue will mark it as done (grey it out), or unmark it
+if it is already marked.  Holding Control or Shift and pressing an
+arrow key likewise marks any clue in the given direction.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{towers-parameters} \I{parameters, for Towers}Towers parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Grid size}
+
+\dd Specifies the size of the grid. Lower limit is 3; upper limit is
+9 (because the user interface would become more difficult with
+\q{digits} bigger than 9!).
+
+\dt \e{Difficulty}
+
+\dd Controls the difficulty of the generated puzzle. At Unreasonable
+level, some backtracking will be required, but the solution should
+still be unique. The remaining levels require increasingly complex
+reasoning to avoid having to backtrack.
+
+
+\C{singles} \i{Singles}
+
+\cfg{winhelp-topic}{games.singles}
+
+You have a grid of white squares, all of which contain numbers. Your task
+is to colour some of the squares black (removing the number) so as to satisfy
+all of the following conditions:
+
+\b No number occurs more than once in any row or column.
+
+\b No black square is horizontally or vertically adjacent to any other black
+square.
+
+\b The remaining white squares must all form one contiguous region
+(connected by edges, not just touching at corners).
+
+Credit for this puzzle goes to \i{Nikoli} \k{nikoli-hitori} who call it
+\i{Hitori}. 
+
+Singles was contributed to this collection by James Harvey.
+
+\B{nikoli-hitori}
+\W{http://www.nikoli.com/en/puzzles/hitori.html}\cw{http://www.nikoli.com/en/puzzles/hitori.html}
+(beware of Flash)
+
+\H{singles-controls} \i{Singles controls}
+
+\IM{Singles controls} controls, for Singles
+
+Left-clicking on an empty square will colour it black; left-clicking again 
+will restore the number. Right-clicking will add a circle (useful for 
+indicating that a cell is definitely not black). 
+
+You can also use the cursor keys to move around the grid. Pressing the
+return or space keys will turn a square black or add a circle respectively,
+and pressing the key again will restore the number or remove the circle.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{singles-parameters} \I{parameters, for Singles}Singles parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares.
+
+\dt \e{Difficulty}
+
+\dd Controls the difficulty of the generated puzzle.
+
+
+\C{magnets} \i{Magnets}
+
+\cfg{winhelp-topic}{games.magnets}
+
+A rectangular grid has been filled with a mixture of magnets (that is,
+dominoes with one positive end and one negative end) and blank dominoes
+(that is, dominoes with two neutral poles).
+These dominoes are initially only seen in silhouette. Around the grid
+are placed a number of clues indicating the number of positive and
+negative poles contained in certain columns and rows.
+
+Your aim is to correctly place the magnets and blank dominoes such that
+all the clues are satisfied, with the additional constraint that no two
+similar magnetic poles may be orthogonally adjacent (since they repel).
+Neutral poles do not repel, and can be adjacent to any other pole. 
+
+Credit for this puzzle goes to \i{Janko} \k{janko-magnets}.
+
+Magnets was contributed to this collection by James Harvey.
+
+\B{janko-magnets}
+\W{http://www.janko.at/Raetsel/Magnete/index.htm}\cw{http://www.janko.at/Raetsel/Magnete/index.htm}
+
+\H{magnets-controls} \i{Magnets controls}
+
+\IM{Magnets controls} controls, for Magnets
+
+Left-clicking on an empty square places a magnet at that position with
+the positive pole on the square and the negative pole on the other half
+of the magnet; left-clicking again reverses the polarity, and a third
+click removes the magnet.
+
+Right-clicking on an empty square places a blank domino there.
+Right-clicking again places two question marks on the domino, signifying
+\q{this cannot be blank} (which can be useful to note deductions while
+solving), and right-clicking again empties the domino. 
+
+Left-clicking a clue will mark it as done (grey it out), or unmark it if
+it is already marked.
+
+You can also use the cursor keys to move a cursor around the grid. 
+Pressing the return key will lay a domino with a positive pole at that
+position; pressing again reverses the polarity and then removes the
+domino, as with left-clicking. Using the space bar allows placement
+of blank dominoes and cannot-be-blank hints, as for right-clicking. 
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{magnets-parameters} \I{parameters, for Magnets}Magnets parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares. There will be half \e{Width} \by \e{Height}
+dominoes in the grid: if this number is odd then one square will be blank.
+
+\lcont{
+
+(Grids with at least one odd dimension tend to be easier to solve.)
+
+}
+
+\dt \e{Difficulty}
+
+\dd Controls the difficulty of the generated puzzle. At Tricky level,
+you are required to make more deductions about empty dominoes and
+row/column counts. 
+
+\dt \e{Strip clues}
+
+\dd If true, some of the clues around the grid are removed at generation
+time, making the puzzle more difficult.
+
+
+\C{signpost} \i{Signpost}
+
+\cfg{winhelp-topic}{games.signpost}
+
+You have a grid of squares; each square (except the last one)
+contains an arrow, and some squares also contain numbers. Your job
+is to connect the squares to form a continuous list of numbers
+starting at 1 and linked in the direction of the arrows \dash so the
+arrow inside the square with the number 1 will point to the square
+containing the number 2, which will point to the square containing
+the number 3, etc. Each square can be any distance away from the
+previous one, as long as it is somewhere in the direction of the
+arrow.
+
+By convention the first and last numbers are shown; one or more
+interim numbers may also appear at the beginning. 
+
+Credit for this puzzle goes to \i{Janko} \k{janko-arrowpath}, who call it
+\q{Pfeilpfad} (\q{arrow path}).
+
+Signpost was contributed to this collection by James Harvey.
+
+\B{janko-arrowpath}
+\W{http://janko.at/Raetsel/Pfeilpfad/index.htm}\cw{http://janko.at/Raetsel/Pfeilpfad/index.htm}
+
+\H{signpost-controls} \I{controls, for Signpost}Signpost controls
+
+To play Signpost, you connect squares together by dragging from one
+square to another, indicating that they are adjacent in the
+sequence. Drag with the left button from a square to its successor,
+or with the right button from a square to its predecessor.
+
+If you connect together two squares in this way and one of them has
+a number in it, the appropriate number will appear in the other
+square. If you connect two non-numbered squares, they will be
+assigned temporary algebraic labels: on the first occasion, they
+will be labelled \cq{a} and \cq{a+1}, and then \cq{b} and \cq{b+1},
+and so on. Connecting more squares on to the ends of such a chain
+will cause them all to be labelled with the same letter.
+
+When you left-click or right-click in a square, the legal squares to
+connect it to will be shown.
+
+The arrow in each square starts off black, and goes grey once you
+connect the square to its successor. Also, each square which needs a
+predecessor has a small dot in the bottom left corner, which
+vanishes once you link a square to it. So your aim is always to
+connect a square with a black arrow to a square with a dot.
+
+To remove any links for a particular square (both incoming and
+outgoing), left-drag it off the grid. To remove a whole chain,
+right-drag any square in the chain off the grid.
+
+You can also use the cursor keys to move around the grid squares and
+lines. Pressing the return key when over a square starts a link
+operation, and pressing the return key again over a square will
+finish the link, if allowable. Pressing the space bar over a square
+will show the other squares pointing to it, and allow you to form a
+backward link, and pressing the space bar again cancels this.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{signpost-parameters} \I{parameters, for Signpost}Signpost parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares.
+
+\dt \e{Force start/end to corners}
+
+\dd If true, the start and end squares are always placed in opposite corners
+(the start at the top left, and the end at the bottom right). If false the start
+and end squares are placed randomly (although always both shown). 
+
+\C{range} \i{Range}
+
+\cfg{winhelp-topic}{games.range}
+
+You have a grid of squares; some squares contain numbers.  Your job is
+to colour some of the squares black, such that several criteria are
+satisfied:
+
+\b no square with a number is coloured black.
+
+\b no two black squares are adjacent (horizontally or vertically).
+
+\b for any two white squares, there is a path between them using only
+white squares.
+
+\b for each square with a number, that number denotes the total number
+of white squares reachable from that square going in a straight line
+in any horizontal or vertical direction until hitting a wall or a
+black square; the square with the number is included in the total
+(once).
+
+For instance, a square containing the number one must have four black
+squares as its neighbours by the last criterion; but then it's
+impossible for it to be connected to any outside white square, which
+violates the second to last criterion.  So no square will contain the
+number one.
+
+Credit for this puzzle goes to \i{Nikoli}, who have variously called
+it \q{Kurodoko}, \q{Kuromasu} or \q{Where is Black Cells}.
+\k{nikoli-range}.
+
+Range was contributed to this collection by Jonas K\u00F6{oe}lker.
+
+\B{nikoli-range}
+\W{http://www.nikoli.co.jp/en/puzzles/where_is_black_cells.html}\cw{http://www.nikoli.co.jp/en/puzzles/where_is_black_cells.html}
+
+\H{range-controls} \I{controls, for Range}Range controls
+
+Click with the left button to paint a square black, or with the right
+button to mark a square with a dot to indicate that you are sure it
+should \e{not} be painted black. Repeated clicking with either button
+will cycle the square through the three possible states (filled,
+dotted or empty) in opposite directions.
+
+You can also use the cursor keys to move around the grid squares.
+Pressing Return does the same as clicking with the left button, while
+pressing Space does the same as a right button click.  Moving with the
+cursor keys while holding Shift will place dots in all squares that
+are moved through.
+
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{range-parameters} \I{parameters, for Range}Range parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares.
+
+\C{pearl} \i{Pearl}
+
+\cfg{winhelp-topic}{games.pearl}
+
+You have a grid of squares. Your job is to draw lines between the
+centres of horizontally or vertically adjacent squares, so that the
+lines form a single closed loop. In the resulting grid, some of the
+squares that the loop passes through will contain corners, and some
+will be straight horizontal or vertical lines. (And some squares can
+be completely empty \dash the loop doesn't have to pass through every
+square.)
+
+Some of the squares contain black and white circles, which are clues
+that the loop must satisfy.
+
+A black circle in a square indicates that that square is a corner, but
+neither of the squares adjacent to it in the loop is also a corner.
+
+A white circle indicates that the square is a straight edge, but \e{at
+least one} of the squares adjacent to it in the loop is a corner.
+
+(In both cases, the clue only constrains the two squares adjacent
+\e{in the loop}, that is, the squares that the loop passes into after
+leaving the clue square. The squares that are only adjacent \e{in the
+grid} are not constrained.)
+
+Credit for this puzzle goes to \i{Nikoli}, who call it \q{Masyu}.
+\k{nikoli-pearl}
+
+Thanks to James Harvey for assistance with the implementation.
+
+\B{nikoli-pearl}
+\W{http://www.nikoli.co.jp/en/puzzles/masyu.html}\cw{http://www.nikoli.co.jp/en/puzzles/masyu.html}
+(beware of Flash)
+
+\H{pearl-controls} \I{controls, for Pearl}Pearl controls
+
+Click with the left button on a grid edge to draw a segment of the
+loop through that edge, or to remove a segment once it is drawn.
+
+Drag with the left button through a series of squares to draw more
+than one segment of the loop in one go. Alternatively, drag over an
+existing part of the loop to undraw it, or to undraw part of it and
+then go in a different direction.
+
+Click with the right button on a grid edge to mark it with a cross,
+indicating that you are sure the loop does not go through that edge.
+(For instance, if you have decided which of the squares adjacent to a
+white clue has to be a corner, but don't yet know which way the corner
+turns, you might mark the one way it \e{can't} go with a cross.)
+
+Alternatively, use the cursor keys to move the cursor.  Use the Enter
+key to begin and end keyboard \q{drag} operations.  Use the Space,
+Escape or Backspace keys to cancel the drag.  Or, hold Control while
+dragging with the cursor keys to toggle segments as you move between
+squares.
+
+Pressing Control-Shift-arrowkey or Shift-arrowkey simulates a left or
+right click, respectively, on the edge in the direction of the key.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{pearl-parameters} \I{parameters, for Pearl}Pearl parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\C{undead} \i{Undead}
+
+\cfg{winhelp-topic}{games.undead}
+
+You are given a grid of squares, some of which contain diagonal
+mirrors. Every square which is not a mirror must be filled with one of
+three types of undead monster: a ghost, a vampire, or a zombie.
+
+Vampires can be seen directly, but are invisible when reflected in
+mirrors. Ghosts are the opposite way round: they can be seen in
+mirrors, but are invisible when looked at directly. Zombies are
+visible by any means.
+
+You are also told the total number of each type of monster in the
+grid. Also around the edge of the grid are written numbers, which
+indicate how many monsters can be seen if you look into the grid along
+a row or column starting from that position. (The diagonal mirrors are
+reflective on both sides. If your reflected line of sight crosses the
+same monster more than once, the number will count it each time it is
+visible, not just once.)
+
+This puzzle type was invented by David Millar, under the name
+\q{Haunted Mirror Maze}. See \k{janko-undead} for more details.
+
+Undead was contributed to this collection by Steffen Bauer.
+
+\B{janko-undead}
+\W{http://www.janko.at/Raetsel/Spukschloss/index.htm}\cw{http://www.janko.at/Raetsel/Spukschloss/index.htm}
+
+\H{undead-controls} \I{controls, for Undead}Undead controls
+
+Undead has a similar control system to Solo, Unequal and Keen.
+
+To play Undead, click the mouse in any empty square and then type a
+letter on the keyboard indicating the type of monster: \q{G} for a
+ghost, \q{V} for a vampire, or \q{Z} for a zombie. If you make a
+mistake, click the mouse in the incorrect square and press Space to
+clear it again (or use the Undo feature).
+
+If you \e{right}-click in a square and then type a letter, the
+corresponding monster will be shown in reduced size in that square, as
+a \q{pencil mark}. You can have pencil marks for multiple monsters in
+the same square. A square containing a full-size monster cannot also
+contain pencil marks.
+
+The game pays no attention to pencil marks, so exactly what you use
+them for is up to you: you can use them as reminders that a particular
+square needs to be re-examined once you know more about a particular
+monster, or you can use them as lists of the possible monster in a
+given square, or anything else you feel like.
+
+To erase a single pencil mark, right-click in the square and type
+the same letter again.
+
+All pencil marks in a square are erased when you left-click and type a
+monster letter, or when you left-click and press Space. Right-clicking
+and pressing space will also erase pencil marks.
+
+As for Solo, the cursor keys can be used in conjunction with the letter
+keys to place monsters or pencil marks. Use the cursor keys to move a
+highlight around the grid, and type a monster letter to enter it in
+the highlighted square. Pressing return toggles the highlight into a
+mode in which you can enter or remove pencil marks.
+
+If you prefer plain letters of the alphabet to cute monster pictures,
+you can press \q{A} to toggle between showing the monsters as monsters or
+showing them as letters.
+
+Left-clicking a clue will mark it as done (grey it out), or unmark it
+if it is already marked.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{undead-parameters} \I{parameters, for Undead}Undead parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares.
+
+\dt \e{Difficulty}
+
+\dd Controls the difficulty of the generated puzzle.
+
+\C{unruly} \i{Unruly}
+
+\cfg{winhelp-topic}{games.unruly}
+
+You are given a grid of squares, which you must colour either black or
+white. Some squares are provided as clues; the rest are left for you
+to fill in. Each row and column must contain the same number of black
+and white squares, and no row or column may contain three consecutive
+squares of the same colour.
+
+This puzzle type was invented by Adolfo Zanellati, under the name
+\q{Tohu wa Vohu}. See \k{janko-unruly} for more details.
+
+Unruly was contributed to this collection by Lennard Sprong.
+
+\B{janko-unruly}
+\W{http://www.janko.at/Raetsel/Tohu-Wa-Vohu/index.htm}\cw{http://www.janko.at/Raetsel/Tohu-Wa-Vohu/index.htm}
+
+\H{unruly-controls} \I{controls, for Unruly}Unruly controls
+
+To play Unruly, click the mouse in a square to change its colour.
+Left-clicking an empty square will turn it black, and right-clicking
+will turn it white. Keep clicking the same button to cycle through the
+three possible states for the square. If you middle-click in a square
+it will be reset to empty.
+
+You can also use the cursor keys to move around the grid. Pressing the
+return or space keys will turn an empty square black or white
+respectively (and then cycle the colours in the same way as the mouse
+buttons), and pressing Backspace will reset a square to empty.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{unruly-parameters} \I{parameters, for Unruly}Unruly parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares. (Note that the rules of the game require
+both the width and height to be even numbers.)
+
+\dt \e{Difficulty}
+
+\dd Controls the difficulty of the generated puzzle.
+
+\dt \e{Unique rows and columns}
+
+\dd If enabled, no two rows are permitted to have exactly the same
+pattern, and likewise columns. (A row and a column can match, though.)
+
+\C{flood} \i{Flood}
+
+\cfg{winhelp-topic}{games.flood}
+
+You are given a grid of squares, coloured at random in multiple
+colours. In each move, you can flood-fill the top left square in a
+colour of your choice (i.e. every square reachable from the starting
+square by an orthogonally connected path of squares all the same
+colour will be filled in the new colour). As you do this, more and
+more of the grid becomes connected to the starting square.
+
+Your aim is to make the whole grid the same colour, in as few moves as
+possible. The game will set a limit on the number of moves, based on
+running its own internal solver. You win if you can make the whole
+grid the same colour in that many moves or fewer.
+
+I saw this game (with a fixed grid size, fixed number of colours, and
+fixed move limit) at http://floodit.appspot.com (no longer accessible).
+
+\H{flood-controls} \I{controls, for Flood}Flood controls
+
+To play Flood, click the mouse in a square. The top left corner and
+everything connected to it will be flood-filled with the colour of the
+square you clicked. Clicking a square the same colour as the top left
+corner has no effect, and therefore does not count as a move.
+
+You can also use the cursor keys to move a cursor (outline black
+square) around the grid. Pressing the return key will fill the top
+left corner in the colour of the square under the cursor.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{flood-parameters} \I{parameters, for Flood}Flood parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of the grid, in squares.
+
+\dt \e{Colours}
+
+\dd Number of colours used to fill the grid. Must be at least 3 (with
+two colours there would only be one legal move at any stage, hence no
+choice to make at all), and at most 10.
+
+\dt \e{Extra moves permitted}
+
+\dd Controls the difficulty of the puzzle, by increasing the move
+limit. In each new grid, Flood will run an internal solver to generate
+its own solution, and then the value in this field will be added to
+the length of Flood's solution to generate the game's move limit. So a
+value of 0 requires you to be just as efficient as Flood's automated
+solver, and a larger value makes it easier.
+
+\lcont{
+
+(Note that Flood's internal solver will not necessarily find the
+shortest possible solution, though I believe it's pretty close. For a
+real challenge, set this value to 0 and then try to solve a grid in
+\e{strictly fewer} moves than the limit you're given!)
+
+}
+
+\C{tracks} \i{Tracks}
+
+\cfg{winhelp-topic}{games.tracks}
+
+You are given a grid of squares, some of which are filled with train
+tracks. You need to complete the track from A to B so that the rows and
+columns contain the same number of track segments as are indicated in the
+clues to the top and right of the grid.
+
+There are only straight and 90 degree curved rails, and the track may not
+cross itself.
+
+Tracks was contributed to this collection by James Harvey.
+
+\H{tracks-controls} \I{controls, for Tracks}Tracks controls
+
+Left-clicking on an edge between two squares adds a track segment between
+the two squares. Right-clicking on an edge adds a cross on the edge,
+indicating no track is possible there.
+
+Left-clicking in a square adds a colour indicator showing that you know the
+square must contain a track, even if you don't know which edges it crosses
+yet. Right-clicking in a square adds a cross indicating it contains no
+track segment.
+
+Left- or right-dragging between squares allows you to lay a straight line
+of is-track or is-not-track indicators, useful for filling in rows or
+columns to match the clue.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{tracks-parameters} \I{parameters, for Tracks}Tracks parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of the grid, in squares.
+
+\dt \e{Difficulty}
+
+\dd Controls the difficulty of the generated puzzle: at Tricky level,
+you are required to make more deductions regarding disregarding moves
+that would lead to impossible crossings later.
+
+\dt \e{Disallow consecutive 1 clues}
+
+\dd Controls whether the Tracks game generation permits two adjacent
+rows or columns to have a 1 clue, or permits the row or column of the
+track's endpoint to have a 1 clue. By default this is not permitted,
+to avoid long straight boring segments of track and make the games
+more twiddly and interesting. If you want to restore the possibility,
+turn this option off.
+
+
+\C{palisade} \i{Palisade}
+
+\cfg{winhelp-topic}{games.palisade}
+
+You're given a grid of squares, some of which contain numbers.  Your
+goal is to subdivide the grid into contiguous regions, all of the same
+(given) size, such that each square containing a number is adjacent to
+exactly that many edges (including those between the inside and the
+outside of the grid).
+
+Credit for this puzzle goes to \i{Nikoli}, who call it \q{Five Cells}.
+\k{nikoli-palisade}.
+
+Palisade was contributed to this collection by Jonas K\u00F6{oe}lker.
+
+\B{nikoli-palisade}
+\W{http://nikoli.co.jp/en/puzzles/five_cells.html}\cw{http://nikoli.co.jp/en/puzzles/five_cells.html}
+
+\H{palisade-controls} \I{controls, for Palisade}Palisade controls
+
+Left-click to place an edge.  Right-click to indicate \q{no edge}.
+Alternatively, the arrow keys will move a keyboard cursor.  Holding
+Control while pressing an arrow key will place an edge.  Press
+Shift-arrowkey to switch off an edge.  Repeat an action to perform
+its inverse.
+
+(All the actions described in \k{common-actions} are also available.)
+
+\H{Palisade-parameters} \I{parameters, for Palisade}Palisade parameters
+
+These parameters are available from the \q{Custom...} option on the
+\q{Type} menu.
+
+\dt \e{Width}, \e{Height}
+
+\dd Size of grid in squares.
+
+\dt \e{Region size}
+
+\dd The size of the regions into which the grid must be subdivided.
+
+\A{licence} \I{MIT licence}\ii{Licence}
+
+This software is \i{copyright} 2004-2014 Simon Tatham.
+
+Portions copyright Richard Boulton, James Harvey, Mike Pinna, Jonas
+K\u00F6{oe}lker, Dariusz Olszewski, Michael Schierl, Lambros Lambrou,
+Bernd Schmidt, Steffen Bauer, Lennard Sprong and Rogier Goossens.
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation files
+(the \q{Software}), to deal in the Software without restriction,
+including without limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of the Software,
+and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED \q{AS IS}, WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+\IM{command-line}{command line} command line
+
+\IM{default parameters, specifying} default parameters, specifying
+\IM{default parameters, specifying} preferences, specifying default
+
+\IM{Unix} Unix
+\IM{Unix} Linux
+
+\IM{generating game IDs} generating game IDs
+\IM{generating game IDs} game ID, generating
+
+\IM{specific} \q{Specific}, menu option
+\IM{custom} \q{Custom}, menu option
+
+\IM{game ID} game ID
+\IM{game ID} ID, game
+\IM{ID format} ID format
+\IM{ID format} format, ID
+\IM{ID format} game ID, format
+
+\IM{keys} keys
+\IM{keys} shortcuts (keyboard)
+
+\IM{initial state} initial state
+\IM{initial state} state, initial
+
+\IM{MIT licence} MIT licence
+\IM{MIT licence} licence, MIT
+
+\versionid Simon Tatham's Portable Puzzle Collection, version 20161228.7cae89f
diff --git a/puzzles.cnt b/puzzles.cnt
new file mode 100644 (file)
index 0000000..f858735
--- /dev/null
@@ -0,0 +1,167 @@
+:Title Simon Tatham's Portable Puzzle Collection\r
+1 Contents=Top
+1 Chapter 1: Introduction
+2 Chapter 1: Introduction=t00000000
+1 Chapter 2: Common features
+2 Chapter 2: Common features=t00000001
+2 Section 2.1: Common actions=t00000002
+2 Section 2.2: Specifying games with the game ID=t00000003
+2 Section 2.3: The \91Type\92 menu=t00000004
+2 Section 2.4: Specifying game parameters on the command line=t00000005
+2 Section 2.5: Unix command-line options=t00000006
+1 Chapter 3: Net
+2 Chapter 3: Net=games.net
+2 Section 3.1: Net controls=t00000007
+2 Section 3.2: Net parameters=t00000008
+1 Chapter 4: Cube
+2 Chapter 4: Cube=games.cube
+2 Section 4.1: Cube controls=t00000009
+2 Section 4.2: Cube parameters=t00000010
+1 Chapter 5: Fifteen
+2 Chapter 5: Fifteen=games.fifteen
+2 Section 5.1: Fifteen controls=t00000011
+2 Section 5.2: Fifteen parameters=t00000012
+1 Chapter 6: Sixteen
+2 Chapter 6: Sixteen=games.sixteen
+2 Section 6.1: Sixteen controls=t00000013
+2 Section 6.2: Sixteen parameters=t00000014
+1 Chapter 7: Twiddle
+2 Chapter 7: Twiddle=games.twiddle
+2 Section 7.1: Twiddle controls=t00000015
+2 Section 7.2: Twiddle parameters=t00000016
+1 Chapter 8: Rectangles
+2 Chapter 8: Rectangles=games.rectangles
+2 Section 8.1: Rectangles controls=t00000017
+2 Section 8.2: Rectangles parameters=t00000018
+1 Chapter 9: Netslide
+2 Chapter 9: Netslide=games.netslide
+1 Chapter 10: Pattern
+2 Chapter 10: Pattern=games.pattern
+2 Section 10.1: Pattern controls=t00000019
+2 Section 10.2: Pattern parameters=t00000020
+1 Chapter 11: Solo
+2 Chapter 11: Solo=games.solo
+2 Section 11.1: Solo controls=t00000021
+2 Section 11.2: Solo parameters=t00000022
+1 Chapter 12: Mines
+2 Chapter 12: Mines=games.mines
+2 Section 12.1: Mines controls=t00000023
+2 Section 12.2: Mines parameters=t00000024
+1 Chapter 13: Same Game
+2 Chapter 13: Same Game=games.samegame
+2 Section 13.1: Same Game controls=t00000025
+2 Section 13.2: Same Game parameters=t00000026
+1 Chapter 14: Flip
+2 Chapter 14: Flip=games.flip
+2 Section 14.1: Flip controls=t00000027
+2 Section 14.2: Flip parameters=t00000028
+1 Chapter 15: Guess
+2 Chapter 15: Guess=games.guess
+2 Section 15.1: Guess controls=t00000029
+2 Section 15.2: Guess parameters=t00000030
+1 Chapter 16: Pegs
+2 Chapter 16: Pegs=games.pegs
+2 Section 16.1: Pegs controls=t00000031
+2 Section 16.2: Pegs parameters=t00000032
+1 Chapter 17: Dominosa
+2 Chapter 17: Dominosa=games.dominosa
+2 Section 17.1: Dominosa controls=t00000033
+2 Section 17.2: Dominosa parameters=t00000034
+1 Chapter 18: Untangle
+2 Chapter 18: Untangle=games.untangle
+2 Section 18.1: Untangle controls=t00000035
+2 Section 18.2: Untangle parameters=t00000036
+1 Chapter 19: Black Box
+2 Chapter 19: Black Box=games.blackbox
+2 Section 19.1: Black Box controls=t00000037
+2 Section 19.2: Black Box parameters=t00000038
+1 Chapter 20: Slant
+2 Chapter 20: Slant=games.slant
+2 Section 20.1: Slant controls=t00000039
+2 Section 20.2: Slant parameters=t00000040
+1 Chapter 21: Light Up
+2 Chapter 21: Light Up=games.lightup
+2 Section 21.1: Light Up controls=t00000041
+2 Section 21.2: Light Up parameters=t00000042
+1 Chapter 22: Map
+2 Chapter 22: Map=games.map
+2 Section 22.1: Map controls=t00000043
+2 Section 22.2: Map parameters=t00000044
+1 Chapter 23: Loopy
+2 Chapter 23: Loopy=games.loopy
+2 Section 23.1: Loopy controls=t00000045
+2 Section 23.2: Loopy parameters=t00000046
+1 Chapter 24: Inertia
+2 Chapter 24: Inertia=games.inertia
+2 Section 24.1: Inertia controls=t00000047
+2 Section 24.2: Inertia parameters=t00000048
+1 Chapter 25: Tents
+2 Chapter 25: Tents=games.tents
+2 Section 25.1: Tents controls=t00000049
+2 Section 25.2: Tents parameters=t00000050
+1 Chapter 26: Bridges
+2 Chapter 26: Bridges=games.bridges
+2 Section 26.1: Bridges controls=t00000051
+2 Section 26.2: Bridges parameters=t00000052
+1 Chapter 27: Unequal
+2 Chapter 27: Unequal=games.unequal
+2 Section 27.1: Unequal controls=t00000053
+2 Section 27.2: Unequal parameters=t00000054
+1 Chapter 28: Galaxies
+2 Chapter 28: Galaxies=games.galaxies
+2 Section 28.1: Galaxies controls=t00000055
+2 Section 28.2: Galaxies parameters=t00000056
+1 Chapter 29: Filling
+2 Chapter 29: Filling=games.filling
+2 Section 29.1: Filling controls=t00000057
+2 Section 29.2: Filling parameters=t00000058
+1 Chapter 30: Keen
+2 Chapter 30: Keen=games.keen
+2 Section 30.1: Keen controls=t00000059
+2 Section 30.2: Keen parameters=t00000060
+1 Chapter 31: Towers
+2 Chapter 31: Towers=games.towers
+2 Section 31.1: Towers controls=t00000061
+2 Section 31.2: Towers parameters=t00000062
+1 Chapter 32: Singles
+2 Chapter 32: Singles=games.singles
+2 Section 32.1: Singles controls=t00000063
+2 Section 32.2: Singles parameters=t00000064
+1 Chapter 33: Magnets
+2 Chapter 33: Magnets=games.magnets
+2 Section 33.1: Magnets controls=t00000065
+2 Section 33.2: Magnets parameters=t00000066
+1 Chapter 34: Signpost
+2 Chapter 34: Signpost=games.signpost
+2 Section 34.1: Signpost controls=t00000067
+2 Section 34.2: Signpost parameters=t00000068
+1 Chapter 35: Range
+2 Chapter 35: Range=games.range
+2 Section 35.1: Range controls=t00000069
+2 Section 35.2: Range parameters=t00000070
+1 Chapter 36: Pearl
+2 Chapter 36: Pearl=games.pearl
+2 Section 36.1: Pearl controls=t00000071
+2 Section 36.2: Pearl parameters=t00000072
+1 Chapter 37: Undead
+2 Chapter 37: Undead=games.undead
+2 Section 37.1: Undead controls=t00000073
+2 Section 37.2: Undead parameters=t00000074
+1 Chapter 38: Unruly
+2 Chapter 38: Unruly=games.unruly
+2 Section 38.1: Unruly controls=t00000075
+2 Section 38.2: Unruly parameters=t00000076
+1 Chapter 39: Flood
+2 Chapter 39: Flood=games.flood
+2 Section 39.1: Flood controls=t00000077
+2 Section 39.2: Flood parameters=t00000078
+1 Chapter 40: Tracks
+2 Chapter 40: Tracks=games.tracks
+2 Section 40.1: Tracks controls=t00000079
+2 Section 40.2: Tracks parameters=t00000080
+1 Chapter 41: Palisade
+2 Chapter 41: Palisade=games.palisade
+2 Section 41.1: Palisade controls=t00000081
+2 Section 41.2: Palisade parameters=t00000082
+1 Appendix A: Licence
+2 Appendix A: Licence=t00000083
diff --git a/puzzles.h b/puzzles.h
new file mode 100644 (file)
index 0000000..1847d9c
--- /dev/null
+++ b/puzzles.h
@@ -0,0 +1,628 @@
+/*
+ * puzzles.h: header file for my puzzle collection
+ */
+
+#ifndef PUZZLES_PUZZLES_H
+#define PUZZLES_PUZZLES_H
+
+#include <stdio.h>  /* for FILE */
+#include <stdlib.h> /* for size_t */
+#include <limits.h> /* for UINT_MAX */
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+#define PI 3.141592653589793238462643383279502884197169399
+
+#define lenof(array) ( sizeof(array) / sizeof(*(array)) )
+
+#define STR_INT(x) #x
+#define STR(x) STR_INT(x)
+
+/* NB not perfect because they evaluate arguments multiple times. */
+#ifndef max
+#define max(x,y) ( (x)>(y) ? (x) : (y) )
+#endif /* max */
+#ifndef min
+#define min(x,y) ( (x)<(y) ? (x) : (y) )
+#endif /* min */
+
+enum {
+    LEFT_BUTTON = 0x0200,
+    MIDDLE_BUTTON,
+    RIGHT_BUTTON,
+    LEFT_DRAG,
+    MIDDLE_DRAG,
+    RIGHT_DRAG,
+    LEFT_RELEASE,
+    MIDDLE_RELEASE,
+    RIGHT_RELEASE,
+    CURSOR_UP,
+    CURSOR_DOWN,
+    CURSOR_LEFT,
+    CURSOR_RIGHT,
+    CURSOR_SELECT,
+    CURSOR_SELECT2,
+    
+    /* made smaller because of 'limited range of datatype' errors. */
+    MOD_CTRL       = 0x1000,
+    MOD_SHFT       = 0x2000,
+    MOD_NUM_KEYPAD = 0x4000,
+    MOD_MASK       = 0x7000 /* mask for all modifiers */
+};
+
+#define IS_MOUSE_DOWN(m) ( (unsigned)((m) - LEFT_BUTTON) <= \
+                               (unsigned)(RIGHT_BUTTON - LEFT_BUTTON))
+#define IS_MOUSE_DRAG(m) ( (unsigned)((m) - LEFT_DRAG) <= \
+                               (unsigned)(RIGHT_DRAG - LEFT_DRAG))
+#define IS_MOUSE_RELEASE(m) ( (unsigned)((m) - LEFT_RELEASE) <= \
+                               (unsigned)(RIGHT_RELEASE - LEFT_RELEASE))
+#define IS_CURSOR_MOVE(m) ( (m) == CURSOR_UP || (m) == CURSOR_DOWN || \
+                            (m) == CURSOR_RIGHT || (m) == CURSOR_LEFT )
+#define IS_CURSOR_SELECT(m) ( (m) == CURSOR_SELECT || (m) == CURSOR_SELECT2)
+
+/*
+ * Flags in the back end's `flags' word.
+ */
+/* Bit flags indicating mouse button priorities */
+#define BUTTON_BEATS(x,y) ( 1 << (((x)-LEFT_BUTTON)*3+(y)-LEFT_BUTTON) )
+/* Flag indicating that Solve operations should be animated */
+#define SOLVE_ANIMATES ( 1 << 9 )
+/* Pocket PC: Game requires right mouse button emulation */
+#define REQUIRE_RBUTTON ( 1 << 10 )
+/* Pocket PC: Game requires numeric input */
+#define REQUIRE_NUMPAD ( 1 << 11 )
+/* end of `flags' word definitions */
+
+#ifdef _WIN32_WCE
+  /* Pocket PC devices have small, portrait screen that requires more vivid colours */
+  #define SMALL_SCREEN
+  #define PORTRAIT_SCREEN
+  #define VIVID_COLOURS
+  #define STYLUS_BASED
+#endif
+
+#define IGNOREARG(x) ( (x) = (x) )
+
+typedef struct frontend frontend;
+typedef struct config_item config_item;
+typedef struct midend midend;
+typedef struct random_state random_state;
+typedef struct game_params game_params;
+typedef struct game_state game_state;
+typedef struct game_ui game_ui;
+typedef struct game_drawstate game_drawstate;
+typedef struct game game;
+typedef struct blitter blitter;
+typedef struct document document;
+typedef struct drawing_api drawing_api;
+typedef struct drawing drawing;
+typedef struct psdata psdata;
+
+#define ALIGN_VNORMAL 0x000
+#define ALIGN_VCENTRE 0x100
+
+#define ALIGN_HLEFT   0x000
+#define ALIGN_HCENTRE 0x001
+#define ALIGN_HRIGHT  0x002
+
+#define FONT_FIXED    0
+#define FONT_VARIABLE 1
+
+/* For printing colours */
+#define HATCH_SLASH     1
+#define HATCH_BACKSLASH 2
+#define HATCH_HORIZ     3
+#define HATCH_VERT      4
+#define HATCH_PLUS      5
+#define HATCH_X         6
+
+/*
+ * Structure used to pass configuration data between frontend and
+ * game
+ */
+enum { C_STRING, C_CHOICES, C_BOOLEAN, C_END };
+struct config_item {
+    /*
+     * `name' is never dynamically allocated.
+     */
+    char *name;
+    /*
+     * `type' contains one of the above values.
+     */
+    int type;
+    /*
+     * For C_STRING, `sval' is always dynamically allocated and
+     * non-NULL. For C_BOOLEAN and C_END, `sval' is always NULL.
+     * For C_CHOICES, `sval' is non-NULL, _not_ dynamically
+     * allocated, and contains a set of option strings separated by
+     * a delimiter. The delimeter is also the first character in
+     * the string, so for example ":Foo:Bar:Baz" gives three
+     * options `Foo', `Bar' and `Baz'.
+     */
+    char *sval;
+    /*
+     * For C_BOOLEAN, this is TRUE or FALSE. For C_CHOICES, it
+     * indicates the chosen index from the `sval' list. In the
+     * above example, 0==Foo, 1==Bar and 2==Baz.
+     */
+    int ival;
+};
+
+/*
+ * Platform routines
+ */
+
+/* We can't use #ifdef DEBUG, because Cygwin defines it by default. */
+#ifdef DEBUGGING
+#define debug(x) (debug_printf x)
+void debug_printf(char *fmt, ...);
+#else
+#define debug(x)
+#endif
+
+void fatal(char *fmt, ...);
+void frontend_default_colour(frontend *fe, float *output);
+void deactivate_timer(frontend *fe);
+void activate_timer(frontend *fe);
+void get_random_seed(void **randseed, int *randseedsize);
+
+/*
+ * drawing.c
+ */
+drawing *drawing_new(const drawing_api *api, midend *me, void *handle);
+void drawing_free(drawing *dr);
+void draw_text(drawing *dr, int x, int y, int fonttype, int fontsize,
+               int align, int colour, char *text);
+void draw_rect(drawing *dr, int x, int y, int w, int h, int colour);
+void draw_line(drawing *dr, int x1, int y1, int x2, int y2, int colour);
+void draw_polygon(drawing *dr, int *coords, int npoints,
+                  int fillcolour, int outlinecolour);
+void draw_circle(drawing *dr, int cx, int cy, int radius,
+                 int fillcolour, int outlinecolour);
+void draw_thick_line(drawing *dr, float thickness,
+                    float x1, float y1, float x2, float y2, int colour);
+void clip(drawing *dr, int x, int y, int w, int h);
+void unclip(drawing *dr);
+void start_draw(drawing *dr);
+void draw_update(drawing *dr, int x, int y, int w, int h);
+void end_draw(drawing *dr);
+char *text_fallback(drawing *dr, const char *const *strings, int nstrings);
+void status_bar(drawing *dr, char *text);
+blitter *blitter_new(drawing *dr, int w, int h);
+void blitter_free(drawing *dr, blitter *bl);
+/* save puts the portion of the current display with top-left corner
+ * (x,y) to the blitter. load puts it back again to the specified
+ * coords, or else wherever it was saved from
+ * (if x = y = BLITTER_FROMSAVED). */
+void blitter_save(drawing *dr, blitter *bl, int x, int y);
+#define BLITTER_FROMSAVED (-1)
+void blitter_load(drawing *dr, blitter *bl, int x, int y);
+void print_begin_doc(drawing *dr, int pages);
+void print_begin_page(drawing *dr, int number);
+void print_begin_puzzle(drawing *dr, float xm, float xc,
+                       float ym, float yc, int pw, int ph, float wmm,
+                       float scale);
+void print_end_puzzle(drawing *dr);
+void print_end_page(drawing *dr, int number);
+void print_end_doc(drawing *dr);
+void print_get_colour(drawing *dr, int colour, int printing_in_colour,
+                     int *hatch, float *r, float *g, float *b);
+int print_mono_colour(drawing *dr, int grey); /* 0==black, 1==white */
+int print_grey_colour(drawing *dr, float grey);
+int print_hatched_colour(drawing *dr, int hatch);
+int print_rgb_mono_colour(drawing *dr, float r, float g, float b, int mono);
+int print_rgb_grey_colour(drawing *dr, float r, float g, float b, float grey);
+int print_rgb_hatched_colour(drawing *dr, float r, float g, float b,
+                            int hatch);
+void print_line_width(drawing *dr, int width);
+void print_line_dotted(drawing *dr, int dotted);
+
+/*
+ * midend.c
+ */
+midend *midend_new(frontend *fe, const game *ourgame,
+                  const drawing_api *drapi, void *drhandle);
+void midend_free(midend *me);
+const game *midend_which_game(midend *me);
+void midend_set_params(midend *me, game_params *params);
+game_params *midend_get_params(midend *me);
+void midend_size(midend *me, int *x, int *y, int user_size);
+void midend_reset_tilesize(midend *me);
+void midend_new_game(midend *me);
+void midend_restart_game(midend *me);
+void midend_stop_anim(midend *me);
+int midend_process_key(midend *me, int x, int y, int button);
+void midend_force_redraw(midend *me);
+void midend_redraw(midend *me);
+float *midend_colours(midend *me, int *ncolours);
+void midend_freeze_timer(midend *me, float tprop);
+void midend_timer(midend *me, float tplus);
+int midend_num_presets(midend *me);
+void midend_fetch_preset(midend *me, int n,
+                         char **name, game_params **params);
+int midend_which_preset(midend *me);
+int midend_wants_statusbar(midend *me);
+enum { CFG_SETTINGS, CFG_SEED, CFG_DESC, CFG_FRONTEND_SPECIFIC };
+config_item *midend_get_config(midend *me, int which, char **wintitle);
+char *midend_set_config(midend *me, int which, config_item *cfg);
+char *midend_game_id(midend *me, char *id);
+char *midend_get_game_id(midend *me);
+char *midend_get_random_seed(midend *me);
+int midend_can_format_as_text_now(midend *me);
+char *midend_text_format(midend *me);
+char *midend_solve(midend *me);
+int midend_status(midend *me);
+int midend_can_undo(midend *me);
+int midend_can_redo(midend *me);
+void midend_supersede_game_desc(midend *me, char *desc, char *privdesc);
+char *midend_rewrite_statusbar(midend *me, char *text);
+void midend_serialise(midend *me,
+                      void (*write)(void *ctx, void *buf, int len),
+                      void *wctx);
+char *midend_deserialise(midend *me,
+                         int (*read)(void *ctx, void *buf, int len),
+                         void *rctx);
+char *identify_game(char **name, int (*read)(void *ctx, void *buf, int len),
+                    void *rctx);
+void midend_request_id_changes(midend *me, void (*notify)(void *), void *ctx);
+/* Printing functions supplied by the mid-end */
+char *midend_print_puzzle(midend *me, document *doc, int with_soln);
+int midend_tilesize(midend *me);
+
+/*
+ * malloc.c
+ */
+void *smalloc(size_t size);
+void *srealloc(void *p, size_t size);
+void sfree(void *p);
+char *dupstr(const char *s);
+#define snew(type) \
+    ( (type *) smalloc (sizeof (type)) )
+#define snewn(number, type) \
+    ( (type *) smalloc ((number) * sizeof (type)) )
+#define sresize(array, number, type) \
+    ( (type *) srealloc ((array), (number) * sizeof (type)) )
+
+/*
+ * misc.c
+ */
+void free_cfg(config_item *cfg);
+void obfuscate_bitmap(unsigned char *bmp, int bits, int decode);
+
+/* allocates output each time. len is always in bytes of binary data.
+ * May assert (or just go wrong) if lengths are unchecked. */
+char *bin2hex(const unsigned char *in, int inlen);
+unsigned char *hex2bin(const char *in, int outlen);
+
+/* Sets (and possibly dims) background from frontend default colour,
+ * and auto-generates highlight and lowlight colours too. */
+void game_mkhighlight(frontend *fe, float *ret,
+                      int background, int highlight, int lowlight);
+/* As above, but starts from a provided background colour rather
+ * than the frontend default. */
+void game_mkhighlight_specific(frontend *fe, float *ret,
+                              int background, int highlight, int lowlight);
+
+/* Randomly shuffles an array of items. */
+void shuffle(void *array, int nelts, int eltsize, random_state *rs);
+
+/* Draw a rectangle outline, using the drawing API's draw_line. */
+void draw_rect_outline(drawing *dr, int x, int y, int w, int h,
+                       int colour);
+
+/* Draw a set of rectangle corners (e.g. for a cursor display). */
+void draw_rect_corners(drawing *dr, int cx, int cy, int r, int col);
+
+void move_cursor(int button, int *x, int *y, int maxw, int maxh, int wrap);
+
+/* Used in netslide.c and sixteen.c for cursor movement around edge. */
+int c2pos(int w, int h, int cx, int cy);
+int c2diff(int w, int h, int cx, int cy, int button);
+void pos2c(int w, int h, int pos, int *cx, int *cy);
+
+/* Draws text with an 'outline' formed by offsetting the text
+ * by one pixel; useful for highlighting. Outline is omitted if -1. */
+void draw_text_outline(drawing *dr, int x, int y, int fonttype,
+                       int fontsize, int align,
+                       int text_colour, int outline_colour, char *text);
+/*
+ * dsf.c
+ */
+int *snew_dsf(int size);
+
+void print_dsf(int *dsf, int size);
+
+/* Return the canonical element of the equivalence class containing element
+ * val.  If 'inverse' is non-NULL, this function will put into it a flag
+ * indicating whether the canonical element is inverse to val. */
+int edsf_canonify(int *dsf, int val, int *inverse);
+int dsf_canonify(int *dsf, int val);
+int dsf_size(int *dsf, int val);
+
+/* Allow the caller to specify that two elements should be in the same
+ * equivalence class.  If 'inverse' is TRUE, the elements are actually opposite
+ * to one another in some sense.  This function will fail an assertion if the
+ * caller gives it self-contradictory data, ie if two elements are claimed to
+ * be both opposite and non-opposite. */
+void edsf_merge(int *dsf, int v1, int v2, int inverse);
+void dsf_merge(int *dsf, int v1, int v2);
+void dsf_init(int *dsf, int len);
+
+/*
+ * tdq.c
+ */
+
+/*
+ * Data structure implementing a 'to-do queue', a simple
+ * de-duplicating to-do list mechanism.
+ *
+ * Specification: a tdq is a queue which can hold integers from 0 to
+ * n-1, where n was some constant specified at tdq creation time. No
+ * integer may appear in the queue's current contents more than once;
+ * an attempt to add an already-present integer again will do nothing,
+ * so that that integer is removed from the queue at the position
+ * where it was _first_ inserted. The add and remove operations take
+ * constant time.
+ *
+ * The idea is that you might use this in applications like solvers:
+ * keep a tdq listing the indices of grid squares that you currently
+ * need to process in some way. Whenever you modify a square in a way
+ * that will require you to re-scan its neighbours, add them to the
+ * list with tdq_add; meanwhile you're constantly taking elements off
+ * the list when you need another square to process. In solvers where
+ * deductions are mostly localised, this should prevent repeated
+ * O(N^2) loops over the whole grid looking for something to do. (But
+ * if only _most_ of the deductions are localised, then you should
+ * respond to an empty to-do list by re-adding everything using
+ * tdq_fill, so _then_ you rescan the whole grid looking for newly
+ * enabled non-local deductions. Only if you've done that and emptied
+ * the list again finding nothing new to do are you actually done.)
+ */
+typedef struct tdq tdq;
+tdq *tdq_new(int n);
+void tdq_free(tdq *tdq);
+void tdq_add(tdq *tdq, int k);
+int tdq_remove(tdq *tdq);        /* returns -1 if nothing available */
+void tdq_fill(tdq *tdq);         /* add everything to the tdq at once */
+
+/*
+ * laydomino.c
+ */
+int *domino_layout(int w, int h, random_state *rs);
+void domino_layout_prealloc(int w, int h, random_state *rs,
+                            int *grid, int *grid2, int *list);
+/*
+ * version.c
+ */
+extern char ver[];
+
+/*
+ * random.c
+ */
+random_state *random_new(const char *seed, int len);
+random_state *random_copy(random_state *tocopy);
+unsigned long random_bits(random_state *state, int bits);
+unsigned long random_upto(random_state *state, unsigned long limit);
+void random_free(random_state *state);
+char *random_state_encode(random_state *state);
+random_state *random_state_decode(const char *input);
+/* random.c also exports SHA, which occasionally comes in useful. */
+#if __STDC_VERSION__ >= 199901L
+#include <stdint.h>
+typedef uint32_t uint32;
+#elif UINT_MAX >= 4294967295L
+typedef unsigned int uint32;
+#else
+typedef unsigned long uint32;
+#endif
+typedef struct {
+    uint32 h[5];
+    unsigned char block[64];
+    int blkused;
+    uint32 lenhi, lenlo;
+} SHA_State;
+void SHA_Init(SHA_State *s);
+void SHA_Bytes(SHA_State *s, const void *p, int len);
+void SHA_Final(SHA_State *s, unsigned char *output);
+void SHA_Simple(const void *p, int len, unsigned char *output);
+
+/*
+ * printing.c
+ */
+document *document_new(int pw, int ph, float userscale);
+void document_free(document *doc);
+void document_add_puzzle(document *doc, const game *game, game_params *par,
+                        game_state *st, game_state *st2);
+void document_print(document *doc, drawing *dr);
+
+/*
+ * ps.c
+ */
+psdata *ps_init(FILE *outfile, int colour);
+void ps_free(psdata *ps);
+drawing *ps_drawing_api(psdata *ps);
+
+/*
+ * combi.c: provides a structure and functions for iterating over
+ * combinations (i.e. choosing r things out of n).
+ */
+typedef struct _combi_ctx {
+  int r, n, nleft, total;
+  int *a;
+} combi_ctx;
+
+combi_ctx *new_combi(int r, int n);
+void reset_combi(combi_ctx *combi);
+combi_ctx *next_combi(combi_ctx *combi); /* returns NULL for end */
+void free_combi(combi_ctx *combi);
+
+/*
+ * divvy.c
+ */
+/* divides w*h rectangle into pieces of size k. Returns w*h dsf. */
+int *divvy_rectangle(int w, int h, int k, random_state *rs);
+
+/*
+ * findloop.c
+ */
+struct findloopstate;
+struct findloopstate *findloop_new_state(int nvertices);
+void findloop_free_state(struct findloopstate *);
+/*
+ * Callback provided by the client code to enumerate the graph
+ * vertices joined directly to a given vertex.
+ *
+ * Semantics: if vertex >= 0, return one of its neighbours; if vertex
+ * < 0, return a previously unmentioned neighbour of whatever vertex
+ * was last passed as input. Write to 'ctx' as necessary to store
+ * state. In either case, return < 0 if no such vertex can be found.
+ */
+typedef int (*neighbour_fn_t)(int vertex, void *ctx);
+/*
+ * Actual function to find loops. 'ctx' will be passed unchanged to
+ * the 'neighbour' function to query graph edges. Returns FALSE if no
+ * loop was found, or TRUE if one was.
+ */
+int findloop_run(struct findloopstate *state, int nvertices,
+                 neighbour_fn_t neighbour, void *ctx);
+/*
+ * Query whether an edge is part of a loop, in the output of
+ * find_loops.
+ *
+ * Due to the internal storage format, if you pass u,v which are not
+ * connected at all, the output will be TRUE. (The algorithm actually
+ * stores an exhaustive list of *non*-loop edges, because there are
+ * fewer of those, so really it's querying whether the edge is on that
+ * list.)
+ */
+int findloop_is_loop_edge(struct findloopstate *state, int u, int v);
+
+/*
+ * Data structure containing the function calls and data specific
+ * to a particular game. This is enclosed in a data structure so
+ * that a particular platform can choose, if it wishes, to compile
+ * all the games into a single combined executable rather than
+ * having lots of little ones.
+ */
+struct game {
+    const char *name;
+    const char *winhelp_topic, *htmlhelp_topic;
+    game_params *(*default_params)(void);
+    int (*fetch_preset)(int i, char **name, game_params **params);
+    void (*decode_params)(game_params *, char const *string);
+    char *(*encode_params)(const game_params *, int full);
+    void (*free_params)(game_params *params);
+    game_params *(*dup_params)(const game_params *params);
+    int can_configure;
+    config_item *(*configure)(const game_params *params);
+    game_params *(*custom_params)(const config_item *cfg);
+    char *(*validate_params)(const game_params *params, int full);
+    char *(*new_desc)(const game_params *params, random_state *rs,
+                     char **aux, int interactive);
+    char *(*validate_desc)(const game_params *params, const char *desc);
+    game_state *(*new_game)(midend *me, const game_params *params,
+                            const char *desc);
+    game_state *(*dup_game)(const game_state *state);
+    void (*free_game)(game_state *state);
+    int can_solve;
+    char *(*solve)(const game_state *orig, const game_state *curr,
+                   const char *aux, char **error);
+    int can_format_as_text_ever;
+    int (*can_format_as_text_now)(const game_params *params);
+    char *(*text_format)(const game_state *state);
+    game_ui *(*new_ui)(const game_state *state);
+    void (*free_ui)(game_ui *ui);
+    char *(*encode_ui)(const game_ui *ui);
+    void (*decode_ui)(game_ui *ui, const char *encoding);
+    void (*changed_state)(game_ui *ui, const game_state *oldstate,
+                          const game_state *newstate);
+    char *(*interpret_move)(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds, int x, int y, int button);
+    game_state *(*execute_move)(const game_state *state, const char *move);
+    int preferred_tilesize;
+    void (*compute_size)(const game_params *params, int tilesize,
+                         int *x, int *y);
+    void (*set_size)(drawing *dr, game_drawstate *ds,
+                    const game_params *params, int tilesize);
+    float *(*colours)(frontend *fe, int *ncolours);
+    game_drawstate *(*new_drawstate)(drawing *dr, const game_state *state);
+    void (*free_drawstate)(drawing *dr, game_drawstate *ds);
+    void (*redraw)(drawing *dr, game_drawstate *ds, const game_state *oldstate,
+                  const game_state *newstate, int dir, const game_ui *ui,
+                   float anim_time, float flash_time);
+    float (*anim_length)(const game_state *oldstate,
+                         const game_state *newstate, int dir, game_ui *ui);
+    float (*flash_length)(const game_state *oldstate,
+                          const game_state *newstate, int dir, game_ui *ui);
+    int (*status)(const game_state *state);
+    int can_print, can_print_in_colour;
+    void (*print_size)(const game_params *params, float *x, float *y);
+    void (*print)(drawing *dr, const game_state *state, int tilesize);
+    int wants_statusbar;
+    int is_timed;
+    int (*timing_state)(const game_state *state, game_ui *ui);
+    int flags;
+};
+
+/*
+ * Data structure containing the drawing API implemented by the
+ * front end and also by cross-platform printing modules such as
+ * PostScript.
+ */
+struct drawing_api {
+    void (*draw_text)(void *handle, int x, int y, int fonttype, int fontsize,
+                     int align, int colour, char *text);
+    void (*draw_rect)(void *handle, int x, int y, int w, int h, int colour);
+    void (*draw_line)(void *handle, int x1, int y1, int x2, int y2,
+                     int colour);
+    void (*draw_polygon)(void *handle, int *coords, int npoints,
+                        int fillcolour, int outlinecolour);
+    void (*draw_circle)(void *handle, int cx, int cy, int radius,
+                       int fillcolour, int outlinecolour);
+    void (*draw_update)(void *handle, int x, int y, int w, int h);
+    void (*clip)(void *handle, int x, int y, int w, int h);
+    void (*unclip)(void *handle);
+    void (*start_draw)(void *handle);
+    void (*end_draw)(void *handle);
+    void (*status_bar)(void *handle, char *text);
+    blitter *(*blitter_new)(void *handle, int w, int h);
+    void (*blitter_free)(void *handle, blitter *bl);
+    void (*blitter_save)(void *handle, blitter *bl, int x, int y);
+    void (*blitter_load)(void *handle, blitter *bl, int x, int y);
+    void (*begin_doc)(void *handle, int pages);
+    void (*begin_page)(void *handle, int number);
+    void (*begin_puzzle)(void *handle, float xm, float xc,
+                        float ym, float yc, int pw, int ph, float wmm);
+    void (*end_puzzle)(void *handle);
+    void (*end_page)(void *handle, int number);
+    void (*end_doc)(void *handle);
+    void (*line_width)(void *handle, float width);
+    void (*line_dotted)(void *handle, int dotted);
+    char *(*text_fallback)(void *handle, const char *const *strings,
+                          int nstrings);
+    void (*draw_thick_line)(void *handle, float thickness,
+                           float x1, float y1, float x2, float y2,
+                           int colour);
+};
+
+/*
+ * For one-game-at-a-time platforms, there's a single structure
+ * like the above, under a fixed name. For all-at-once platforms,
+ * there's a list of all available puzzles in array form.
+ */
+#ifdef COMBINED
+extern const game *gamelist[];
+extern const int gamecount;
+#else
+extern const game thegame;
+#endif
+
+/* A little bit of help to lazy developers */
+#define DEFAULT_STATUSBAR_TEXT "Use status_bar() to fill this in."
+
+#endif /* PUZZLES_PUZZLES_H */
diff --git a/puzzles.hlp b/puzzles.hlp
new file mode 100644 (file)
index 0000000..b8a8542
Binary files /dev/null and b/puzzles.hlp differ
diff --git a/puzzles.rc2 b/puzzles.rc2
new file mode 100644 (file)
index 0000000..4442a9b
--- /dev/null
@@ -0,0 +1,65 @@
+/* Standard stuff that goes into the Windows resources for all puzzles. */
+
+#ifdef _WIN32_WCE
+
+#include <winuser.h>
+#include <commctrl.h>
+
+#define SHMENUBAR       RCDATA
+#define I_IMAGENONE     (-2)
+#define IDC_STATIC      (-1)
+
+#include "resource.h"
+
+IDR_MENUBAR1 MENU DISCARDABLE 
+BEGIN
+    POPUP "Game"
+    BEGIN
+        MENUITEM "Dummy", 0
+    END
+    POPUP "Type"
+    BEGIN
+        MENUITEM "Dummy", 0
+    END
+END
+
+IDR_MENUBAR1 SHMENUBAR DISCARDABLE 
+BEGIN
+    IDR_MENUBAR1, 2,
+    I_IMAGENONE, ID_GAME, TBSTATE_ENABLED, 
+    TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE, IDS_CAP_GAME, 0, 0,
+    I_IMAGENONE, ID_TYPE, TBSTATE_ENABLED, 
+    TBSTYLE_DROPDOWN | TBSTYLE_AUTOSIZE, IDS_CAP_TYPE, 0, 1,
+END
+
+STRINGTABLE DISCARDABLE 
+BEGIN
+    IDS_CAP_GAME            "Game"
+    IDS_CAP_TYPE            "Type"
+END
+
+IDD_ABOUT DIALOG DISCARDABLE  0, 0, 0, 0
+STYLE WS_POPUP
+FONT 8, "Tahoma"
+BEGIN
+    LTEXT "", IDC_ABOUT_CAPTION,        4,  4, 150,  8
+    LTEXT "", IDC_ABOUT_LINE,           4, 16, 150,  1, WS_BORDER
+    LTEXT "", IDC_ABOUT_GAME,           4, 22, 150,  8
+    LTEXT "from Simon Tatham's Portable Puzzle Collection",
+             IDC_STATIC,               4, 36, 150,  8, SS_LEFTNOWORDWRAP
+    LTEXT "Pocket PC port by Darek Olszewski",
+             IDC_STATIC,               4, 46, 150,  8
+    LTEXT "", IDC_ABOUT_VERSION,        4, 60, 150,  8
+END
+
+IDD_CONFIG DIALOG DISCARDABLE  0, 0, 0, 0
+STYLE WS_POPUP
+FONT 8, "Tahoma"
+BEGIN
+    LTEXT "", IDC_CONFIG_CAPTION,       4,  4, 150,  8
+    LTEXT "", IDC_CONFIG_LINE,          4, 16, 150,  1, WS_BORDER
+END
+
+IDR_PADTOOLBAR BITMAP DISCARDABLE "padtoolbar.bmp"
+
+#endif /* _WIN32_WCE */
diff --git a/puzzles.txt b/puzzles.txt
new file mode 100644 (file)
index 0000000..f11346a
--- /dev/null
@@ -0,0 +1,3164 @@
+                 Simon Tatham's Portable Puzzle Collection
+                 =========================================
+
+This is a collection of small one-player puzzle games.
+
+This manual is copyright 2004-2014 Simon Tatham. All rights reserved. You
+may distribute this documentation under the MIT licence. See appendix A for
+the licence text in full.
+
+Chapter 1: Introduction
+-----------------------
+
+       I wrote this collection because I thought there should be more small
+       desktop toys available: little games you can pop up in a window and
+       play for two or three minutes while you take a break from whatever
+       else you were doing. And I was also annoyed that every time I found
+       a good game on (say) Unix, it wasn't available the next time I was
+       sitting at a Windows machine, or vice versa; so I arranged that
+       everything in my personal puzzle collection will happily run on
+       both, and have more recently done a port to Mac OS X as well. When I
+       find (or perhaps invent) further puzzle games that I like, they'll
+       be added to this collection and will immediately be available on
+       both platforms. And if anyone feels like writing any other front
+       ends - PocketPC, Mac OS pre-10, or whatever it might be - then all
+       the games in this framework will immediately become available on
+       another platform as well.
+
+       The actual games in this collection were mostly not my invention;
+       they are re-implementations of existing game concepts within my
+       portable puzzle framework. I do not claim credit, in general, for
+       inventing the rules of any of these puzzles. (I don't even claim
+       authorship of all the code; some of the puzzles have been submitted
+       by other authors.)
+
+       This collection is distributed under the MIT licence (see appendix
+       A). This means that you can do pretty much anything you like with
+       the game binaries or the code, except pretending you wrote them
+       yourself, or suing me if anything goes wrong.
+
+       The most recent versions, and source code, can be found at
+       http://www.chiark.greenend.org.uk/~sgtatham/puzzles/.
+
+       Please report bugs to anakin@pobox.com. You might find it helpful to
+       read this article before reporting a bug:
+
+       http://www.chiark.greenend.org.uk/~sgtatham/bugs.html
+
+       Patches are welcome. Especially if they provide a new front end (to
+       make all these games run on another platform), or a new game.
+
+Chapter 2: Common features
+--------------------------
+
+       This chapter describes features that are common to all the games.
+
+   2.1 Common actions
+
+       These actions are all available from the `Game' menu and via
+       keyboard shortcuts, in addition to any game-specific actions.
+
+       (On Mac OS X, to conform with local user interface standards, these
+       actions are situated on the `File' and `Edit' menus instead.)
+
+       _New game_ (`N', Ctrl+`N')
+
+           Starts a new game, with a random initial state.
+
+       _Restart game_
+
+           Resets the current game to its initial state. (This can be
+           undone.)
+
+       _Load_
+
+           Loads a saved game from a file on disk.
+
+       _Save_
+
+           Saves the current state of your game to a file on disk.
+
+           The Load and Save operations preserve your entire game history
+           (so you can save, reload, and still Undo and Redo things you had
+           done before saving).
+
+       _Print_
+
+           Where supported (currently only on Windows), brings up a dialog
+           allowing you to print an arbitrary number of puzzles randomly
+           generated from the current parameters, optionally including
+           the current puzzle. (Only for puzzles which make sense to
+           print, of course - it's hard to think of a sensible printable
+           representation of Fifteen!)
+
+       _Undo_ (`U', Ctrl+`Z', Ctrl+`_')
+
+           Undoes a single move. (You can undo moves back to the start of
+           the session.)
+
+       _Redo_ (`R', Ctrl+`R')
+
+           Redoes a previously undone move.
+
+       _Copy_
+
+           Copies the current state of your game to the clipboard in text
+           format, so that you can paste it into (say) an e-mail client or
+           a web message board if you're discussing the game with someone
+           else. (Not all games support this feature.)
+
+       _Solve_
+
+           Transforms the puzzle instantly into its solved state. For some
+           games (Cube) this feature is not supported at all because it is
+           of no particular use. For other games (such as Pattern), the
+           solved state can be used to give you information, if you can't
+           see how a solution can exist at all or you want to know where
+           you made a mistake. For still other games (such as Sixteen),
+           automatic solution tells you nothing about how to _get_ to
+           the solution, but it does provide a useful way to get there
+           quickly so that you can experiment with set-piece moves and
+           transformations.
+
+           Some games (such as Solo) are capable of solving a game ID you
+           have typed in from elsewhere. Other games (such as Rectangles)
+           cannot solve a game ID they didn't invent themself, but when
+           they did invent the game ID they know what the solution is
+           already. Still other games (Pattern) can solve _some_ external
+           game IDs, but only if they aren't too difficult.
+
+           The `Solve' command adds the solved state to the end of the undo
+           chain for the puzzle. In other words, if you want to go back to
+           solving it yourself after seeing the answer, you can just press
+           Undo.
+
+       _Quit_ (`Q', Ctrl+`Q')
+
+           Closes the application entirely.
+
+   2.2 Specifying games with the game ID
+
+       There are two ways to save a game specification out of a puzzle and
+       recreate it later, or recreate it in somebody else's copy of the
+       same puzzle.
+
+       The `Specific' and `Random Seed' options from the `Game' menu (or
+       the `File' menu, on Mac OS X) each show a piece of text (a `game
+       ID') which is sufficient to reconstruct precisely the same game at a
+       later date.
+
+       You can enter either of these pieces of text back into the program
+       (via the same `Specific' or `Random Seed' menu options) at a later
+       point, and it will recreate the same game. You can also use either
+       one as a command line argument (on Windows or Unix); see section 2.4
+       for more detail.
+
+       The difference between the two forms is that a descriptive game ID
+       is a literal _description_ of the initial state of the game, whereas
+       a random seed is just a piece of arbitrary text which was provided
+       as input to the random number generator used to create the puzzle.
+       This means that:
+
+        -  Descriptive game IDs tend to be longer in many puzzles
+           (although some, such as Cube (chapter 4), only need very short
+           descriptions). So a random seed is often a _quicker_ way to
+           note down the puzzle you're currently playing, or to tell it to
+           somebody else so they can play the same one as you.
+
+        -  Any text at all is a valid random seed. The automatically
+           generated ones are fifteen-digit numbers, but anything will do;
+           you can type in your full name, or a word you just made up, and
+           a valid puzzle will be generated from it. This provides a way
+           for two or more people to race to complete the same puzzle:
+           you think of a random seed, then everybody types it in at the
+           same time, and nobody has an advantage due to having seen the
+           generated puzzle before anybody else.
+
+        -  It is often possible to convert puzzles from other sources (such
+           as `nonograms' or `sudoku' from newspapers) into descriptive
+           game IDs suitable for use with these programs.
+
+        -  Random seeds are not guaranteed to produce the same result
+           if you use them with a different _version_ of the puzzle
+           program. This is because the generation algorithm might have
+           been improved or modified in later versions of the code, and
+           will therefore produce a different result when given the same
+           sequence of random numbers. Use a descriptive game ID if you
+           aren't sure that it will be used on the same version of the
+           program as yours.
+
+           (Use the `About' menu option to find out the version number of
+           the program. Programs with the same version number running on
+           different platforms should still be random-seed compatible.)
+
+       A descriptive game ID starts with a piece of text which encodes the
+       _parameters_ of the current game (such as grid size). Then there is
+       a colon, and after that is the description of the game's initial
+       state. A random seed starts with a similar string of parameters, but
+       then it contains a hash sign followed by arbitrary data.
+
+       If you enter a descriptive game ID, the program will not be able
+       to show you the random seed which generated it, since it wasn't
+       generated _from_ a random seed. If you _enter_ a random seed,
+       however, the program will be able to show you the descriptive game
+       ID derived from that random seed.
+
+       Note that the game parameter strings are not always identical
+       between the two forms. For some games, there will be parameter
+       data provided with the random seed which is not included in the
+       descriptive game ID. This is because that parameter information is
+       only relevant when _generating_ puzzle grids, and is not important
+       when playing them. Thus, for example, the difficulty level in Solo
+       (chapter 11) is not mentioned in the descriptive game ID.
+
+       These additional parameters are also not set permanently if you type
+       in a game ID. For example, suppose you have Solo set to `Advanced'
+       difficulty level, and then a friend wants your help with a `Trivial'
+       puzzle; so the friend reads out a random seed specifying `Trivial'
+       difficulty, and you type it in. The program will generate you the
+       same `Trivial' grid which your friend was having trouble with, but
+       once you have finished playing it, when you ask for a new game it
+       will automatically go back to the `Advanced' difficulty which it was
+       previously set on.
+
+   2.3 The `Type' menu
+
+       The `Type' menu, if present, may contain a list of preset game
+       settings. Selecting one of these will start a new random game with
+       the parameters specified.
+
+       The `Type' menu may also contain a `Custom' option which allows you
+       to fine-tune game parameters. The parameters available are specific
+       to each game and are described in the following sections.
+
+   2.4 Specifying game parameters on the command line
+
+       (This section does not apply to the Mac OS X version.)
+
+       The games in this collection deliberately do not ever save
+       information on to the computer they run on: they have no high score
+       tables and no saved preferences. (This is because I expect at least
+       some people to play them at work, and those people will probably
+       appreciate leaving as little evidence as possible!)
+
+       However, if you do want to arrange for one of these games to default
+       to a particular set of parameters, you can specify them on the
+       command line.
+
+       The easiest way to do this is to set up the parameters you want
+       using the `Type' menu (see section 2.3), and then to select `Random
+       Seed' from the `Game' or `File' menu (see section 2.2). The text
+       in the `Game ID' box will be composed of two parts, separated by a
+       hash. The first of these parts represents the game parameters (the
+       size of the playing area, for example, and anything else you set
+       using the `Type' menu).
+
+       If you run the game with just that parameter text on the command
+       line, it will start up with the settings you specified.
+
+       For example: if you run Cube (see chapter 4), select `Octahedron'
+       from the `Type' menu, and then go to the game ID selection, you
+       will see a string of the form `o2x2#338686542711620'. Take only the
+       part before the hash (`o2x2'), and start Cube with that text on the
+       command line: `PREFIX-cube o2x2'.
+
+       If you copy the _entire_ game ID on to the command line, the game
+       will start up in the specific game that was described. This is
+       occasionally a more convenient way to start a particular game ID
+       than by pasting it into the game ID selection box.
+
+       (You could also retrieve the encoded game parameters using the
+       `Specific' menu option instead of `Random Seed', but if you do then
+       some options, such as the difficulty level in Solo, will be missing.
+       See section 2.2 for more details on this.)
+
+   2.5 Unix command-line options
+
+       (This section only applies to the Unix port.)
+
+       In addition to being able to specify game parameters on the command
+       line (see section 2.4), there are various other options:
+
+       --game
+
+       --load
+
+           These options respectively determine whether the command-line
+           argument is treated as specifying game parameters or a save
+           file to load. Only one should be specified. If neither of these
+           options is specified, a guess is made based on the format of the
+           argument.
+
+       --generate _n_
+
+           If this option is specified, instead of a puzzle being
+           displayed, a number of descriptive game IDs will be invented and
+           printed on standard output. This is useful for gaining access
+           to the game generation algorithms without necessarily using the
+           frontend.
+
+           If game parameters are specified on the command-line, they will
+           be used to generate the game IDs; otherwise a default set of
+           parameters will be used.
+
+           The most common use of this option is in conjunction with `--
+           print', in which case its behaviour is slightly different; see
+           below.
+
+       --print _w_x_h_
+
+           If this option is specified, instead of a puzzle being
+           displayed, a printed representation of one or more unsolved
+           puzzles is sent to standard output, in PostScript format.
+
+           On each page of puzzles, there will be _w_ across and _h_ down.
+           If there are more puzzles than _w_x_h_, more than one page will
+           be printed.
+
+           If `--generate' has also been specified, the invented game
+           IDs will be used to generate the printed output. Otherwise,
+           a list of game IDs is expected on standard input (which can
+           be descriptive or random seeds; see section 2.2), in the same
+           format produced by `--generate'.
+
+           For example:
+
+             PREFIX-net --generate 12 --print 2x3 7x7w | lpr
+
+           will generate two pages of printed Net puzzles (each of which
+           will have a 7x7 wrapping grid), and pipe the output to the `lpr'
+           command, which on many systems will send them to an actual
+           printer.
+
+           There are various other options which affect printing; see
+           below.
+
+       --save _file-prefix_ [ --save-suffix _file-suffix_ ]
+
+           If this option is specified, instead of a puzzle being
+           displayed, saved-game files for one or more unsolved puzzles are
+           written to files constructed from the supplied prefix and/or
+           suffix.
+
+           If `--generate' has also been specified, the invented game
+           IDs will be used to generate the printed output. Otherwise,
+           a list of game IDs is expected on standard input (which can
+           be descriptive or random seeds; see section 2.2), in the same
+           format produced by `--generate'.
+
+           For example:
+
+             PREFIX-net --generate 12 --save game --save-suffix .sav
+
+           will generate twelve Net saved-game files with the names
+           game0.sav to game11.sav.
+
+       --version
+
+           Prints version information about the game, and then quits.
+
+       The following options are only meaningful if `--print' is also
+       specified:
+
+       --with-solutions
+
+           The set of pages filled with unsolved puzzles will be followed
+           by the solutions to those puzzles.
+
+       --scale _n_
+
+           Adjusts how big each puzzle is when printed. Larger numbers make
+           puzzles bigger; the default is 1.0.
+
+       --colour
+
+           Puzzles will be printed in colour, rather than in black and
+           white (if supported by the puzzle).
+
+Chapter 3: Net
+--------------
+
+       (_Note:_ the Windows version of this game is called NETGAME.EXE to
+       avoid clashing with Windows's own NET.EXE.)
+
+       I originally saw this in the form of a Flash game called
+       FreeNet [1], written by Pavils Jurjans; there are several other
+       implementations under the name NetWalk. The computer prepares a
+       network by connecting up the centres of squares in a grid, and then
+       shuffles the network by rotating every tile randomly. Your job is
+       to rotate it all back into place. The successful solution will be
+       an entirely connected network, with no closed loops. As a visual
+       aid, all tiles which are connected to the one in the middle are
+       highlighted.
+
+       [1] http://www.jurjans.lv/stuff/net/FreeNet.htm
+
+   3.1 Net controls
+
+       This game can be played with either the keyboard or the mouse. The
+       controls are:
+
+       _Select tile_: mouse pointer, arrow keys
+
+       _Rotate tile anticlockwise_: left mouse button, `A' key
+
+       _Rotate tile clockwise_: right mouse button, `D' key
+
+       _Rotate tile by 180 degrees_: `F' key
+
+       _Lock (or unlock) tile_: middle mouse button, shift-click, `S' key
+
+           You can lock a tile once you're sure of its orientation. You
+           can also unlock it again, but while it's locked you can't
+           accidentally turn it.
+
+       The following controls are not necessary to complete the game, but
+       may be useful:
+
+       _Shift grid_: Shift + arrow keys
+
+           On grids that wrap, you can move the origin of the grid, so
+           that tiles that were on opposite sides of the grid can be seen
+           together.
+
+       _Move centre_: Ctrl + arrow keys
+
+           You can change which tile is used as the source of highlighting.
+           (It doesn't ultimately matter which tile this is, as every tile
+           will be connected to every other tile in a correct solution,
+           but it may be helpful in the intermediate stages of solving the
+           puzzle.)
+
+       _Jumble tiles_: `J' key
+
+           This key turns all tiles that are not locked to random
+           orientations.
+
+       (All the actions described in section 2.1 are also available.)
+
+   3.2 Net parameters
+
+       These parameters are available from the `Custom...' option on the
+       `Type' menu.
+
+       _Width_, _Height_
+
+           Size of grid in tiles.
+
+       _Walls wrap around_
+
+           If checked, flow can pass from the left edge to the right edge,
+           and from top to bottom, and vice versa.
+
+       _Barrier probability_
+
+           A number between 0.0 and 1.0 controlling whether an immovable
+           barrier is placed between two tiles to prevent flow between
+           them (a higher number gives more barriers). Since barriers
+           are immovable, they act as constraints on the solution (i.e.,
+           hints).
+
+           The grid generation in Net has been carefully arranged so that
+           the barriers are independent of the rest of the grid. This
+           means that if you note down the random seed used to generate
+           the current puzzle (see section 2.2), change the _Barrier
+           probability_ parameter, and then re-enter the same random seed,
+           you should see exactly the same starting grid, with the only
+           change being the number of barriers. So if you're stuck on a
+           particular grid and need a hint, you could start up another
+           instance of Net, set up the same parameters but a higher barrier
+           probability, and enter the game seed from the original Net
+           window.
+
+       _Ensure unique solution_
+
+           Normally, Net will make sure that the puzzles it presents have
+           only one solution. Puzzles with ambiguous sections can be more
+           difficult and more subtle, so if you like you can turn off this
+           feature and risk having ambiguous puzzles. (Also, finding _all_
+           the possible solutions can be an additional challenge for an
+           advanced player.)
+
+Chapter 4: Cube
+---------------
+
+       This is another one I originally saw as a web game. This one was a
+       Java game [2], by Paul Scott. You have a grid of 16 squares, six of
+       which are blue; on one square rests a cube. Your move is to use the
+       arrow keys to roll the cube through 90 degrees so that it moves to
+       an adjacent square. If you roll the cube on to a blue square, the
+       blue square is picked up on one face of the cube; if you roll a blue
+       face of the cube on to a non-blue square, the blueness is put down
+       again. (In general, whenever you roll the cube, the two faces that
+       come into contact swap colours.) Your job is to get all six blue
+       squares on to the six faces of the cube at the same time. Count your
+       moves and try to do it in as few as possible.
+
+       Unlike the original Java game, my version has an additional feature:
+       once you've mastered the game with a cube rolling on a square grid,
+       you can change to a triangular grid and roll any of a tetrahedron,
+       an octahedron or an icosahedron.
+
+       [2] http://www3.sympatico.ca/paulscott/cube/cube.htm
+
+   4.1 Cube controls
+
+       This game can be played with either the keyboard or the mouse.
+
+       Left-clicking anywhere on the window will move the cube (or other
+       solid) towards the mouse pointer.
+
+       The arrow keys can also used to roll the cube on its square grid in
+       the four cardinal directions. On the triangular grids, the mapping
+       of arrow keys to directions is more approximate. Vertical movement
+       is disallowed where it doesn't make sense. The four keys surrounding
+       the arrow keys on the numeric keypad (`7', `9', `1', `3') can be
+       used for diagonal movement.
+
+       (All the actions described in section 2.1 are also available.)
+
+   4.2 Cube parameters
+
+       These parameters are available from the `Custom...' option on the
+       `Type' menu.
+
+       _Type of solid_
+
+           Selects the solid to roll (and hence the shape of the grid):
+           tetrahedron, cube, octahedron, or icosahedron.
+
+       _Width / top_, _Height / bottom_
+
+           On a square grid, horizontal and vertical dimensions. On a
+           triangular grid, the number of triangles on the top and bottom
+           rows respectively.
+
+Chapter 5: Fifteen
+------------------
+
+       The old ones are the best: this is the good old `15-puzzle' with
+       sliding tiles. You have a 4x4 square grid; 15 squares contain
+       numbered tiles, and the sixteenth is empty. Your move is to choose a
+       tile next to the empty space, and slide it into the space. The aim
+       is to end up with the tiles in numerical order, with the space in
+       the bottom right (so that the top row reads 1,2,3,4 and the bottom
+       row reads 13,14,15,_space_).
+
+   5.1 Fifteen controls
+
+       This game can be controlled with the mouse or the keyboard.
+
+       A left-click with the mouse in the row or column containing the
+       empty space will move as many tiles as necessary to move the space
+       to the mouse pointer.
+
+       The arrow keys will move a tile adjacent to the space in the
+       direction indicated (moving the space in the _opposite_ direction).
+
+       Pressing `h' will make a suggested move. Pressing `h' enough times
+       will solve the game, but it may scramble your progress while doing
+       so.
+
+       (All the actions described in section 2.1 are also available.)
+
+   5.2 Fifteen parameters
+
+       The only options available from the `Custom...' option on the `Type'
+       menu are _Width_ and _Height_, which are self-explanatory. (Once
+       you've changed these, it's not a `15-puzzle' any more, of course!)
+
+Chapter 6: Sixteen
+------------------
+
+       Another sliding tile puzzle, visually similar to Fifteen (see
+       chapter 5) but with a different type of move. This time, there is no
+       hole: all 16 squares on the grid contain numbered squares. Your move
+       is to shift an entire row left or right, or shift an entire column
+       up or down; every time you do that, the tile you shift off the grid
+       re-appears at the other end of the same row, in the space you just
+       vacated. To win, arrange the tiles into numerical order (1,2,3,4 on
+       the top row, 13,14,15,16 on the bottom). When you've done that, try
+       playing on different sizes of grid.
+
+       I _might_ have invented this game myself, though only by accident
+       if so (and I'm sure other people have independently invented it). I
+       thought I was imitating a screensaver I'd seen, but I have a feeling
+       that the screensaver might actually have been a Fifteen-type puzzle
+       rather than this slightly different kind. So this might be the one
+       thing in my puzzle collection which represents creativity on my part
+       rather than just engineering.
+
+   6.1 Sixteen controls
+
+       Left-clicking on an arrow will move the appropriate row or column in
+       the direction indicated. Right-clicking will move it in the opposite
+       direction.
+
+       Alternatively, use the cursor keys to move the position indicator
+       around the edge of the grid, and use the return key to move the
+       row/column in the direction indicated.
+
+       You can also move the tiles directly. Move the cursor onto a tile,
+       hold Control and press an arrow key to move the tile under the
+       cursor and move the cursor along with the tile. Or, hold Shift to
+       move only the tile. Pressing Enter simulates holding down Control
+       (press Enter again to release), while pressing Space simulates
+       holding down shift.
+
+       (All the actions described in section 2.1 are also available.)
+
+   6.2 Sixteen parameters
+
+       The parameters available from the `Custom...' option on the `Type'
+       menu are:
+
+        -  _Width_ and _Height_, which are self-explanatory.
+
+        -  You can ask for a limited shuffling operation to be performed on
+           the grid. By default, Sixteen will shuffle the grid in such a
+           way that any arrangement is about as probable as any other. You
+           can override this by requesting a precise number of shuffling
+           moves to be performed. Typically your aim is then to determine
+           the precise set of shuffling moves and invert them exactly,
+           so that you answer (say) a four-move shuffle with a four-move
+           solution. Note that the more moves you ask for, the more likely
+           it is that solutions shorter than the target length will turn
+           out to be possible.
+
+Chapter 7: Twiddle
+------------------
+
+       Twiddle is a tile-rearrangement puzzle, visually similar to Sixteen
+       (see chapter 6): you are given a grid of square tiles, each
+       containing a number, and your aim is to arrange the numbers into
+       ascending order.
+
+       In basic Twiddle, your move is to rotate a square group of four
+       tiles about their common centre. (Orientation is not significant
+       in the basic puzzle, although you can select it.) On more advanced
+       settings, you can rotate a larger square group of tiles.
+
+       I first saw this type of puzzle in the GameCube game `Metroid
+       Prime 2'. In the Main Gyro Chamber in that game, there is a puzzle
+       you solve to unlock a door, which is a special case of Twiddle. I
+       developed this game as a generalisation of that puzzle.
+
+   7.1 Twiddle controls
+
+       To play Twiddle, click the mouse in the centre of the square group
+       you wish to rotate. In the basic mode, you rotate a 2x2 square,
+       which means you have to click at a corner point where four tiles
+       meet.
+
+       In more advanced modes you might be rotating 3x3 or even more at a
+       time; if the size of the square is odd then you simply click in the
+       centre tile of the square you want to rotate.
+
+       Clicking with the left mouse button rotates the group anticlockwise.
+       Clicking with the right button rotates it clockwise.
+
+       You can also move an outline square around the grid with the cursor
+       keys; the square is the size above (2x2 by default, or larger).
+       Pressing the return key or space bar will rotate the current square
+       anticlockwise or clockwise respectively.
+
+       (All the actions described in section 2.1 are also available.)
+
+   7.2 Twiddle parameters
+
+       Twiddle provides several configuration options via the `Custom'
+       option on the `Type' menu:
+
+        -  You can configure the width and height of the puzzle grid.
+
+        -  You can configure the size of square block that rotates at a
+           time.
+
+        -  You can ask for every square in the grid to be distinguishable
+           (the default), or you can ask for a simplified puzzle in which
+           there are groups of identical numbers. In the simplified puzzle
+           your aim is just to arrange all the 1s into the first row, all
+           the 2s into the second row, and so on.
+
+        -  You can configure whether the orientation of tiles matters. If
+           you ask for an orientable puzzle, each tile will have a triangle
+           drawn in it. All the triangles must be pointing upwards to
+           complete the puzzle.
+
+        -  You can ask for a limited shuffling operation to be performed
+           on the grid. By default, Twiddle will shuffle the grid so much
+           that any arrangement is about as probable as any other. You can
+           override this by requesting a precise number of shuffling moves
+           to be performed. Typically your aim is then to determine the
+           precise set of shuffling moves and invert them exactly, so that
+           you answer (say) a four-move shuffle with a four-move solution.
+           Note that the more moves you ask for, the more likely it is that
+           solutions shorter than the target length will turn out to be
+           possible.
+
+Chapter 8: Rectangles
+---------------------
+
+       You have a grid of squares, with numbers written in some (but
+       not all) of the squares. Your task is to subdivide the grid into
+       rectangles of various sizes, such that (a) every rectangle contains
+       exactly one numbered square, and (b) the area of each rectangle is
+       equal to the number written in its numbered square.
+
+       Credit for this game goes to the Japanese puzzle magazine Nikoli [3]
+       ; I've also seen a Palm implementation at Puzzle Palace [4]. Unlike
+       Puzzle Palace's implementation, my version automatically generates
+       random grids of any size you like. The quality of puzzle design is
+       therefore not quite as good as hand-crafted puzzles would be, but on
+       the plus side you get an inexhaustible supply of puzzles tailored to
+       your own specification.
+
+       [3] http://www.nikoli.co.jp/en/puzzles/shikaku.html (beware of
+       Flash)
+
+       [4]
+       https://web.archive.org/web/20041024001459/http://www.puzzle.gr.jp/puzzle/sikaku/palm/index.html.en
+
+   8.1 Rectangles controls
+
+       This game is played with the mouse or cursor keys.
+
+       Left-click any edge to toggle it on or off, or left-click and
+       drag to draw an entire rectangle (or line) on the grid in one go
+       (removing any existing edges within that rectangle). Right-clicking
+       and dragging will allow you to erase the contents of a rectangle
+       without affecting its edges.
+
+       Alternatively, use the cursor keys to move the position indicator
+       around the board. Pressing the return key then allows you to use the
+       cursor keys to drag a rectangle out from that position, and pressing
+       the return key again completes the rectangle. Using the space bar
+       instead of the return key allows you to erase the contents of a
+       rectangle without affecting its edges, as above. Pressing escape
+       cancels a drag.
+
+       When a rectangle of the correct size is completed, it will be
+       shaded.
+
+       (All the actions described in section 2.1 are also available.)
+
+   8.2 Rectangles parameters
+
+       These parameters are available from the `Custom...' option on the
+       `Type' menu.
+
+       _Width_, _Height_
+
+           Size of grid, in squares.
+
+       _Expansion factor_
+
+           This is a mechanism for changing the type of grids generated by
+           the program. Some people prefer a grid containing a few large
+           rectangles to one containing many small ones. So you can ask
+           Rectangles to essentially generate a _smaller_ grid than the
+           size you specified, and then to expand it by adding rows and
+           columns.
+
+           The default expansion factor of zero means that Rectangles will
+           simply generate a grid of the size you ask for, and do nothing
+           further. If you set an expansion factor of (say) 0.5, it means
+           that each dimension of the grid will be expanded to half again
+           as big after generation. In other words, the initial grid will
+           be 2/3 the size in each dimension, and will be expanded to its
+           full size without adding any more rectangles.
+
+           Setting an expansion factor of around 0.5 tends to make the
+           game more difficult, and also (in my experience) rewards a
+           less deductive and more intuitive playing style. If you set it
+           _too_ high, though, the game simply cannot generate more than a
+           few rectangles to cover the entire grid, and the game becomes
+           trivial.
+
+       _Ensure unique solution_
+
+           Normally, Rectangles will make sure that the puzzles it presents
+           have only one solution. Puzzles with ambiguous sections can be
+           more difficult and more subtle, so if you like you can turn off
+           this feature and risk having ambiguous puzzles. Also, finding
+           _all_ the possible solutions can be an additional challenge for
+           an advanced player. Turning off this option can also speed up
+           puzzle generation.
+
+Chapter 9: Netslide
+-------------------
+
+       This game combines the grid generation of Net (see chapter 3) with
+       the movement of Sixteen (see chapter 6): you have a Net grid, but
+       instead of rotating tiles back into place you have to slide them
+       into place by moving a whole row at a time.
+
+       As in Sixteen, control is with the mouse or cursor keys. See section
+       6.1.
+
+       The available game parameters have similar meanings to those in Net
+       (see section 3.2) and Sixteen (see section 6.2).
+
+       Netslide was contributed to this collection by Richard Boulton.
+
+Chapter 10: Pattern
+-------------------
+
+       You have a grid of squares, which must all be filled in either black
+       or white. Beside each row of the grid are listed the lengths of the
+       runs of black squares on that row; above each column are listed the
+       lengths of the runs of black squares in that column. Your aim is to
+       fill in the entire grid black or white.
+
+       I first saw this puzzle form around 1995, under the name
+       `nonograms'. I've seen it in various places since then, under
+       different names.
+
+       Normally, puzzles of this type turn out to be a meaningful picture
+       of something once you've solved them. However, since this version
+       generates the puzzles automatically, they will just look like random
+       groupings of squares. (One user has suggested that this is actually
+       a _good_ thing, since it prevents you from guessing the colour of
+       squares based on the picture, and forces you to use logic instead.)
+       The advantage, though, is that you never run out of them.
+
+  10.1 Pattern controls
+
+       This game is played with the mouse.
+
+       Left-click in a square to colour it black. Right-click to colour it
+       white. If you make a mistake, you can middle-click, or hold down
+       Shift while clicking with any button, to colour the square in the
+       default grey (meaning `undecided') again.
+
+       You can click and drag with the left or right mouse button to colour
+       a vertical or horizontal line of squares black or white at a time
+       (respectively). If you click and drag with the middle button, or
+       with Shift held down, you can colour a whole rectangle of squares
+       grey.
+
+       You can also move around the grid with the cursor keys. Pressing the
+       return key will cycle the current cell through empty, then black,
+       then white, then empty, and the space bar does the same cycle in
+       reverse.
+
+       Moving the cursor while holding Control will colour the moved-over
+       squares black. Holding Shift will colour the moved-over squares
+       white, and holding both will colour them grey.
+
+       (All the actions described in section 2.1 are also available.)
+
+  10.2 Pattern parameters
+
+       The only options available from the `Custom...' option on the `Type'
+       menu are _Width_ and _Height_, which are self-explanatory.
+
+Chapter 11: Solo
+----------------
+
+       You have a square grid, which is divided into as many equally sized
+       sub-blocks as the grid has rows. Each square must be filled in with
+       a digit from 1 to the size of the grid, in such a way that
+
+        -  every row contains only one occurrence of each digit
+
+        -  every column contains only one occurrence of each digit
+
+        -  every block contains only one occurrence of each digit.
+
+        -  (optionally, by default off) each of the square's two main
+           diagonals contains only one occurrence of each digit.
+
+       You are given some of the numbers as clues; your aim is to place the
+       rest of the numbers correctly.
+
+       Under the default settings, the sub-blocks are square or
+       rectangular. The default puzzle size is 3x3 (a 9x9 actual grid,
+       divided into nine 3x3 blocks). You can also select sizes with
+       rectangular blocks instead of square ones, such as 2x3 (a 6x6 grid
+       divided into six 3x2 blocks). Alternatively, you can select `jigsaw'
+       mode, in which the sub-blocks are arbitrary shapes which differ
+       between individual puzzles.
+
+       Another available mode is `killer'. In this mode, clues are not
+       given in the form of filled-in squares; instead, the grid is divided
+       into `cages' by coloured lines, and for each cage the game tells
+       you what the sum of all the digits in that cage should be. Also,
+       no digit may appear more than once within a cage, even if the cage
+       crosses the boundaries of existing regions.
+
+       If you select a puzzle size which requires more than 9 digits, the
+       additional digits will be letters of the alphabet. For example, if
+       you select 3x4 then the digits which go in your grid will be 1 to 9,
+       plus `a', `b' and `c'. This cannot be selected for killer puzzles.
+
+       I first saw this puzzle in Nikoli [5], although it's also been
+       popularised by various newspapers under the name `Sudoku' or `Su
+       Doku'. Howard Garns is considered the inventor of the modern form of
+       the puzzle, and it was first published in _Dell Pencil Puzzles and
+       Word Games_. A more elaborate treatment of the history of the puzzle
+       can be found on Wikipedia [6].
+
+       [5] http://www.nikoli.co.jp/en/puzzles/sudoku.html (beware of Flash)
+
+       [6] http://en.wikipedia.org/wiki/Sudoku
+
+  11.1 Solo controls
+
+       To play Solo, simply click the mouse in any empty square and then
+       type a digit or letter on the keyboard to fill that square. If you
+       make a mistake, click the mouse in the incorrect square and press
+       Space to clear it again (or use the Undo feature).
+
+       If you _right_-click in a square and then type a number, that
+       number will be entered in the square as a `pencil mark'. You can
+       have pencil marks for multiple numbers in the same square. Squares
+       containing filled-in numbers cannot also contain pencil marks.
+
+       The game pays no attention to pencil marks, so exactly what you
+       use them for is up to you: you can use them as reminders that a
+       particular square needs to be re-examined once you know more about
+       a particular number, or you can use them as lists of the possible
+       numbers in a given square, or anything else you feel like.
+
+       To erase a single pencil mark, right-click in the square and type
+       the same number again.
+
+       All pencil marks in a square are erased when you left-click and type
+       a number, or when you left-click and press space. Right-clicking and
+       pressing space will also erase pencil marks.
+
+       Alternatively, use the cursor keys to move the mark around the grid.
+       Pressing the return key toggles the mark (from a normal mark to a
+       pencil mark), and typing a number in is entered in the square in the
+       appropriate way; typing in a 0 or using the space bar will clear a
+       filled square.
+
+       (All the actions described in section 2.1 are also available.)
+
+  11.2 Solo parameters
+
+       Solo allows you to configure two separate dimensions of the puzzle
+       grid on the `Type' menu: the number of columns, and the number of
+       rows, into which the main grid is divided. (The size of a block is
+       the inverse of this: for example, if you select 2 columns and 3
+       rows, each actual block will have 3 columns and 2 rows.)
+
+       If you tick the `X' checkbox, Solo will apply the optional extra
+       constraint that the two main diagonals of the grid also contain
+       one of every digit. (This is sometimes known as `Sudoku-X' in
+       newspapers.) In this mode, the squares on the two main diagonals
+       will be shaded slightly so that you know it's enabled.
+
+       If you tick the `Jigsaw' checkbox, Solo will generate randomly
+       shaped sub-blocks. In this mode, the actual grid size will be taken
+       to be the product of the numbers entered in the `Columns' and `Rows'
+       boxes. There is no reason why you have to enter a number greater
+       than 1 in both boxes; Jigsaw mode has no constraint on the grid
+       size, and it can even be a prime number if you feel like it.
+
+       If you tick the `Killer' checkbox, Solo will generate a set of
+       of cages, which are randomly shaped and drawn in an outline of a
+       different colour. Each of these regions contains a smaller clue
+       which shows the digit sum of all the squares in this region.
+
+       You can also configure the type of symmetry shown in the generated
+       puzzles. More symmetry makes the puzzles look prettier but may also
+       make them easier, since the symmetry constraints can force more
+       clues than necessary to be present. Completely asymmetric puzzles
+       have the freedom to contain as few clues as possible.
+
+       Finally, you can configure the difficulty of the generated puzzles.
+       Difficulty levels are judged by the complexity of the techniques
+       of deduction required to solve the puzzle: each level requires a
+       mode of reasoning which was not necessary in the previous one. In
+       particular, on difficulty levels `Trivial' and `Basic' there will be
+       a square you can fill in with a single number at all times, whereas
+       at `Intermediate' level and beyond you will have to make partial
+       deductions about the _set_ of squares a number could be in (or the
+       set of numbers that could be in a square). At `Unreasonable' level,
+       even this is not enough, and you will eventually have to make a
+       guess, and then backtrack if it turns out to be wrong.
+
+       Generating difficult puzzles is itself difficult: if you select one
+       of the higher difficulty levels, Solo may have to make many attempts
+       at generating a puzzle before it finds one hard enough for you. Be
+       prepared to wait, especially if you have also configured a large
+       puzzle size.
+
+Chapter 12: Mines
+-----------------
+
+       You have a grid of covered squares, some of which contain mines, but
+       you don't know which. Your job is to uncover every square which does
+       _not_ contain a mine. If you uncover a square containing a mine, you
+       lose. If you uncover a square which does not contain a mine, you
+       are told how many mines are contained within the eight surrounding
+       squares.
+
+       This game needs no introduction; popularised by Windows, it is
+       perhaps the single best known desktop puzzle game in existence.
+
+       This version of it has an unusual property. By default, it will
+       generate its mine positions in such a way as to ensure that you
+       never need to _guess_ where a mine is: you will always be able
+       to deduce it somehow. So you will never, as can happen in other
+       versions, get to the last four squares and discover that there are
+       two mines left but you have no way of knowing for sure where they
+       are.
+
+  12.1 Mines controls
+
+       This game is played with the mouse.
+
+       If you left-click in a covered square, it will be uncovered.
+
+       If you right-click in a covered square, it will place a flag which
+       indicates that the square is believed to be a mine. Left-clicking in
+       a marked square will not uncover it, for safety. You can right-click
+       again to remove a mark placed in error.
+
+       If you left-click in an _uncovered_ square, it will `clear around'
+       the square. This means: if the square has exactly as many flags
+       surrounding it as it should have mines, then all the covered squares
+       next to it which are _not_ flagged will be uncovered. So once you
+       think you know the location of all the mines around a square, you
+       can use this function as a shortcut to avoid having to click on each
+       of the remaining squares one by one.
+
+       If you uncover a square which has _no_ mines in the surrounding
+       eight squares, then it is obviously safe to uncover those squares in
+       turn, and so on if any of them also has no surrounding mines. This
+       will be done for you automatically; so sometimes when you uncover a
+       square, a whole new area will open up to be explored.
+
+       You can also use the cursor keys to move around the minefield.
+       Pressing the return key in a covered square uncovers it, and in
+       an uncovered square will clear around it (so it acts as the left
+       button), pressing the space bar in a covered square will place a
+       flag (similarly, it acts as the right button).
+
+       All the actions described in section 2.1 are also available.
+
+       Even Undo is available, although you might consider it cheating to
+       use it. If you step on a mine, the program will only reveal the mine
+       in question (unlike most other implementations, which reveal all of
+       them). You can then Undo your fatal move and continue playing if you
+       like. The program will track the number of times you died (and Undo
+       will not reduce that counter), so when you get to the end of the
+       game you know whether or not you did it without making any errors.
+
+       (If you really want to know the full layout of the grid, which other
+       implementations will show you after you die, you can always use the
+       Solve menu option.)
+
+  12.2 Mines parameters
+
+       The options available from the `Custom...' option on the `Type' menu
+       are:
+
+       _Width_, _Height_
+
+           Size of grid in squares.
+
+       _Mines_
+
+           Number of mines in the grid. You can enter this as an absolute
+           mine count, or alternatively you can put a % sign on the end
+           in which case the game will arrange for that proportion of the
+           squares in the grid to be mines.
+
+           Beware of setting the mine count too high. At very high
+           densities, the program may spend forever searching for a
+           solvable grid.
+
+       _Ensure solubility_
+
+           When this option is enabled (as it is by default), Mines will
+           ensure that the entire grid can be fully deduced starting
+           from the initial open space. If you prefer the riskier grids
+           generated by other implementations, you can switch off this
+           option.
+
+Chapter 13: Same Game
+---------------------
+
+       You have a grid of coloured squares, which you have to clear by
+       highlighting contiguous regions of more than one coloured square;
+       the larger the region you highlight, the more points you get (and
+       the faster you clear the arena).
+
+       If you clear the grid you win. If you end up with nothing but single
+       squares (i.e., there are no more clickable regions left) you lose.
+
+       Removing a region causes the rest of the grid to shuffle up: blocks
+       that are suspended will fall down (first), and then empty columns
+       are filled from the right.
+
+       Same Game was contributed to this collection by James Harvey.
+
+  13.1 Same Game controls
+
+       This game can be played with either the keyboard or the mouse.
+
+       If you left-click an unselected region, it becomes selected
+       (possibly clearing the current selection).
+
+       If you left-click the selected region, it will be removed (and the
+       rest of the grid shuffled immediately).
+
+       If you right-click the selected region, it will be unselected.
+
+       The cursor keys move a cursor around the grid. Pressing the Space or
+       Enter keys while the cursor is in an unselected region selects it;
+       pressing Space or Enter again removes it as above.
+
+       (All the actions described in section 2.1 are also available.)
+
+  13.2 Same Game parameters
+
+       These parameters are available from the `Custom...' option on the
+       `Type' menu.
+
+       _Width_, _Height_
+
+           Size of grid in squares.
+
+       _No. of colours_
+
+           Number of different colours used to fill the grid; the more
+           colours, the fewer large regions of colour and thus the more
+           difficult it is to successfully clear the grid.
+
+       _Scoring system_
+
+           Controls the precise mechanism used for scoring. With the
+           default system, `(n-2)^2', only regions of three squares or more
+           will score any points at all. With the alternative `(n-1)^2'
+           system, regions of two squares score a point each, and larger
+           regions score relatively more points.
+
+       _Ensure solubility_
+
+           If this option is ticked (the default state), generated grids
+           will be guaranteed to have at least one solution.
+
+           If you turn it off, the game generator will not try to guarantee
+           soluble grids; it will, however, still ensure that there are at
+           least 2 squares of each colour on the grid at the start (since a
+           grid with exactly one square of a given colour is _definitely_
+           insoluble). Grids generated with this option disabled may
+           contain more large areas of contiguous colour, leading to
+           opportunities for higher scores; they can also take less time to
+           generate.
+
+Chapter 14: Flip
+----------------
+
+       You have a grid of squares, some light and some dark. Your aim is to
+       light all the squares up at the same time. You can choose any square
+       and flip its state from light to dark or dark to light, but when you
+       do so, other squares around it change state as well.
+
+       Each square contains a small diagram showing which other squares
+       change when you flip it.
+
+  14.1 Flip controls
+
+       This game can be played with either the keyboard or the mouse.
+
+       Left-click in a square to flip it and its associated squares, or use
+       the cursor keys to choose a square and the space bar or Enter key to
+       flip.
+
+       If you use the `Solve' function on this game, it will mark some of
+       the squares in red. If you click once in every square with a red
+       mark, the game should be solved. (If you click in a square _without_
+       a red mark, a red mark will appear in it to indicate that you will
+       need to reverse that operation to reach the solution.)
+
+       (All the actions described in section 2.1 are also available.)
+
+  14.2 Flip parameters
+
+       These parameters are available from the `Custom...' option on the
+       `Type' menu.
+
+       _Width_, _Height_
+
+           Size of grid in squares.
+
+       _Shape type_
+
+           This control determines the shape of the region which is flipped
+           by clicking in any given square. The default setting, `Crosses',
+           causes every square to flip itself and its four immediate
+           neighbours (or three or two if it's at an edge or corner). The
+           other setting, `Random', causes a random shape to be chosen for
+           every square, so the game is different every time.
+
+Chapter 15: Guess
+-----------------
+
+       You have a set of coloured pegs, and have to reproduce a
+       predetermined sequence of them (chosen by the computer) within a
+       certain number of guesses.
+
+       Each guess gets marked with the number of correctly-coloured pegs
+       in the correct places (in black), and also the number of correctly-
+       coloured pegs in the wrong places (in white).
+
+       This game is also known (and marketed, by Hasbro, mainly) as a board
+       game `Mastermind', with 6 colours, 4 pegs per row, and 10 guesses.
+       However, this version allows custom settings of number of colours
+       (up to 10), number of pegs per row, and number of guesses.
+
+       Guess was contributed to this collection by James Harvey.
+
+  15.1 Guess controls
+
+       This game can be played with either the keyboard or the mouse.
+
+       With the mouse, drag a coloured peg from the tray on the left-hand
+       side to its required position in the current guess; pegs may also
+       be dragged from current and past guesses to copy them elsewhere. To
+       remove a peg, drag it off its current position to somewhere invalid.
+
+       Right-clicking in the current guess adds a `hold' marker; pegs that
+       have hold markers will be automatically added to the next guess
+       after marking.
+
+       Alternatively, with the keyboard, the up and down cursor keys can
+       be used to select a peg colour, the left and right keys to select a
+       peg position, and the space bar or Enter key to place a peg of the
+       selected colour in the chosen position. `D' or Backspace removes a
+       peg, and Space adds a hold marker.
+
+       Pressing `h' or `?' will fill the current guess with a suggested
+       guess. Using this is not recommended for 10 or more pegs as it is
+       slow.
+
+       When the guess is complete, the smaller feedback pegs will be
+       highlighted; clicking on these (or moving the peg cursor to them
+       with the arrow keys and pressing the space bar or Enter key) will
+       mark the current guess, copy any held pegs to the next guess, and
+       move the `current guess' marker.
+
+       If you correctly position all the pegs the solution will be
+       displayed below; if you run out of guesses (or select `Solve...')
+       the solution will also be revealed.
+
+       (All the actions described in section 2.1 are also available.)
+
+  15.2 Guess parameters
+
+       These parameters are available from the `Custom...' option on the
+       `Type' menu. The default game matches the parameters for the board
+       game `Mastermind'.
+
+       _Colours_
+
+           Number of colours the solution is chosen from; from 2 to 10
+           (more is harder).
+
+       _Pegs per guess_
+
+           Number of pegs per guess (more is harder).
+
+       _Guesses_
+
+           Number of guesses you have to find the solution in (fewer is
+           harder).
+
+       _Allow blanks_
+
+           Allows blank pegs to be given as part of a guess (makes it
+           easier, because you know that those will never be counted as
+           part of the solution). This is turned off by default.
+
+           Note that this doesn't allow blank pegs in the solution; if you
+           really wanted that, use one extra colour.
+
+       _Allow duplicates_
+
+           Allows the solution (and the guesses) to contain colours more
+           than once; this increases the search space (making things
+           harder), and is turned on by default.
+
+Chapter 16: Pegs
+----------------
+
+       A number of pegs are placed in holes on a board. You can remove a
+       peg by jumping an adjacent peg over it (horizontally or vertically)
+       to a vacant hole on the other side. Your aim is to remove all but
+       one of the pegs initially present.
+
+       This game, best known as `Peg Solitaire', is possibly one of the
+       oldest puzzle games still commonly known.
+
+  16.1 Pegs controls
+
+       To move a peg, drag it with the mouse from its current position to
+       its final position. If the final position is exactly two holes away
+       from the initial position, is currently unoccupied by a peg, and
+       there is a peg in the intervening square, the move will be permitted
+       and the intervening peg will be removed.
+
+       Vacant spaces which you can move a peg into are marked with holes. A
+       space with no peg and no hole is not available for moving at all: it
+       is an obstacle which you must work around.
+
+       You can also use the cursor keys to move a position indicator around
+       the board. Pressing the return key while over a peg, followed by a
+       cursor key, will jump the peg in that direction (if that is a legal
+       move).
+
+       (All the actions described in section 2.1 are also available.)
+
+  16.2 Pegs parameters
+
+       These parameters are available from the `Custom...' option on the
+       `Type' menu.
+
+       _Width_, _Height_
+
+           Size of grid in holes.
+
+       _Board type_
+
+           Controls whether you are given a board of a standard shape or
+           a randomly generated shape. The two standard shapes currently
+           supported are `Cross' and `Octagon' (also commonly known as the
+           English and European traditional board layouts respectively).
+           Selecting `Random' will give you a different board shape every
+           time (but always one that is known to have a solution).
+
+Chapter 17: Dominosa
+--------------------
+
+       A normal set of dominoes - that is, one instance of every
+       (unordered) pair of numbers from 0 to 6 - has been arranged
+       irregularly into a rectangle; then the number in each square has
+       been written down and the dominoes themselves removed. Your task is
+       to reconstruct the pattern by arranging the set of dominoes to match
+       the provided array of numbers.
+
+       This puzzle is widely credited to O. S. Adler, and takes part of its
+       name from those initials.
+
+  17.1 Dominosa controls
+
+       Left-clicking between any two adjacent numbers places a domino
+       covering them, or removes one if it is already present. Trying to
+       place a domino which overlaps existing dominoes will remove the ones
+       it overlaps.
+
+       Right-clicking between two adjacent numbers draws a line between
+       them, which you can use to remind yourself that you know those two
+       numbers are _not_ covered by a single domino. Right-clicking again
+       removes the line.
+
+       You can also use the cursor keys to move a cursor around the grid.
+       When the cursor is half way between two adjacent numbers, pressing
+       the return key will place a domino covering those numbers, or
+       pressing the space bar will lay a line between the two squares.
+       Repeating either action removes the domino or line.
+
+       Pressing a number key will highlight all occurrences of that number.
+       Pressing that number again will clear the highlighting. Up to two
+       different numbers can be highlighted at any given time.
+
+       (All the actions described in section 2.1 are also available.)
+
+  17.2 Dominosa parameters
+
+       These parameters are available from the `Custom...' option on the
+       `Type' menu.
+
+       _Maximum number on dominoes_
+
+           Controls the size of the puzzle, by controlling the size of the
+           set of dominoes used to make it. Dominoes with numbers going
+           up to N will give rise to an (N+2) x (N+1) rectangle; so, in
+           particular, the default value of 6 gives an 8x7 grid.
+
+       _Ensure unique solution_
+
+           Normally, Dominosa will make sure that the puzzles it presents
+           have only one solution. Puzzles with ambiguous sections can be
+           more difficult and sometimes more subtle, so if you like you
+           can turn off this feature. Also, finding _all_ the possible
+           solutions can be an additional challenge for an advanced player.
+           Turning off this option can also speed up puzzle generation.
+
+Chapter 18: Untangle
+--------------------
+
+       You are given a number of points, some of which have lines drawn
+       between them. You can move the points about arbitrarily; your aim is
+       to position the points so that no line crosses another.
+
+       I originally saw this in the form of a Flash game called Planarity
+       [7], written by John Tantalo.
+
+       [7] http://planarity.net
+
+  18.1 Untangle controls
+
+       To move a point, click on it with the left mouse button and drag it
+       into a new position.
+
+       (All the actions described in section 2.1 are also available.)
+
+  18.2 Untangle parameters
+
+       There is only one parameter available from the `Custom...' option on
+       the `Type' menu:
+
+       _Number of points_
+
+           Controls the size of the puzzle, by specifying the number of
+           points in the generated graph.
+
+Chapter 19: Black Box
+---------------------
+
+       A number of balls are hidden in a rectangular arena. You have to
+       deduce the positions of the balls by firing lasers positioned at the
+       edges of the arena and observing how their beams are deflected.
+
+       Beams will travel straight from their origin until they hit the
+       opposite side of the arena (at which point they emerge), unless
+       affected by balls in one of the following ways:
+
+        -  A beam that hits a ball head-on is absorbed and will never re-
+           emerge. This includes beams that meet a ball on the first rank
+           of the arena.
+
+        -  A beam with a ball in its front-left square and no ball ahead of
+           it gets deflected 90 degrees to the right.
+
+        -  A beam with a ball in its front-right square and no ball ahead
+           of it gets similarly deflected to the left.
+
+        -  A beam that would re-emerge from its entry location is
+           considered to be `reflected'.
+
+        -  A beam which would get deflected before entering the arena by a
+           ball to the front-left or front-right of its entry point is also
+           considered to be `reflected'.
+
+       Beams that are reflected appear as a `R'; beams that hit balls head-
+       on appear as `H'. Otherwise, a number appears at the firing point
+       and the location where the beam emerges (this number is unique to
+       that shot).
+
+       You can place guesses as to the location of the balls, based on the
+       entry and exit patterns of the beams; once you have placed enough
+       balls a button appears enabling you to have your guesses checked.
+
+       Here is a diagram showing how the positions of balls can create each
+       of the beam behaviours shown above:
+
+          1RHR---- 
+         |..O.O...|
+         2........3
+         |........|
+         |........|
+         3........|
+         |......O.|
+         H........|
+         |.....O..|
+          12-RR---
+
+       As shown, it is possible for a beam to receive multiple reflections
+       before re-emerging (see turn 3). Similarly, a beam may be reflected
+       (possibly more than once) before receiving a hit (the `H' on the
+       left side of the example).
+
+       Note that any layout with more than 4 balls may have a non-unique
+       solution. The following diagram illustrates this; if you know the
+       board contains 5 balls, it is impossible to determine where the
+       fifth ball is (possible positions marked with an x):
+
+          -------- 
+         |........|
+         |........|
+         |..O..O..|
+         |...xx...|
+         |...xx...|
+         |..O..O..|
+         |........|
+         |........|
+          --------
+
+       For this reason, when you have your guesses checked, the game
+       will check that your solution _produces the same results_ as the
+       computer's, rather than that your solution is identical to the
+       computer's. So in the above example, you could put the fifth ball at
+       _any_ of the locations marked with an x, and you would still win.
+
+       Black Box was contributed to this collection by James Harvey.
+
+  19.1 Black Box controls
+
+       To fire a laser beam, left-click in a square around the edge of
+       the arena. The results will be displayed immediately. Clicking or
+       holding the left button on one of these squares will highlight the
+       current go (or a previous go) to confirm the exit point for that
+       laser, if applicable.
+
+       To guess the location of a ball, left-click within the arena and a
+       black circle will appear marking the guess; click again to remove
+       the guessed ball.
+
+       Locations in the arena may be locked against modification by right-
+       clicking; whole rows and columns may be similarly locked by right-
+       clicking in the laser square above/below that column, or to the
+       left/right of that row.
+
+       The cursor keys may also be used to move around the grid. Pressing
+       the Enter key will fire a laser or add a new ball-location guess,
+       and pressing Space will lock a cell, row, or column.
+
+       When an appropriate number of balls have been guessed, a button will
+       appear at the top-left corner of the grid; clicking that (with mouse
+       or cursor) will check your guesses.
+
+       If you click the `check' button and your guesses are not correct,
+       the game will show you the minimum information necessary to
+       demonstrate this to you, so you can try again. If your ball
+       positions are not consistent with the beam paths you already know
+       about, one beam path will be circled to indicate that it proves you
+       wrong. If your positions match all the existing beam paths but are
+       still wrong, one new beam path will be revealed (written in red)
+       which is not consistent with your current guesses.
+
+       If you decide to give up completely, you can select Solve to reveal
+       the actual ball positions. At this point, correctly-placed balls
+       will be displayed as filled black circles, incorrectly-placed balls
+       as filled black circles with red crosses, and missing balls as
+       filled red circles. In addition, a red circle marks any laser you
+       had already fired which is not consistent with your ball layout
+       (just as when you press the `check' button), and red text marks
+       any laser you _could_ have fired in order to distinguish your ball
+       layout from the correct one.
+
+       (All the actions described in section 2.1 are also available.)
+
+  19.2 Black Box parameters
+
+       These parameters are available from the `Custom...' option on the
+       `Type' menu.
+
+       _Width_, _Height_
+
+           Size of grid in squares. There are 2 x _Width_ x _Height_ lasers
+           per grid, two per row and two per column.
+
+       _No. of balls_
+
+           Number of balls to place in the grid. This can be a single
+           number, or a range (separated with a hyphen, like `2-6'),
+           and determines the number of balls to place on the grid.
+           The `reveal' button is only enabled if you have guessed an
+           appropriate number of balls; a guess using a different number
+           to the original solution is still acceptable, if all the beam
+           inputs and outputs match.
+
+Chapter 20: Slant
+-----------------
+
+       You have a grid of squares. Your aim is to draw a diagonal line
+       through each square, and choose which way each line slants so that
+       the following conditions are met:
+
+        -  The diagonal lines never form a loop.
+
+        -  Any point with a circled number has precisely that many lines
+           meeting at it. (Thus, a 4 is the centre of a cross shape,
+           whereas a zero is the centre of a diamond shape - or rather, a
+           partial diamond shape, because a zero can never appear in the
+           middle of the grid because that would immediately cause a loop.)
+
+       Credit for this puzzle goes to Nikoli [8].
+
+       [8] http://www.nikoli.co.jp/ja/puzzles/gokigen_naname (in Japanese)
+
+  20.1 Slant controls
+
+       Left-clicking in a blank square will place a \ in it (a line leaning
+       to the left, i.e. running from the top left of the square to the
+       bottom right). Right-clicking in a blank square will place a / in it
+       (leaning to the right, running from top right to bottom left).
+
+       Continuing to click either button will cycle between the three
+       possible square contents. Thus, if you left-click repeatedly in a
+       blank square it will change from blank to \ to / back to blank, and
+       if you right-click repeatedly the square will change from blank to /
+       to \ back to blank. (Therefore, you can play the game entirely with
+       one button if you need to.)
+
+       You can also use the cursor keys to move around the grid. Pressing
+       the return or space keys will place a \ or a /, respectively, and
+       will then cycle them as above. You can also press / or \ to place a
+       / or \, respectively, independent of what is already in the cursor
+       square. Backspace removes any line from the cursor square.
+
+       (All the actions described in section 2.1 are also available.)
+
+  20.2 Slant parameters
+
+       These parameters are available from the `Custom...' option on the
+       `Type' menu.
+
+       _Width_, _Height_
+
+           Size of grid in squares.
+
+       _Difficulty_
+
+           Controls the difficulty of the generated puzzle. At Hard
+           level, you are required to do deductions based on knowledge of
+           _relationships_ between squares rather than always being able to
+           deduce the exact contents of one square at a time. (For example,
+           you might know that two squares slant in the same direction,
+           even if you don't yet know what that direction is, and this
+           might enable you to deduce something about still other squares.)
+           Even at Hard level, guesswork and backtracking should never be
+           necessary.
+
+Chapter 21: Light Up
+--------------------
+
+       You have a grid of squares. Some are filled in black; some of the
+       black squares are numbered. Your aim is to `light up' all the empty
+       squares by placing light bulbs in some of them.
+
+       Each light bulb illuminates the square it is on, plus all squares
+       in line with it horizontally or vertically unless a black square is
+       blocking the way.
+
+       To win the game, you must satisfy the following conditions:
+
+        -  All non-black squares are lit.
+
+        -  No light is lit by another light.
+
+        -  All numbered black squares have exactly that number of lights
+           adjacent to them (in the four squares above, below, and to the
+           side).
+
+       Non-numbered black squares may have any number of lights adjacent to
+       them.
+
+       Credit for this puzzle goes to Nikoli [9].
+
+       Light Up was contributed to this collection by James Harvey.
+
+       [9] http://www.nikoli.co.jp/en/puzzles/akari.html (beware of Flash)
+
+  21.1 Light Up controls
+
+       Left-clicking in a non-black square will toggle the presence of a
+       light in that square. Right-clicking in a non-black square toggles a
+       mark there to aid solving; it can be used to highlight squares that
+       cannot be lit, for example.
+
+       You may not place a light in a marked square, nor place a mark in a
+       lit square.
+
+       The game will highlight obvious errors in red. Lights lit by other
+       lights are highlighted in this way, as are numbered squares which do
+       not (or cannot) have the right number of lights next to them.
+
+       Thus, the grid is solved when all non-black squares have yellow
+       highlights and there are no red lights.
+
+       (All the actions described in section 2.1 are also available.)
+
+  21.2 Light Up parameters
+
+       These parameters are available from the `Custom...' option on the
+       `Type' menu.
+
+       _Width_, _Height_
+
+           Size of grid in squares.
+
+       _%age of black squares_
+
+           Rough percentage of black squares in the grid.
+
+           This is a hint rather than an instruction. If the grid generator
+           is unable to generate a puzzle to this precise specification, it
+           will increase the proportion of black squares until it can.
+
+       _Symmetry_
+
+           Allows you to specify the required symmetry of the black squares
+           in the grid. (This does not affect the difficulty of the puzzles
+           noticeably.)
+
+       _Difficulty_
+
+           `Easy' means that the puzzles should be soluble without
+           backtracking or guessing, `Hard' means that some guesses will
+           probably be necessary.
+
+Chapter 22: Map
+---------------
+
+       You are given a map consisting of a number of regions. Your task is
+       to colour each region with one of four colours, in such a way that
+       no two regions sharing a boundary have the same colour. You are
+       provided with some regions already coloured, sufficient to make the
+       remainder of the solution unique.
+
+       Only regions which share a length of border are required to be
+       different colours. Two regions which meet at only one _point_ (i.e.
+       are diagonally separated) may be the same colour.
+
+       I believe this puzzle is original; I've never seen an implementation
+       of it anywhere else. The concept of a four-colouring puzzle was
+       suggested by Owen Dunn; credit must also go to Nikoli and to Verity
+       Allan for inspiring the train of thought that led to me realising
+       Owen's suggestion was a viable puzzle. Thanks also to Gareth Taylor
+       for many detailed suggestions.
+
+  22.1 Map controls
+
+       To colour a region, click the left mouse button on an existing
+       region of the desired colour and drag that colour into the new
+       region.
+
+       (The program will always ensure the starting puzzle has at least one
+       region of each colour, so that this is always possible!)
+
+       If you need to clear a region, you can drag from an empty region, or
+       from the puzzle boundary if there are no empty regions left.
+
+       Dragging a colour using the _right_ mouse button will stipple the
+       region in that colour, which you can use as a note to yourself that
+       you think the region _might_ be that colour. A region can contain
+       stipples in multiple colours at once. (This is often useful at the
+       harder difficulty levels.)
+
+       You can also use the cursor keys to move around the map: the colour
+       of the cursor indicates the position of the colour you would drag
+       (which is not obvious if you're on a region's boundary, since it
+       depends on the direction from which you approached the boundary).
+       Pressing the return key starts a drag of that colour, as above,
+       which you control with the cursor keys; pressing the return key
+       again finishes the drag. The space bar can be used similarly to
+       create a stippled region. Double-pressing the return key (without
+       moving the cursor) will clear the region, as a drag from an empty
+       region does: this is useful with the cursor mode if you have filled
+       the entire map in but need to correct the layout.
+
+       If you press L during play, the game will toggle display of a number
+       in each region of the map. This is useful if you want to discuss a
+       particular puzzle instance with a friend - having an unambiguous
+       name for each region is much easier than trying to refer to them all
+       by names such as `the one down and right of the brown one on the top
+       border'.
+
+       (All the actions described in section 2.1 are also available.)
+
+  22.2 Map parameters
+
+       These parameters are available from the `Custom...' option on the
+       `Type' menu.
+
+       _Width_, _Height_
+
+           Size of grid in squares.
+
+       _Regions_
+
+           Number of regions in the generated map.
+
+       _Difficulty_
+
+           In `Easy' mode, there should always be at least one region whose
+           colour can be determined trivially. In `Normal' and `Hard'
+           modes, you will have to use increasingly complex logic to deduce
+           the colour of some regions. However, it will always be possible
+           without having to guess or backtrack.
+
+           In `Unreasonable' mode, the program will feel free to generate
+           puzzles which are as hard as it can possibly make them: the
+           only constraint is that they should still have a unique
+           solution. Solving Unreasonable puzzles may require guessing and
+           backtracking.
+
+Chapter 23: Loopy
+-----------------
+
+       You are given a grid of dots, marked with yellow lines to indicate
+       which dots you are allowed to connect directly together. Your aim is
+       to use some subset of those yellow lines to draw a single unbroken
+       loop from dot to dot within the grid.
+
+       Some of the spaces between the lines contain numbers. These numbers
+       indicate how many of the lines around that space form part of the
+       loop. The loop you draw must correctly satisfy all of these clues to
+       be considered a correct solution.
+
+       In the default mode, the dots are arranged in a grid of squares;
+       however, you can also play on triangular or hexagonal grids, or even
+       more exotic ones.
+
+       Credit for the basic puzzle idea goes to Nikoli [10].
+
+       Loopy was originally contributed to this collection by Mike Pinna,
+       and subsequently enhanced to handle various types of non-square grid
+       by Lambros Lambrou.
+
+       [10] http://www.nikoli.co.jp/en/puzzles/slitherlink.html (beware of
+       Flash)
+
+  23.1 Loopy controls
+
+       Click the left mouse button on a yellow line to turn it black,
+       indicating that you think it is part of the loop. Click again to
+       turn the line yellow again (meaning you aren't sure yet).
+
+       If you are sure that a particular line segment is _not_ part of the
+       loop, you can click the right mouse button to remove it completely.
+       Again, clicking a second time will turn the line back to yellow.
+
+       (All the actions described in section 2.1 are also available.)
+
+  23.2 Loopy parameters
+
+       These parameters are available from the `Custom...' option on the
+       `Type' menu.
+
+       _Width_, _Height_
+
+           Size of grid, measured in number of regions across and down. For
+           square grids, it's clear how this is counted; for other types of
+           grid you may have to think a bit to see how the dimensions are
+           measured.
+
+       _Grid type_
+
+           Allows you to choose between a selection of types of tiling.
+           Some have all the faces the same but may have multiple different
+           types of vertex (e.g. the _Cairo_ or _Kites_ mode); others
+           have all the vertices the same but may have different types of
+           face (e.g. the _Great Hexagonal_). The square, triangular and
+           honeycomb grids are fully regular, and have all their vertices
+           _and_ faces the same; this makes them the least confusing to
+           play.
+
+       _Difficulty_
+
+           Controls the difficulty of the generated puzzle.
+
+Chapter 24: Inertia
+-------------------
+
+       You are a small green ball sitting in a grid full of obstacles. Your
+       aim is to collect all the gems without running into any mines.
+
+       You can move the ball in any orthogonal _or diagonal_ direction.
+       Once the ball starts moving, it will continue until something stops
+       it. A wall directly in its path will stop it (but if it is moving
+       diagonally, it will move through a diagonal gap between two other
+       walls without stopping). Also, some of the squares are `stops'; when
+       the ball moves on to a stop, it will stop moving no matter what
+       direction it was going in. Gems do _not_ stop the ball; it picks
+       them up and keeps on going.
+
+       Running into a mine is fatal. Even if you picked up the last gem in
+       the same move which then hit a mine, the game will count you as dead
+       rather than victorious.
+
+       This game was originally implemented for Windows by Ben Olmstead
+       [11], who was kind enough to release his source code on request so
+       that it could be re-implemented for this collection.
+
+       [11] http://xn13.com/
+
+  24.1 Inertia controls
+
+       You can move the ball in any of the eight directions using the
+       numeric keypad. Alternatively, if you click the left mouse button
+       on the grid, the ball will begin a move in the general direction of
+       where you clicked.
+
+       If you use the `Solve' function on this game, the program will
+       compute a path through the grid which collects all the remaining
+       gems and returns to the current position. A hint arrow will appear
+       on the ball indicating the direction in which you should move to
+       begin on this path. If you then move in that direction, the arrow
+       will update to indicate the next direction on the path. You can
+       also press Space to automatically move in the direction of the hint
+       arrow. If you move in a different direction from the one shown
+       by the arrow, arrows will be shown only if the puzzle is still
+       solvable.
+
+       All the actions described in section 2.1 are also available. In
+       particular, if you do run into a mine and die, you can use the Undo
+       function and resume playing from before the fatal move. The game
+       will keep track of the number of times you have done this.
+
+  24.2 Inertia parameters
+
+       These parameters are available from the `Custom...' option on the
+       `Type' menu.
+
+       _Width_, _Height_
+
+           Size of grid in squares.
+
+Chapter 25: Tents
+-----------------
+
+       You have a grid of squares, some of which contain trees. Your aim is
+       to place tents in some of the remaining squares, in such a way that
+       the following conditions are met:
+
+        -  There are exactly as many tents as trees.
+
+        -  The tents and trees can be matched up in such a way that each
+           tent is directly adjacent (horizontally or vertically, but not
+           diagonally) to its own tree. However, a tent may be adjacent to
+           other trees as well as its own.
+
+        -  No two tents are adjacent horizontally, vertically _or
+           diagonally_.
+
+        -  The number of tents in each row, and in each column, matches the
+           numbers given round the sides of the grid.
+
+       This puzzle can be found in several places on the Internet, and was
+       brought to my attention by e-mail. I don't know who I should credit
+       for inventing it.
+
+  25.1 Tents controls
+
+       Left-clicking in a blank square will place a tent in it. Right-
+       clicking in a blank square will colour it green, indicating that you
+       are sure it _isn't_ a tent. Clicking either button in an occupied
+       square will clear it.
+
+       If you _drag_ with the right button along a row or column, every
+       blank square in the region you cover will be turned green, and no
+       other squares will be affected. (This is useful for clearing the
+       remainder of a row once you have placed all its tents.)
+
+       You can also use the cursor keys to move around the grid. Pressing
+       the return key over an empty square will place a tent, and pressing
+       the space bar over an empty square will colour it green; either key
+       will clear an occupied square. Holding Shift and pressing the cursor
+       keys will colour empty squares green. Holding Control and pressing
+       the cursor keys will colour green both empty squares and squares
+       with tents.
+
+       (All the actions described in section 2.1 are also available.)
+
+  25.2 Tents parameters
+
+       These parameters are available from the `Custom...' option on the
+       `Type' menu.
+
+       _Width_, _Height_
+
+           Size of grid in squares.
+
+       _Difficulty_
+
+           Controls the difficulty of the generated puzzle. More difficult
+           puzzles require more complex deductions, but at present none
+           of the available difficulty levels requires guesswork or
+           backtracking.
+
+Chapter 26: Bridges
+-------------------
+
+       You have a set of islands distributed across the playing area.
+       Each island contains a number. Your aim is to connect the islands
+       together with bridges, in such a way that:
+
+        -  Bridges run horizontally or vertically.
+
+        -  The number of bridges terminating at any island is equal to the
+           number written in that island.
+
+        -  Two bridges may run in parallel between the same two islands,
+           but no more than two may do so.
+
+        -  No bridge crosses another bridge.
+
+        -  All the islands are connected together.
+
+       There are some configurable alternative modes, which involve
+       changing the parallel-bridge limit to something other than 2, and
+       introducing the additional constraint that no sequence of bridges
+       may form a loop from one island back to the same island. The rules
+       stated above are the default ones.
+
+       Credit for this puzzle goes to Nikoli [12].
+
+       Bridges was contributed to this collection by James Harvey.
+
+       [12] http://www.nikoli.co.jp/en/puzzles/hashiwokakero.html (beware
+       of Flash)
+
+  26.1 Bridges controls
+
+       To place a bridge between two islands, click the mouse down on one
+       island and drag it towards the other. You do not need to drag all
+       the way to the other island; you only need to move the mouse far
+       enough for the intended bridge direction to be unambiguous. (So you
+       can keep the mouse near the starting island and conveniently throw
+       bridges out from it in many directions.)
+
+       Doing this again when a bridge is already present will add another
+       parallel bridge. If there are already as many bridges between the
+       two islands as permitted by the current game rules (i.e. two by
+       default), the same dragging action will remove all of them.
+
+       If you want to remind yourself that two islands definitely _do not_
+       have a bridge between them, you can right-drag between them in the
+       same way to draw a `non-bridge' marker.
+
+       If you think you have finished with an island (i.e. you have placed
+       all its bridges and are confident that they are in the right
+       places), you can mark the island as finished by left-clicking on it.
+       This will highlight it and all the bridges connected to it, and you
+       will be prevented from accidentally modifying any of those bridges
+       in future. Left-clicking again on a highlighted island will unmark
+       it and restore your ability to modify it.
+
+       You can also use the cursor keys to move around the grid: if
+       possible the cursor will always move orthogonally, otherwise it
+       will move towards the nearest island to the indicated direction.
+       Holding Control and pressing a cursor key will lay a bridge in that
+       direction (if available); Shift and a cursor key will lay a `non-
+       bridge' marker. Pressing the return key followed by a cursor key
+       will also lay a bridge in that direction.
+
+       You can mark an island as finished by pressing the space bar or by
+       pressing the return key twice.
+
+       By pressing a number key, you can jump to the nearest island with
+       that number. Letters `a', ..., `f' count as 10, ..., 15 and `0' as
+       16.
+
+       Violations of the puzzle rules will be marked in red:
+
+        -  An island with too many bridges will be highlighted in red.
+
+        -  An island with too few bridges will be highlighted in red if it
+           is definitely an error (as opposed to merely not being finished
+           yet): if adding enough bridges would involve having to cross
+           another bridge or remove a non-bridge marker, or if the island
+           has been highlighted as complete.
+
+        -  A group of islands and bridges may be highlighted in red if it
+           is a closed subset of the puzzle with no way to connect it to
+           the rest of the islands. For example, if you directly connect
+           two 1s together with a bridge and they are not the only two
+           islands on the grid, they will light up red to indicate that
+           such a group cannot be contained in any valid solution.
+
+        -  If you have selected the (non-default) option to disallow loops
+           in the solution, a group of bridges which forms a loop will be
+           highlighted.
+
+       (All the actions described in section 2.1 are also available.)
+
+  26.2 Bridges parameters
+
+       These parameters are available from the `Custom...' option on the
+       `Type' menu.
+
+       _Width_, _Height_
+
+           Size of grid in squares.
+
+       _Difficulty_
+
+           Difficulty level of puzzle.
+
+       _Allow loops_
+
+           This is set by default. If cleared, puzzles will be generated in
+           such a way that they are always soluble without creating a loop,
+           and solutions which do involve a loop will be disallowed.
+
+       _Max. bridges per direction_
+
+           Maximum number of bridges in any particular direction. The
+           default is 2, but you can change it to 1, 3 or 4. In general,
+           fewer is easier.
+
+       _%age of island squares_
+
+           Gives a rough percentage of islands the generator will try and
+           lay before finishing the puzzle. Certain layouts will not manage
+           to lay enough islands; this is an upper bound.
+
+       _Expansion factor (%age)_
+
+           The grid generator works by picking an existing island at random
+           (after first creating an initial island somewhere). It then
+           decides on a direction (at random), and then works out how far
+           it could extend before creating another island. This parameter
+           determines how likely it is to extend as far as it can, rather
+           than choosing somewhere closer.
+
+           High expansion factors usually mean easier puzzles with fewer
+           possible islands; low expansion factors can create lots of
+           tightly-packed islands.
+
+Chapter 27: Unequal
+-------------------
+
+       You have a square grid; each square may contain a digit from 1 to
+       the size of the grid, and some squares have clue signs between them.
+       Your aim is to fully populate the grid with numbers such that:
+
+        -  Each row contains only one occurrence of each digit
+
+        -  Each column contains only one occurrence of each digit
+
+        -  All the clue signs are satisfied.
+
+       There are two modes for this game, `Unequal' and `Adjacent'.
+
+       In `Unequal' mode, the clue signs are greater-than symbols
+       indicating one square's value is greater than its neighbour's. In
+       this mode not all clues may be visible, particularly at higher
+       difficulty levels.
+
+       In `Adjacent' mode, the clue signs are bars indicating one square's
+       value is numerically adjacent (i.e. one higher or one lower) than
+       its neighbour. In this mode all clues are always visible: absence of
+       a bar thus means that a square's value is definitely not numerically
+       adjacent to that neighbour's.
+
+       In `Trivial' difficulty level (available via the `Custom' game type
+       selector), there are no greater-than signs in `Unequal' mode; the
+       puzzle is to solve the Latin square only.
+
+       At the time of writing, the `Unequal' mode of this puzzle is
+       appearing in the Guardian weekly under the name `Futoshiki'.
+
+       Unequal was contributed to this collection by James Harvey.
+
+  27.1 Unequal controls
+
+       Unequal shares much of its control system with Solo.
+
+       To play Unequal, simply click the mouse in any empty square and then
+       type a digit or letter on the keyboard to fill that square. If you
+       make a mistake, click the mouse in the incorrect square and press
+       Space to clear it again (or use the Undo feature).
+
+       If you _right_-click in a square and then type a number, that
+       number will be entered in the square as a `pencil mark'. You can
+       have pencil marks for multiple numbers in the same square. Squares
+       containing filled-in numbers cannot also contain pencil marks.
+
+       The game pays no attention to pencil marks, so exactly what you
+       use them for is up to you: you can use them as reminders that a
+       particular square needs to be re-examined once you know more about
+       a particular number, or you can use them as lists of the possible
+       numbers in a given square, or anything else you feel like.
+
+       To erase a single pencil mark, right-click in the square and type
+       the same number again.
+
+       All pencil marks in a square are erased when you left-click and type
+       a number, or when you left-click and press space. Right-clicking and
+       pressing space will also erase pencil marks.
+
+       As for Solo, the cursor keys can be used in conjunction with the
+       digit keys to set numbers or pencil marks. You can also use the `M'
+       key to auto-fill every numeric hint, ready for removal as required,
+       or the `H' key to do the same but also to remove all obvious hints.
+
+       Alternatively, use the cursor keys to move the mark around the grid.
+       Pressing the return key toggles the mark (from a normal mark to a
+       pencil mark), and typing a number in is entered in the square in the
+       appropriate way; typing in a 0 or using the space bar will clear a
+       filled square.
+
+       Left-clicking a clue will mark it as done (grey it out), or unmark
+       it if it is already marked. Holding Control or Shift and pressing
+       an arrow key likewise marks any clue adjacent to the cursor in the
+       given direction.
+
+       (All the actions described in section 2.1 are also available.)
+
+  27.2 Unequal parameters
+
+       These parameters are available from the `Custom...' option on the
+       `Type' menu.
+
+       _Mode_
+
+           Mode of the puzzle (`Unequal' or `Adjacent')
+
+       _Size (s*s)_
+
+           Size of grid.
+
+       _Difficulty_
+
+           Controls the difficulty of the generated puzzle. At Trivial
+           level, there are no greater-than signs; the puzzle is to solve
+           the Latin square only. At Recursive level (only available via
+           the `Custom' game type selector) backtracking will be required,
+           but the solution should still be unique. The levels in between
+           require increasingly complex reasoning to avoid having to
+           backtrack.
+
+Chapter 28: Galaxies
+--------------------
+
+       You have a rectangular grid containing a number of dots. Your aim is
+       to draw edges along the grid lines which divide the rectangle into
+       regions in such a way that every region is 180-degree rotationally
+       symmetric, and contains exactly one dot which is located at its
+       centre of symmetry.
+
+       This puzzle was invented by Nikoli [13], under the name `Tentai
+       Show'; its name is commonly translated into English as `Spiral
+       Galaxies'.
+
+       Galaxies was contributed to this collection by James Harvey.
+
+       [13] http://www.nikoli.co.jp/en/puzzles/astronomical_show.html
+
+  28.1 Galaxies controls
+
+       Left-click on any grid line to draw an edge if there isn't one
+       already, or to remove one if there is. When you create a valid
+       region (one which is closed, contains exactly one dot, is 180-degree
+       symmetric about that dot, and contains no extraneous edges inside
+       it) it will be highlighted automatically; so your aim is to have the
+       whole grid highlighted in that way.
+
+       During solving, you might know that a particular grid square belongs
+       to a specific dot, but not be sure of where the edges go and which
+       other squares are connected to the dot. In order to mark this so you
+       don't forget, you can right-click on the dot and drag, which will
+       create an arrow marker pointing at the dot. Drop that in a square of
+       your choice and it will remind you which dot it's associated with.
+       You can also right-click on existing arrows to pick them up and move
+       them, or destroy them by dropping them off the edge of the grid.
+       (Also, if you're not sure which dot an arrow is pointing at, you can
+       pick it up and move it around to make it clearer. It will swivel
+       constantly as you drag it, to stay pointed at its parent dot.)
+
+       You can also use the cursor keys to move around the grid squares and
+       lines. Pressing the return key when over a grid line will draw or
+       clear its edge, as above. Pressing the return key when over a dot
+       will pick up an arrow, to be dropped the next time the return key
+       is pressed; this can also be used to move existing arrows around,
+       removing them by dropping them on a dot or another arrow.
+
+       (All the actions described in section 2.1 are also available.)
+
+  28.2 Galaxies parameters
+
+       These parameters are available from the `Custom...' option on the
+       `Type' menu.
+
+       _Width_, _Height_
+
+           Size of grid in squares.
+
+       _Difficulty_
+
+           Controls the difficulty of the generated puzzle. More difficult
+           puzzles require more complex deductions, and the `Unreasonable'
+           difficulty level may require backtracking.
+
+Chapter 29: Filling
+-------------------
+
+       You have a grid of squares, some of which contain digits, and the
+       rest of which are empty. Your job is to fill in digits in the empty
+       squares, in such a way that each connected region of squares all
+       containing the same digit has an area equal to that digit.
+
+       (`Connected region', for the purposes of this game, does not count
+       diagonally separated squares as adjacent.)
+
+       For example, it follows that no square can contain a zero, and that
+       two adjacent squares can not both contain a one. No region has an
+       area greater than 9 (because then its area would not be a single
+       digit).
+
+       Credit for this puzzle goes to Nikoli [14].
+
+       Filling was contributed to this collection by Jonas Koelker.
+
+       [14] http://www.nikoli.co.jp/en/puzzles/fillomino.html
+
+  29.1 Filling controls
+
+       To play Filling, simply click the mouse in any empty square and
+       then type a digit on the keyboard to fill that square. By dragging
+       the mouse, you can select multiple squares to fill with a single
+       keypress. If you make a mistake, click the mouse in the incorrect
+       square and press 0, Space, Backspace or Enter to clear it again (or
+       use the Undo feature).
+
+       You can also move around the grid with the cursor keys; typing a
+       digit will fill the square containing the cursor with that number;
+       typing 0 will clear it. You can also select multiple squares for
+       numbering or clearing with the return and arrow keys, before typing
+       a digit to fill or clear the highlighted squares (as above). The
+       space bar adds and removes single squares to and from the selection.
+       Backspace and escape remove all squares from the selection.
+
+       (All the actions described in section 2.1 are also available.)
+
+  29.2 Filling parameters
+
+       Filling allows you to configure the number of rows and columns of
+       the grid, through the `Type' menu.
+
+Chapter 30: Keen
+----------------
+
+       You have a square grid; each square may contain a digit from 1 to
+       the size of the grid. The grid is divided into blocks of varying
+       shape and size, with arithmetic clues written in them. Your aim is
+       to fully populate the grid with digits such that:
+
+        -  Each row contains only one occurrence of each digit
+
+        -  Each column contains only one occurrence of each digit
+
+        -  The digits in each block can be combined to form the number
+           stated in the clue, using the arithmetic operation given in the
+           clue. That is:
+
+            -  An addition clue means that the sum of the digits in the
+               block must be the given number. For example, `15+' means the
+               contents of the block adds up to fifteen.
+
+            -  A multiplication clue (e.g. `60*'), similarly, means that
+               the product of the digits in the block must be the given
+               number.
+
+            -  A subtraction clue will always be written in a block of
+               size two, and it means that one of the digits in the block
+               is greater than the other by the given amount. For example,
+               `2-' means that one of the digits in the block is 2 more
+               than the other, or equivalently that one digit minus the
+               other one is 2. The two digits could be either way round,
+               though.
+
+            -  A division clue (e.g. `3/'), similarly, is always in a block
+               of size two and means that one digit divided by the other is
+               equal to the given amount.
+
+           Note that a block may contain the same digit more than once
+           (provided the identical ones are not in the same row and
+           column). This rule is precisely the opposite of the rule in
+           Solo's `Killer' mode (see chapter 11).
+
+       This puzzle appears in the Times under the name `KenKen'.
+
+  30.1 Keen controls
+
+       Keen shares much of its control system with Solo (and Unequal).
+
+       To play Keen, simply click the mouse in any empty square and then
+       type a digit on the keyboard to fill that square. If you make a
+       mistake, click the mouse in the incorrect square and press Space to
+       clear it again (or use the Undo feature).
+
+       If you _right_-click in a square and then type a number, that
+       number will be entered in the square as a `pencil mark'. You can
+       have pencil marks for multiple numbers in the same square. Squares
+       containing filled-in numbers cannot also contain pencil marks.
+
+       The game pays no attention to pencil marks, so exactly what you
+       use them for is up to you: you can use them as reminders that a
+       particular square needs to be re-examined once you know more about
+       a particular number, or you can use them as lists of the possible
+       numbers in a given square, or anything else you feel like.
+
+       To erase a single pencil mark, right-click in the square and type
+       the same number again.
+
+       All pencil marks in a square are erased when you left-click and type
+       a number, or when you left-click and press space. Right-clicking and
+       pressing space will also erase pencil marks.
+
+       As for Solo, the cursor keys can be used in conjunction with the
+       digit keys to set numbers or pencil marks. Use the cursor keys to
+       move a highlight around the grid, and type a digit to enter it in
+       the highlighted square. Pressing return toggles the highlight into a
+       mode in which you can enter or remove pencil marks.
+
+       Pressing M will fill in a full set of pencil marks in every square
+       that does not have a main digit in it.
+
+       (All the actions described in section 2.1 are also available.)
+
+  30.2 Keen parameters
+
+       These parameters are available from the `Custom...' option on the
+       `Type' menu.
+
+       _Grid size_
+
+           Specifies the size of the grid. Lower limit is 3; upper limit is
+           9 (because the user interface would become more difficult with
+           `digits' bigger than 9!).
+
+       _Difficulty_
+
+           Controls the difficulty of the generated puzzle. At Unreasonable
+           level, some backtracking will be required, but the solution
+           should still be unique. The remaining levels require
+           increasingly complex reasoning to avoid having to backtrack.
+
+       _Multiplication only_
+
+           If this is enabled, all boxes will be multiplication boxes. With
+           this rule, the puzzle is known as `Inshi No Heya'.
+
+Chapter 31: Towers
+------------------
+
+       You have a square grid. On each square of the grid you can build
+       a tower, with its height ranging from 1 to the size of the grid.
+       Around the edge of the grid are some numeric clues.
+
+       Your task is to build a tower on every square, in such a way that:
+
+        -  Each row contains every possible height of tower once
+
+        -  Each column contains every possible height of tower once
+
+        -  Each numeric clue describes the number of towers that can be
+           seen if you look into the square from that direction, assuming
+           that shorter towers are hidden behind taller ones. For example,
+           in a 5x5 grid, a clue marked `5' indicates that the five tower
+           heights must appear in increasing order (otherwise you would
+           not be able to see all five towers), whereas a clue marked `1'
+           indicates that the tallest tower (the one marked 5) must come
+           first.
+
+       In harder or larger puzzles, some towers will be specified for you
+       as well as the clues round the edge, and some edge clues may be
+       missing.
+
+       This puzzle appears on the web under various names, particularly
+       `Skyscrapers', but I don't know who first invented it.
+
+  31.1 Towers controls
+
+       Towers shares much of its control system with Solo, Unequal and
+       Keen.
+
+       To play Towers, simply click the mouse in any empty square and then
+       type a digit on the keyboard to fill that square with a tower of
+       the given height. If you make a mistake, click the mouse in the
+       incorrect square and press Space to clear it again (or use the Undo
+       feature).
+
+       If you _right_-click in a square and then type a number, that
+       number will be entered in the square as a `pencil mark'. You can
+       have pencil marks for multiple numbers in the same square. A square
+       containing a tower cannot also contain pencil marks.
+
+       The game pays no attention to pencil marks, so exactly what you
+       use them for is up to you: you can use them as reminders that a
+       particular square needs to be re-examined once you know more about
+       a particular number, or you can use them as lists of the possible
+       numbers in a given square, or anything else you feel like.
+
+       To erase a single pencil mark, right-click in the square and type
+       the same number again.
+
+       All pencil marks in a square are erased when you left-click and type
+       a number, or when you left-click and press space. Right-clicking and
+       pressing space will also erase pencil marks.
+
+       As for Solo, the cursor keys can be used in conjunction with the
+       digit keys to set numbers or pencil marks. Use the cursor keys to
+       move a highlight around the grid, and type a digit to enter it in
+       the highlighted square. Pressing return toggles the highlight into a
+       mode in which you can enter or remove pencil marks.
+
+       Pressing M will fill in a full set of pencil marks in every square
+       that does not have a main digit in it.
+
+       Left-clicking a clue will mark it as done (grey it out), or unmark
+       it if it is already marked. Holding Control or Shift and pressing an
+       arrow key likewise marks any clue in the given direction.
+
+       (All the actions described in section 2.1 are also available.)
+
+  31.2 Towers parameters
+
+       These parameters are available from the `Custom...' option on the
+       `Type' menu.
+
+       _Grid size_
+
+           Specifies the size of the grid. Lower limit is 3; upper limit is
+           9 (because the user interface would become more difficult with
+           `digits' bigger than 9!).
+
+       _Difficulty_
+
+           Controls the difficulty of the generated puzzle. At Unreasonable
+           level, some backtracking will be required, but the solution
+           should still be unique. The remaining levels require
+           increasingly complex reasoning to avoid having to backtrack.
+
+Chapter 32: Singles
+-------------------
+
+       You have a grid of white squares, all of which contain numbers. Your
+       task is to colour some of the squares black (removing the number) so
+       as to satisfy all of the following conditions:
+
+        -  No number occurs more than once in any row or column.
+
+        -  No black square is horizontally or vertically adjacent to any
+           other black square.
+
+        -  The remaining white squares must all form one contiguous region
+           (connected by edges, not just touching at corners).
+
+       Credit for this puzzle goes to Nikoli [15] who call it Hitori.
+
+       Singles was contributed to this collection by James Harvey.
+
+       [15] http://www.nikoli.com/en/puzzles/hitori.html (beware of Flash)
+
+  32.1 Singles controls
+
+       Left-clicking on an empty square will colour it black; left-clicking
+       again will restore the number. Right-clicking will add a circle
+       (useful for indicating that a cell is definitely not black).
+
+       You can also use the cursor keys to move around the grid. Pressing
+       the return or space keys will turn a square black or add a circle
+       respectively, and pressing the key again will restore the number or
+       remove the circle.
+
+       (All the actions described in section 2.1 are also available.)
+
+  32.2 Singles parameters
+
+       These parameters are available from the `Custom...' option on the
+       `Type' menu.
+
+       _Width_, _Height_
+
+           Size of grid in squares.
+
+       _Difficulty_
+
+           Controls the difficulty of the generated puzzle.
+
+Chapter 33: Magnets
+-------------------
+
+       A rectangular grid has been filled with a mixture of magnets (that
+       is, dominoes with one positive end and one negative end) and blank
+       dominoes (that is, dominoes with two neutral poles). These dominoes
+       are initially only seen in silhouette. Around the grid are placed a
+       number of clues indicating the number of positive and negative poles
+       contained in certain columns and rows.
+
+       Your aim is to correctly place the magnets and blank dominoes such
+       that all the clues are satisfied, with the additional constraint
+       that no two similar magnetic poles may be orthogonally adjacent
+       (since they repel). Neutral poles do not repel, and can be adjacent
+       to any other pole.
+
+       Credit for this puzzle goes to Janko [16].
+
+       Magnets was contributed to this collection by James Harvey.
+
+       [16] http://www.janko.at/Raetsel/Magnete/index.htm
+
+  33.1 Magnets controls
+
+       Left-clicking on an empty square places a magnet at that position
+       with the positive pole on the square and the negative pole on the
+       other half of the magnet; left-clicking again reverses the polarity,
+       and a third click removes the magnet.
+
+       Right-clicking on an empty square places a blank domino there.
+       Right-clicking again places two question marks on the domino,
+       signifying `this cannot be blank' (which can be useful to note
+       deductions while solving), and right-clicking again empties the
+       domino.
+
+       Left-clicking a clue will mark it as done (grey it out), or unmark
+       it if it is already marked.
+
+       You can also use the cursor keys to move a cursor around the grid.
+       Pressing the return key will lay a domino with a positive pole at
+       that position; pressing again reverses the polarity and then removes
+       the domino, as with left-clicking. Using the space bar allows
+       placement of blank dominoes and cannot-be-blank hints, as for right-
+       clicking.
+
+       (All the actions described in section 2.1 are also available.)
+
+  33.2 Magnets parameters
+
+       These parameters are available from the `Custom...' option on the
+       `Type' menu.
+
+       _Width_, _Height_
+
+           Size of grid in squares. There will be half _Width_ x _Height_
+           dominoes in the grid: if this number is odd then one square will
+           be blank.
+
+           (Grids with at least one odd dimension tend to be easier to
+           solve.)
+
+       _Difficulty_
+
+           Controls the difficulty of the generated puzzle. At Tricky
+           level, you are required to make more deductions about empty
+           dominoes and row/column counts.
+
+       _Strip clues_
+
+           If true, some of the clues around the grid are removed at
+           generation time, making the puzzle more difficult.
+
+Chapter 34: Signpost
+--------------------
+
+       You have a grid of squares; each square (except the last one)
+       contains an arrow, and some squares also contain numbers. Your job
+       is to connect the squares to form a continuous list of numbers
+       starting at 1 and linked in the direction of the arrows - so the
+       arrow inside the square with the number 1 will point to the square
+       containing the number 2, which will point to the square containing
+       the number 3, etc. Each square can be any distance away from the
+       previous one, as long as it is somewhere in the direction of the
+       arrow.
+
+       By convention the first and last numbers are shown; one or more
+       interim numbers may also appear at the beginning.
+
+       Credit for this puzzle goes to Janko [17], who call it `Pfeilpfad'
+       (`arrow path').
+
+       Signpost was contributed to this collection by James Harvey.
+
+       [17] http://janko.at/Raetsel/Pfeilpfad/index.htm
+
+  34.1 Signpost controls
+
+       To play Signpost, you connect squares together by dragging from
+       one square to another, indicating that they are adjacent in the
+       sequence. Drag with the left button from a square to its successor,
+       or with the right button from a square to its predecessor.
+
+       If you connect together two squares in this way and one of them has
+       a number in it, the appropriate number will appear in the other
+       square. If you connect two non-numbered squares, they will be
+       assigned temporary algebraic labels: on the first occasion, they
+       will be labelled `a' and `a+1', and then `b' and `b+1', and so on.
+       Connecting more squares on to the ends of such a chain will cause
+       them all to be labelled with the same letter.
+
+       When you left-click or right-click in a square, the legal squares to
+       connect it to will be shown.
+
+       The arrow in each square starts off black, and goes grey once you
+       connect the square to its successor. Also, each square which needs
+       a predecessor has a small dot in the bottom left corner, which
+       vanishes once you link a square to it. So your aim is always to
+       connect a square with a black arrow to a square with a dot.
+
+       To remove any links for a particular square (both incoming and
+       outgoing), left-drag it off the grid. To remove a whole chain,
+       right-drag any square in the chain off the grid.
+
+       You can also use the cursor keys to move around the grid squares
+       and lines. Pressing the return key when over a square starts a link
+       operation, and pressing the return key again over a square will
+       finish the link, if allowable. Pressing the space bar over a square
+       will show the other squares pointing to it, and allow you to form a
+       backward link, and pressing the space bar again cancels this.
+
+       (All the actions described in section 2.1 are also available.)
+
+  34.2 Signpost parameters
+
+       These parameters are available from the `Custom...' option on the
+       `Type' menu.
+
+       _Width_, _Height_
+
+           Size of grid in squares.
+
+       _Force start/end to corners_
+
+           If true, the start and end squares are always placed in opposite
+           corners (the start at the top left, and the end at the bottom
+           right). If false the start and end squares are placed randomly
+           (although always both shown).
+
+Chapter 35: Range
+-----------------
+
+       You have a grid of squares; some squares contain numbers. Your job
+       is to colour some of the squares black, such that several criteria
+       are satisfied:
+
+        -  no square with a number is coloured black.
+
+        -  no two black squares are adjacent (horizontally or vertically).
+
+        -  for any two white squares, there is a path between them using
+           only white squares.
+
+        -  for each square with a number, that number denotes the total
+           number of white squares reachable from that square going in a
+           straight line in any horizontal or vertical direction until
+           hitting a wall or a black square; the square with the number is
+           included in the total (once).
+
+       For instance, a square containing the number one must have four
+       black squares as its neighbours by the last criterion; but then it's
+       impossible for it to be connected to any outside white square, which
+       violates the second to last criterion. So no square will contain the
+       number one.
+
+       Credit for this puzzle goes to Nikoli, who have variously called it
+       `Kurodoko', `Kuromasu' or `Where is Black Cells'. [18].
+
+       Range was contributed to this collection by Jonas Koelker.
+
+       [18] http://www.nikoli.co.jp/en/puzzles/where_is_black_cells.html
+
+  35.1 Range controls
+
+       Click with the left button to paint a square black, or with the
+       right button to mark a square with a dot to indicate that you are
+       sure it should _not_ be painted black. Repeated clicking with either
+       button will cycle the square through the three possible states
+       (filled, dotted or empty) in opposite directions.
+
+       You can also use the cursor keys to move around the grid squares.
+       Pressing Return does the same as clicking with the left button,
+       while pressing Space does the same as a right button click. Moving
+       with the cursor keys while holding Shift will place dots in all
+       squares that are moved through.
+
+       (All the actions described in section 2.1 are also available.)
+
+  35.2 Range parameters
+
+       These parameters are available from the `Custom...' option on the
+       `Type' menu.
+
+       _Width_, _Height_
+
+           Size of grid in squares.
+
+Chapter 36: Pearl
+-----------------
+
+       You have a grid of squares. Your job is to draw lines between the
+       centres of horizontally or vertically adjacent squares, so that the
+       lines form a single closed loop. In the resulting grid, some of the
+       squares that the loop passes through will contain corners, and some
+       will be straight horizontal or vertical lines. (And some squares can
+       be completely empty - the loop doesn't have to pass through every
+       square.)
+
+       Some of the squares contain black and white circles, which are clues
+       that the loop must satisfy.
+
+       A black circle in a square indicates that that square is a corner,
+       but neither of the squares adjacent to it in the loop is also a
+       corner.
+
+       A white circle indicates that the square is a straight edge, but _at
+       least one_ of the squares adjacent to it in the loop is a corner.
+
+       (In both cases, the clue only constrains the two squares adjacent
+       _in the loop_, that is, the squares that the loop passes into after
+       leaving the clue square. The squares that are only adjacent _in the
+       grid_ are not constrained.)
+
+       Credit for this puzzle goes to Nikoli, who call it `Masyu'. [19]
+
+       Thanks to James Harvey for assistance with the implementation.
+
+       [19] http://www.nikoli.co.jp/en/puzzles/masyu.html (beware of Flash)
+
+  36.1 Pearl controls
+
+       Click with the left button on a grid edge to draw a segment of the
+       loop through that edge, or to remove a segment once it is drawn.
+
+       Drag with the left button through a series of squares to draw more
+       than one segment of the loop in one go. Alternatively, drag over an
+       existing part of the loop to undraw it, or to undraw part of it and
+       then go in a different direction.
+
+       Click with the right button on a grid edge to mark it with a cross,
+       indicating that you are sure the loop does not go through that edge.
+       (For instance, if you have decided which of the squares adjacent
+       to a white clue has to be a corner, but don't yet know which way
+       the corner turns, you might mark the one way it _can't_ go with a
+       cross.)
+
+       Alternatively, use the cursor keys to move the cursor. Use the Enter
+       key to begin and end keyboard `drag' operations. Use the Space,
+       Escape or Backspace keys to cancel the drag. Or, hold Control while
+       dragging with the cursor keys to toggle segments as you move between
+       squares.
+
+       Pressing Control-Shift-arrowkey or Shift-arrowkey simulates a left
+       or right click, respectively, on the edge in the direction of the
+       key.
+
+       (All the actions described in section 2.1 are also available.)
+
+  36.2 Pearl parameters
+
+       These parameters are available from the `Custom...' option on the
+       `Type' menu.
+
+Chapter 37: Undead
+------------------
+
+       You are given a grid of squares, some of which contain diagonal
+       mirrors. Every square which is not a mirror must be filled with one
+       of three types of undead monster: a ghost, a vampire, or a zombie.
+
+       Vampires can be seen directly, but are invisible when reflected in
+       mirrors. Ghosts are the opposite way round: they can be seen in
+       mirrors, but are invisible when looked at directly. Zombies are
+       visible by any means.
+
+       You are also told the total number of each type of monster in the
+       grid. Also around the edge of the grid are written numbers, which
+       indicate how many monsters can be seen if you look into the grid
+       along a row or column starting from that position. (The diagonal
+       mirrors are reflective on both sides. If your reflected line of
+       sight crosses the same monster more than once, the number will count
+       it each time it is visible, not just once.)
+
+       This puzzle type was invented by David Millar, under the name
+       `Haunted Mirror Maze'. See [20] for more details.
+
+       Undead was contributed to this collection by Steffen Bauer.
+
+       [20] http://www.janko.at/Raetsel/Spukschloss/index.htm
+
+  37.1 Undead controls
+
+       Undead has a similar control system to Solo, Unequal and Keen.
+
+       To play Undead, click the mouse in any empty square and then type
+       a letter on the keyboard indicating the type of monster: `G' for
+       a ghost, `V' for a vampire, or `Z' for a zombie. If you make a
+       mistake, click the mouse in the incorrect square and press Space to
+       clear it again (or use the Undo feature).
+
+       If you _right_-click in a square and then type a letter, the
+       corresponding monster will be shown in reduced size in that square,
+       as a `pencil mark'. You can have pencil marks for multiple monsters
+       in the same square. A square containing a full-size monster cannot
+       also contain pencil marks.
+
+       The game pays no attention to pencil marks, so exactly what you
+       use them for is up to you: you can use them as reminders that a
+       particular square needs to be re-examined once you know more about
+       a particular monster, or you can use them as lists of the possible
+       monster in a given square, or anything else you feel like.
+
+       To erase a single pencil mark, right-click in the square and type
+       the same letter again.
+
+       All pencil marks in a square are erased when you left-click and type
+       a monster letter, or when you left-click and press Space. Right-
+       clicking and pressing space will also erase pencil marks.
+
+       As for Solo, the cursor keys can be used in conjunction with the
+       letter keys to place monsters or pencil marks. Use the cursor keys
+       to move a highlight around the grid, and type a monster letter to
+       enter it in the highlighted square. Pressing return toggles the
+       highlight into a mode in which you can enter or remove pencil marks.
+
+       If you prefer plain letters of the alphabet to cute monster
+       pictures, you can press `A' to toggle between showing the monsters
+       as monsters or showing them as letters.
+
+       Left-clicking a clue will mark it as done (grey it out), or unmark
+       it if it is already marked.
+
+       (All the actions described in section 2.1 are also available.)
+
+  37.2 Undead parameters
+
+       These parameters are available from the `Custom...' option on the
+       `Type' menu.
+
+       _Width_, _Height_
+
+           Size of grid in squares.
+
+       _Difficulty_
+
+           Controls the difficulty of the generated puzzle.
+
+Chapter 38: Unruly
+------------------
+
+       You are given a grid of squares, which you must colour either black
+       or white. Some squares are provided as clues; the rest are left for
+       you to fill in. Each row and column must contain the same number
+       of black and white squares, and no row or column may contain three
+       consecutive squares of the same colour.
+
+       This puzzle type was invented by Adolfo Zanellati, under the name
+       `Tohu wa Vohu'. See [21] for more details.
+
+       Unruly was contributed to this collection by Lennard Sprong.
+
+       [21] http://www.janko.at/Raetsel/Tohu-Wa-Vohu/index.htm
+
+  38.1 Unruly controls
+
+       To play Unruly, click the mouse in a square to change its colour.
+       Left-clicking an empty square will turn it black, and right-clicking
+       will turn it white. Keep clicking the same button to cycle through
+       the three possible states for the square. If you middle-click in a
+       square it will be reset to empty.
+
+       You can also use the cursor keys to move around the grid. Pressing
+       the return or space keys will turn an empty square black or white
+       respectively (and then cycle the colours in the same way as the
+       mouse buttons), and pressing Backspace will reset a square to empty.
+
+       (All the actions described in section 2.1 are also available.)
+
+  38.2 Unruly parameters
+
+       These parameters are available from the `Custom...' option on the
+       `Type' menu.
+
+       _Width_, _Height_
+
+           Size of grid in squares. (Note that the rules of the game
+           require both the width and height to be even numbers.)
+
+       _Difficulty_
+
+           Controls the difficulty of the generated puzzle.
+
+       _Unique rows and columns_
+
+           If enabled, no two rows are permitted to have exactly the same
+           pattern, and likewise columns. (A row and a column can match,
+           though.)
+
+Chapter 39: Flood
+-----------------
+
+       You are given a grid of squares, coloured at random in multiple
+       colours. In each move, you can flood-fill the top left square in a
+       colour of your choice (i.e. every square reachable from the starting
+       square by an orthogonally connected path of squares all the same
+       colour will be filled in the new colour). As you do this, more and
+       more of the grid becomes connected to the starting square.
+
+       Your aim is to make the whole grid the same colour, in as few moves
+       as possible. The game will set a limit on the number of moves, based
+       on running its own internal solver. You win if you can make the
+       whole grid the same colour in that many moves or fewer.
+
+       I saw this game (with a fixed grid size, fixed number of colours,
+       and fixed move limit) at http://floodit.appspot.com (no longer
+       accessible).
+
+  39.1 Flood controls
+
+       To play Flood, click the mouse in a square. The top left corner and
+       everything connected to it will be flood-filled with the colour of
+       the square you clicked. Clicking a square the same colour as the top
+       left corner has no effect, and therefore does not count as a move.
+
+       You can also use the cursor keys to move a cursor (outline black
+       square) around the grid. Pressing the return key will fill the top
+       left corner in the colour of the square under the cursor.
+
+       (All the actions described in section 2.1 are also available.)
+
+  39.2 Flood parameters
+
+       These parameters are available from the `Custom...' option on the
+       `Type' menu.
+
+       _Width_, _Height_
+
+           Size of the grid, in squares.
+
+       _Colours_
+
+           Number of colours used to fill the grid. Must be at least 3
+           (with two colours there would only be one legal move at any
+           stage, hence no choice to make at all), and at most 10.
+
+       _Extra moves permitted_
+
+           Controls the difficulty of the puzzle, by increasing the move
+           limit. In each new grid, Flood will run an internal solver to
+           generate its own solution, and then the value in this field
+           will be added to the length of Flood's solution to generate the
+           game's move limit. So a value of 0 requires you to be just as
+           efficient as Flood's automated solver, and a larger value makes
+           it easier.
+
+           (Note that Flood's internal solver will not necessarily find the
+           shortest possible solution, though I believe it's pretty close.
+           For a real challenge, set this value to 0 and then try to solve
+           a grid in _strictly fewer_ moves than the limit you're given!)
+
+Chapter 40: Tracks
+------------------
+
+       You are given a grid of squares, some of which are filled with train
+       tracks. You need to complete the track from A to B so that the
+       rows and columns contain the same number of track segments as are
+       indicated in the clues to the top and right of the grid.
+
+       There are only straight and 90 degree curved rails, and the track
+       may not cross itself.
+
+       Tracks was contributed to this collection by James Harvey.
+
+  40.1 Tracks controls
+
+       Left-clicking on an edge between two squares adds a track segment
+       between the two squares. Right-clicking on an edge adds a cross on
+       the edge, indicating no track is possible there.
+
+       Left-clicking in a square adds a colour indicator showing that
+       you know the square must contain a track, even if you don't know
+       which edges it crosses yet. Right-clicking in a square adds a cross
+       indicating it contains no track segment.
+
+       Left- or right-dragging between squares allows you to lay a straight
+       line of is-track or is-not-track indicators, useful for filling in
+       rows or columns to match the clue.
+
+       (All the actions described in section 2.1 are also available.)
+
+  40.2 Tracks parameters
+
+       These parameters are available from the `Custom...' option on the
+       `Type' menu.
+
+       _Width_, _Height_
+
+           Size of the grid, in squares.
+
+       _Difficulty_
+
+           Controls the difficulty of the generated puzzle: at Tricky
+           level, you are required to make more deductions regarding
+           disregarding moves that would lead to impossible crossings
+           later.
+
+       _Disallow consecutive 1 clues_
+
+           Controls whether the Tracks game generation permits two adjacent
+           rows or columns to have a 1 clue, or permits the row or column
+           of the track's endpoint to have a 1 clue. By default this is
+           not permitted, to avoid long straight boring segments of track
+           and make the games more twiddly and interesting. If you want to
+           restore the possibility, turn this option off.
+
+Chapter 41: Palisade
+--------------------
+
+       You're given a grid of squares, some of which contain numbers. Your
+       goal is to subdivide the grid into contiguous regions, all of the
+       same (given) size, such that each square containing a number is
+       adjacent to exactly that many edges (including those between the
+       inside and the outside of the grid).
+
+       Credit for this puzzle goes to Nikoli, who call it `Five Cells'.
+       [22].
+
+       Palisade was contributed to this collection by Jonas Koelker.
+
+       [22] http://nikoli.co.jp/en/puzzles/five_cells.html
+
+  41.1 Palisade controls
+
+       Left-click to place an edge. Right-click to indicate `no edge'.
+       Alternatively, the arrow keys will move a keyboard cursor. Holding
+       Control while pressing an arrow key will place an edge. Press Shift-
+       arrowkey to switch off an edge. Repeat an action to perform its
+       inverse.
+
+       (All the actions described in section 2.1 are also available.)
+
+  41.2 Palisade parameters
+
+       These parameters are available from the `Custom...' option on the
+       `Type' menu.
+
+       _Width_, _Height_
+
+           Size of grid in squares.
+
+       _Region size_
+
+           The size of the regions into which the grid must be subdivided.
+
+Appendix A: Licence
+-------------------
+
+       This software is copyright 2004-2014 Simon Tatham.
+
+       Portions copyright Richard Boulton, James Harvey, Mike Pinna, Jonas
+       Koelker, Dariusz Olszewski, Michael Schierl, Lambros Lambrou, Bernd
+       Schmidt, Steffen Bauer, Lennard Sprong and Rogier Goossens.
+
+       Permission is hereby granted, free of charge, to any person
+       obtaining a copy of this software and associated documentation files
+       (the `Software'), to deal in the Software without restriction,
+       including without limitation the rights to use, copy, modify, merge,
+       publish, distribute, sublicense, and/or sell copies of the Software,
+       and to permit persons to whom the Software is furnished to do so,
+       subject to the following conditions:
+
+       The above copyright notice and this permission notice shall be
+       included in all copies or substantial portions of the Software.
+
+       THE SOFTWARE IS PROVIDED `AS IS', WITHOUT WARRANTY OF ANY KIND,
+       EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+       OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+       NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+       BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+       ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+       CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+       SOFTWARE.
+
+[Simon Tatham's Portable Puzzle Collection, version 20161228.7cae89f]
diff --git a/random.c b/random.c
new file mode 100644 (file)
index 0000000..fb54560
--- /dev/null
+++ b/random.c
@@ -0,0 +1,351 @@
+/*
+ * random.c: Internal random number generator, guaranteed to work
+ * the same way on all platforms. Used when generating an initial
+ * game state from a random game seed; required to ensure that game
+ * seeds can be exchanged between versions of a puzzle compiled for
+ * different platforms.
+ * 
+ * The generator is based on SHA-1. This is almost certainly
+ * overkill, but I had the SHA-1 code kicking around and it was
+ * easier to reuse it than to do anything else!
+ */
+
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "puzzles.h"
+
+/* ----------------------------------------------------------------------
+ * Core SHA algorithm: processes 16-word blocks into a message digest.
+ */
+
+#define rol(x,y) ( ((x) << (y)) | (((uint32)x) >> (32-y)) )
+
+static void SHA_Core_Init(uint32 h[5])
+{
+    h[0] = 0x67452301;
+    h[1] = 0xefcdab89;
+    h[2] = 0x98badcfe;
+    h[3] = 0x10325476;
+    h[4] = 0xc3d2e1f0;
+}
+
+static void SHATransform(uint32 * digest, uint32 * block)
+{
+    uint32 w[80];
+    uint32 a, b, c, d, e;
+    int t;
+
+    for (t = 0; t < 16; t++)
+       w[t] = block[t];
+
+    for (t = 16; t < 80; t++) {
+       uint32 tmp = w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16];
+       w[t] = rol(tmp, 1);
+    }
+
+    a = digest[0];
+    b = digest[1];
+    c = digest[2];
+    d = digest[3];
+    e = digest[4];
+
+    for (t = 0; t < 20; t++) {
+       uint32 tmp =
+           rol(a, 5) + ((b & c) | (d & ~b)) + e + w[t] + 0x5a827999;
+       e = d;
+       d = c;
+       c = rol(b, 30);
+       b = a;
+       a = tmp;
+    }
+    for (t = 20; t < 40; t++) {
+       uint32 tmp = rol(a, 5) + (b ^ c ^ d) + e + w[t] + 0x6ed9eba1;
+       e = d;
+       d = c;
+       c = rol(b, 30);
+       b = a;
+       a = tmp;
+    }
+    for (t = 40; t < 60; t++) {
+       uint32 tmp = rol(a,
+                        5) + ((b & c) | (b & d) | (c & d)) + e + w[t] +
+           0x8f1bbcdc;
+       e = d;
+       d = c;
+       c = rol(b, 30);
+       b = a;
+       a = tmp;
+    }
+    for (t = 60; t < 80; t++) {
+       uint32 tmp = rol(a, 5) + (b ^ c ^ d) + e + w[t] + 0xca62c1d6;
+       e = d;
+       d = c;
+       c = rol(b, 30);
+       b = a;
+       a = tmp;
+    }
+
+    digest[0] += a;
+    digest[1] += b;
+    digest[2] += c;
+    digest[3] += d;
+    digest[4] += e;
+}
+
+/* ----------------------------------------------------------------------
+ * Outer SHA algorithm: take an arbitrary length byte string,
+ * convert it into 16-word blocks with the prescribed padding at
+ * the end, and pass those blocks to the core SHA algorithm.
+ */
+
+void SHA_Init(SHA_State * s)
+{
+    SHA_Core_Init(s->h);
+    s->blkused = 0;
+    s->lenhi = s->lenlo = 0;
+}
+
+void SHA_Bytes(SHA_State * s, const void *p, int len)
+{
+    unsigned char *q = (unsigned char *) p;
+    uint32 wordblock[16];
+    uint32 lenw = len;
+    int i;
+
+    /*
+     * Update the length field.
+     */
+    s->lenlo += lenw;
+    s->lenhi += (s->lenlo < lenw);
+
+    if (s->blkused && s->blkused + len < 64) {
+       /*
+        * Trivial case: just add to the block.
+        */
+       memcpy(s->block + s->blkused, q, len);
+       s->blkused += len;
+    } else {
+       /*
+        * We must complete and process at least one block.
+        */
+       while (s->blkused + len >= 64) {
+           memcpy(s->block + s->blkused, q, 64 - s->blkused);
+           q += 64 - s->blkused;
+           len -= 64 - s->blkused;
+           /* Now process the block. Gather bytes big-endian into words */
+           for (i = 0; i < 16; i++) {
+               wordblock[i] =
+                   (((uint32) s->block[i * 4 + 0]) << 24) |
+                   (((uint32) s->block[i * 4 + 1]) << 16) |
+                   (((uint32) s->block[i * 4 + 2]) << 8) |
+                   (((uint32) s->block[i * 4 + 3]) << 0);
+           }
+           SHATransform(s->h, wordblock);
+           s->blkused = 0;
+       }
+       memcpy(s->block, q, len);
+       s->blkused = len;
+    }
+}
+
+void SHA_Final(SHA_State * s, unsigned char *output)
+{
+    int i;
+    int pad;
+    unsigned char c[64];
+    uint32 lenhi, lenlo;
+
+    if (s->blkused >= 56)
+       pad = 56 + 64 - s->blkused;
+    else
+       pad = 56 - s->blkused;
+
+    lenhi = (s->lenhi << 3) | (s->lenlo >> (32 - 3));
+    lenlo = (s->lenlo << 3);
+
+    memset(c, 0, pad);
+    c[0] = 0x80;
+    SHA_Bytes(s, &c, pad);
+
+    c[0] = (unsigned char)((lenhi >> 24) & 0xFF);
+    c[1] = (unsigned char)((lenhi >> 16) & 0xFF);
+    c[2] = (unsigned char)((lenhi >> 8) & 0xFF);
+    c[3] = (unsigned char)((lenhi >> 0) & 0xFF);
+    c[4] = (unsigned char)((lenlo >> 24) & 0xFF);
+    c[5] = (unsigned char)((lenlo >> 16) & 0xFF);
+    c[6] = (unsigned char)((lenlo >> 8) & 0xFF);
+    c[7] = (unsigned char)((lenlo >> 0) & 0xFF);
+
+    SHA_Bytes(s, &c, 8);
+
+    for (i = 0; i < 5; i++) {
+       output[i * 4] = (unsigned char)((s->h[i] >> 24) & 0xFF);
+       output[i * 4 + 1] = (unsigned char)((s->h[i] >> 16) & 0xFF);
+       output[i * 4 + 2] = (unsigned char)((s->h[i] >> 8) & 0xFF);
+       output[i * 4 + 3] = (unsigned char)((s->h[i]) & 0xFF);
+    }
+}
+
+void SHA_Simple(const void *p, int len, unsigned char *output)
+{
+    SHA_State s;
+
+    SHA_Init(&s);
+    SHA_Bytes(&s, p, len);
+    SHA_Final(&s, output);
+}
+
+/* ----------------------------------------------------------------------
+ * The random number generator.
+ */
+
+struct random_state {
+    unsigned char seedbuf[40];
+    unsigned char databuf[20];
+    int pos;
+};
+
+random_state *random_new(const char *seed, int len)
+{
+    random_state *state;
+
+    state = snew(random_state);
+
+    SHA_Simple(seed, len, state->seedbuf);
+    SHA_Simple(state->seedbuf, 20, state->seedbuf + 20);
+    SHA_Simple(state->seedbuf, 40, state->databuf);
+    state->pos = 0;
+
+    return state;
+}
+
+random_state *random_copy(random_state *tocopy)
+{
+    random_state *result;
+    result = snew(random_state);
+    memcpy(result->seedbuf, tocopy->seedbuf, sizeof(result->seedbuf));
+    memcpy(result->databuf, tocopy->databuf, sizeof(result->databuf));
+    result->pos = tocopy->pos;
+    return result;
+}
+
+unsigned long random_bits(random_state *state, int bits)
+{
+    unsigned long ret = 0;
+    int n;
+
+    for (n = 0; n < bits; n += 8) {
+       if (state->pos >= 20) {
+           int i;
+
+           for (i = 0; i < 20; i++) {
+               if (state->seedbuf[i] != 0xFF) {
+                   state->seedbuf[i]++;
+                   break;
+               } else
+                   state->seedbuf[i] = 0;
+           }
+           SHA_Simple(state->seedbuf, 40, state->databuf);
+           state->pos = 0;
+       }
+       ret = (ret << 8) | state->databuf[state->pos++];
+    }
+
+    /*
+     * `(1 << bits) - 1' is not good enough, since if bits==32 on a
+     * 32-bit machine, behaviour is undefined and Intel has a nasty
+     * habit of shifting left by zero instead. We'll shift by
+     * bits-1 and then separately shift by one.
+     */
+    ret &= (1 << (bits-1)) * 2 - 1;
+    return ret;
+}
+
+unsigned long random_upto(random_state *state, unsigned long limit)
+{
+    int bits = 0;
+    unsigned long max, divisor, data;
+
+    while ((limit >> bits) != 0)
+       bits++;
+
+    bits += 3;
+    assert(bits < 32);
+
+    max = 1L << bits;
+    divisor = max / limit;
+    max = limit * divisor;
+
+    do {
+       data = random_bits(state, bits);
+    } while (data >= max);
+
+    return data / divisor;
+}
+
+void random_free(random_state *state)
+{
+    sfree(state);
+}
+
+char *random_state_encode(random_state *state)
+{
+    char retbuf[256];
+    int len = 0, i;
+
+    for (i = 0; i < lenof(state->seedbuf); i++)
+       len += sprintf(retbuf+len, "%02x", state->seedbuf[i]);
+    for (i = 0; i < lenof(state->databuf); i++)
+       len += sprintf(retbuf+len, "%02x", state->databuf[i]);
+    len += sprintf(retbuf+len, "%02x", state->pos);
+
+    return dupstr(retbuf);
+}
+
+random_state *random_state_decode(const char *input)
+{
+    random_state *state;
+    int pos, byte, digits;
+
+    state = snew(random_state);
+
+    memset(state->seedbuf, 0, sizeof(state->seedbuf));
+    memset(state->databuf, 0, sizeof(state->databuf));
+    state->pos = 0;
+
+    byte = digits = 0;
+    pos = 0;
+    while (*input) {
+       int v = *input++;
+
+       if (v >= '0' && v <= '9')
+           v = v - '0';
+       else if (v >= 'A' && v <= 'F')
+           v = v - 'A' + 10;
+       else if (v >= 'a' && v <= 'f')
+           v = v - 'a' + 10;
+       else
+           v = 0;
+
+       byte = (byte << 4) | v;
+       digits++;
+
+       if (digits == 2) {
+           /*
+            * We have a byte. Put it somewhere.
+            */
+           if (pos < lenof(state->seedbuf))
+               state->seedbuf[pos++] = byte;
+           else if (pos < lenof(state->seedbuf) + lenof(state->databuf))
+               state->databuf[pos++ - lenof(state->seedbuf)] = byte;
+           else if (pos == lenof(state->seedbuf) + lenof(state->databuf) &&
+                    byte <= lenof(state->databuf))
+               state->pos = byte;
+           byte = digits = 0;
+       }
+    }
+
+    return state;
+}
diff --git a/range.R b/range.R
new file mode 100644 (file)
index 0000000..f1256ef
--- /dev/null
+++ b/range.R
@@ -0,0 +1,21 @@
+# -*- makefile -*-
+
+RANGE_EXTRA = dsf
+
+range    : [X] GTK COMMON range RANGE_EXTRA range-icon|no-icon
+
+range    : [G] WINDOWS COMMON range RANGE_EXTRA range.res|noicon.res
+
+ALL += range[COMBINED] RANGE_EXTRA
+
+!begin am gtk
+GAMES += range
+!end
+
+!begin >list.c
+    A(range) \
+!end
+
+!begin >gamedesc.txt
+range:range.exe:Range:Visible-distance puzzle:Place black squares to limit the visible distance from each numbered cell.
+!end
diff --git a/range.c b/range.c
new file mode 100644 (file)
index 0000000..3f2bdac
--- /dev/null
+++ b/range.c
@@ -0,0 +1,1833 @@
+/*
+ * range.c: implementation of the Nikoli game 'Kurodoko' / 'Kuromasu'.
+ */
+
+/*
+ * Puzzle rules: the player is given a WxH grid of white squares, some
+ * of which contain numbers. The goal is to paint some of the squares
+ * black, such that:
+ *
+ *  - no cell (err, cell = square) with a number is painted black
+ *  - no black cells have an adjacent (horz/vert) black cell
+ *  - the white cells are all connected (through other white cells)
+ *  - if a cell contains a number n, let h and v be the lengths of the
+ *    maximal horizontal and vertical white sequences containing that
+ *    cell.  Then n must equal h + v - 1.
+ */
+
+/* example instance with its encoding and textual representation, both
+ * solved and unsolved (made by thegame.solve and thegame.text_format)
+ *
+ * +--+--+--+--+--+--+--+
+ * |  |  |  |  | 7|  |  |
+ * +--+--+--+--+--+--+--+
+ * | 3|  |  |  |  |  | 8|
+ * +--+--+--+--+--+--+--+
+ * |  |  |  |  |  | 5|  |
+ * +--+--+--+--+--+--+--+
+ * |  |  | 7|  | 7|  |  |
+ * +--+--+--+--+--+--+--+
+ * |  |13|  |  |  |  |  |
+ * +--+--+--+--+--+--+--+
+ * | 4|  |  |  |  |  | 8|
+ * +--+--+--+--+--+--+--+
+ * |  |  | 4|  |  |  |  |
+ * +--+--+--+--+--+--+--+
+ *
+ * 7x7:d7b3e8e5c7a7c13e4d8b4d
+ *
+ * +--+--+--+--+--+--+--+
+ * |..|..|..|..| 7|..|..|
+ * +--+--+--+--+--+--+--+
+ * | 3|..|##|..|##|..| 8|
+ * +--+--+--+--+--+--+--+
+ * |##|..|..|##|..| 5|..|
+ * +--+--+--+--+--+--+--+
+ * |..|..| 7|..| 7|##|..|
+ * +--+--+--+--+--+--+--+
+ * |..|13|..|..|..|..|..|
+ * +--+--+--+--+--+--+--+
+ * | 4|..|##|..|##|..| 8|
+ * +--+--+--+--+--+--+--+
+ * |##|..| 4|..|..|##|..|
+ * +--+--+--+--+--+--+--+
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+
+#include <stdarg.h>
+
+#define setmember(obj, field) ( (obj) . field = field )
+
+static char *nfmtstr(int n, char *fmt, ...) {
+    va_list va;
+    char *ret = snewn(n+1, char);
+    va_start(va, fmt);
+    vsprintf(ret, fmt, va);
+    va_end(va);
+    return ret;
+}
+
+#define SWAP(type, lvar1, lvar2) do { \
+    type tmp = (lvar1); \
+    (lvar1) = (lvar2); \
+    (lvar2) = tmp; \
+} while (0)
+
+/* ----------------------------------------------------------------------
+ * Game parameters, presets, states
+ */
+
+typedef signed char puzzle_size;
+
+struct game_params {
+    puzzle_size w;
+    puzzle_size h;
+};
+
+struct game_state {
+    struct game_params params;
+    unsigned int has_cheated: 1;
+    unsigned int was_solved: 1;
+    puzzle_size *grid;
+};
+
+#define DEFAULT_PRESET 0
+static struct game_params range_presets[] = {{9, 6}, {12, 8}, {13, 9}, {16, 11}};
+/* rationale: I want all four combinations of {odd/even, odd/even}, as
+ * they play out differently with respect to two-way symmetry.  I also
+ * want them to be generated relatively fast yet still be large enough
+ * to be entertaining for a decent amount of time, and I want them to
+ * make good use of monitor real estate (the typical screen resolution
+ * is why I do 13x9 and not 9x13).
+ */
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+    *ret = range_presets[DEFAULT_PRESET]; /* structure copy */
+    return ret;
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params; /* structure copy */
+    return ret;
+}
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params *ret;
+
+    if (i < 0 || i >= lenof(range_presets)) return FALSE;
+
+    ret = default_params();
+    *ret = range_presets[i]; /* struct copy */
+    *params = ret;
+
+    *name = nfmtstr(40, "%d x %d", range_presets[i].w, range_presets[i].h);
+
+    return TRUE;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static void decode_params(game_params *params, char const *string)
+{
+    /* FIXME check for puzzle_size overflow and decoding issues */
+    params->w = params->h = atoi(string);
+    while (*string && isdigit((unsigned char) *string)) ++string;
+    if (*string == 'x') {
+        string++;
+        params->h = atoi(string);
+        while (*string && isdigit((unsigned char)*string)) string++;
+    }
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char str[80];
+    sprintf(str, "%dx%d", params->w, params->h);
+    return dupstr(str);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+
+    ret = snewn(3, config_item);
+
+    ret[0].name = "Width";
+    ret[0].type = C_STRING;
+    ret[0].sval = nfmtstr(10, "%d", params->w);
+    ret[0].ival = 0;
+
+    ret[1].name = "Height";
+    ret[1].type = C_STRING;
+    ret[1].sval = nfmtstr(10, "%d", params->h);
+    ret[1].ival = 0;
+
+    ret[2].name = NULL;
+    ret[2].type = C_END;
+    ret[2].sval = NULL;
+    ret[2].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *configuration)
+{
+    game_params *ret = snew(game_params);
+    ret->w = atoi(configuration[0].sval);
+    ret->h = atoi(configuration[1].sval);
+    return ret;
+}
+
+#define memdup(dst, src, n, type) do { \
+    dst = snewn(n, type); \
+    memcpy(dst, src, n * sizeof (type)); \
+} while (0)
+
+static game_state *dup_game(const game_state *state)
+{
+    game_state *ret = snew(game_state);
+    int const n = state->params.w * state->params.h;
+
+    *ret = *state; /* structure copy */
+
+    /* copy the poin_tee_, set a new value of the poin_ter_ */
+    memdup(ret->grid, state->grid, n, puzzle_size);
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    sfree(state->grid);
+    sfree(state);
+}
+
+
+/* ----------------------------------------------------------------------
+ * The solver subsystem.
+ *
+ * The solver is used for two purposes:
+ *  - To solve puzzles when the user selects `Solve'.
+ *  - To test solubility of a grid as clues are being removed from it
+ *    during the puzzle generation.
+ *
+ * It supports the following ways of reasoning:
+ *
+ *  - A cell adjacent to a black cell must be white.
+ *
+ *  - If painting a square black would bisect the white regions, that
+ *    square is white (by finding biconnected components' cut points)
+ *
+ *  - A cell with number n, covering at most k white squares in three
+ *    directions must white-cover n-k squares in the last direction.
+ *
+ *  - A cell with number n known to cover k squares, if extending the
+ *    cover by one square in a given direction causes the cell to
+ *    cover _more_ than n squares, that extension cell must be black.
+ *
+ *    (either if the square already covers n, or if it extends into a
+ *    chunk of size > n - k)
+ *
+ *  - Recursion.  Pick any cell and see if this leads to either a
+ *    contradiction or a solution (and then act appropriately).
+ *
+ *
+ * TODO:
+ *
+ * (propagation upper limit)
+ *  - If one has two numbers on the same line, the smaller limits the
+ *    larger.  Example: in |b|_|_|8|4|_|_|b|, only two _'s can be both
+ *    white and connected to the "8" cell; so that cell will propagate
+ *    at least four cells orthogonally to the displayed line (which is
+ *    better than the current "at least 2").
+ *
+ * (propagation upper limit)
+ *  - cells can't propagate into other cells if doing so exceeds that
+ *    number.  Example: in |b|4|.|.|2|b|, at most one _ can be white;
+ *    otherwise, the |2| would have too many reaching white cells.
+ *
+ * (propagation lower and upper limit)
+ *  - `Full Combo': in each four directions d_1 ... d_4, find a set of
+ *    possible propagation distances S_1 ... S_4.  For each i=1..4,
+ *    for each x in S_i: if not exists (y, z, w) in the other sets
+ *    such that (x+y+z+w+1 == clue value): then remove x from S_i.
+ *    Repeat until this stabilizes.  If any cell would contradict
+ */
+
+#define idx(i, j, w) ((i)*(w) + (j))
+#define out_of_bounds(r, c, w, h) \
+   ((r) < 0 || (r) >= h || (c) < 0 || (c) >= w)
+
+typedef struct square {
+    puzzle_size r, c;
+} square;
+
+enum {BLACK = -2, WHITE, EMPTY};
+/* white is for pencil marks, empty is undecided */
+
+static int const dr[4] = {+1,  0, -1,  0};
+static int const dc[4] = { 0, +1,  0, -1};
+static int const cursors[4] = /* must match dr and dc */
+{CURSOR_DOWN, CURSOR_RIGHT, CURSOR_UP, CURSOR_LEFT};
+
+typedef struct move {
+    square square;
+    unsigned int colour: 1;
+} move;
+enum {M_BLACK = 0, M_WHITE = 1};
+
+typedef move *(reasoning)(game_state *state,
+                          int nclues,
+                          const square *clues,
+                          move *buf);
+
+static reasoning solver_reasoning_not_too_big;
+static reasoning solver_reasoning_adjacency;
+static reasoning solver_reasoning_connectedness;
+static reasoning solver_reasoning_recursion;
+
+enum {
+    DIFF_NOT_TOO_BIG,
+    DIFF_ADJACENCY,
+    DIFF_CONNECTEDNESS,
+    DIFF_RECURSION
+};
+
+static move *solve_internal(const game_state *state, move *base, int diff);
+
+static char *solve_game(const game_state *orig, const game_state *curpos,
+                        const char *aux, char **error)
+{
+    int const n = orig->params.w * orig->params.h;
+    move *const base = snewn(n, move);
+    move *moves = solve_internal(orig, base, DIFF_RECURSION);
+
+    char *ret = NULL;
+
+    if (moves != NULL) {
+        int const k = moves - base;
+        char *str = ret = snewn(15*k + 2, char);
+        char colour[2] = "BW";
+        move *it;
+        *str++ = 'S';
+        *str = '\0';
+        for (it = base; it < moves; ++it)
+            str += sprintf(str, "%c,%d,%d", colour[it->colour],
+                           it->square.r, it->square.c);
+    } else *error = "This puzzle instance contains a contradiction";
+
+    sfree(base);
+    return ret;
+}
+
+static square *find_clues(const game_state *state, int *ret_nclues);
+static move *do_solve(game_state *state,
+                      int nclues,
+                      const square *clues,
+                      move *move_buffer,
+                      int difficulty);
+
+/* new_game_desc entry point in the solver subsystem */
+static move *solve_internal(const game_state *state, move *base, int diff)
+{
+    int nclues;
+    square *const clues = find_clues(state, &nclues);
+    game_state *dup = dup_game(state);
+    move *const moves = do_solve(dup, nclues, clues, base, diff);
+    free_game(dup);
+    sfree(clues);
+    return moves;
+}
+
+static reasoning *const reasonings[] = {
+    solver_reasoning_not_too_big,
+    solver_reasoning_adjacency,
+    solver_reasoning_connectedness,
+    solver_reasoning_recursion
+};
+
+static move *do_solve(game_state *state,
+                      int nclues,
+                      const square *clues,
+                      move *move_buffer,
+                      int difficulty)
+{
+    struct move *buf = move_buffer, *oldbuf;
+    int i;
+
+    do {
+        oldbuf = buf;
+        for (i = 0; i < lenof(reasonings) && i <= difficulty; ++i) {
+            /* only recurse if all else fails */
+            if (i == DIFF_RECURSION && buf > oldbuf) continue;
+            buf = (*reasonings[i])(state, nclues, clues, buf);
+            if (buf == NULL) return NULL;
+        }
+    } while (buf > oldbuf);
+
+    return buf;
+}
+
+#define MASK(n) (1 << ((n) + 2))
+
+static int runlength(puzzle_size r, puzzle_size c,
+                     puzzle_size dr, puzzle_size dc,
+                     const game_state *state, int colourmask)
+{
+    int const w = state->params.w, h = state->params.h;
+    int sz = 0;
+    while (TRUE) {
+        int cell = idx(r, c, w);
+        if (out_of_bounds(r, c, w, h)) break;
+        if (state->grid[cell] > 0) {
+            if (!(colourmask & ~(MASK(BLACK) | MASK(WHITE) | MASK(EMPTY))))
+                break;
+        } else if (!(MASK(state->grid[cell]) & colourmask)) break;
+        ++sz;
+        r += dr;
+        c += dc;
+    }
+    return sz;
+}
+
+static void solver_makemove(puzzle_size r, puzzle_size c, int colour,
+                            game_state *state, move **buffer_ptr)
+{
+    int const cell = idx(r, c, state->params.w);
+    if (out_of_bounds(r, c, state->params.w, state->params.h)) return;
+    if (state->grid[cell] != EMPTY) return;
+    setmember((*buffer_ptr)->square, r);
+    setmember((*buffer_ptr)->square, c);
+    setmember(**buffer_ptr, colour);
+    ++*buffer_ptr;
+    state->grid[cell] = (colour == M_BLACK ? BLACK : WHITE);
+}
+
+static move *solver_reasoning_adjacency(game_state *state,
+                                        int nclues,
+                                        const square *clues,
+                                        move *buf)
+{
+    int r, c, i;
+    for (r = 0; r < state->params.h; ++r)
+        for (c = 0; c < state->params.w; ++c) {
+            int const cell = idx(r, c, state->params.w);
+            if (state->grid[cell] != BLACK) continue;
+            for (i = 0; i < 4; ++i)
+                solver_makemove(r + dr[i], c + dc[i], M_WHITE, state, &buf);
+        }
+    return buf;
+}
+
+enum {NOT_VISITED = -1};
+
+static int dfs_biconnect_visit(puzzle_size r, puzzle_size c,
+                               game_state *state,
+                               square *dfs_parent, int *dfs_depth,
+                               move **buf);
+
+static move *solver_reasoning_connectedness(game_state *state,
+                                            int nclues,
+                                            const square *clues,
+                                            move *buf)
+{
+    int const w = state->params.w, h = state->params.h, n = w * h;
+
+    square *const dfs_parent = snewn(n, square);
+    int *const dfs_depth = snewn(n, int);
+
+    int i;
+    for (i = 0; i < n; ++i) {
+        dfs_parent[i].r = NOT_VISITED;
+        dfs_depth[i] = -n;
+    }
+
+    for (i = 0; i < n && state->grid[i] == BLACK; ++i);
+
+    dfs_parent[i].r = i / w;
+    dfs_parent[i].c = i % w; /* `dfs root`.parent == `dfs root` */
+    dfs_depth[i] = 0;
+
+    dfs_biconnect_visit(i / w, i % w, state, dfs_parent, dfs_depth, &buf);
+
+    sfree(dfs_parent);
+    sfree(dfs_depth);
+
+    return buf;
+}
+
+/* returns the `lowpoint` of (r, c) */
+static int dfs_biconnect_visit(puzzle_size r, puzzle_size c,
+                               game_state *state,
+                               square *dfs_parent, int *dfs_depth,
+                               move **buf)
+{
+    const puzzle_size w = state->params.w, h = state->params.h;
+    int const i = idx(r, c, w), mydepth = dfs_depth[i];
+    int lowpoint = mydepth, j, nchildren = 0;
+
+    for (j = 0; j < 4; ++j) {
+        const puzzle_size rr = r + dr[j], cc = c + dc[j];
+        int const cell = idx(rr, cc, w);
+
+        if (out_of_bounds(rr, cc, w, h)) continue;
+        if (state->grid[cell] == BLACK) continue;
+
+        if (dfs_parent[cell].r == NOT_VISITED) {
+            int child_lowpoint;
+            dfs_parent[cell].r = r;
+            dfs_parent[cell].c = c;
+            dfs_depth[cell] = mydepth + 1;
+            child_lowpoint = dfs_biconnect_visit(rr, cc, state, dfs_parent,
+                                                 dfs_depth, buf);
+
+            if (child_lowpoint >= mydepth && mydepth > 0)
+                solver_makemove(r, c, M_WHITE, state, buf);
+
+            lowpoint = min(lowpoint, child_lowpoint);
+            ++nchildren;
+        } else if (rr != dfs_parent[i].r || cc != dfs_parent[i].c) {
+            lowpoint = min(lowpoint, dfs_depth[cell]);
+        }
+    }
+
+    if (mydepth == 0 && nchildren >= 2)
+        solver_makemove(r, c, M_WHITE, state, buf);
+
+    return lowpoint;
+}
+
+static move *solver_reasoning_not_too_big(game_state *state,
+                                          int nclues,
+                                          const square *clues,
+                                          move *buf)
+{
+    int const w = state->params.w, runmasks[4] = {
+        ~(MASK(BLACK) | MASK(EMPTY)),
+        MASK(EMPTY),
+        ~(MASK(BLACK) | MASK(EMPTY)),
+        ~(MASK(BLACK))
+    };
+    enum {RUN_WHITE, RUN_EMPTY, RUN_BEYOND, RUN_SPACE};
+
+    int i, runlengths[4][4];
+
+    for (i = 0; i < nclues; ++i) {
+        int j, k, whites, space;
+
+        const puzzle_size row = clues[i].r, col = clues[i].c;
+        int const clue = state->grid[idx(row, col, w)];
+
+        for (j = 0; j < 4; ++j) {
+            puzzle_size r = row + dr[j], c = col + dc[j];
+            runlengths[RUN_SPACE][j] = 0;
+            for (k = 0; k <= RUN_SPACE; ++k) {
+                int l = runlength(r, c, dr[j], dc[j], state, runmasks[k]);
+                if (k < RUN_SPACE) {
+                    runlengths[k][j] = l;
+                    r += dr[j] * l;
+                    c += dc[j] * l;
+                }
+                runlengths[RUN_SPACE][j] += l;
+            }
+        }
+
+        whites = 1;
+        for (j = 0; j < 4; ++j) whites += runlengths[RUN_WHITE][j];
+
+        for (j = 0; j < 4; ++j) {
+            int const delta = 1 + runlengths[RUN_WHITE][j];
+            const puzzle_size r = row + delta * dr[j];
+            const puzzle_size c = col + delta * dc[j];
+
+            if (whites == clue) {
+                solver_makemove(r, c, M_BLACK, state, &buf);
+                continue;
+            }
+
+            if (runlengths[RUN_EMPTY][j] == 1 &&
+                whites
+                + runlengths[RUN_EMPTY][j]
+                + runlengths[RUN_BEYOND][j]
+                > clue) {
+                solver_makemove(r, c, M_BLACK, state, &buf);
+                continue;
+            }
+
+            if (whites
+                + runlengths[RUN_EMPTY][j]
+                + runlengths[RUN_BEYOND][j]
+                > clue) {
+                runlengths[RUN_SPACE][j] =
+                    runlengths[RUN_WHITE][j] +
+                    runlengths[RUN_EMPTY][j] - 1;
+
+                if (runlengths[RUN_EMPTY][j] == 1)
+                    solver_makemove(r, c, M_BLACK, state, &buf);
+            }
+        }
+
+        space = 1;
+        for (j = 0; j < 4; ++j) space += runlengths[RUN_SPACE][j];
+        for (j = 0; j < 4; ++j) {
+            puzzle_size r = row + dr[j], c = col + dc[j];
+
+            int k = space - runlengths[RUN_SPACE][j];
+            if (k >= clue) continue;
+
+            for (; k < clue; ++k, r += dr[j], c += dc[j])
+                solver_makemove(r, c, M_WHITE, state, &buf);
+        }
+    }
+    return buf;
+}
+
+static move *solver_reasoning_recursion(game_state *state,
+                                        int nclues,
+                                        const square *clues,
+                                        move *buf)
+{
+    int const w = state->params.w, n = w * state->params.h;
+    int cell, colour;
+
+    for (cell = 0; cell < n; ++cell) {
+        int const r = cell / w, c = cell % w;
+        int i;
+        game_state *newstate;
+        move *recursive_result;
+
+        if (state->grid[cell] != EMPTY) continue;
+
+        /* FIXME: add enum alias for smallest and largest (or N) */
+        for (colour = M_BLACK; colour <= M_WHITE; ++colour) {
+            newstate = dup_game(state);
+            newstate->grid[cell] = colour;
+            recursive_result = do_solve(newstate, nclues, clues, buf,
+                                        DIFF_RECURSION);
+            free_game(newstate);
+            if (recursive_result == NULL) {
+                solver_makemove(r, c, M_BLACK + M_WHITE - colour, state, &buf);
+                return buf;
+            }
+            for (i = 0; i < n && newstate->grid[i] != EMPTY; ++i);
+            if (i == n) return buf;
+        }
+    }
+    return buf;
+}
+
+static square *find_clues(const game_state *state, int *ret_nclues)
+{
+    int r, c, i, nclues = 0;
+    square *ret = snewn(state->params.w * state->params.h, struct square);
+
+    for (i = r = 0; r < state->params.h; ++r)
+        for (c = 0; c < state->params.w; ++c, ++i)
+            if (state->grid[i] > 0) {
+                ret[nclues].r = r;
+                ret[nclues].c = c;
+                ++nclues;
+            }
+
+    *ret_nclues = nclues;
+    return sresize(ret, nclues + (nclues == 0), square);
+}
+
+/* ----------------------------------------------------------------------
+ * Puzzle generation
+ *
+ * Generating kurodoko instances is rather straightforward:
+ *
+ *  - Start with a white grid and add black squares at randomly chosen
+ *    locations, unless colouring that square black would violate
+ *    either the adjacency or connectedness constraints.
+ *
+ *  - For each white square, compute the number it would contain if it
+ *    were given as a clue.
+ *
+ *  - From a starting point of "give _every_ white square as a clue",
+ *    for each white square (in a random order), see if the board is
+ *    solvable when that square is not given as a clue.  If not, don't
+ *    give it as a clue, otherwise do.
+ *
+ * This never fails, but it's only _almost_ what I do.  The real final
+ * step is this:
+ *
+ *  - From a starting point of "give _every_ white square as a clue",
+ *    first remove all clues that are two-way rotationally symmetric
+ *    to a black square.  If this leaves the puzzle unsolvable, throw
+ *    it out and try again.  Otherwise, remove all _pairs_ of clues
+ *    (that are rotationally symmetric) which can be removed without
+ *    rendering the puzzle unsolvable.
+ *
+ * This can fail even if one only removes the black and symmetric
+ * clues; indeed it happens often (avg. once or twice per puzzle) when
+ * generating 1xN instances.  (If you add black cells they must be in
+ * the end, and if you only add one, it's ambiguous where).
+ */
+
+/* forward declarations of internal calls */
+static void newdesc_choose_black_squares(game_state *state,
+                                         const int *shuffle_1toN);
+static void newdesc_compute_clues(game_state *state);
+static int newdesc_strip_clues(game_state *state, int *shuffle_1toN);
+static char *newdesc_encode_game_description(int n, puzzle_size *grid);
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                           char **aux, int interactive)
+{
+    int const w = params->w, h = params->h, n = w * h;
+
+    puzzle_size *const grid = snewn(n, puzzle_size);
+    int *const shuffle_1toN = snewn(n, int);
+
+    int i, clues_removed;
+
+    char *encoding;
+
+    game_state state;
+    state.params = *params;
+    state.grid = grid;
+
+    interactive = 0; /* I don't need it, I shouldn't use it*/
+
+    for (i = 0; i < n; ++i) shuffle_1toN[i] = i;
+
+    while (TRUE) {
+        shuffle(shuffle_1toN, n, sizeof (int), rs);
+        newdesc_choose_black_squares(&state, shuffle_1toN);
+
+        newdesc_compute_clues(&state);
+
+        shuffle(shuffle_1toN, n, sizeof (int), rs);
+        clues_removed = newdesc_strip_clues(&state, shuffle_1toN);
+
+        if (clues_removed < 0) continue; else break;
+    }
+
+    encoding = newdesc_encode_game_description(n, grid);
+
+    sfree(grid);
+    sfree(shuffle_1toN);
+
+    return encoding;
+}
+
+static int dfs_count_white(game_state *state, int cell);
+
+static void newdesc_choose_black_squares(game_state *state,
+                                         const int *shuffle_1toN)
+{
+    int const w = state->params.w, h = state->params.h, n = w * h;
+
+    int k, any_white_cell, n_black_cells;
+
+    for (k = 0; k < n; ++k) state->grid[k] = WHITE;
+
+    any_white_cell = shuffle_1toN[n - 1];
+    n_black_cells = 0;
+
+    /* I like the puzzles that result from n / 3, but maybe this
+     * could be made a (generation, i.e. non-full) parameter? */
+    for (k = 0; k < n / 3; ++k) {
+        int const i = shuffle_1toN[k], c = i % w, r = i / w;
+
+        int j;
+        for (j = 0; j < 4; ++j) {
+            int const rr = r + dr[j], cc = c + dc[j], cell = idx(rr, cc, w);
+            /* if you're out of bounds, we skip you */
+            if (out_of_bounds(rr, cc, w, h)) continue;
+            if (state->grid[cell] == BLACK) break; /* I can't be black */
+        } if (j < 4) continue; /* I have black neighbour: I'm white */
+
+        state->grid[i] = BLACK;
+        ++n_black_cells;
+
+        j = dfs_count_white(state, any_white_cell);
+        if (j + n_black_cells < n) {
+            state->grid[i] = WHITE;
+            --n_black_cells;
+        }
+    }
+}
+
+static void newdesc_compute_clues(game_state *state)
+{
+    int const w = state->params.w, h = state->params.h;
+    int r, c;
+
+    for (r = 0; r < h; ++r) {
+        int run_size = 0, c, cc;
+        for (c = 0; c <= w; ++c) {
+            if (c == w || state->grid[idx(r, c, w)] == BLACK) {
+                for (cc = c - run_size; cc < c; ++cc)
+                    state->grid[idx(r, cc, w)] += run_size;
+                run_size = 0;
+            } else ++run_size;
+        }
+    }
+
+    for (c = 0; c < w; ++c) {
+        int run_size = 0, r, rr;
+        for (r = 0; r <= h; ++r) {
+            if (r == h || state->grid[idx(r, c, w)] == BLACK) {
+                for (rr = r - run_size; rr < r; ++rr)
+                    state->grid[idx(rr, c, w)] += run_size;
+                run_size = 0;
+            } else ++run_size;
+        }
+    }
+}
+
+#define rotate(x) (n - 1 - (x))
+
+static int newdesc_strip_clues(game_state *state, int *shuffle_1toN)
+{
+    int const w = state->params.w, n = w * state->params.h;
+
+    move *const move_buffer = snewn(n, move);
+    move *buf;
+    game_state *dupstate;
+
+    /*
+     * do a partition/pivot of shuffle_1toN into three groups:
+     * (1) squares rotationally-symmetric to (3)
+     * (2) squares not in (1) or (3)
+     * (3) black squares
+     *
+     * They go from [0, left), [left, right) and [right, n) in
+     * shuffle_1toN (and from there into state->grid[ ])
+     *
+     * Then, remove clues from the grid one by one in shuffle_1toN
+     * order, until the solver becomes unhappy.  If we didn't remove
+     * all of (1), return (-1).  Else, we're happy.
+     */
+
+    /* do the partition */
+    int clues_removed, k = 0, left = 0, right = n;
+
+    for (;; ++k) {
+        while (k < right && state->grid[shuffle_1toN[k]] == BLACK) {
+            --right;
+            SWAP(int, shuffle_1toN[right], shuffle_1toN[k]);
+            assert(state->grid[shuffle_1toN[right]] == BLACK);
+        }
+        if (k >= right) break;
+        assert (k >= left);
+        if (state->grid[rotate(shuffle_1toN[k])] == BLACK) {
+            SWAP(int, shuffle_1toN[k], shuffle_1toN[left]);
+            ++left;
+        }
+        assert (state->grid[rotate(shuffle_1toN[k])] != BLACK
+                || k == left - 1);
+    }
+
+    for (k = 0; k < left; ++k) {
+        assert (state->grid[rotate(shuffle_1toN[k])] == BLACK);
+        state->grid[shuffle_1toN[k]] = EMPTY;
+    }
+    for (k = left; k < right; ++k) {
+        assert (state->grid[rotate(shuffle_1toN[k])] != BLACK);
+        assert (state->grid[shuffle_1toN[k]] != BLACK);
+    }
+    for (k = right; k < n; ++k) {
+        assert (state->grid[shuffle_1toN[k]] == BLACK);
+        state->grid[shuffle_1toN[k]] = EMPTY;
+    }
+
+    clues_removed = (left - 0) + (n - right);
+
+    dupstate = dup_game(state);
+    buf = solve_internal(dupstate, move_buffer, DIFF_RECURSION - 1);
+    free_game(dupstate);
+    if (buf - move_buffer < clues_removed) {
+        /* branch prediction: I don't think I'll go here */
+        clues_removed = -1;
+        goto ret;
+    }
+
+    for (k = left; k < right; ++k) {
+        const int i = shuffle_1toN[k], j = rotate(i);
+        int const clue = state->grid[i], clue_rot = state->grid[j];
+        if (clue == BLACK) continue;
+        state->grid[i] = state->grid[j] = EMPTY;
+        dupstate = dup_game(state);
+        buf = solve_internal(dupstate, move_buffer, DIFF_RECURSION - 1);
+        free_game(dupstate);
+        clues_removed += 2 - (i == j);
+        /* if i is the center square, then i == (j = rotate(i))
+         * when i and j are one, removing i and j removes only one */
+        if (buf - move_buffer == clues_removed) continue;
+        /* if the solver is sound, refilling all removed clues means
+         * we have filled all squares, i.e. solved the puzzle. */
+        state->grid[i] = clue;
+        state->grid[j] = clue_rot;
+        clues_removed -= 2 - (i == j);
+    }
+    
+ret:
+    sfree(move_buffer);
+    return clues_removed;
+}
+
+static int dfs_count_rec(puzzle_size *grid, int r, int c, int w, int h)
+{
+    int const cell = idx(r, c, w);
+    if (out_of_bounds(r, c, w, h)) return 0;
+    if (grid[cell] != WHITE) return 0;
+    grid[cell] = EMPTY;
+    return 1 +
+        dfs_count_rec(grid, r + 0, c + 1, w, h) +
+        dfs_count_rec(grid, r + 0, c - 1, w, h) +
+        dfs_count_rec(grid, r + 1, c + 0, w, h) +
+        dfs_count_rec(grid, r - 1, c + 0, w, h);
+}
+
+static int dfs_count_white(game_state *state, int cell)
+{
+    int const w = state->params.w, h = state->params.h, n = w * h;
+    int const r = cell / w, c = cell % w;
+    int i, k = dfs_count_rec(state->grid, r, c, w, h);
+    for (i = 0; i < n; ++i)
+        if (state->grid[i] == EMPTY)
+            state->grid[i] = WHITE;
+    return k;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    int const w = params->w, h = params->h;
+    if (w < 1) return "Error: width is less than 1";
+    if (h < 1) return "Error: height is less than 1";
+    if (w * h < 1) return "Error: size is less than 1";
+    if (w + h - 1 > SCHAR_MAX) return "Error: w + h is too big";
+    /* I might be unable to store clues in my puzzle_size *grid; */
+    if (full) {
+        if (w == 2 && h == 2) return "Error: can't create 2x2 puzzles";
+        if (w == 1 && h == 2) return "Error: can't create 1x2 puzzles";
+        if (w == 2 && h == 1) return "Error: can't create 2x1 puzzles";
+        if (w == 1 && h == 1) return "Error: can't create 1x1 puzzles";
+    }
+    return NULL;
+}
+
+/* Definition: a puzzle instance is _good_ if:
+ *  - it has a unique solution
+ *  - the solver can find this solution without using recursion
+ *  - the solution contains at least one black square
+ *  - the clues are 2-way rotationally symmetric
+ *
+ * (the idea being: the generator can not output any _bad_ puzzles)
+ *
+ * Theorem: validate_params, when full != 0, discards exactly the set
+ * of parameters for which there are _no_ good puzzle instances.
+ *
+ * Proof: it's an immediate consequence of the five lemmas below.
+ *
+ * Observation: not only do puzzles on non-tiny grids exist, the
+ * generator is pretty fast about coming up with them.  On my pre-2004
+ * desktop box, it generates 100 puzzles on the highest preset (16x11)
+ * in 8.383 seconds, or <= 0.1 second per puzzle.
+ *
+ * ----------------------------------------------------------------------
+ *
+ * Lemma: On a 1x1 grid, there are no good puzzles.
+ *
+ * Proof: the one square can't be a clue because at least one square
+ * is black.  But both a white square and a black square satisfy the
+ * solution criteria, so the puzzle is ambiguous (and hence bad).
+ *
+ * Lemma: On a 1x2 grid, there are no good puzzles.
+ *
+ * Proof: let's name the squares l and r.  Note that there can be at
+ * most one black square, or adjacency is violated.  By assumption at
+ * least one square is black, so let's call that one l.  By clue
+ * symmetry, neither l nor r can be given as a clue, so the puzzle
+ * instance is blank and thus ambiguous.
+ *
+ * Corollary: On a 2x1 grid, there are no good puzzles.
+ * Proof: rotate the above proof 90 degrees ;-)
+ *
+ * ----------------------------------------------------------------------
+ *
+ * Lemma: On a 2x2 grid, there are no soluble puzzles with 2-way
+ * rotational symmetric clues and at least one black square.
+ *
+ * Proof: Let's name the squares a, b, c, and d, with a and b on the
+ * top row, a and c in the left column.  Let's consider the case where
+ * a is black.  Then no other square can be black: b and c would both
+ * violate the adjacency constraint; d would disconnect b from c.
+ *
+ * So exactly one square is black (and by 4-way rotation symmetry of
+ * the 2x2 square, it doesn't matter which one, so let's stick to a).
+ * By 2-way rotational symmetry of the clues and the rule about not
+ * painting numbers black, neither a nor d can be clues.  A blank
+ * puzzle would be ambiguous, so one of {b, c} is a clue; by symmetry,
+ * so is the other one.
+ *
+ * It is readily seen that their clue value is 2.  But "a is black"
+ * and "d is black" are both valid solutions in this case, so the
+ * puzzle is ambiguous (and hence bad).
+ *
+ * ----------------------------------------------------------------------
+ *
+ * Lemma: On a wxh grid with w, h >= 1 and (w > 2 or h > 2), there is
+ * at least one good puzzle.
+ *
+ * Proof: assume that w > h (otherwise rotate the proof again).  Paint
+ * the top left and bottom right corners black, and fill a clue into
+ * all the other squares.  Present this board to the solver code (or
+ * player, hypothetically), except with the two black squares as blank
+ * squares.
+ *
+ * For an Nx1 puzzle, observe that every clue is N - 2, and there are
+ * N - 2 of them in one connected sequence, so the remaining two
+ * squares can be deduced to be black, which solves the puzzle.
+ *
+ * For any other puzzle, let j be a cell in the same row as a black
+ * cell, but not in the same column (such a cell doesn't exist in 2x3
+ * puzzles, but we assume w > h and such cells exist in 3x2 puzzles).
+ *
+ * Note that the number of cells in axis parallel `rays' going out
+ * from j exceeds j's clue value by one.  Only one such cell is a
+ * non-clue, so it must be black.  Similarly for the other corner (let
+ * j' be a cell in the same row as the _other_ black cell, but not in
+ * the same column as _any_ black cell; repeat this argument at j').
+ *
+ * This fills the grid and satisfies all clues and the adjacency
+ * constraint and doesn't paint on top of any clues.  All that is left
+ * to see is connectedness.
+ *
+ * Observe that the white cells in each column form a single connected
+ * `run', and each column contains a white cell adjacent to a white
+ * cell in the column to the right, if that column exists.
+ *
+ * Thus, any cell in the left-most column can reach any other cell:
+ * first go to the target column (by repeatedly going to the cell in
+ * your current column that lets you go right, then going right), then
+ * go up or down to the desired cell.
+ *
+ * As reachability is symmetric (in undirected graphs) and transitive,
+ * any cell can reach any left-column cell, and from there any other
+ * cell.
+ */
+
+/* ----------------------------------------------------------------------
+ * Game encoding and decoding
+ */
+
+#define NDIGITS_BASE '!'
+
+static char *newdesc_encode_game_description(int area, puzzle_size *grid)
+{
+    char *desc = NULL;
+    int desclen = 0, descsize = 0;
+    int run, i;
+
+    run = 0;
+    for (i = 0; i <= area; i++) {
+       int n = (i < area ? grid[i] : -1);
+
+       if (!n)
+           run++;
+       else {
+           if (descsize < desclen + 40) {
+               descsize = desclen * 3 / 2 + 40;
+               desc = sresize(desc, descsize, char);
+           }
+           if (run) {
+               while (run > 0) {
+                   int c = 'a' - 1 + run;
+                   if (run > 26)
+                       c = 'z';
+                   desc[desclen++] = c;
+                   run -= c - ('a' - 1);
+               }
+           } else {
+               /*
+                * If there's a number in the very top left or
+                * bottom right, there's no point putting an
+                * unnecessary _ before or after it.
+                */
+               if (desclen > 0 && n > 0)
+                   desc[desclen++] = '_';
+           }
+           if (n > 0)
+               desclen += sprintf(desc+desclen, "%d", n);
+           run = 0;
+       }
+    }
+    desc[desclen] = '\0';
+    return desc;
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    int const n = params->w * params->h;
+    int squares = 0;
+    int range = params->w + params->h - 1;   /* maximum cell value */
+
+    while (*desc && *desc != ',') {
+        int c = *desc++;
+        if (c >= 'a' && c <= 'z') {
+            squares += c - 'a' + 1;
+        } else if (c == '_') {
+            /* do nothing */;
+        } else if (c > '0' && c <= '9') {
+            int val = atoi(desc-1);
+            if (val < 1 || val > range)
+                return "Out-of-range number in game description";
+            squares++;
+            while (*desc >= '0' && *desc <= '9')
+                desc++;
+        } else
+            return "Invalid character in game description";
+    }
+
+    if (squares < n)
+        return "Not enough data to fill grid";
+
+    if (squares > n)
+        return "Too much data to fit in grid";
+
+    return NULL;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    int i;
+    const char *p;
+
+    int const n = params->w * params->h;
+    game_state *state = snew(game_state);
+
+    me = NULL; /* I don't need it, I shouldn't use it */
+
+    state->params = *params; /* structure copy */
+    state->grid = snewn(n, puzzle_size);
+
+    p = desc;
+    i = 0;
+    while (i < n && *p) {
+        int c = *p++;
+        if (c >= 'a' && c <= 'z') {
+            int squares = c - 'a' + 1;
+           while (squares--)
+               state->grid[i++] = 0;
+        } else if (c == '_') {
+            /* do nothing */;
+        } else if (c > '0' && c <= '9') {
+            int val = atoi(p-1);
+            assert(val >= 1 && val <= params->w+params->h-1);
+            state->grid[i++] = val;
+            while (*p >= '0' && *p <= '9')
+                p++;
+        }
+    }
+    assert(i == n);
+    state->has_cheated = FALSE;
+    state->was_solved = FALSE;
+
+    return state;
+}
+
+/* ----------------------------------------------------------------------
+ * User interface: ascii
+ */
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    int cellsize, r, c, i, w_string, h_string, n_string;
+    char *ret, *buf, *gridline;
+
+    int const w = state->params.w, h = state->params.h;
+
+    cellsize = 0; /* or may be used uninitialized */
+
+    for (c = 0; c < w; ++c) {
+        for (r = 0; r < h; ++r) {
+            puzzle_size k = state->grid[idx(r, c, w)];
+            int d;
+            for (d = 0; k; k /= 10, ++d);
+            cellsize = max(cellsize, d);
+        }
+    }
+
+    ++cellsize;
+
+    w_string = w * cellsize + 2; /* "|%d|%d|...|\n" */
+    h_string = 2 * h + 1; /* "+--+--+...+\n%s\n+--+--+...+\n" */
+    n_string = w_string * h_string;
+
+    gridline = snewn(w_string + 1, char); /* +1: NUL terminator */
+    memset(gridline, '-', w_string);
+    for (c = 0; c <= w; ++c) gridline[c * cellsize] = '+';
+    gridline[w_string - 1] = '\n';
+    gridline[w_string - 0] = '\0';
+
+    buf = ret = snewn(n_string + 1, char); /* +1: NUL terminator */
+    for (i = r = 0; r < h; ++r) {
+        memcpy(buf, gridline, w_string);
+        buf += w_string;
+        for (c = 0; c < w; ++c, ++i) {
+            char ch;
+            switch (state->grid[i]) {
+             case BLACK: ch = '#'; break;
+             case WHITE: ch = '.'; break;
+             case EMPTY: ch = ' '; break;
+             default:
+                buf += sprintf(buf, "|%*d", cellsize - 1, state->grid[i]);
+                continue;
+            }
+            *buf++ = '|';
+            memset(buf, ch, cellsize - 1);
+            buf += cellsize - 1;
+        }
+        buf += sprintf(buf, "|\n");
+    }
+    memcpy(buf, gridline, w_string);
+    buf += w_string;
+    assert (buf - ret == n_string);
+    *buf = '\0';
+
+    sfree(gridline);
+
+    return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * User interfaces: interactive
+ */
+
+struct game_ui {
+    puzzle_size r, c; /* cursor position */
+    unsigned int cursor_show: 1;
+};
+
+static game_ui *new_ui(const game_state *state)
+{
+    struct game_ui *ui = snew(game_ui);
+    ui->r = ui->c = 0;
+    ui->cursor_show = FALSE;
+    return ui;
+}
+
+static void free_ui(game_ui *ui)
+{
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+}
+
+typedef struct drawcell {
+    puzzle_size value;
+    unsigned int error: 1;
+    unsigned int cursor: 1;
+    unsigned int flash: 1;
+} drawcell;
+
+struct game_drawstate {
+    int tilesize;
+    drawcell *grid;
+    unsigned int started: 1;
+};
+
+#define TILESIZE (ds->tilesize)
+#define BORDER (TILESIZE / 2)
+#define COORD(x) ((x) * TILESIZE + BORDER)
+#define FROMCOORD(x) (((x) - BORDER) / TILESIZE)
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    enum {none, forwards, backwards, hint};
+    int const w = state->params.w, h = state->params.h;
+    int r = ui->r, c = ui->c, action = none, cell;
+    int shift = button & MOD_SHFT;
+    button &= ~shift;
+
+    if (IS_CURSOR_SELECT(button) && !ui->cursor_show) return NULL;
+
+    if (IS_MOUSE_DOWN(button)) {
+        r = FROMCOORD(y + TILESIZE) - 1; /* or (x, y) < TILESIZE) */
+        c = FROMCOORD(x + TILESIZE) - 1; /* are considered inside */
+        if (out_of_bounds(r, c, w, h)) return NULL;
+        ui->r = r;
+        ui->c = c;
+        ui->cursor_show = FALSE;
+    }
+
+    if (button == LEFT_BUTTON || button == RIGHT_BUTTON) {
+       /*
+        * Utterly awful hack, exactly analogous to the one in Slant,
+        * to configure the left and right mouse buttons the opposite
+        * way round.
+        *
+        * The original puzzle submitter thought it would be more
+        * useful to have the left button turn an empty square into a
+        * dotted one, on the grounds that that was what you did most
+        * often; I (SGT) felt instinctively that the left button
+        * ought to place black squares and the right button place
+        * dots, on the grounds that that was consistent with many
+        * other puzzles in which the left button fills in the data
+        * used by the solution checker while the right button places
+        * pencil marks for the user's convenience.
+        *
+        * My first beta-player wasn't sure either, so I thought I'd
+        * pre-emptively put in a 'configuration' mechanism just in
+        * case.
+        */
+       {
+           static int swap_buttons = -1;
+           if (swap_buttons < 0) {
+               char *env = getenv("RANGE_SWAP_BUTTONS");
+               swap_buttons = (env && (env[0] == 'y' || env[0] == 'Y'));
+           }
+           if (swap_buttons) {
+               if (button == LEFT_BUTTON)
+                   button = RIGHT_BUTTON;
+               else
+                   button = LEFT_BUTTON;
+           }
+       }
+    }
+
+    switch (button) {
+      case CURSOR_SELECT : case   LEFT_BUTTON: action = backwards; break;
+      case CURSOR_SELECT2: case  RIGHT_BUTTON: action =  forwards; break;
+      case 'h': case 'H' :                     action =      hint; break;
+      case CURSOR_UP: case CURSOR_DOWN:
+      case CURSOR_LEFT: case CURSOR_RIGHT:
+        if (ui->cursor_show) {
+            int i;
+            for (i = 0; i < 4 && cursors[i] != button; ++i);
+            assert (i < 4);
+            if (shift) {
+                int pre_r = r, pre_c = c, do_pre, do_post;
+                cell = state->grid[idx(r, c, state->params.w)];
+                do_pre = (cell == EMPTY);
+
+                if (out_of_bounds(ui->r + dr[i], ui->c + dc[i], w, h)) {
+                    if (do_pre)
+                        return nfmtstr(40, "W,%d,%d", pre_r, pre_c);
+                    else
+                        return NULL;
+                }
+
+                ui->r += dr[i];
+                ui->c += dc[i];
+
+                cell = state->grid[idx(ui->r, ui->c, state->params.w)];
+                do_post = (cell == EMPTY);
+
+                /* (do_pre ? "..." : "") concat (do_post ? "..." : "") */
+                if (do_pre && do_post)
+                    return nfmtstr(80, "W,%d,%dW,%d,%d",
+                                   pre_r, pre_c, ui->r, ui->c);
+                else if (do_pre)
+                    return nfmtstr(40, "W,%d,%d", pre_r, pre_c);
+                else if (do_post)
+                    return nfmtstr(40, "W,%d,%d", ui->r, ui->c);
+                else
+                    return "";
+
+            } else if (!out_of_bounds(ui->r + dr[i], ui->c + dc[i], w, h)) {
+                ui->r += dr[i];
+                ui->c += dc[i];
+            }
+        } else ui->cursor_show = TRUE;
+        return "";
+    }
+
+    if (action == hint) {
+        move *end, *buf = snewn(state->params.w * state->params.h,
+                                struct move);
+        char *ret = NULL;
+        end = solve_internal(state, buf, DIFF_RECURSION);
+        if (end != NULL && end > buf) {
+            ret = nfmtstr(40, "%c,%d,%d",
+                          buf->colour == M_BLACK ? 'B' : 'W',
+                          buf->square.r, buf->square.c);
+            /* We used to set a flag here in the game_ui indicating
+             * that the player had used the hint function. I (SGT)
+             * retired it, on grounds of consistency with other games
+             * (most of these games will still flash to indicate
+             * completion if you solved and undid it, so why not if
+             * you got a hint?) and because the flash is as much about
+             * checking you got it all right than about congratulating
+             * you on a job well done. */
+        }
+        sfree(buf);
+        return ret;
+    }
+
+    cell = state->grid[idx(r, c, state->params.w)];
+    if (cell > 0) return NULL;
+
+    if (action == forwards) switch (cell) {
+      case EMPTY: return nfmtstr(40, "W,%d,%d", r, c);
+      case WHITE: return nfmtstr(40, "B,%d,%d", r, c);
+      case BLACK: return nfmtstr(40, "E,%d,%d", r, c);
+    }
+
+    else if (action == backwards) switch (cell) {
+      case BLACK: return nfmtstr(40, "W,%d,%d", r, c);
+      case WHITE: return nfmtstr(40, "E,%d,%d", r, c);
+      case EMPTY: return nfmtstr(40, "B,%d,%d", r, c);
+    }
+
+    return NULL;
+}
+
+static int find_errors(const game_state *state, int *report)
+{
+    int const w = state->params.w, h = state->params.h, n = w * h;
+    int *dsf;
+
+    int r, c, i;
+
+    int nblack = 0, any_white_cell = -1;
+    game_state *dup = dup_game(state);
+
+    for (i = r = 0; r < h; ++r)
+        for (c = 0; c < w; ++c, ++i) {
+            switch (state->grid[i]) {
+
+             case BLACK:
+               {
+                   int j;
+                   ++nblack;
+                   for (j = 0; j < 4; ++j) {
+                       int const rr = r + dr[j], cc = c + dc[j];
+                       if (out_of_bounds(rr, cc, w, h)) continue;
+                       if (state->grid[idx(rr, cc, w)] != BLACK) continue;
+                       if (!report) goto found_error;
+                       report[i] = TRUE;
+                       break;
+                   }
+               }
+                break;
+             default:
+               {
+                   int j, runs;
+                   for (runs = 1, j = 0; j < 4; ++j) {
+                       int const rr = r + dr[j], cc = c + dc[j];
+                       runs += runlength(rr, cc, dr[j], dc[j], state,
+                                         ~MASK(BLACK));
+                   }
+                   if (!report) {
+                       if (runs != state->grid[i]) goto found_error;
+                   } else if (runs < state->grid[i]) report[i] = TRUE;
+                   else {
+                       for (runs = 1, j = 0; j < 4; ++j) {
+                           int const rr = r + dr[j], cc = c + dc[j];
+                           runs += runlength(rr, cc, dr[j], dc[j], state,
+                                             ~(MASK(BLACK) | MASK(EMPTY)));
+                       }
+                       if (runs > state->grid[i]) report[i] = TRUE;
+                   }
+               }
+
+                /* note: fallthrough _into_ these cases */
+             case EMPTY:
+             case WHITE: any_white_cell = i;
+            }
+        }
+
+    /*
+     * Check that all the white cells form a single connected component.
+     */
+    dsf = snew_dsf(n);
+    for (r = 0; r < h-1; ++r)
+        for (c = 0; c < w; ++c)
+            if (state->grid[r*w+c] != BLACK &&
+                state->grid[(r+1)*w+c] != BLACK)
+                dsf_merge(dsf, r*w+c, (r+1)*w+c);
+    for (r = 0; r < h; ++r)
+        for (c = 0; c < w-1; ++c)
+            if (state->grid[r*w+c] != BLACK &&
+                state->grid[r*w+(c+1)] != BLACK)
+                dsf_merge(dsf, r*w+c, r*w+(c+1));
+    if (nblack + dsf_size(dsf, any_white_cell) < n) {
+        int biggest, canonical;
+
+        if (!report) {
+            sfree(dsf);
+            goto found_error;
+        }
+
+        /*
+         * Report this error by choosing one component to be the
+         * canonical one (we pick the largest, arbitrarily
+         * tie-breaking towards lower array indices) and highlighting
+         * as an error any square in a different component.
+         */
+        canonical = -1;
+        biggest = 0;
+        for (i = 0; i < n; ++i)
+            if (state->grid[i] != BLACK) {
+                int size = dsf_size(dsf, i);
+                if (size > biggest) {
+                    biggest = size;
+                    canonical = dsf_canonify(dsf, i);
+                }
+            }
+
+        for (i = 0; i < n; ++i)
+            if (state->grid[i] != BLACK && dsf_canonify(dsf, i) != canonical)
+                report[i] = TRUE;
+    }
+    sfree(dsf);
+
+    free_game(dup);
+    return FALSE; /* if report != NULL, this is ignored */
+
+found_error:
+    free_game(dup);
+    return TRUE;
+}
+
+static game_state *execute_move(const game_state *state, const char *move)
+{
+    signed int r, c, value, nchars, ntok;
+    signed char what_to_do;
+    game_state *ret;
+
+    assert (move);
+
+    ret = dup_game(state);
+
+    if (*move == 'S') {
+        ++move;
+        ret->has_cheated = ret->was_solved = TRUE;
+    }
+
+    for (; *move; move += nchars) {
+        ntok = sscanf(move, "%c,%d,%d%n", &what_to_do, &r, &c, &nchars);
+        if (ntok < 3) goto failure;
+        switch (what_to_do) {
+         case 'W': value = WHITE; break;
+         case 'E': value = EMPTY; break;
+         case 'B': value = BLACK; break;
+         default: goto failure;
+        }
+        if (out_of_bounds(r, c, ret->params.w, ret->params.h)) goto failure;
+        ret->grid[idx(r, c, ret->params.w)] = value;
+    }
+
+    if (ret->was_solved == FALSE)
+        ret->was_solved = !find_errors(ret, NULL);
+
+    return ret;
+
+failure:
+    free_game(ret);
+    return NULL;
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return 0.0F;
+}
+
+#define FLASH_TIME 0.7F
+
+static float game_flash_length(const game_state *from,
+                               const game_state *to, int dir, game_ui *ui)
+{
+    if (!from->was_solved && to->was_solved && !to->has_cheated)
+        return FLASH_TIME;
+    return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    return state->was_solved ? +1 : 0;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+#define PREFERRED_TILE_SIZE 32
+
+enum {
+    COL_BACKGROUND = 0,
+    COL_GRID,
+    COL_BLACK = COL_GRID,
+    COL_TEXT = COL_GRID,
+    COL_USER = COL_GRID,
+    COL_ERROR,
+    COL_LOWLIGHT,
+    COL_HIGHLIGHT = COL_ERROR, /* mkhighlight needs it, I don't */
+    COL_CURSOR = COL_LOWLIGHT,
+    NCOLOURS
+};
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    *x = (1 + params->w) * tilesize;
+    *y = (1 + params->h) * tilesize;
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+}
+
+#define COLOUR(ret, i, r, g, b) \
+   ((ret[3*(i)+0] = (r)), (ret[3*(i)+1] = (g)), (ret[3*(i)+2] = (b)))
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+
+    game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT);
+    COLOUR(ret, COL_GRID,  0.0F, 0.0F, 0.0F);
+    COLOUR(ret, COL_ERROR, 1.0F, 0.0F, 0.0F);
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static drawcell makecell(puzzle_size value, int error, int cursor, int flash)
+{
+    drawcell ret;
+    setmember(ret, value);
+    setmember(ret, error);
+    setmember(ret, cursor);
+    setmember(ret, flash);
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    int const w = state->params.w, h = state->params.h, n = w * h;
+    struct game_drawstate *ds = snew(struct game_drawstate);
+    int i;
+
+    ds->tilesize = 0;
+    ds->started = FALSE;
+
+    ds->grid = snewn(n, drawcell);
+    for (i = 0; i < n; ++i)
+        ds->grid[i] = makecell(w + h, FALSE, FALSE, FALSE);
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds->grid);
+    sfree(ds);
+}
+
+#define cmpmember(a, b, field) ((a) . field == (b) . field)
+
+static int cell_eq(drawcell a, drawcell b)
+{
+    return
+        cmpmember(a, b, value) &&
+        cmpmember(a, b, error) &&
+        cmpmember(a, b, cursor) &&
+        cmpmember(a, b, flash);
+}
+
+static void draw_cell(drawing *dr, game_drawstate *ds, int r, int c,
+                      drawcell cell);
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int const w = state->params.w, h = state->params.h, n = w * h;
+    int const wpx = (w+1) * ds->tilesize, hpx = (h+1) * ds->tilesize;
+    int const flash = ((int) (flashtime * 5 / FLASH_TIME)) % 2;
+
+    int r, c, i;
+
+    int *errors = snewn(n, int);
+    memset(errors, FALSE, n * sizeof (int));
+    find_errors(state, errors);
+
+    assert (oldstate == NULL); /* only happens if animating moves */
+
+    if (!ds->started) {
+        ds->started = TRUE;
+        draw_rect(dr, 0, 0, wpx, hpx, COL_BACKGROUND);
+        draw_update(dr, 0, 0, wpx, hpx);
+    }
+
+    for (i = r = 0; r < h; ++r) {
+        for (c = 0; c < w; ++c, ++i) {
+            drawcell cell = makecell(state->grid[i], errors[i], FALSE, flash);
+            if (r == ui->r && c == ui->c && ui->cursor_show)
+                cell.cursor = TRUE;
+            if (!cell_eq(cell, ds->grid[i])) {
+                draw_cell(dr, ds, r, c, cell);
+                ds->grid[i] = cell;
+            }
+        }
+    }
+
+    sfree(errors);
+}
+
+static void draw_cell(drawing *draw, game_drawstate *ds, int r, int c,
+                      drawcell cell)
+{
+    int const ts = ds->tilesize;
+    int const y = BORDER + ts * r, x = BORDER + ts * c;
+    int const tx = x + (ts / 2), ty = y + (ts / 2);
+    int const dotsz = (ds->tilesize + 9) / 10;
+
+    int const colour = (cell.value == BLACK ?
+                        cell.error ? COL_ERROR : COL_BLACK :
+                        cell.flash || cell.cursor ?
+                        COL_LOWLIGHT : COL_BACKGROUND);
+
+    draw_rect_outline(draw, x,     y,     ts + 1, ts + 1, COL_GRID);
+    draw_rect        (draw, x + 1, y + 1, ts - 1, ts - 1, colour);
+    if (cell.error)
+       draw_rect_outline(draw, x + 1, y + 1, ts - 1, ts - 1, COL_ERROR);
+
+    switch (cell.value) {
+      case WHITE: draw_rect(draw, tx - dotsz / 2, ty - dotsz / 2, dotsz, dotsz,
+                           cell.error ? COL_ERROR : COL_USER);
+      case BLACK: case EMPTY: break;
+      default:
+       {
+           int const colour = (cell.error ? COL_ERROR : COL_GRID);
+           char *msg = nfmtstr(10, "%d", cell.value);
+           draw_text(draw, tx, ty, FONT_VARIABLE, ts * 3 / 5,
+                     ALIGN_VCENTRE | ALIGN_HCENTRE, colour, msg);
+           sfree(msg);
+       }
+    }
+
+    draw_update(draw, x, y, ts + 1, ts + 1);
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    puts("warning: game_timing_state was called (this shouldn't happen)");
+    return FALSE; /* the (non-existing) timer should not be running */
+}
+
+/* ----------------------------------------------------------------------
+ * User interface: print
+ */
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+    int print_width, print_height;
+    game_compute_size(params, 800, &print_width, &print_height);
+    *x = print_width  / 100.0F;
+    *y = print_height / 100.0F;
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+    int const w = state->params.w, h = state->params.h;
+    game_drawstate ds_obj, *ds = &ds_obj;
+    int r, c, i, colour;
+
+    ds->tilesize = tilesize;
+
+    colour = print_mono_colour(dr, 1); assert(colour == COL_BACKGROUND);
+    colour = print_mono_colour(dr, 0); assert(colour == COL_GRID);
+    colour = print_mono_colour(dr, 1); assert(colour == COL_ERROR);
+    colour = print_mono_colour(dr, 0); assert(colour == COL_LOWLIGHT);
+    colour = print_mono_colour(dr, 0); assert(colour == NCOLOURS);
+
+    for (i = r = 0; r < h; ++r)
+        for (c = 0; c < w; ++c, ++i)
+            draw_cell(dr, ds, r, c,
+                      makecell(state->grid[i], FALSE, FALSE, FALSE));
+
+    print_line_width(dr, 3 * tilesize / 40);
+    draw_rect_outline(dr, BORDER, BORDER, w*TILESIZE, h*TILESIZE, COL_GRID);
+}
+
+/* And that's about it ;-) **************************************************/
+
+#ifdef COMBINED
+#define thegame range
+#endif
+
+struct game const thegame = {
+    "Range", "games.range", "range",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    TRUE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    TRUE, FALSE, game_print_size, game_print,
+    FALSE, /* wants_statusbar */
+    FALSE, game_timing_state,
+    0, /* flags */
+};
diff --git a/rect.R b/rect.R
new file mode 100644 (file)
index 0000000..1448c0f
--- /dev/null
+++ b/rect.R
@@ -0,0 +1,19 @@
+# -*- makefile -*-
+
+rect     : [X] GTK COMMON rect rect-icon|no-icon
+
+rect     : [G] WINDOWS COMMON rect rect.res|noicon.res
+
+ALL += rect[COMBINED]
+
+!begin am gtk
+GAMES += rect
+!end
+
+!begin >list.c
+    A(rect) \
+!end
+
+!begin >gamedesc.txt
+rect:rect.exe:Rectangles:Rectangles puzzle:Divide the grid into rectangles with areas equal to the numbers.
+!end
diff --git a/rect.c b/rect.c
new file mode 100644 (file)
index 0000000..2f603cb
--- /dev/null
+++ b/rect.c
@@ -0,0 +1,3000 @@
+/*
+ * rect.c: Puzzle from nikoli.co.jp. You have a square grid with
+ * numbers in some squares; you must divide the square grid up into
+ * variously sized rectangles, such that every rectangle contains
+ * exactly one numbered square and the area of each rectangle is
+ * equal to the number contained in it.
+ */
+
+/*
+ * TODO:
+ * 
+ *  - Improve singleton removal.
+ *     + It would be nice to limit the size of the generated
+ *       rectangles in accordance with existing constraints such as
+ *       the maximum rectangle size and the one about not
+ *       generating a rectangle the full width or height of the
+ *       grid.
+ *     + This could be achieved by making a less random choice
+ *       about which of the available options to use.
+ *     + Alternatively, we could create our rectangle and then
+ *       split it up.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+
+enum {
+    COL_BACKGROUND,
+    COL_CORRECT,
+    COL_LINE,
+    COL_TEXT,
+    COL_GRID,
+    COL_DRAG, COL_DRAGERASE,
+    COL_CURSOR,
+    NCOLOURS
+};
+
+struct game_params {
+    int w, h;
+    float expandfactor;
+    int unique;
+};
+
+#define INDEX(state, x, y)    (((y) * (state)->w) + (x))
+#define index(state, a, x, y) ((a) [ INDEX(state,x,y) ])
+#define grid(state,x,y)       index(state, (state)->grid, x, y)
+#define vedge(state,x,y)      index(state, (state)->vedge, x, y)
+#define hedge(state,x,y)      index(state, (state)->hedge, x, y)
+
+#define CRANGE(state,x,y,dx,dy) ( (x) >= dx && (x) < (state)->w && \
+                               (y) >= dy && (y) < (state)->h )
+#define RANGE(state,x,y)  CRANGE(state,x,y,0,0)
+#define HRANGE(state,x,y) CRANGE(state,x,y,0,1)
+#define VRANGE(state,x,y) CRANGE(state,x,y,1,0)
+
+#define PREFERRED_TILE_SIZE 24
+#define TILE_SIZE (ds->tilesize)
+#ifdef SMALL_SCREEN
+#define BORDER (2)
+#else
+#define BORDER (TILE_SIZE * 3 / 4)
+#endif
+
+#define CORNER_TOLERANCE 0.15F
+#define CENTRE_TOLERANCE 0.15F
+
+#define FLASH_TIME 0.13F
+
+#define COORD(x) ( (x) * TILE_SIZE + BORDER )
+#define FROMCOORD(x) ( ((x) - BORDER) / TILE_SIZE )
+
+struct game_state {
+    int w, h;
+    int *grid;                        /* contains the numbers */
+    unsigned char *vedge;             /* (w+1) x h */
+    unsigned char *hedge;             /* w x (h+1) */
+    int completed, cheated;
+    unsigned char *correct;
+};
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = ret->h = 7;
+    ret->expandfactor = 0.0F;
+    ret->unique = TRUE;
+
+    return ret;
+}
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params *ret;
+    int w, h;
+    char buf[80];
+
+    switch (i) {
+      case 0: w = 7, h = 7; break;
+      case 1: w = 9, h = 9; break;
+      case 2: w = 11, h = 11; break;
+      case 3: w = 13, h = 13; break;
+      case 4: w = 15, h = 15; break;
+#ifndef SMALL_SCREEN
+      case 5: w = 17, h = 17; break;
+      case 6: w = 19, h = 19; break;
+#endif
+      default: return FALSE;
+    }
+
+    sprintf(buf, "%dx%d", w, h);
+    *name = dupstr(buf);
+    *params = ret = snew(game_params);
+    ret->w = w;
+    ret->h = h;
+    ret->expandfactor = 0.0F;
+    ret->unique = TRUE;
+    return TRUE;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+static void decode_params(game_params *ret, char const *string)
+{
+    ret->w = ret->h = atoi(string);
+    while (*string && isdigit((unsigned char)*string)) string++;
+    if (*string == 'x') {
+        string++;
+        ret->h = atoi(string);
+       while (*string && isdigit((unsigned char)*string)) string++;
+    }
+    if (*string == 'e') {
+       string++;
+       ret->expandfactor = (float)atof(string);
+       while (*string &&
+              (*string == '.' || isdigit((unsigned char)*string))) string++;
+    }
+    if (*string == 'a') {
+       string++;
+       ret->unique = FALSE;
+    }
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char data[256];
+
+    sprintf(data, "%dx%d", params->w, params->h);
+    if (full && params->expandfactor)
+        sprintf(data + strlen(data), "e%g", params->expandfactor);
+    if (full && !params->unique)
+        strcat(data, "a");
+
+    return dupstr(data);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+    char buf[80];
+
+    ret = snewn(5, config_item);
+
+    ret[0].name = "Width";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->w);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Height";
+    ret[1].type = C_STRING;
+    sprintf(buf, "%d", params->h);
+    ret[1].sval = dupstr(buf);
+    ret[1].ival = 0;
+
+    ret[2].name = "Expansion factor";
+    ret[2].type = C_STRING;
+    sprintf(buf, "%g", params->expandfactor);
+    ret[2].sval = dupstr(buf);
+    ret[2].ival = 0;
+
+    ret[3].name = "Ensure unique solution";
+    ret[3].type = C_BOOLEAN;
+    ret[3].sval = NULL;
+    ret[3].ival = params->unique;
+
+    ret[4].name = NULL;
+    ret[4].type = C_END;
+    ret[4].sval = NULL;
+    ret[4].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = atoi(cfg[0].sval);
+    ret->h = atoi(cfg[1].sval);
+    ret->expandfactor = (float)atof(cfg[2].sval);
+    ret->unique = cfg[3].ival;
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    if (params->w <= 0 || params->h <= 0)
+       return "Width and height must both be greater than zero";
+    if (params->w*params->h < 2)
+       return "Grid area must be greater than one";
+    if (params->expandfactor < 0.0F)
+       return "Expansion factor may not be negative";
+    return NULL;
+}
+
+struct point {
+    int x, y;
+};
+
+struct rect {
+    int x, y;
+    int w, h;
+};
+
+struct rectlist {
+    struct rect *rects;
+    int n;
+};
+
+struct numberdata {
+    int area;
+    int npoints;
+    struct point *points;
+};
+
+/* ----------------------------------------------------------------------
+ * Solver for Rectangles games.
+ * 
+ * This solver is souped up beyond the needs of actually _solving_
+ * a puzzle. It is also designed to cope with uncertainty about
+ * where the numbers have been placed. This is because I run it on
+ * my generated grids _before_ placing the numbers, and have it
+ * tell me where I need to place the numbers to ensure a unique
+ * solution.
+ */
+
+static void remove_rect_placement(int w, int h,
+                                  struct rectlist *rectpositions,
+                                  int *overlaps,
+                                  int rectnum, int placement)
+{
+    int x, y, xx, yy;
+
+#ifdef SOLVER_DIAGNOSTICS
+    printf("ruling out rect %d placement at %d,%d w=%d h=%d\n", rectnum,
+           rectpositions[rectnum].rects[placement].x,
+           rectpositions[rectnum].rects[placement].y,
+           rectpositions[rectnum].rects[placement].w,
+           rectpositions[rectnum].rects[placement].h);
+#endif
+
+    /*
+     * Decrement each entry in the overlaps array to reflect the
+     * removal of this rectangle placement.
+     */
+    for (yy = 0; yy < rectpositions[rectnum].rects[placement].h; yy++) {
+        y = yy + rectpositions[rectnum].rects[placement].y;
+        for (xx = 0; xx < rectpositions[rectnum].rects[placement].w; xx++) {
+            x = xx + rectpositions[rectnum].rects[placement].x;
+
+            assert(overlaps[(rectnum * h + y) * w + x] != 0);
+
+            if (overlaps[(rectnum * h + y) * w + x] > 0)
+                overlaps[(rectnum * h + y) * w + x]--;
+        }
+    }
+
+    /*
+     * Remove the placement from the list of positions for that
+     * rectangle, by interchanging it with the one on the end.
+     */
+    if (placement < rectpositions[rectnum].n - 1) {
+        struct rect t;
+
+        t = rectpositions[rectnum].rects[rectpositions[rectnum].n - 1];
+        rectpositions[rectnum].rects[rectpositions[rectnum].n - 1] =
+            rectpositions[rectnum].rects[placement];
+        rectpositions[rectnum].rects[placement] = t;
+    }
+    rectpositions[rectnum].n--;
+}
+
+static void remove_number_placement(int w, int h, struct numberdata *number,
+                                    int index, int *rectbyplace)
+{
+    /*
+     * Remove the entry from the rectbyplace array.
+     */
+    rectbyplace[number->points[index].y * w + number->points[index].x] = -1;
+
+    /*
+     * Remove the placement from the list of candidates for that
+     * number, by interchanging it with the one on the end.
+     */
+    if (index < number->npoints - 1) {
+        struct point t;
+
+        t = number->points[number->npoints - 1];
+        number->points[number->npoints - 1] = number->points[index];
+        number->points[index] = t;
+    }
+    number->npoints--;
+}
+
+/*
+ * Returns 0 for failure to solve due to inconsistency; 1 for
+ * success; 2 for failure to complete a solution due to either
+ * ambiguity or it being too difficult.
+ */
+static int rect_solver(int w, int h, int nrects, struct numberdata *numbers,
+                       unsigned char *hedge, unsigned char *vedge,
+                      random_state *rs)
+{
+    struct rectlist *rectpositions;
+    int *overlaps, *rectbyplace, *workspace;
+    int i, ret;
+
+    /*
+     * Start by setting up a list of candidate positions for each
+     * rectangle.
+     */
+    rectpositions = snewn(nrects, struct rectlist);
+    for (i = 0; i < nrects; i++) {
+        int rw, rh, area = numbers[i].area;
+        int j, minx, miny, maxx, maxy;
+        struct rect *rlist;
+        int rlistn, rlistsize;
+
+        /*
+         * For each rectangle, begin by finding the bounding
+         * rectangle of its candidate number placements.
+         */
+        maxx = maxy = -1;
+        minx = w;
+        miny = h;
+        for (j = 0; j < numbers[i].npoints; j++) {
+            if (minx > numbers[i].points[j].x) minx = numbers[i].points[j].x;
+            if (miny > numbers[i].points[j].y) miny = numbers[i].points[j].y;
+            if (maxx < numbers[i].points[j].x) maxx = numbers[i].points[j].x;
+            if (maxy < numbers[i].points[j].y) maxy = numbers[i].points[j].y;
+        }
+
+        /*
+         * Now loop over all possible rectangle placements
+         * overlapping a point within that bounding rectangle;
+         * ensure each one actually contains a candidate number
+         * placement, and add it to the list.
+         */
+        rlist = NULL;
+        rlistn = rlistsize = 0;
+
+        for (rw = 1; rw <= area && rw <= w; rw++) {
+            int x, y;
+
+            if (area % rw)
+                continue;
+            rh = area / rw;
+            if (rh > h)
+                continue;
+
+            for (y = miny - rh + 1; y <= maxy; y++) {
+                if (y < 0 || y+rh > h)
+                    continue;
+
+                for (x = minx - rw + 1; x <= maxx; x++) {
+                    if (x < 0 || x+rw > w)
+                        continue;
+
+                    /*
+                     * See if we can find a candidate number
+                     * placement within this rectangle.
+                     */
+                    for (j = 0; j < numbers[i].npoints; j++)
+                        if (numbers[i].points[j].x >= x &&
+                            numbers[i].points[j].x < x+rw &&
+                            numbers[i].points[j].y >= y &&
+                            numbers[i].points[j].y < y+rh)
+                            break;
+
+                    if (j < numbers[i].npoints) {
+                        /*
+                         * Add this to the list of candidate
+                         * placements for this rectangle.
+                         */
+                        if (rlistn >= rlistsize) {
+                            rlistsize = rlistn + 32;
+                            rlist = sresize(rlist, rlistsize, struct rect);
+                        }
+                        rlist[rlistn].x = x;
+                        rlist[rlistn].y = y;
+                        rlist[rlistn].w = rw;
+                        rlist[rlistn].h = rh;
+#ifdef SOLVER_DIAGNOSTICS
+                        printf("rect %d [area %d]: candidate position at"
+                               " %d,%d w=%d h=%d\n",
+                               i, area, x, y, rw, rh);
+#endif
+                        rlistn++;
+                    }
+                }
+            }
+        }
+
+        rectpositions[i].rects = rlist;
+        rectpositions[i].n = rlistn;
+    }
+
+    /*
+     * Next, construct a multidimensional array tracking how many
+     * candidate positions for each rectangle overlap each square.
+     * 
+     * Indexing of this array is by the formula
+     * 
+     *   overlaps[(rectindex * h + y) * w + x]
+     * 
+     * A positive or zero value indicates what it sounds as if it
+     * should; -1 indicates that this square _cannot_ be part of
+     * this rectangle; and -2 indicates that it _definitely_ is
+     * (which is distinct from 1, because one might very well know
+     * that _if_ square S is part of rectangle R then it must be
+     * because R is placed in a certain position without knowing
+     * that it definitely _is_).
+     */
+    overlaps = snewn(nrects * w * h, int);
+    memset(overlaps, 0, nrects * w * h * sizeof(int));
+    for (i = 0; i < nrects; i++) {
+        int j;
+
+        for (j = 0; j < rectpositions[i].n; j++) {
+            int xx, yy;
+
+            for (yy = 0; yy < rectpositions[i].rects[j].h; yy++)
+                for (xx = 0; xx < rectpositions[i].rects[j].w; xx++)
+                    overlaps[(i * h + yy+rectpositions[i].rects[j].y) * w +
+                             xx+rectpositions[i].rects[j].x]++;
+        }
+    }
+
+    /*
+     * Also we want an array covering the grid once, to make it
+     * easy to figure out which squares are candidate number
+     * placements for which rectangles. (The existence of this
+     * single array assumes that no square starts off as a
+     * candidate number placement for more than one rectangle. This
+     * assumption is justified, because this solver is _either_
+     * used to solve real problems - in which case there is a
+     * single placement for every number - _or_ used to decide on
+     * number placements for a new puzzle, in which case each
+     * number's placements are confined to the intended position of
+     * the rectangle containing that number.)
+     */
+    rectbyplace = snewn(w * h, int);
+    for (i = 0; i < w*h; i++)
+        rectbyplace[i] = -1;
+
+    for (i = 0; i < nrects; i++) {
+        int j;
+
+        for (j = 0; j < numbers[i].npoints; j++) {
+            int x = numbers[i].points[j].x;
+            int y = numbers[i].points[j].y;
+
+            assert(rectbyplace[y * w + x] == -1);
+            rectbyplace[y * w + x] = i;
+        }
+    }
+
+    workspace = snewn(nrects, int);
+
+    /*
+     * Now run the actual deduction loop.
+     */
+    while (1) {
+        int done_something = FALSE;
+
+#ifdef SOLVER_DIAGNOSTICS
+        printf("starting deduction loop\n");
+
+        for (i = 0; i < nrects; i++) {
+            printf("rect %d overlaps:\n", i);
+            {
+                int x, y;
+                for (y = 0; y < h; y++) {
+                    for (x = 0; x < w; x++) {
+                        printf("%3d", overlaps[(i * h + y) * w + x]);
+                    }
+                    printf("\n");
+                }
+            }
+        }
+        printf("rectbyplace:\n");
+        {
+            int x, y;
+            for (y = 0; y < h; y++) {
+                for (x = 0; x < w; x++) {
+                    printf("%3d", rectbyplace[y * w + x]);
+                }
+                printf("\n");
+            }
+        }
+#endif
+
+        /*
+         * Housekeeping. Look for rectangles whose number has only
+         * one candidate position left, and mark that square as
+         * known if it isn't already.
+         */
+        for (i = 0; i < nrects; i++) {
+            if (numbers[i].npoints == 1) {
+                int x = numbers[i].points[0].x;
+                int y = numbers[i].points[0].y;
+                if (overlaps[(i * h + y) * w + x] >= -1) {
+                    int j;
+
+                    if (overlaps[(i * h + y) * w + x] <= 0) {
+                        ret = 0;       /* inconsistency */
+                        goto cleanup;
+                    }
+#ifdef SOLVER_DIAGNOSTICS
+                    printf("marking %d,%d as known for rect %d"
+                           " (sole remaining number position)\n", x, y, i);
+#endif
+
+                    for (j = 0; j < nrects; j++)
+                        overlaps[(j * h + y) * w + x] = -1;
+                    
+                    overlaps[(i * h + y) * w + x] = -2;
+                }
+            }
+        }
+
+        /*
+         * Now look at the intersection of all possible placements
+         * for each rectangle, and mark all squares in that
+         * intersection as known for that rectangle if they aren't
+         * already.
+         */
+        for (i = 0; i < nrects; i++) {
+            int minx, miny, maxx, maxy, xx, yy, j;
+
+            minx = miny = 0;
+            maxx = w;
+            maxy = h;
+
+            for (j = 0; j < rectpositions[i].n; j++) {
+                int x = rectpositions[i].rects[j].x;
+                int y = rectpositions[i].rects[j].y;
+                int w = rectpositions[i].rects[j].w;
+                int h = rectpositions[i].rects[j].h;
+
+                if (minx < x) minx = x;
+                if (miny < y) miny = y;
+                if (maxx > x+w) maxx = x+w;
+                if (maxy > y+h) maxy = y+h;
+            }
+
+            for (yy = miny; yy < maxy; yy++)
+                for (xx = minx; xx < maxx; xx++)
+                    if (overlaps[(i * h + yy) * w + xx] >= -1) {
+                        if (overlaps[(i * h + yy) * w + xx] <= 0) {
+                            ret = 0;   /* inconsistency */
+                            goto cleanup;
+                        }
+#ifdef SOLVER_DIAGNOSTICS
+                        printf("marking %d,%d as known for rect %d"
+                               " (intersection of all placements)\n",
+                               xx, yy, i);
+#endif
+
+                        for (j = 0; j < nrects; j++)
+                            overlaps[(j * h + yy) * w + xx] = -1;
+                    
+                        overlaps[(i * h + yy) * w + xx] = -2;
+                    }
+        }
+
+        /*
+         * Rectangle-focused deduction. Look at each rectangle in
+         * turn and try to rule out some of its candidate
+         * placements.
+         */
+        for (i = 0; i < nrects; i++) {
+            int j;
+
+            for (j = 0; j < rectpositions[i].n; j++) {
+                int xx, yy, k;
+                int del = FALSE;
+
+                for (k = 0; k < nrects; k++)
+                    workspace[k] = 0;
+
+                for (yy = 0; yy < rectpositions[i].rects[j].h; yy++) {
+                    int y = yy + rectpositions[i].rects[j].y;
+                    for (xx = 0; xx < rectpositions[i].rects[j].w; xx++) {
+                        int x = xx + rectpositions[i].rects[j].x;
+                        if (overlaps[(i * h + y) * w + x] == -1) {
+                            /*
+                             * This placement overlaps a square
+                             * which is _known_ to be part of
+                             * another rectangle. Therefore we must
+                             * rule it out.
+                             */
+#ifdef SOLVER_DIAGNOSTICS
+                            printf("rect %d placement at %d,%d w=%d h=%d "
+                                   "contains %d,%d which is known-other\n", i,
+                                   rectpositions[i].rects[j].x,
+                                   rectpositions[i].rects[j].y,
+                                   rectpositions[i].rects[j].w,
+                                   rectpositions[i].rects[j].h,
+                                   x, y);
+#endif
+                            del = TRUE;
+                        }
+
+                        if (rectbyplace[y * w + x] != -1) {
+                            /*
+                             * This placement overlaps one of the
+                             * candidate number placements for some
+                             * rectangle. Count it.
+                             */
+                            workspace[rectbyplace[y * w + x]]++;
+                        }
+                    }
+                }
+
+                if (!del) {
+                    /*
+                     * If we haven't ruled this placement out
+                     * already, see if it overlaps _all_ of the
+                     * candidate number placements for any
+                     * rectangle. If so, we can rule it out.
+                     */
+                    for (k = 0; k < nrects; k++)
+                        if (k != i && workspace[k] == numbers[k].npoints) {
+#ifdef SOLVER_DIAGNOSTICS
+                            printf("rect %d placement at %d,%d w=%d h=%d "
+                                   "contains all number points for rect %d\n",
+                                   i,
+                                   rectpositions[i].rects[j].x,
+                                   rectpositions[i].rects[j].y,
+                                   rectpositions[i].rects[j].w,
+                                   rectpositions[i].rects[j].h,
+                                   k);
+#endif
+                            del = TRUE;
+                            break;
+                        }
+
+                    /*
+                     * Failing that, see if it overlaps at least
+                     * one of the candidate number placements for
+                     * itself! (This might not be the case if one
+                     * of those number placements has been removed
+                     * recently.).
+                     */
+                    if (!del && workspace[i] == 0) {
+#ifdef SOLVER_DIAGNOSTICS
+                        printf("rect %d placement at %d,%d w=%d h=%d "
+                               "contains none of its own number points\n",
+                               i,
+                               rectpositions[i].rects[j].x,
+                               rectpositions[i].rects[j].y,
+                               rectpositions[i].rects[j].w,
+                               rectpositions[i].rects[j].h);
+#endif
+                        del = TRUE;
+                    }
+                }
+
+                if (del) {
+                    remove_rect_placement(w, h, rectpositions, overlaps, i, j);
+
+                    j--;               /* don't skip over next placement */
+
+                    done_something = TRUE;
+                }
+            }
+        }
+
+        /*
+         * Square-focused deduction. Look at each square not marked
+         * as known, and see if there are any which can only be
+         * part of a single rectangle.
+         */
+        {
+            int x, y, n, index;
+            for (y = 0; y < h; y++) for (x = 0; x < w; x++) {
+                /* Known squares are marked as <0 everywhere, so we only need
+                 * to check the overlaps entry for rect 0. */
+                if (overlaps[y * w + x] < 0)
+                    continue;          /* known already */
+
+                n = 0;
+                index = -1;
+                for (i = 0; i < nrects; i++)
+                    if (overlaps[(i * h + y) * w + x] > 0)
+                        n++, index = i;
+
+                if (n == 1) {
+                    int j;
+
+                    /*
+                     * Now we can rule out all placements for
+                     * rectangle `index' which _don't_ contain
+                     * square x,y.
+                     */
+#ifdef SOLVER_DIAGNOSTICS
+                    printf("square %d,%d can only be in rectangle %d\n",
+                           x, y, index);
+#endif
+                    for (j = 0; j < rectpositions[index].n; j++) {
+                        struct rect *r = &rectpositions[index].rects[j];
+                        if (x >= r->x && x < r->x + r->w &&
+                            y >= r->y && y < r->y + r->h)
+                            continue;  /* this one is OK */
+                        remove_rect_placement(w, h, rectpositions, overlaps,
+                                              index, j);
+                        j--;           /* don't skip over next placement */
+                        done_something = TRUE;
+                    }
+                }
+            }
+        }
+
+        /*
+         * If we've managed to deduce anything by normal means,
+         * loop round again and see if there's more to be done.
+         * Only if normal deduction has completely failed us should
+         * we now move on to narrowing down the possible number
+         * placements.
+         */
+        if (done_something)
+            continue;
+
+        /*
+         * Now we have done everything we can with the current set
+         * of number placements. So we need to winnow the number
+         * placements so as to narrow down the possibilities. We do
+         * this by searching for a candidate placement (of _any_
+         * rectangle) which overlaps a candidate placement of the
+         * number for some other rectangle.
+         */
+        if (rs) {
+            struct rpn {
+                int rect;
+                int placement;
+                int number;
+            } *rpns = NULL;
+            size_t nrpns = 0, rpnsize = 0;
+            int j;
+
+            for (i = 0; i < nrects; i++) {
+                for (j = 0; j < rectpositions[i].n; j++) {
+                    int xx, yy;
+
+                    for (yy = 0; yy < rectpositions[i].rects[j].h; yy++) {
+                        int y = yy + rectpositions[i].rects[j].y;
+                        for (xx = 0; xx < rectpositions[i].rects[j].w; xx++) {
+                            int x = xx + rectpositions[i].rects[j].x;
+
+                            if (rectbyplace[y * w + x] >= 0 &&
+                                rectbyplace[y * w + x] != i) {
+                                /*
+                                 * Add this to the list of
+                                 * winnowing possibilities.
+                                 */
+                                if (nrpns >= rpnsize) {
+                                    rpnsize = rpnsize * 3 / 2 + 32;
+                                    rpns = sresize(rpns, rpnsize, struct rpn);
+                                }
+                                rpns[nrpns].rect = i;
+                                rpns[nrpns].placement = j;
+                                rpns[nrpns].number = rectbyplace[y * w + x];
+                                nrpns++;
+                            }
+                        }
+                    }
+                }
+            }
+
+#ifdef SOLVER_DIAGNOSTICS
+            printf("%d candidate rect placements we could eliminate\n", nrpns);
+#endif
+            if (nrpns > 0) {
+                /*
+                 * Now choose one of these unwanted rectangle
+                 * placements, and eliminate it.
+                 */
+                int index = random_upto(rs, nrpns);
+                int k, m;
+                struct rpn rpn = rpns[index];
+                struct rect r;
+                sfree(rpns);
+
+                i = rpn.rect;
+                j = rpn.placement;
+                k = rpn.number;
+                r = rectpositions[i].rects[j];
+
+                /*
+                 * We rule out placement j of rectangle i by means
+                 * of removing all of rectangle k's candidate
+                 * number placements which do _not_ overlap it.
+                 * This will ensure that it is eliminated during
+                 * the next pass of rectangle-focused deduction.
+                 */
+#ifdef SOLVER_DIAGNOSTICS
+                printf("ensuring number for rect %d is within"
+                       " rect %d's placement at %d,%d w=%d h=%d\n",
+                       k, i, r.x, r.y, r.w, r.h);
+#endif
+
+                for (m = 0; m < numbers[k].npoints; m++) {
+                    int x = numbers[k].points[m].x;
+                    int y = numbers[k].points[m].y;
+
+                    if (x < r.x || x >= r.x + r.w ||
+                        y < r.y || y >= r.y + r.h) {
+#ifdef SOLVER_DIAGNOSTICS
+                        printf("eliminating number for rect %d at %d,%d\n",
+                               k, x, y);
+#endif
+                        remove_number_placement(w, h, &numbers[k],
+                                                m, rectbyplace);
+                        m--;           /* don't skip the next one */
+                        done_something = TRUE;
+                    }
+                }
+            }
+        }
+
+        if (!done_something) {
+#ifdef SOLVER_DIAGNOSTICS
+            printf("terminating deduction loop\n");
+#endif
+            break;
+        }
+    }
+
+    cleanup:
+    ret = 1;
+    for (i = 0; i < nrects; i++) {
+#ifdef SOLVER_DIAGNOSTICS
+        printf("rect %d has %d possible placements\n",
+               i, rectpositions[i].n);
+#endif
+        if (rectpositions[i].n <= 0) {
+            ret = 0;                   /* inconsistency */
+        } else if (rectpositions[i].n > 1) {
+            ret = 2;                   /* remaining uncertainty */
+        } else if (hedge && vedge) {
+            /*
+             * Place the rectangle in its only possible position.
+             */
+            int x, y;
+            struct rect *r = &rectpositions[i].rects[0];
+
+            for (y = 0; y < r->h; y++) {
+                if (r->x > 0)
+                   vedge[(r->y+y) * w + r->x] = 1;
+                if (r->x+r->w < w)
+                   vedge[(r->y+y) * w + r->x+r->w] = 1;
+            }
+            for (x = 0; x < r->w; x++) {
+                if (r->y > 0)
+                    hedge[r->y * w + r->x+x] = 1;
+                if (r->y+r->h < h)
+                    hedge[(r->y+r->h) * w + r->x+x] = 1;
+            }
+       }
+    }
+
+    /*
+     * Free up all allocated storage.
+     */
+    sfree(workspace);
+    sfree(rectbyplace);
+    sfree(overlaps);
+    for (i = 0; i < nrects; i++)
+        sfree(rectpositions[i].rects);
+    sfree(rectpositions);
+
+    return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * Grid generation code.
+ */
+
+/*
+ * This function does one of two things. If passed r==NULL, it
+ * counts the number of possible rectangles which cover the given
+ * square, and returns it in *n. If passed r!=NULL then it _reads_
+ * *n to find an index, counts the possible rectangles until it
+ * reaches the nth, and writes it into r.
+ * 
+ * `scratch' is expected to point to an array of 2 * params->w
+ * ints, used internally as scratch space (and passed in like this
+ * to avoid re-allocating and re-freeing it every time round a
+ * tight loop).
+ */
+static void enum_rects(game_params *params, int *grid, struct rect *r, int *n,
+                       int sx, int sy, int *scratch)
+{
+    int rw, rh, mw, mh;
+    int x, y, dx, dy;
+    int maxarea, realmaxarea;
+    int index = 0;
+    int *top, *bottom;
+
+    /*
+     * Maximum rectangle area is 1/6 of total grid size, unless
+     * this means we can't place any rectangles at all in which
+     * case we set it to 2 at minimum.
+     */
+    maxarea = params->w * params->h / 6;
+    if (maxarea < 2)
+        maxarea = 2;
+
+    /*
+     * Scan the grid to find the limits of the region within which
+     * any rectangle containing this point must fall. This will
+     * save us trawling the inside of every rectangle later on to
+     * see if it contains any used squares.
+     */
+    top = scratch;
+    bottom = scratch + params->w;
+    for (dy = -1; dy <= +1; dy += 2) {
+        int *array = (dy == -1 ? top : bottom);
+        for (dx = -1; dx <= +1; dx += 2) {
+            for (x = sx; x >= 0 && x < params->w; x += dx) {
+                array[x] = -2 * params->h * dy;
+                for (y = sy; y >= 0 && y < params->h; y += dy) {
+                    if (index(params, grid, x, y) == -1 &&
+                        (x == sx || dy*y <= dy*array[x-dx]))
+                        array[x] = y;
+                    else
+                        break;
+                }
+            }
+        }
+    }
+
+    /*
+     * Now scan again to work out the largest rectangles we can fit
+     * in the grid, so that we can terminate the following loops
+     * early once we get down to not having much space left in the
+     * grid.
+     */
+    realmaxarea = 0;
+    for (x = 0; x < params->w; x++) {
+        int x2;
+
+        rh = bottom[x] - top[x] + 1;
+        if (rh <= 0)
+            continue;                  /* no rectangles can start here */
+
+        dx = (x > sx ? -1 : +1);
+        for (x2 = x; x2 >= 0 && x2 < params->w; x2 += dx)
+            if (bottom[x2] < bottom[x] || top[x2] > top[x])
+                break;
+
+        rw = abs(x2 - x);
+        if (realmaxarea < rw * rh)
+            realmaxarea = rw * rh;
+    }
+
+    if (realmaxarea > maxarea)
+        realmaxarea = maxarea;
+
+    /*
+     * Rectangles which go right the way across the grid are
+     * boring, although they can't be helped in the case of
+     * extremely small grids. (Also they might be generated later
+     * on by the singleton-removal process; we can't help that.)
+     */
+    mw = params->w - 1;
+    if (mw < 3) mw++;
+    mh = params->h - 1;
+    if (mh < 3) mh++;
+
+    for (rw = 1; rw <= mw; rw++)
+        for (rh = 1; rh <= mh; rh++) {
+            if (rw * rh > realmaxarea)
+                continue;
+            if (rw * rh == 1)
+                continue;
+            for (x = max(sx - rw + 1, 0); x <= min(sx, params->w - rw); x++)
+                for (y = max(sy - rh + 1, 0); y <= min(sy, params->h - rh);
+                     y++) {
+                    /*
+                     * Check this rectangle against the region we
+                     * defined above.
+                     */
+                    if (top[x] <= y && top[x+rw-1] <= y &&
+                        bottom[x] >= y+rh-1 && bottom[x+rw-1] >= y+rh-1) {
+                        if (r && index == *n) {
+                            r->x = x;
+                            r->y = y;
+                            r->w = rw;
+                            r->h = rh;
+                            return;
+                        }
+                        index++;
+                    }
+                }
+        }
+
+    assert(!r);
+    *n = index;
+}
+
+static void place_rect(game_params *params, int *grid, struct rect r)
+{
+    int idx = INDEX(params, r.x, r.y);
+    int x, y;
+
+    for (x = r.x; x < r.x+r.w; x++)
+        for (y = r.y; y < r.y+r.h; y++) {
+            index(params, grid, x, y) = idx;
+        }
+#ifdef GENERATION_DIAGNOSTICS
+    printf("    placing rectangle at (%d,%d) size %d x %d\n",
+           r.x, r.y, r.w, r.h);
+#endif
+}
+
+static struct rect find_rect(game_params *params, int *grid, int x, int y)
+{
+    int idx, w, h;
+    struct rect r;
+
+    /*
+     * Find the top left of the rectangle.
+     */
+    idx = index(params, grid, x, y);
+
+    if (idx < 0) {
+        r.x = x;
+        r.y = y;
+        r.w = r.h = 1;
+        return r;                      /* 1x1 singleton here */
+    }
+
+    y = idx / params->w;
+    x = idx % params->w;
+
+    /*
+     * Find the width and height of the rectangle.
+     */
+    for (w = 1;
+         (x+w < params->w && index(params,grid,x+w,y)==idx);
+         w++);
+    for (h = 1;
+         (y+h < params->h && index(params,grid,x,y+h)==idx);
+         h++);
+
+    r.x = x;
+    r.y = y;
+    r.w = w;
+    r.h = h;
+
+    return r;
+}
+
+#ifdef GENERATION_DIAGNOSTICS
+static void display_grid(game_params *params, int *grid, int *numbers, int all)
+{
+    unsigned char *egrid = snewn((params->w*2+3) * (params->h*2+3),
+                                 unsigned char);
+    int x, y;
+    int r = (params->w*2+3);
+
+    memset(egrid, 0, (params->w*2+3) * (params->h*2+3));
+
+    for (x = 0; x < params->w; x++)
+        for (y = 0; y < params->h; y++) {
+            int i = index(params, grid, x, y);
+            if (x == 0 || index(params, grid, x-1, y) != i)
+                egrid[(2*y+2) * r + (2*x+1)] = 1;
+            if (x == params->w-1 || index(params, grid, x+1, y) != i)
+                egrid[(2*y+2) * r + (2*x+3)] = 1;
+            if (y == 0 || index(params, grid, x, y-1) != i)
+                egrid[(2*y+1) * r + (2*x+2)] = 1;
+            if (y == params->h-1 || index(params, grid, x, y+1) != i)
+                egrid[(2*y+3) * r + (2*x+2)] = 1;
+        }
+
+    for (y = 1; y < 2*params->h+2; y++) {
+        for (x = 1; x < 2*params->w+2; x++) {
+            if (!((y|x)&1)) {
+                int k = numbers ? index(params, numbers, x/2-1, y/2-1) : 0;
+                if (k || (all && numbers)) printf("%2d", k); else printf("  ");
+            } else if (!((y&x)&1)) {
+                int v = egrid[y*r+x];
+                if ((y&1) && v) v = '-';
+                if ((x&1) && v) v = '|';
+                if (!v) v = ' ';
+                putchar(v);
+                if (!(x&1)) putchar(v);
+            } else {
+                int c, d = 0;
+                if (egrid[y*r+(x+1)]) d |= 1;
+                if (egrid[(y-1)*r+x]) d |= 2;
+                if (egrid[y*r+(x-1)]) d |= 4;
+                if (egrid[(y+1)*r+x]) d |= 8;
+                c = " ??+?-++?+|+++++"[d];
+                putchar(c);
+                if (!(x&1)) putchar(c);
+            }
+        }
+        putchar('\n');
+    }
+
+    sfree(egrid);
+}
+#endif
+
+static char *new_game_desc(const game_params *params_in, random_state *rs,
+                          char **aux, int interactive)
+{
+    game_params params_copy = *params_in; /* structure copy */
+    game_params *params = &params_copy;
+    int *grid, *numbers = NULL;
+    int x, y, y2, y2last, yx, run, i, nsquares;
+    char *desc, *p;
+    int *enum_rects_scratch;
+    game_params params2real, *params2 = &params2real;
+
+    while (1) {
+        /*
+         * Set up the smaller width and height which we will use to
+         * generate the base grid.
+         */
+        params2->w = (int)((float)params->w / (1.0F + params->expandfactor));
+        if (params2->w < 2 && params->w >= 2) params2->w = 2;
+        params2->h = (int)((float)params->h / (1.0F + params->expandfactor));
+        if (params2->h < 2 && params->h >= 2) params2->h = 2;
+
+        grid = snewn(params2->w * params2->h, int);
+
+        enum_rects_scratch = snewn(2 * params2->w, int);
+
+        nsquares = 0;
+        for (y = 0; y < params2->h; y++)
+            for (x = 0; x < params2->w; x++) {
+                index(params2, grid, x, y) = -1;
+                nsquares++;
+            }
+
+        /*
+         * Place rectangles until we can't any more. We do this by
+         * finding a square we haven't yet covered, and randomly
+         * choosing a rectangle to cover it.
+         */
+        
+        while (nsquares > 0) {
+            int square = random_upto(rs, nsquares);
+            int n;
+            struct rect r;
+
+            x = params2->w;
+            y = params2->h;
+            for (y = 0; y < params2->h; y++) {
+                for (x = 0; x < params2->w; x++) {
+                    if (index(params2, grid, x, y) == -1 && square-- == 0)
+                        break;
+                }
+                if (x < params2->w)
+                    break;
+            }
+            assert(x < params2->w && y < params2->h);
+
+            /*
+             * Now see how many rectangles fit around this one.
+             */
+            enum_rects(params2, grid, NULL, &n, x, y, enum_rects_scratch);
+
+            if (!n) {
+                /*
+                 * There are no possible rectangles covering this
+                 * square, meaning it must be a singleton. Mark it
+                 * -2 so we know not to keep trying.
+                 */
+                index(params2, grid, x, y) = -2;
+                nsquares--;
+            } else {
+                /*
+                 * Pick one at random.
+                 */
+                n = random_upto(rs, n);
+                enum_rects(params2, grid, &r, &n, x, y, enum_rects_scratch);
+
+                /*
+                 * Place it.
+                 */
+                place_rect(params2, grid, r);
+                nsquares -= r.w * r.h;
+            }
+        }
+
+        sfree(enum_rects_scratch);
+
+        /*
+         * Deal with singleton spaces remaining in the grid, one by
+         * one.
+         *
+         * We do this by making a local change to the layout. There are
+         * several possibilities:
+         *
+         *     +-----+-----+    Here, we can remove the singleton by
+         *     |     |     |    extending the 1x2 rectangle below it
+         *     +--+--+-----+    into a 1x3.
+         *     |  |  |     |
+         *     |  +--+     |
+         *     |  |  |     |
+         *     |  |  |     |
+         *     |  |  |     |
+         *     +--+--+-----+
+         *
+         *     +--+--+--+       Here, that trick doesn't work: there's no
+         *     |     |  |       1 x n rectangle with the singleton at one
+         *     |     |  |       end. Instead, we extend a 1 x n rectangle
+         *     |     |  |       _out_ from the singleton, shaving a layer
+         *     +--+--+  |       off the end of another rectangle. So if we
+         *     |  |  |  |       extended up, we'd make our singleton part
+         *     |  +--+--+       of a 1x3 and generate a 1x2 where the 2x2
+         *     |  |     |       used to be; or we could extend right into
+         *     +--+-----+       a 2x1, turning the 1x3 into a 1x2.
+         *
+         *     +-----+--+       Here, we can't even do _that_, since any
+         *     |     |  |       direction we choose to extend the singleton
+         *     +--+--+  |       will produce a new singleton as a result of
+         *     |  |  |  |       truncating one of the size-2 rectangles.
+         *     |  +--+--+       Fortunately, this case can _only_ occur when
+         *     |  |     |       a singleton is surrounded by four size-2s
+         *     +--+-----+       in this fashion; so instead we can simply
+         *                      replace the whole section with a single 3x3.
+         */
+        for (x = 0; x < params2->w; x++) {
+            for (y = 0; y < params2->h; y++) {
+                if (index(params2, grid, x, y) < 0) {
+                    int dirs[4], ndirs;
+
+#ifdef GENERATION_DIAGNOSTICS
+                    display_grid(params2, grid, NULL, FALSE);
+                    printf("singleton at %d,%d\n", x, y);
+#endif
+
+                    /*
+                     * Check in which directions we can feasibly extend
+                     * the singleton. We can extend in a particular
+                     * direction iff either:
+                     *
+                     *  - the rectangle on that side of the singleton
+                     *    is not 2x1, and we are at one end of the edge
+                     *    of it we are touching
+                     *
+                     *  - it is 2x1 but we are on its short side.
+                     *
+                     * FIXME: we could plausibly choose between these
+                     * based on the sizes of the rectangles they would
+                     * create?
+                     */
+                    ndirs = 0;
+                    if (x < params2->w-1) {
+                        struct rect r = find_rect(params2, grid, x+1, y);
+                        if ((r.w * r.h > 2 && (r.y==y || r.y+r.h-1==y)) || r.h==1)
+                            dirs[ndirs++] = 1;   /* right */
+                    }
+                    if (y > 0) {
+                        struct rect r = find_rect(params2, grid, x, y-1);
+                        if ((r.w * r.h > 2 && (r.x==x || r.x+r.w-1==x)) || r.w==1)
+                            dirs[ndirs++] = 2;   /* up */
+                    }
+                    if (x > 0) {
+                        struct rect r = find_rect(params2, grid, x-1, y);
+                        if ((r.w * r.h > 2 && (r.y==y || r.y+r.h-1==y)) || r.h==1)
+                            dirs[ndirs++] = 4;   /* left */
+                    }
+                    if (y < params2->h-1) {
+                        struct rect r = find_rect(params2, grid, x, y+1);
+                        if ((r.w * r.h > 2 && (r.x==x || r.x+r.w-1==x)) || r.w==1)
+                            dirs[ndirs++] = 8;   /* down */
+                    }
+
+                    if (ndirs > 0) {
+                        int which, dir;
+                        struct rect r1, r2;
+                        memset(&r1, 0, sizeof(struct rect));
+                        memset(&r2, 0, sizeof(struct rect));
+                        which = random_upto(rs, ndirs);
+                        dir = dirs[which];
+
+                        switch (dir) {
+                          case 1:          /* right */
+                            assert(x < params2->w+1);
+#ifdef GENERATION_DIAGNOSTICS
+                            printf("extending right\n");
+#endif
+                            r1 = find_rect(params2, grid, x+1, y);
+                            r2.x = x;
+                            r2.y = y;
+                            r2.w = 1 + r1.w;
+                            r2.h = 1;
+                            if (r1.y == y)
+                                r1.y++;
+                            r1.h--;
+                            break;
+                          case 2:          /* up */
+                            assert(y > 0);
+#ifdef GENERATION_DIAGNOSTICS
+                            printf("extending up\n");
+#endif
+                            r1 = find_rect(params2, grid, x, y-1);
+                            r2.x = x;
+                            r2.y = r1.y;
+                            r2.w = 1;
+                            r2.h = 1 + r1.h;
+                            if (r1.x == x)
+                                r1.x++;
+                            r1.w--;
+                            break;
+                          case 4:          /* left */
+                            assert(x > 0);
+#ifdef GENERATION_DIAGNOSTICS
+                            printf("extending left\n");
+#endif
+                            r1 = find_rect(params2, grid, x-1, y);
+                            r2.x = r1.x;
+                            r2.y = y;
+                            r2.w = 1 + r1.w;
+                            r2.h = 1;
+                            if (r1.y == y)
+                                r1.y++;
+                            r1.h--;
+                            break;
+                          case 8:          /* down */
+                            assert(y < params2->h+1);
+#ifdef GENERATION_DIAGNOSTICS
+                            printf("extending down\n");
+#endif
+                            r1 = find_rect(params2, grid, x, y+1);
+                            r2.x = x;
+                            r2.y = y;
+                            r2.w = 1;
+                            r2.h = 1 + r1.h;
+                            if (r1.x == x)
+                                r1.x++;
+                            r1.w--;
+                            break;
+                          default:     /* should never happen */
+                            assert(!"invalid direction");
+                        }
+                        if (r1.h > 0 && r1.w > 0)
+                            place_rect(params2, grid, r1);
+                        place_rect(params2, grid, r2);
+                    } else {
+#ifndef NDEBUG
+                        /*
+                         * Sanity-check that there really is a 3x3
+                         * rectangle surrounding this singleton and it
+                         * contains absolutely everything we could
+                         * possibly need.
+                         */
+                        {
+                            int xx, yy;
+                            assert(x > 0 && x < params2->w-1);
+                            assert(y > 0 && y < params2->h-1);
+
+                            for (xx = x-1; xx <= x+1; xx++)
+                                for (yy = y-1; yy <= y+1; yy++) {
+                                    struct rect r = find_rect(params2,grid,xx,yy);
+                                    assert(r.x >= x-1);
+                                    assert(r.y >= y-1);
+                                    assert(r.x+r.w-1 <= x+1);
+                                    assert(r.y+r.h-1 <= y+1);
+                                }
+                        }
+#endif
+
+#ifdef GENERATION_DIAGNOSTICS
+                        printf("need the 3x3 trick\n");
+#endif
+
+                        /*
+                         * FIXME: If the maximum rectangle area for
+                         * this grid is less than 9, we ought to
+                         * subdivide the 3x3 in some fashion. There are
+                         * five other possibilities:
+                         *
+                         *  - a 6 and a 3
+                         *  - a 4, a 3 and a 2
+                         *  - three 3s
+                         *  - a 3 and three 2s (two different arrangements).
+                         */
+
+                        {
+                            struct rect r;
+                            r.x = x-1;
+                            r.y = y-1;
+                            r.w = r.h = 3;
+                            place_rect(params2, grid, r);
+                        }
+                    }
+                }
+            }
+        }
+
+        /*
+         * We have now constructed a grid of the size specified in
+         * params2. Now we extend it into a grid of the size specified
+         * in params. We do this in two passes: we extend it vertically
+         * until it's the right height, then we transpose it, then
+         * extend it vertically again (getting it effectively the right
+         * width), then finally transpose again.
+         */
+        for (i = 0; i < 2; i++) {
+            int *grid2, *expand, *where;
+            game_params params3real, *params3 = &params3real;
+
+#ifdef GENERATION_DIAGNOSTICS
+            printf("before expansion:\n");
+            display_grid(params2, grid, NULL, TRUE);
+#endif
+
+            /*
+             * Set up the new grid.
+             */
+            grid2 = snewn(params2->w * params->h, int);
+            expand = snewn(params2->h-1, int);
+            where = snewn(params2->w, int);
+            params3->w = params2->w;
+            params3->h = params->h;
+
+            /*
+             * Decide which horizontal edges are going to get expanded,
+             * and by how much.
+             */
+            for (y = 0; y < params2->h-1; y++)
+                expand[y] = 0;
+            for (y = params2->h; y < params->h; y++) {
+                x = random_upto(rs, params2->h-1);
+                expand[x]++;
+            }
+
+#ifdef GENERATION_DIAGNOSTICS
+            printf("expand[] = {");
+            for (y = 0; y < params2->h-1; y++)
+                printf(" %d", expand[y]);
+            printf(" }\n");
+#endif
+
+            /*
+             * Perform the expansion. The way this works is that we
+             * alternately:
+             *
+             *  - copy a row from grid into grid2
+             *
+             *  - invent some number of additional rows in grid2 where
+             *    there was previously only a horizontal line between
+             *    rows in grid, and make random decisions about where
+             *    among these to place each rectangle edge that ran
+             *    along this line.
+             */
+            for (y = y2 = y2last = 0; y < params2->h; y++) {
+                /*
+                 * Copy a single line from row y of grid into row y2 of
+                 * grid2.
+                 */
+                for (x = 0; x < params2->w; x++) {
+                    int val = index(params2, grid, x, y);
+                    if (val / params2->w == y &&   /* rect starts on this line */
+                        (y2 == 0 ||           /* we're at the very top, or... */
+                         index(params3, grid2, x, y2-1) / params3->w < y2last
+                         /* this rect isn't already started */))
+                        index(params3, grid2, x, y2) =
+                        INDEX(params3, val % params2->w, y2);
+                    else
+                        index(params3, grid2, x, y2) =
+                        index(params3, grid2, x, y2-1);
+                }
+
+                /*
+                 * If that was the last line, terminate the loop early.
+                 */
+                if (++y2 == params3->h)
+                    break;
+
+                y2last = y2;
+
+                /*
+                 * Invent some number of additional lines. First walk
+                 * along this line working out where to put all the
+                 * edges that coincide with it.
+                 */
+                yx = -1;
+                for (x = 0; x < params2->w; x++) {
+                    if (index(params2, grid, x, y) !=
+                        index(params2, grid, x, y+1)) {
+                        /*
+                         * This is a horizontal edge, so it needs
+                         * placing.
+                         */
+                        if (x == 0 ||
+                            (index(params2, grid, x-1, y) !=
+                             index(params2, grid, x, y) &&
+                             index(params2, grid, x-1, y+1) !=
+                             index(params2, grid, x, y+1))) {
+                            /*
+                             * Here we have the chance to make a new
+                             * decision.
+                             */
+                            yx = random_upto(rs, expand[y]+1);
+                        } else {
+                            /*
+                             * Here we just reuse the previous value of
+                             * yx.
+                             */
+                        }
+                    } else
+                        yx = -1;
+                    where[x] = yx;
+                }
+
+                for (yx = 0; yx < expand[y]; yx++) {
+                    /*
+                     * Invent a single row. For each square in the row,
+                     * we copy the grid entry from the square above it,
+                     * unless we're starting the new rectangle here.
+                     */
+                    for (x = 0; x < params2->w; x++) {
+                        if (yx == where[x]) {
+                            int val = index(params2, grid, x, y+1);
+                            val %= params2->w;
+                            val = INDEX(params3, val, y2);
+                            index(params3, grid2, x, y2) = val;
+                        } else
+                            index(params3, grid2, x, y2) =
+                            index(params3, grid2, x, y2-1);
+                    }
+
+                    y2++;
+                }
+            }
+
+            sfree(expand);
+            sfree(where);
+
+#ifdef GENERATION_DIAGNOSTICS
+            printf("after expansion:\n");
+            display_grid(params3, grid2, NULL, TRUE);
+#endif
+            /*
+             * Transpose.
+             */
+            params2->w = params3->h;
+            params2->h = params3->w;
+            sfree(grid);
+            grid = snewn(params2->w * params2->h, int);
+            for (x = 0; x < params2->w; x++)
+                for (y = 0; y < params2->h; y++) {
+                    int idx1 = INDEX(params2, x, y);
+                    int idx2 = INDEX(params3, y, x);
+                    int tmp;
+
+                    tmp = grid2[idx2];
+                    tmp = (tmp % params3->w) * params2->w + (tmp / params3->w);
+                    grid[idx1] = tmp;
+                }
+
+            sfree(grid2);
+
+            {
+                int tmp;
+                tmp = params->w;
+                params->w = params->h;
+                params->h = tmp;
+            }
+
+#ifdef GENERATION_DIAGNOSTICS
+            printf("after transposition:\n");
+            display_grid(params2, grid, NULL, TRUE);
+#endif
+        }
+
+        /*
+         * Run the solver to narrow down the possible number
+         * placements.
+         */
+        {
+            struct numberdata *nd;
+            int nnumbers, i, ret;
+
+            /* Count the rectangles. */
+            nnumbers = 0;
+            for (y = 0; y < params->h; y++) {
+                for (x = 0; x < params->w; x++) {
+                    int idx = INDEX(params, x, y);
+                    if (index(params, grid, x, y) == idx)
+                        nnumbers++;
+                }
+            }
+
+            nd = snewn(nnumbers, struct numberdata);
+
+            /* Now set up each number's candidate position list. */
+            i = 0;
+            for (y = 0; y < params->h; y++) {
+                for (x = 0; x < params->w; x++) {
+                    int idx = INDEX(params, x, y);
+                    if (index(params, grid, x, y) == idx) {
+                        struct rect r = find_rect(params, grid, x, y);
+                        int j, k, m;
+
+                        nd[i].area = r.w * r.h;
+                        nd[i].npoints = nd[i].area;
+                        nd[i].points = snewn(nd[i].npoints, struct point);
+                        m = 0;
+                        for (j = 0; j < r.h; j++)
+                            for (k = 0; k < r.w; k++) {
+                                nd[i].points[m].x = k + r.x;
+                                nd[i].points[m].y = j + r.y;
+                                m++;
+                            }
+                        assert(m == nd[i].npoints);
+
+                        i++;
+                    }
+                }
+            }
+
+           if (params->unique)
+               ret = rect_solver(params->w, params->h, nnumbers, nd,
+                                 NULL, NULL, rs);
+           else
+               ret = 1;               /* allow any number placement at all */
+
+            if (ret == 1) {
+                /*
+                 * Now place the numbers according to the solver's
+                 * recommendations.
+                 */
+                numbers = snewn(params->w * params->h, int);
+
+                for (y = 0; y < params->h; y++)
+                    for (x = 0; x < params->w; x++) {
+                        index(params, numbers, x, y) = 0;
+                    }
+
+                for (i = 0; i < nnumbers; i++) {
+                    int idx = random_upto(rs, nd[i].npoints);
+                    int x = nd[i].points[idx].x;
+                    int y = nd[i].points[idx].y;
+                    index(params,numbers,x,y) = nd[i].area;
+                }
+            }
+
+            /*
+             * Clean up.
+             */
+            for (i = 0; i < nnumbers; i++)
+                sfree(nd[i].points);
+            sfree(nd);
+
+            /*
+             * If we've succeeded, then terminate the loop.
+             */
+            if (ret == 1)
+                break;
+        }
+
+        /*
+         * Give up and go round again.
+         */
+        sfree(grid);
+    }
+
+    /*
+     * Store the solution in aux.
+     */
+    {
+        char *ai;
+        int len;
+
+        len = 2 + (params->w-1)*params->h + (params->h-1)*params->w;
+        ai = snewn(len, char);
+
+        ai[0] = 'S';
+
+        p = ai+1;
+
+        for (y = 0; y < params->h; y++)
+            for (x = 1; x < params->w; x++)
+                *p++ = (index(params, grid, x, y) !=
+                        index(params, grid, x-1, y) ? '1' : '0');
+
+        for (y = 1; y < params->h; y++)
+            for (x = 0; x < params->w; x++)
+                *p++ = (index(params, grid, x, y) !=
+                        index(params, grid, x, y-1) ? '1' : '0');
+
+        assert(p - ai == len-1);
+        *p = '\0';
+
+        *aux = ai;
+    }
+
+#ifdef GENERATION_DIAGNOSTICS
+    display_grid(params, grid, numbers, FALSE);
+#endif
+
+    desc = snewn(11 * params->w * params->h, char);
+    p = desc;
+    run = 0;
+    for (i = 0; i <= params->w * params->h; i++) {
+        int n = (i < params->w * params->h ? numbers[i] : -1);
+
+        if (!n)
+            run++;
+        else {
+            if (run) {
+                while (run > 0) {
+                    int c = 'a' - 1 + run;
+                    if (run > 26)
+                        c = 'z';
+                    *p++ = c;
+                    run -= c - ('a' - 1);
+                }
+            } else {
+                /*
+                 * If there's a number in the very top left or
+                 * bottom right, there's no point putting an
+                 * unnecessary _ before or after it.
+                 */
+                if (p > desc && n > 0)
+                    *p++ = '_';
+            }
+            if (n > 0)
+                p += sprintf(p, "%d", n);
+            run = 0;
+        }
+    }
+    *p = '\0';
+
+    sfree(grid);
+    sfree(numbers);
+
+    return desc;
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    int area = params->w * params->h;
+    int squares = 0;
+
+    while (*desc) {
+        int n = *desc++;
+        if (n >= 'a' && n <= 'z') {
+            squares += n - 'a' + 1;
+        } else if (n == '_') {
+            /* do nothing */;
+        } else if (n > '0' && n <= '9') {
+            squares++;
+            while (*desc >= '0' && *desc <= '9')
+                desc++;
+        } else
+            return "Invalid character in game description";
+    }
+
+    if (squares < area)
+        return "Not enough data to fill grid";
+
+    if (squares > area)
+        return "Too much data to fit in grid";
+
+    return NULL;
+}
+
+static unsigned char *get_correct(game_state *state)
+{
+    unsigned char *ret;
+    int x, y;
+
+    ret = snewn(state->w * state->h, unsigned char);
+    memset(ret, 0xFF, state->w * state->h);
+
+    for (x = 0; x < state->w; x++)
+       for (y = 0; y < state->h; y++)
+           if (index(state,ret,x,y) == 0xFF) {
+               int rw, rh;
+               int xx, yy;
+               int num, area, valid;
+
+               /*
+                * Find a rectangle starting at this point.
+                */
+               rw = 1;
+               while (x+rw < state->w && !vedge(state,x+rw,y))
+                   rw++;
+               rh = 1;
+               while (y+rh < state->h && !hedge(state,x,y+rh))
+                   rh++;
+
+               /*
+                * We know what the dimensions of the rectangle
+                * should be if it's there at all. Find out if we
+                * really have a valid rectangle.
+                */
+               valid = TRUE;
+               /* Check the horizontal edges. */
+               for (xx = x; xx < x+rw; xx++) {
+                   for (yy = y; yy <= y+rh; yy++) {
+                       int e = !HRANGE(state,xx,yy) || hedge(state,xx,yy);
+                       int ec = (yy == y || yy == y+rh);
+                       if (e != ec)
+                           valid = FALSE;
+                   }
+               }
+               /* Check the vertical edges. */
+               for (yy = y; yy < y+rh; yy++) {
+                   for (xx = x; xx <= x+rw; xx++) {
+                       int e = !VRANGE(state,xx,yy) || vedge(state,xx,yy);
+                       int ec = (xx == x || xx == x+rw);
+                       if (e != ec)
+                           valid = FALSE;
+                   }
+               }
+
+               /*
+                * If this is not a valid rectangle with no other
+                * edges inside it, we just mark this square as not
+                * complete and proceed to the next square.
+                */
+               if (!valid) {
+                   index(state, ret, x, y) = 0;
+                   continue;
+               }
+
+               /*
+                * We have a rectangle. Now see what its area is,
+                * and how many numbers are in it.
+                */
+               num = 0;
+               area = 0;
+               for (xx = x; xx < x+rw; xx++) {
+                   for (yy = y; yy < y+rh; yy++) {
+                       area++;
+                       if (grid(state,xx,yy)) {
+                           if (num > 0)
+                               valid = FALSE;   /* two numbers */
+                           num = grid(state,xx,yy);
+                       }
+                   }
+               }
+               if (num != area)
+                   valid = FALSE;
+
+               /*
+                * Now fill in the whole rectangle based on the
+                * value of `valid'.
+                */
+               for (xx = x; xx < x+rw; xx++) {
+                   for (yy = y; yy < y+rh; yy++) {
+                       index(state, ret, xx, yy) = valid;
+                   }
+               }
+           }
+
+    return ret;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    game_state *state = snew(game_state);
+    int x, y, i, area;
+
+    state->w = params->w;
+    state->h = params->h;
+
+    area = state->w * state->h;
+
+    state->grid = snewn(area, int);
+    state->vedge = snewn(area, unsigned char);
+    state->hedge = snewn(area, unsigned char);
+    state->completed = state->cheated = FALSE;
+
+    i = 0;
+    while (*desc) {
+        int n = *desc++;
+        if (n >= 'a' && n <= 'z') {
+            int run = n - 'a' + 1;
+            assert(i + run <= area);
+            while (run-- > 0)
+                state->grid[i++] = 0;
+        } else if (n == '_') {
+            /* do nothing */;
+        } else if (n > '0' && n <= '9') {
+            assert(i < area);
+            state->grid[i++] = atoi(desc-1);
+            while (*desc >= '0' && *desc <= '9')
+                desc++;
+        } else {
+            assert(!"We can't get here");
+        }
+    }
+    assert(i == area);
+
+    for (y = 0; y < state->h; y++)
+       for (x = 0; x < state->w; x++)
+           vedge(state,x,y) = hedge(state,x,y) = 0;
+
+    state->correct = get_correct(state);
+
+    return state;
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    game_state *ret = snew(game_state);
+
+    ret->w = state->w;
+    ret->h = state->h;
+
+    ret->vedge = snewn(state->w * state->h, unsigned char);
+    ret->hedge = snewn(state->w * state->h, unsigned char);
+    ret->grid = snewn(state->w * state->h, int);
+    ret->correct = snewn(ret->w * ret->h, unsigned char);
+
+    ret->completed = state->completed;
+    ret->cheated = state->cheated;
+
+    memcpy(ret->grid, state->grid, state->w * state->h * sizeof(int));
+    memcpy(ret->vedge, state->vedge, state->w*state->h*sizeof(unsigned char));
+    memcpy(ret->hedge, state->hedge, state->w*state->h*sizeof(unsigned char));
+
+    memcpy(ret->correct, state->correct, state->w*state->h*sizeof(unsigned char));
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    sfree(state->grid);
+    sfree(state->vedge);
+    sfree(state->hedge);
+    sfree(state->correct);
+    sfree(state);
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *ai, char **error)
+{
+    unsigned char *vedge, *hedge;
+    int x, y, len;
+    char *ret, *p;
+    int i, j, n;
+    struct numberdata *nd;
+
+    if (ai)
+        return dupstr(ai);
+
+    /*
+     * Attempt the in-built solver.
+     */
+
+    /* Set up each number's (very short) candidate position list. */
+    for (i = n = 0; i < state->h * state->w; i++)
+        if (state->grid[i])
+            n++;
+
+    nd = snewn(n, struct numberdata);
+
+    for (i = j = 0; i < state->h * state->w; i++)
+        if (state->grid[i]) {
+            nd[j].area = state->grid[i];
+            nd[j].npoints = 1;
+            nd[j].points = snewn(1, struct point);
+            nd[j].points[0].x = i % state->w;
+            nd[j].points[0].y = i / state->w;
+            j++;
+        }
+
+    assert(j == n);
+
+    vedge = snewn(state->w * state->h, unsigned char);
+    hedge = snewn(state->w * state->h, unsigned char);
+    memset(vedge, 0, state->w * state->h);
+    memset(hedge, 0, state->w * state->h);
+
+    rect_solver(state->w, state->h, n, nd, hedge, vedge, NULL);
+
+    /*
+     * Clean up.
+     */
+    for (i = 0; i < n; i++)
+        sfree(nd[i].points);
+    sfree(nd);
+
+    len = 2 + (state->w-1)*state->h + (state->h-1)*state->w;
+    ret = snewn(len, char);
+
+    p = ret;
+    *p++ = 'S';
+    for (y = 0; y < state->h; y++)
+        for (x = 1; x < state->w; x++)
+            *p++ = vedge[y*state->w+x] ? '1' : '0';
+    for (y = 1; y < state->h; y++)
+       for (x = 0; x < state->w; x++)
+           *p++ = hedge[y*state->w+x] ? '1' : '0';
+    *p++ = '\0';
+    assert(p - ret == len);
+
+    sfree(vedge);
+    sfree(hedge);
+
+    return ret;
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    char *ret, *p, buf[80];
+    int i, x, y, col, maxlen;
+
+    /*
+     * First determine the number of spaces required to display a
+     * number. We'll use at least two, because one looks a bit
+     * silly.
+     */
+    col = 2;
+    for (i = 0; i < state->w * state->h; i++) {
+       x = sprintf(buf, "%d", state->grid[i]);
+       if (col < x) col = x;
+    }
+
+    /*
+     * Now we know the exact total size of the grid we're going to
+     * produce: it's got 2*h+1 rows, each containing w lots of col,
+     * w+1 boundary characters and a trailing newline.
+     */
+    maxlen = (2*state->h+1) * (state->w * (col+1) + 2);
+
+    ret = snewn(maxlen+1, char);
+    p = ret;
+
+    for (y = 0; y <= 2*state->h; y++) {
+       for (x = 0; x <= 2*state->w; x++) {
+           if (x & y & 1) {
+               /*
+                * Display a number.
+                */
+               int v = grid(state, x/2, y/2);
+               if (v)
+                   sprintf(buf, "%*d", col, v);
+               else
+                   sprintf(buf, "%*s", col, "");
+               memcpy(p, buf, col);
+               p += col;
+           } else if (x & 1) {
+               /*
+                * Display a horizontal edge or nothing.
+                */
+               int h = (y==0 || y==2*state->h ? 1 :
+                        HRANGE(state, x/2, y/2) && hedge(state, x/2, y/2));
+               int i;
+               if (h)
+                   h = '-';
+               else
+                   h = ' ';
+               for (i = 0; i < col; i++)
+                   *p++ = h;
+           } else if (y & 1) {
+               /*
+                * Display a vertical edge or nothing.
+                */
+               int v = (x==0 || x==2*state->w ? 1 :
+                        VRANGE(state, x/2, y/2) && vedge(state, x/2, y/2));
+               if (v)
+                   *p++ = '|';
+               else
+                   *p++ = ' ';
+           } else {
+               /*
+                * Display a corner, or a vertical edge, or a
+                * horizontal edge, or nothing.
+                */
+               int hl = (y==0 || y==2*state->h ? 1 :
+                         HRANGE(state, (x-1)/2, y/2) && hedge(state, (x-1)/2, y/2));
+               int hr = (y==0 || y==2*state->h ? 1 :
+                         HRANGE(state, (x+1)/2, y/2) && hedge(state, (x+1)/2, y/2));
+               int vu = (x==0 || x==2*state->w ? 1 :
+                         VRANGE(state, x/2, (y-1)/2) && vedge(state, x/2, (y-1)/2));
+               int vd = (x==0 || x==2*state->w ? 1 :
+                         VRANGE(state, x/2, (y+1)/2) && vedge(state, x/2, (y+1)/2));
+               if (!hl && !hr && !vu && !vd)
+                   *p++ = ' ';
+               else if (hl && hr && !vu && !vd)
+                   *p++ = '-';
+               else if (!hl && !hr && vu && vd)
+                   *p++ = '|';
+               else
+                   *p++ = '+';
+           }
+       }
+       *p++ = '\n';
+    }
+
+    assert(p - ret == maxlen);
+    *p = '\0';
+    return ret;
+}
+
+struct game_ui {
+    /*
+     * These coordinates are 2 times the obvious grid coordinates.
+     * Hence, the top left of the grid is (0,0), the grid point to
+     * the right of that is (2,0), the one _below that_ is (2,2)
+     * and so on. This is so that we can specify a drag start point
+     * on an edge (one odd coordinate) or in the middle of a square
+     * (two odd coordinates) rather than always at a corner.
+     * 
+     * -1,-1 means no drag is in progress.
+     */
+    int drag_start_x;
+    int drag_start_y;
+    int drag_end_x;
+    int drag_end_y;
+    /*
+     * This flag is set as soon as a dragging action moves the
+     * mouse pointer away from its starting point, so that even if
+     * the pointer _returns_ to its starting point the action is
+     * treated as a small drag rather than a click.
+     */
+    int dragged;
+    /* This flag is set if we're doing an erase operation (i.e.
+     * removing edges in the centre of the rectangle without altering
+     * the outlines).
+     */
+    int erasing;
+    /*
+     * These are the co-ordinates of the top-left and bottom-right squares
+     * in the drag box, respectively, or -1 otherwise.
+     */
+    int x1;
+    int y1;
+    int x2;
+    int y2;
+    /*
+     * These are the coordinates of a cursor, whether it's visible, and
+     * whether it was used to start a drag.
+     */
+    int cur_x, cur_y, cur_visible, cur_dragging;
+};
+
+static void reset_ui(game_ui *ui)
+{
+    ui->drag_start_x = -1;
+    ui->drag_start_y = -1;
+    ui->drag_end_x = -1;
+    ui->drag_end_y = -1;
+    ui->x1 = -1;
+    ui->y1 = -1;
+    ui->x2 = -1;
+    ui->y2 = -1;
+    ui->dragged = FALSE;
+}
+
+static game_ui *new_ui(const game_state *state)
+{
+    game_ui *ui = snew(game_ui);
+    reset_ui(ui);
+    ui->erasing = FALSE;
+    ui->cur_x = ui->cur_y = ui->cur_visible = ui->cur_dragging = 0;
+    return ui;
+}
+
+static void free_ui(game_ui *ui)
+{
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+}
+
+static void coord_round(float x, float y, int *xr, int *yr)
+{
+    float xs, ys, xv, yv, dx, dy, dist;
+
+    /*
+     * Find the nearest square-centre.
+     */
+    xs = (float)floor(x) + 0.5F;
+    ys = (float)floor(y) + 0.5F;
+
+    /*
+     * And find the nearest grid vertex.
+     */
+    xv = (float)floor(x + 0.5F);
+    yv = (float)floor(y + 0.5F);
+
+    /*
+     * We allocate clicks in parts of the grid square to either
+     * corners, edges or square centres, as follows:
+     * 
+     *   +--+--------+--+
+     *   |  |        |  |
+     *   +--+        +--+
+     *   |   `.    ,'   |
+     *   |     +--+     |
+     *   |     |  |     |
+     *   |     +--+     |
+     *   |   ,'    `.   |
+     *   +--+        +--+
+     *   |  |        |  |
+     *   +--+--------+--+
+     * 
+     * (Not to scale!)
+     * 
+     * In other words: we measure the square distance (i.e.
+     * max(dx,dy)) from the click to the nearest corner, and if
+     * it's within CORNER_TOLERANCE then we return a corner click.
+     * We measure the square distance from the click to the nearest
+     * centre, and if that's within CENTRE_TOLERANCE we return a
+     * centre click. Failing that, we find which of the two edge
+     * centres is nearer to the click and return that edge.
+     */
+
+    /*
+     * Check for corner click.
+     */
+    dx = (float)fabs(x - xv);
+    dy = (float)fabs(y - yv);
+    dist = (dx > dy ? dx : dy);
+    if (dist < CORNER_TOLERANCE) {
+        *xr = 2 * (int)xv;
+        *yr = 2 * (int)yv;
+    } else {
+        /*
+         * Check for centre click.
+         */
+        dx = (float)fabs(x - xs);
+        dy = (float)fabs(y - ys);
+        dist = (dx > dy ? dx : dy);
+        if (dist < CENTRE_TOLERANCE) {
+            *xr = 1 + 2 * (int)xs;
+            *yr = 1 + 2 * (int)ys;
+        } else {
+            /*
+             * Failing both of those, see which edge we're closer to.
+             * Conveniently, this is simply done by testing the relative
+             * magnitude of dx and dy (which are currently distances from
+             * the square centre).
+             */
+            if (dx > dy) {
+                /* Vertical edge: x-coord of corner,
+                 * y-coord of square centre. */
+                *xr = 2 * (int)xv;
+                *yr = 1 + 2 * (int)floor(ys);
+            } else {
+                /* Horizontal edge: x-coord of square centre,
+                 * y-coord of corner. */
+                *xr = 1 + 2 * (int)floor(xs);
+                *yr = 2 * (int)yv;
+            }
+        }
+    }
+}
+
+/*
+ * Returns TRUE if it has made any change to the grid.
+ */
+static int grid_draw_rect(const game_state *state,
+                         unsigned char *hedge, unsigned char *vedge,
+                         int c, int really, int outline,
+                         int x1, int y1, int x2, int y2)
+{
+    int x, y;
+    int changed = FALSE;
+
+    /*
+     * Draw horizontal edges of rectangles.
+     */
+    for (x = x1; x < x2; x++)
+        for (y = y1; y <= y2; y++)
+            if (HRANGE(state,x,y)) {
+                int val = index(state,hedge,x,y);
+                if (y == y1 || y == y2) {
+                    if (!outline) continue;
+                    val = c;
+                } else if (c == 1)
+                    val = 0;
+               changed = changed || (index(state,hedge,x,y) != val);
+               if (really)
+                   index(state,hedge,x,y) = val;
+            }
+
+    /*
+     * Draw vertical edges of rectangles.
+     */
+    for (y = y1; y < y2; y++)
+        for (x = x1; x <= x2; x++)
+            if (VRANGE(state,x,y)) {
+                int val = index(state,vedge,x,y);
+                if (x == x1 || x == x2) {
+                    if (!outline) continue;
+                    val = c;
+                } else if (c == 1)
+                    val = 0;
+               changed = changed || (index(state,vedge,x,y) != val);
+                if (really)
+                   index(state,vedge,x,y) = val;
+            }
+
+    return changed;
+}
+
+static int ui_draw_rect(const game_state *state, const game_ui *ui,
+                       unsigned char *hedge, unsigned char *vedge, int c,
+                       int really, int outline)
+{
+    return grid_draw_rect(state, hedge, vedge, c, really, outline,
+                         ui->x1, ui->y1, ui->x2, ui->y2);
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+}
+
+struct game_drawstate {
+    int started;
+    int w, h, tilesize;
+    unsigned long *visible;
+};
+
+static char *interpret_move(const game_state *from, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    int xc, yc;
+    int startdrag = FALSE, enddrag = FALSE, active = FALSE, erasing = FALSE;
+    char buf[80], *ret;
+
+    button &= ~MOD_MASK;
+
+    coord_round(FROMCOORD((float)x), FROMCOORD((float)y), &xc, &yc);
+
+    if (button == LEFT_BUTTON || button == RIGHT_BUTTON) {
+        if (ui->drag_start_x >= 0 && ui->cur_dragging)
+            reset_ui(ui); /* cancel keyboard dragging */
+        startdrag = TRUE;
+        ui->cur_visible = ui->cur_dragging = FALSE;
+        active = TRUE;
+        erasing = (button == RIGHT_BUTTON);
+    } else if (button == LEFT_RELEASE || button == RIGHT_RELEASE) {
+        /* We assert we should have had a LEFT_BUTTON first. */
+        if (ui->cur_visible) {
+            ui->cur_visible = FALSE;
+            active = TRUE;
+        }
+        assert(!ui->cur_dragging);
+        enddrag = TRUE;
+        erasing = (button == RIGHT_RELEASE);
+    } else if (IS_CURSOR_MOVE(button)) {
+        move_cursor(button, &ui->cur_x, &ui->cur_y, from->w, from->h, 0);
+        ui->cur_visible = TRUE;
+        active = TRUE;
+        if (!ui->cur_dragging) return "";
+        coord_round((float)ui->cur_x + 0.5F, (float)ui->cur_y + 0.5F, &xc, &yc);
+    } else if (IS_CURSOR_SELECT(button)) {
+        if (ui->drag_start_x >= 0 && !ui->cur_dragging) {
+            /*
+             * If a mouse drag is in progress, ignore attempts to
+             * start a keyboard one.
+             */
+            return NULL;
+        }
+        if (!ui->cur_visible) {
+            assert(!ui->cur_dragging);
+            ui->cur_visible = TRUE;
+            return "";
+        }
+        coord_round((float)ui->cur_x + 0.5F, (float)ui->cur_y + 0.5F, &xc, &yc);
+        erasing = (button == CURSOR_SELECT2);
+        if (ui->cur_dragging) {
+            ui->cur_dragging = FALSE;
+            enddrag = TRUE;
+            active = TRUE;
+        } else {
+            ui->cur_dragging = TRUE;
+            startdrag = TRUE;
+            active = TRUE;
+        }
+    } else if (button == '\b' || button == 27) {
+        if (!ui->cur_dragging) {
+            ui->cur_visible = FALSE;
+        } else {
+            assert(ui->cur_visible);
+            reset_ui(ui); /* cancel keyboard dragging */
+            ui->cur_dragging = FALSE;
+        }
+        return "";
+    } else if (button != LEFT_DRAG && button != RIGHT_DRAG) {
+        return NULL;
+    }
+
+    if (startdrag &&
+       xc >= 0 && xc <= 2*from->w &&
+       yc >= 0 && yc <= 2*from->h) {
+
+        ui->drag_start_x = xc;
+        ui->drag_start_y = yc;
+        ui->drag_end_x = -1;
+        ui->drag_end_y = -1;
+        ui->dragged = FALSE;
+        ui->erasing = erasing;
+        active = TRUE;
+    }
+
+    if (ui->drag_start_x >= 0 &&
+       (xc != ui->drag_end_x || yc != ui->drag_end_y)) {
+       int t;
+
+       if (ui->drag_end_x != -1 && ui->drag_end_y != -1)
+           ui->dragged = TRUE;
+        ui->drag_end_x = xc;
+        ui->drag_end_y = yc;
+        active = TRUE;
+
+       if (xc >= 0 && xc <= 2*from->w &&
+           yc >= 0 && yc <= 2*from->h) {
+            ui->x1 = ui->drag_start_x;
+            ui->x2 = ui->drag_end_x;
+            if (ui->x2 < ui->x1) { t = ui->x1; ui->x1 = ui->x2; ui->x2 = t; }
+
+            ui->y1 = ui->drag_start_y;
+            ui->y2 = ui->drag_end_y;
+            if (ui->y2 < ui->y1) { t = ui->y1; ui->y1 = ui->y2; ui->y2 = t; }
+
+            ui->x1 = ui->x1 / 2;               /* rounds down */
+            ui->x2 = (ui->x2+1) / 2;           /* rounds up */
+            ui->y1 = ui->y1 / 2;               /* rounds down */
+            ui->y2 = (ui->y2+1) / 2;           /* rounds up */
+        } else {
+            ui->x1 = -1;
+            ui->y1 = -1;
+            ui->x2 = -1;
+            ui->y2 = -1;
+        }
+    }
+
+    ret = NULL;
+
+    if (enddrag && (ui->drag_start_x >= 0)) {
+       if (xc >= 0 && xc <= 2*from->w &&
+           yc >= 0 && yc <= 2*from->h &&
+            erasing == ui->erasing) {
+
+           if (ui->dragged) {
+               if (ui_draw_rect(from, ui, from->hedge,
+                                from->vedge, 1, FALSE, !ui->erasing)) {
+                   sprintf(buf, "%c%d,%d,%d,%d",
+                           (int)(ui->erasing ? 'E' : 'R'),
+                           ui->x1, ui->y1, ui->x2 - ui->x1, ui->y2 - ui->y1);
+                   ret = dupstr(buf);
+               }
+           } else {
+               if ((xc & 1) && !(yc & 1) && HRANGE(from,xc/2,yc/2)) {
+                   sprintf(buf, "H%d,%d", xc/2, yc/2);
+                   ret = dupstr(buf);
+               }
+               if ((yc & 1) && !(xc & 1) && VRANGE(from,xc/2,yc/2)) {
+                   sprintf(buf, "V%d,%d", xc/2, yc/2);
+                   ret = dupstr(buf);
+               }
+           }
+       }
+
+        reset_ui(ui);
+       active = TRUE;
+    }
+
+    if (ret)
+       return ret;                    /* a move has been made */
+    else if (active)
+        return "";                    /* UI activity has occurred */
+    else
+       return NULL;
+}
+
+static game_state *execute_move(const game_state *from, const char *move)
+{
+    game_state *ret;
+    int x1, y1, x2, y2, mode;
+
+    if (move[0] == 'S') {
+       const char *p = move+1;
+       int x, y;
+
+       ret = dup_game(from);
+       ret->cheated = TRUE;
+
+       for (y = 0; y < ret->h; y++)
+           for (x = 1; x < ret->w; x++) {
+               vedge(ret, x, y) = (*p == '1');
+               if (*p) p++;
+           }
+       for (y = 1; y < ret->h; y++)
+           for (x = 0; x < ret->w; x++) {
+               hedge(ret, x, y) = (*p == '1');
+               if (*p) p++;
+           }
+
+       sfree(ret->correct);
+       ret->correct = get_correct(ret);
+
+       return ret;
+
+    } else if ((move[0] == 'R' || move[0] == 'E') &&
+       sscanf(move+1, "%d,%d,%d,%d", &x1, &y1, &x2, &y2) == 4 &&
+       x1 >= 0 && x2 >= 0 && x1+x2 <= from->w &&
+       y1 >= 0 && y2 >= 0 && y1+y2 <= from->h) {
+       x2 += x1;
+       y2 += y1;
+       mode = move[0];
+    } else if ((move[0] == 'H' || move[0] == 'V') &&
+              sscanf(move+1, "%d,%d", &x1, &y1) == 2 &&
+              (move[0] == 'H' ? HRANGE(from, x1, y1) :
+               VRANGE(from, x1, y1))) {
+       mode = move[0];
+    } else
+       return NULL;                   /* can't parse move string */
+
+    ret = dup_game(from);
+
+    if (mode == 'R' || mode == 'E') {
+       grid_draw_rect(ret, ret->hedge, ret->vedge, 1, TRUE,
+                       mode == 'R', x1, y1, x2, y2);
+    } else if (mode == 'H') {
+       hedge(ret,x1,y1) = !hedge(ret,x1,y1);
+    } else if (mode == 'V') {
+       vedge(ret,x1,y1) = !vedge(ret,x1,y1);
+    }
+
+    sfree(ret->correct);
+    ret->correct = get_correct(ret);
+
+    /*
+     * We've made a real change to the grid. Check to see
+     * if the game has been completed.
+     */
+    if (!ret->completed) {
+       int x, y, ok;
+
+       ok = TRUE;
+       for (x = 0; x < ret->w; x++)
+           for (y = 0; y < ret->h; y++)
+               if (!index(ret, ret->correct, x, y))
+                   ok = FALSE;
+
+       if (ok)
+           ret->completed = TRUE;
+    }
+
+    return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+#define CORRECT (1L<<16)
+#define CURSOR  (1L<<17)
+
+#define COLOUR(k) ( (k)==1 ? COL_LINE : (k)==2 ? COL_DRAG : COL_DRAGERASE )
+#define MAX4(x,y,z,w) ( max(max(x,y),max(z,w)) )
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    struct { int tilesize; } ads, *ds = &ads;
+    ads.tilesize = tilesize;
+
+    *x = params->w * TILE_SIZE + 2*BORDER + 1;
+    *y = params->h * TILE_SIZE + 2*BORDER + 1;
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+
+    frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
+
+    ret[COL_GRID * 3 + 0] = 0.5F * ret[COL_BACKGROUND * 3 + 0];
+    ret[COL_GRID * 3 + 1] = 0.5F * ret[COL_BACKGROUND * 3 + 1];
+    ret[COL_GRID * 3 + 2] = 0.5F * ret[COL_BACKGROUND * 3 + 2];
+
+    ret[COL_DRAG * 3 + 0] = 1.0F;
+    ret[COL_DRAG * 3 + 1] = 0.0F;
+    ret[COL_DRAG * 3 + 2] = 0.0F;
+
+    ret[COL_DRAGERASE * 3 + 0] = 0.2F;
+    ret[COL_DRAGERASE * 3 + 1] = 0.2F;
+    ret[COL_DRAGERASE * 3 + 2] = 1.0F;
+
+    ret[COL_CORRECT * 3 + 0] = 0.75F * ret[COL_BACKGROUND * 3 + 0];
+    ret[COL_CORRECT * 3 + 1] = 0.75F * ret[COL_BACKGROUND * 3 + 1];
+    ret[COL_CORRECT * 3 + 2] = 0.75F * ret[COL_BACKGROUND * 3 + 2];
+
+    ret[COL_LINE * 3 + 0] = 0.0F;
+    ret[COL_LINE * 3 + 1] = 0.0F;
+    ret[COL_LINE * 3 + 2] = 0.0F;
+
+    ret[COL_TEXT * 3 + 0] = 0.0F;
+    ret[COL_TEXT * 3 + 1] = 0.0F;
+    ret[COL_TEXT * 3 + 2] = 0.0F;
+
+    ret[COL_CURSOR * 3 + 0] = 1.0F;
+    ret[COL_CURSOR * 3 + 1] = 0.5F;
+    ret[COL_CURSOR * 3 + 2] = 0.5F;
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    struct game_drawstate *ds = snew(struct game_drawstate);
+    int i;
+
+    ds->started = FALSE;
+    ds->w = state->w;
+    ds->h = state->h;
+    ds->visible = snewn(ds->w * ds->h, unsigned long);
+    ds->tilesize = 0;                  /* not decided yet */
+    for (i = 0; i < ds->w * ds->h; i++)
+        ds->visible[i] = 0xFFFF;
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds->visible);
+    sfree(ds);
+}
+
+static void draw_tile(drawing *dr, game_drawstate *ds, const game_state *state,
+                      int x, int y, unsigned char *hedge, unsigned char *vedge,
+                      unsigned char *corners, unsigned long bgflags)
+{
+    int cx = COORD(x), cy = COORD(y);
+    char str[80];
+
+    draw_rect(dr, cx, cy, TILE_SIZE+1, TILE_SIZE+1, COL_GRID);
+    draw_rect(dr, cx+1, cy+1, TILE_SIZE-1, TILE_SIZE-1,
+             (bgflags & CURSOR) ? COL_CURSOR :
+              (bgflags & CORRECT) ? COL_CORRECT : COL_BACKGROUND);
+
+    if (grid(state,x,y)) {
+       sprintf(str, "%d", grid(state,x,y));
+       draw_text(dr, cx+TILE_SIZE/2, cy+TILE_SIZE/2, FONT_VARIABLE,
+                 TILE_SIZE/2, ALIGN_HCENTRE | ALIGN_VCENTRE, COL_TEXT, str);
+    }
+
+    /*
+     * Draw edges.
+     */
+    if (!HRANGE(state,x,y) || index(state,hedge,x,y))
+       draw_rect(dr, cx, cy, TILE_SIZE+1, 2,
+                  HRANGE(state,x,y) ? COLOUR(index(state,hedge,x,y)) :
+                  COL_LINE);
+    if (!HRANGE(state,x,y+1) || index(state,hedge,x,y+1))
+       draw_rect(dr, cx, cy+TILE_SIZE-1, TILE_SIZE+1, 2,
+                  HRANGE(state,x,y+1) ? COLOUR(index(state,hedge,x,y+1)) :
+                  COL_LINE);
+    if (!VRANGE(state,x,y) || index(state,vedge,x,y))
+       draw_rect(dr, cx, cy, 2, TILE_SIZE+1,
+                  VRANGE(state,x,y) ? COLOUR(index(state,vedge,x,y)) :
+                  COL_LINE);
+    if (!VRANGE(state,x+1,y) || index(state,vedge,x+1,y))
+       draw_rect(dr, cx+TILE_SIZE-1, cy, 2, TILE_SIZE+1,
+                  VRANGE(state,x+1,y) ? COLOUR(index(state,vedge,x+1,y)) :
+                  COL_LINE);
+
+    /*
+     * Draw corners.
+     */
+    if (index(state,corners,x,y))
+       draw_rect(dr, cx, cy, 2, 2,
+                  COLOUR(index(state,corners,x,y)));
+    if (x+1 < state->w && index(state,corners,x+1,y))
+       draw_rect(dr, cx+TILE_SIZE-1, cy, 2, 2,
+                  COLOUR(index(state,corners,x+1,y)));
+    if (y+1 < state->h && index(state,corners,x,y+1))
+       draw_rect(dr, cx, cy+TILE_SIZE-1, 2, 2,
+                  COLOUR(index(state,corners,x,y+1)));
+    if (x+1 < state->w && y+1 < state->h && index(state,corners,x+1,y+1))
+       draw_rect(dr, cx+TILE_SIZE-1, cy+TILE_SIZE-1, 2, 2,
+                  COLOUR(index(state,corners,x+1,y+1)));
+
+    draw_update(dr, cx, cy, TILE_SIZE+1, TILE_SIZE+1);
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int x, y;
+    unsigned char *hedge, *vedge, *corners;
+
+    if (ui->dragged) {
+        hedge = snewn(state->w*state->h, unsigned char);
+        vedge = snewn(state->w*state->h, unsigned char);
+        memcpy(hedge, state->hedge, state->w*state->h);
+        memcpy(vedge, state->vedge, state->w*state->h);
+        ui_draw_rect(state, ui, hedge, vedge, ui->erasing ? 3 : 2, TRUE, TRUE);
+    } else {
+        hedge = state->hedge;
+        vedge = state->vedge;
+    }
+
+    corners = snewn(state->w * state->h, unsigned char);
+    memset(corners, 0, state->w * state->h);
+    for (x = 0; x < state->w; x++)
+       for (y = 0; y < state->h; y++) {
+           if (x > 0) {
+               int e = index(state, vedge, x, y);
+               if (index(state,corners,x,y) < e)
+                   index(state,corners,x,y) = e;
+               if (y+1 < state->h &&
+                   index(state,corners,x,y+1) < e)
+                   index(state,corners,x,y+1) = e;
+           }
+           if (y > 0) {
+               int e = index(state, hedge, x, y);
+               if (index(state,corners,x,y) < e)
+                   index(state,corners,x,y) = e;
+               if (x+1 < state->w &&
+                   index(state,corners,x+1,y) < e)
+                   index(state,corners,x+1,y) = e;
+           }
+       }
+
+    if (!ds->started) {
+       draw_rect(dr, 0, 0,
+                 state->w * TILE_SIZE + 2*BORDER + 1,
+                 state->h * TILE_SIZE + 2*BORDER + 1, COL_BACKGROUND);
+       draw_rect(dr, COORD(0)-1, COORD(0)-1,
+                 ds->w*TILE_SIZE+3, ds->h*TILE_SIZE+3, COL_LINE);
+       ds->started = TRUE;
+       draw_update(dr, 0, 0,
+                   state->w * TILE_SIZE + 2*BORDER + 1,
+                   state->h * TILE_SIZE + 2*BORDER + 1);
+    }
+
+    for (x = 0; x < state->w; x++)
+       for (y = 0; y < state->h; y++) {
+           unsigned long c = 0;
+
+           if (HRANGE(state,x,y))
+                c |= index(state,hedge,x,y);
+           if (HRANGE(state,x,y+1))
+               c |= index(state,hedge,x,y+1) << 2;
+           if (VRANGE(state,x,y))
+               c |= index(state,vedge,x,y) << 4;
+           if (VRANGE(state,x+1,y))
+               c |= index(state,vedge,x+1,y) << 6;
+           c |= index(state,corners,x,y) << 8;
+           if (x+1 < state->w)
+               c |= index(state,corners,x+1,y) << 10;
+           if (y+1 < state->h)
+               c |= index(state,corners,x,y+1) << 12;
+           if (x+1 < state->w && y+1 < state->h)
+               /* cast to prevent 2<<14 sign-extending on promotion to long */
+               c |= (unsigned long)index(state,corners,x+1,y+1) << 14;
+           if (index(state, state->correct, x, y) && !flashtime)
+               c |= CORRECT;
+            if (ui->cur_visible && ui->cur_x == x && ui->cur_y == y)
+                c |= CURSOR;
+
+           if (index(ds,ds->visible,x,y) != c) {
+               draw_tile(dr, ds, state, x, y, hedge, vedge, corners,
+                          (c & (CORRECT|CURSOR)) );
+               index(ds,ds->visible,x,y) = c;
+           }
+       }
+
+    {
+       char buf[256];
+
+       if (ui->dragged &&
+           ui->x1 >= 0 && ui->y1 >= 0 &&
+           ui->x2 >= 0 && ui->y2 >= 0) {
+           sprintf(buf, "%dx%d ",
+                   ui->x2-ui->x1,
+                   ui->y2-ui->y1);
+       } else {
+           buf[0] = '\0';
+       }
+
+        if (state->cheated)
+            strcat(buf, "Auto-solved.");
+        else if (state->completed)
+            strcat(buf, "COMPLETED!");
+
+        status_bar(dr, buf);
+    }
+
+    if (hedge != state->hedge) {
+        sfree(hedge);
+        sfree(vedge);
+    }
+
+    sfree(corners);
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return 0.0F;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    if (!oldstate->completed && newstate->completed &&
+       !oldstate->cheated && !newstate->cheated)
+        return FLASH_TIME;
+    return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    return state->completed ? +1 : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+    int pw, ph;
+
+    /*
+     * I'll use 5mm squares by default.
+     */
+    game_compute_size(params, 500, &pw, &ph);
+    *x = pw / 100.0F;
+    *y = ph / 100.0F;
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+    int w = state->w, h = state->h;
+    int ink = print_mono_colour(dr, 0);
+    int x, y;
+
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    game_drawstate ads, *ds = &ads;
+    game_set_size(dr, ds, NULL, tilesize);
+
+    /*
+     * Border.
+     */
+    print_line_width(dr, TILE_SIZE / 10);
+    draw_rect_outline(dr, COORD(0), COORD(0), w*TILE_SIZE, h*TILE_SIZE, ink);
+
+    /*
+     * Grid. We have to make the grid lines particularly thin,
+     * because users will be drawing lines _along_ them and we want
+     * those lines to be visible.
+     */
+    print_line_width(dr, TILE_SIZE / 256);
+    for (x = 1; x < w; x++)
+       draw_line(dr, COORD(x), COORD(0), COORD(x), COORD(h), ink);
+    for (y = 1; y < h; y++)
+       draw_line(dr, COORD(0), COORD(y), COORD(w), COORD(y), ink);
+
+    /*
+     * Solution.
+     */
+    print_line_width(dr, TILE_SIZE / 10);
+    for (y = 0; y <= h; y++)
+       for (x = 0; x <= w; x++) {
+           if (HRANGE(state,x,y) && hedge(state,x,y))
+               draw_line(dr, COORD(x), COORD(y), COORD(x+1), COORD(y), ink);
+           if (VRANGE(state,x,y) && vedge(state,x,y))
+               draw_line(dr, COORD(x), COORD(y), COORD(x), COORD(y+1), ink);
+       }
+
+    /*
+     * Clues.
+     */
+    for (y = 0; y < h; y++)
+       for (x = 0; x < w; x++)
+           if (grid(state,x,y)) {
+               char str[80];
+               sprintf(str, "%d", grid(state,x,y));
+               draw_text(dr, COORD(x)+TILE_SIZE/2, COORD(y)+TILE_SIZE/2,
+                         FONT_VARIABLE, TILE_SIZE/2,
+                         ALIGN_HCENTRE | ALIGN_VCENTRE, ink, str);
+           }
+}
+
+#ifdef COMBINED
+#define thegame rect
+#endif
+
+const struct game thegame = {
+    "Rectangles", "games.rectangles", "rect",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    TRUE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    TRUE, FALSE, game_print_size, game_print,
+    TRUE,                             /* wants_statusbar */
+    FALSE, game_timing_state,
+    0,                                /* flags */
+};
+
+/* vim: set shiftwidth=4 tabstop=8: */
diff --git a/resource.h b/resource.h
new file mode 100644 (file)
index 0000000..f0bfa16
--- /dev/null
@@ -0,0 +1,20 @@
+
+#define IDR_MENUBAR1                    101
+
+#define ID_GAME                         40005
+#define ID_TYPE                         40006
+
+#define IDS_CAP_GAME                    40105
+#define IDS_CAP_TYPE                    40106
+
+#define IDD_ABOUT                      2000
+#define IDC_ABOUT_CAPTION              2001
+#define IDC_ABOUT_LINE                 2002
+#define IDC_ABOUT_GAME                 2003
+#define IDC_ABOUT_VERSION              2004
+
+#define IDD_CONFIG                     2100
+#define IDC_CONFIG_CAPTION             2101
+#define IDC_CONFIG_LINE                        2102
+
+#define IDR_PADTOOLBAR                  4000
diff --git a/samegame.R b/samegame.R
new file mode 100644 (file)
index 0000000..cc0d350
--- /dev/null
@@ -0,0 +1,19 @@
+# -*- makefile -*-
+
+samegame : [X] GTK COMMON samegame samegame-icon|no-icon
+
+samegame : [G] WINDOWS COMMON samegame samegame.res|noicon.res
+
+ALL += samegame[COMBINED]
+
+!begin am gtk
+GAMES += samegame
+!end
+
+!begin >list.c
+    A(samegame) \
+!end
+
+!begin >gamedesc.txt
+samegame:samegame.exe:Same Game:Block-clearing puzzle:Clear the grid by removing touching groups of the same colour squares.
+!end
diff --git a/samegame.c b/samegame.c
new file mode 100644 (file)
index 0000000..8e428bb
--- /dev/null
@@ -0,0 +1,1679 @@
+/*
+ * 'same game' -- try to remove all the coloured squares by
+ *                selecting regions of contiguous colours.
+ */
+
+/*
+ * TODO on grid generation:
+ * 
+ *  - Generation speed could still be improved.
+ *     * 15x10c3 is the only really difficult one of the existing
+ *       presets. The others are all either small enough, or have
+ *       the great flexibility given by four colours, that they
+ *       don't take long at all.
+ *     * I still suspect many problems arise from separate
+ *      subareas. I wonder if we can also somehow prioritise left-
+ *      or rightmost insertions so as to avoid area splitting at
+ *      all where feasible? It's not easy, though, because the
+ *      current shuffle-then-try-all-options approach to move
+ *      choice doesn't leave room for `soft' probabilistic
+ *      prioritisation: we either try all class A moves before any
+ *      class B ones, or we don't.
+ *
+ *  - The current generation algorithm inserts exactly two squares
+ *    at a time, with a single exception at the beginning of
+ *    generation for grids of odd overall size. An obvious
+ *    extension would be to permit larger inverse moves during
+ *    generation.
+ *     * this might reduce the number of failed generations by
+ *       making the insertion algorithm more flexible
+ *     * on the other hand, it would be significantly more complex
+ *     * if I do this I'll need to take out the odd-subarea
+ *       avoidance
+ *     * a nice feature of the current algorithm is that the
+ *       computer's `intended' solution always receives the minimum
+ *       possible score, so that pretty much the player's entire
+ *       score represents how much better they did than the
+ *       computer.
+ *
+ *  - Is it possible we can _temporarily_ tolerate neighbouring
+ *    squares of the same colour, until we've finished setting up
+ *    our inverse move?
+ *     * or perhaps even not choose the colour of our inserted
+ *       region until we have finished placing it, and _then_ look
+ *       at what colours border on it?
+ *     * I don't think this is currently meaningful unless we're
+ *       placing more than a domino at a time.
+ *
+ *  - possibly write out a full solution so that Solve can somehow
+ *    show it step by step?
+ *     * aux_info would have to encode the click points
+ *     * solve_game() would have to encode not only those click
+ *      points but also give a move string which reconstructed the
+ *      initial state
+ *     * the game_state would include a pointer to a solution move
+ *      list, plus an index into that list
+ *     * game_changed_state would auto-select the next move if
+ *      handed a new state which had a solution move list active
+ *     * execute_move, if passed such a state as input, would check
+ *      to see whether the move being made was the same as the one
+ *      stated by the solution, and if so would advance the move
+ *      index. Failing that it would return a game_state without a
+ *      solution move list active at all.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+
+#define TILE_INNER (ds->tileinner)
+#define TILE_GAP (ds->tilegap)
+#define TILE_SIZE (TILE_INNER + TILE_GAP)
+#define PREFERRED_TILE_SIZE 32
+#define BORDER (TILE_SIZE / 2)
+#define HIGHLIGHT_WIDTH 2
+
+#define FLASH_FRAME 0.13F
+
+#define COORD(x)  ( (x) * TILE_SIZE + BORDER )
+#define FROMCOORD(x)  ( ((x) - BORDER + TILE_SIZE) / TILE_SIZE - 1 )
+
+#define X(state, i) ( (i) % (state)->params.w )
+#define Y(state, i) ( (i) / (state)->params.w )
+#define C(state, x, y) ( (y) * (state)->w + (x) )
+
+enum {
+    COL_BACKGROUND,
+    COL_1, COL_2, COL_3, COL_4, COL_5, COL_6, COL_7, COL_8, COL_9,
+    COL_IMPOSSIBLE, COL_SEL, COL_HIGHLIGHT, COL_LOWLIGHT,
+    NCOLOURS
+};
+
+/* scoresub is 1 or 2 (for (n-1)^2 or (n-2)^2) */
+struct game_params {
+    int w, h, ncols, scoresub;
+    int soluble;                      /* choose generation algorithm */
+};
+
+/* These flags must be unique across all uses; in the game_state,
+ * the game_ui, and the drawstate (as they all get combined in the
+ * drawstate). */
+#define TILE_COLMASK    0x00ff
+#define TILE_SELECTED   0x0100 /* used in ui and drawstate */
+#define TILE_JOINRIGHT  0x0200 /* used in drawstate */
+#define TILE_JOINDOWN   0x0400 /* used in drawstate */
+#define TILE_JOINDIAG   0x0800 /* used in drawstate */
+#define TILE_HASSEL     0x1000 /* used in drawstate */
+#define TILE_IMPOSSIBLE 0x2000 /* used in drawstate */
+
+#define TILE(gs,x,y) ((gs)->tiles[(gs)->params.w*(y)+(x)])
+#define COL(gs,x,y) (TILE(gs,x,y) & TILE_COLMASK)
+#define ISSEL(gs,x,y) (TILE(gs,x,y) & TILE_SELECTED)
+
+#define SWAPTILE(gs,x1,y1,x2,y2) do {   \
+    int t = TILE(gs,x1,y1);               \
+    TILE(gs,x1,y1) = TILE(gs,x2,y2);      \
+    TILE(gs,x2,y2) = t;                   \
+} while (0)
+
+static int npoints(const game_params *params, int nsel)
+{
+    int sdiff = nsel - params->scoresub;
+    return (sdiff > 0) ? sdiff * sdiff : 0;
+}
+
+struct game_state {
+    struct game_params params;
+    int n;
+    int *tiles; /* colour only */
+    int score;
+    int complete, impossible;
+};
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+    ret->w = 5;
+    ret->h = 5;
+    ret->ncols = 3;
+    ret->scoresub = 2;
+    ret->soluble = TRUE;
+    return ret;
+}
+
+static const struct game_params samegame_presets[] = {
+    { 5, 5, 3, 2, TRUE },
+    { 10, 5, 3, 2, TRUE },
+#ifdef SLOW_SYSTEM
+    { 10, 10, 3, 2, TRUE },
+#else
+    { 15, 10, 3, 2, TRUE },
+#endif
+    { 15, 10, 4, 2, TRUE },
+    { 20, 15, 4, 2, TRUE }
+};
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params *ret;
+    char str[80];
+
+    if (i < 0 || i >= lenof(samegame_presets))
+       return FALSE;
+
+    ret = snew(game_params);
+    *ret = samegame_presets[i];
+
+    sprintf(str, "%dx%d, %d colours", ret->w, ret->h, ret->ncols);
+
+    *name = dupstr(str);
+    *params = ret;
+    return TRUE;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+static void decode_params(game_params *params, char const *string)
+{
+    char const *p = string;
+
+    params->w = atoi(p);
+    while (*p && isdigit((unsigned char)*p)) p++;
+    if (*p == 'x') {
+       p++;
+       params->h = atoi(p);
+       while (*p && isdigit((unsigned char)*p)) p++;
+    } else {
+       params->h = params->w;
+    }
+    if (*p == 'c') {
+       p++;
+       params->ncols = atoi(p);
+       while (*p && isdigit((unsigned char)*p)) p++;
+    } else {
+       params->ncols = 3;
+    }
+    if (*p == 's') {
+       p++;
+       params->scoresub = atoi(p);
+       while (*p && isdigit((unsigned char)*p)) p++;
+    } else {
+       params->scoresub = 2;
+    }
+    if (*p == 'r') {
+       p++;
+       params->soluble = FALSE;
+    }
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char ret[80];
+
+    sprintf(ret, "%dx%dc%ds%d%s",
+           params->w, params->h, params->ncols, params->scoresub,
+           full && !params->soluble ? "r" : "");
+    return dupstr(ret);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+    char buf[80];
+
+    ret = snewn(6, config_item);
+
+    ret[0].name = "Width";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->w);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Height";
+    ret[1].type = C_STRING;
+    sprintf(buf, "%d", params->h);
+    ret[1].sval = dupstr(buf);
+    ret[1].ival = 0;
+
+    ret[2].name = "No. of colours";
+    ret[2].type = C_STRING;
+    sprintf(buf, "%d", params->ncols);
+    ret[2].sval = dupstr(buf);
+    ret[2].ival = 0;
+
+    ret[3].name = "Scoring system";
+    ret[3].type = C_CHOICES;
+    ret[3].sval = ":(n-1)^2:(n-2)^2";
+    ret[3].ival = params->scoresub-1;
+
+    ret[4].name = "Ensure solubility";
+    ret[4].type = C_BOOLEAN;
+    ret[4].sval = NULL;
+    ret[4].ival = params->soluble;
+
+    ret[5].name = NULL;
+    ret[5].type = C_END;
+    ret[5].sval = NULL;
+    ret[5].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = atoi(cfg[0].sval);
+    ret->h = atoi(cfg[1].sval);
+    ret->ncols = atoi(cfg[2].sval);
+    ret->scoresub = cfg[3].ival + 1;
+    ret->soluble = cfg[4].ival;
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    if (params->w < 1 || params->h < 1)
+       return "Width and height must both be positive";
+
+    if (params->ncols > 9)
+       return "Maximum of 9 colours";
+
+    if (params->soluble) {
+       if (params->ncols < 3)
+           return "Number of colours must be at least three";
+       if (params->w * params->h <= 1)
+           return "Grid area must be greater than 1";
+    } else {
+       if (params->ncols < 2)
+           return "Number of colours must be at least three";
+       /* ...and we must make sure we can generate at least 2 squares
+        * of each colour so it's theoretically soluble. */
+       if ((params->w * params->h) < (params->ncols * 2))
+           return "Too many colours makes given grid size impossible";
+    }
+
+    if ((params->scoresub < 1) || (params->scoresub > 2))
+       return "Scoring system not recognised";
+
+    return NULL;
+}
+
+/*
+ * Guaranteed-soluble grid generator.
+ */
+static void gen_grid(int w, int h, int nc, int *grid, random_state *rs)
+{
+    int wh = w*h, tc = nc+1;
+    int i, j, k, c, x, y, pos, n;
+    int *list, *grid2;
+    int ok, failures = 0;
+
+    /*
+     * We'll use `list' to track the possible places to put our
+     * next insertion. There are up to h places to insert in each
+     * column: in a column of height n there are n+1 places because
+     * we can insert at the very bottom or the very top, but a
+     * column of height h can't have anything at all inserted in it
+     * so we have up to h in each column. Likewise, with n columns
+     * present there are n+1 places to fit a new one in between but
+     * we can't insert a column if there are already w; so there
+     * are a maximum of w new columns too. Total is wh + w.
+     */
+    list = snewn(wh + w, int);
+    grid2 = snewn(wh, int);
+
+    do {
+        /*
+         * Start with two or three squares - depending on parity of w*h
+         * - of a random colour.
+         */
+        for (i = 0; i < wh; i++)
+            grid[i] = 0;
+        j = 2 + (wh % 2);
+        c = 1 + random_upto(rs, nc);
+       if (j <= w) {
+           for (i = 0; i < j; i++)
+               grid[(h-1)*w+i] = c;
+       } else {
+           assert(j <= h);
+           for (i = 0; i < j; i++)
+               grid[(h-1-i)*w] = c;
+       }
+
+        /*
+         * Now repeatedly insert a two-square blob in the grid, of
+         * whatever colour will go at the position we chose.
+         */
+        while (1) {
+            n = 0;
+
+            /*
+             * Build up a list of insertion points. Each point is
+             * encoded as y*w+x; insertion points between columns are
+             * encoded as h*w+x.
+             */
+
+            if (grid[wh - 1] == 0) {
+                /*
+                 * The final column is empty, so we can insert new
+                 * columns.
+                 */
+                for (i = 0; i < w; i++) {
+                    list[n++] = wh + i;
+                    if (grid[(h-1)*w + i] == 0)
+                        break;
+                }
+            }
+
+            /*
+             * Now look for places to insert within columns.
+             */
+            for (i = 0; i < w; i++) {
+                if (grid[(h-1)*w+i] == 0)
+                    break;                    /* no more columns */
+
+                if (grid[i] != 0)
+                    continue;         /* this column is full */
+
+                for (j = h; j-- > 0 ;) {
+                    list[n++] = j*w+i;
+                    if (grid[j*w+i] == 0)
+                        break;        /* this column is exhausted */
+                }
+            }
+
+            if (n == 0)
+                break;                /* we're done */
+
+#ifdef GENERATION_DIAGNOSTICS
+            printf("initial grid:\n");
+            {
+                int x,y;
+                for (y = 0; y < h; y++) {
+                    for (x = 0; x < w; x++) {
+                        if (grid[y*w+x] == 0)
+                            printf("-");
+                        else
+                            printf("%d", grid[y*w+x]);
+                    }
+                    printf("\n");
+                }
+            }
+#endif
+
+            /*
+             * Now go through the list one element at a time in
+             * random order, and actually attempt to insert
+             * something there.
+             */
+            while (n-- > 0) {
+                int dirs[4], ndirs, dir;
+
+                i = random_upto(rs, n+1);
+                pos = list[i];
+                list[i] = list[n];
+
+                x = pos % w;
+                y = pos / w;
+
+                memcpy(grid2, grid, wh * sizeof(int));
+
+                if (y == h) {
+                    /*
+                     * Insert a column at position x.
+                     */
+                    for (i = w-1; i > x; i--)
+                        for (j = 0; j < h; j++)
+                            grid2[j*w+i] = grid2[j*w+(i-1)];
+                    /*
+                     * Clear the new column.
+                     */
+                    for (j = 0; j < h; j++)
+                        grid2[j*w+x] = 0;
+                    /*
+                     * Decrement y so that our first square is actually
+                     * inserted _in_ the grid rather than just below it.
+                     */
+                    y--;
+                }
+
+                /*
+                 * Insert a square within column x at position y.
+                 */
+                for (i = 0; i+1 <= y; i++)
+                    grid2[i*w+x] = grid2[(i+1)*w+x];
+
+#ifdef GENERATION_DIAGNOSTICS
+                printf("trying at n=%d (%d,%d)\n", n, x, y);
+                grid2[y*w+x] = tc;
+                {
+                    int x,y;
+                    for (y = 0; y < h; y++) {
+                        for (x = 0; x < w; x++) {
+                            if (grid2[y*w+x] == 0)
+                                printf("-");
+                            else if (grid2[y*w+x] <= nc)
+                                printf("%d", grid2[y*w+x]);
+                            else
+                                printf("*");
+                        }
+                        printf("\n");
+                    }
+                }
+#endif
+
+                /*
+                 * Pick our square colour so that it doesn't match any
+                 * of its neighbours.
+                 */
+                {
+                    int wrongcol[4], nwrong = 0;
+
+                    /*
+                     * List the neighbouring colours.
+                     */
+                    if (x > 0)
+                        wrongcol[nwrong++] = grid2[y*w+(x-1)];
+                    if (x+1 < w)
+                        wrongcol[nwrong++] = grid2[y*w+(x+1)];
+                    if (y > 0)
+                        wrongcol[nwrong++] = grid2[(y-1)*w+x];
+                    if (y+1 < h)
+                        wrongcol[nwrong++] = grid2[(y+1)*w+x];
+
+                    /*
+                     * Eliminate duplicates. We can afford a shoddy
+                     * algorithm here because the problem size is
+                     * bounded.
+                     */
+                    for (i = j = 0 ;; i++) {
+                        int pos = -1, min = 0;
+                        if (j > 0)
+                            min = wrongcol[j-1];
+                        for (k = i; k < nwrong; k++)
+                            if (wrongcol[k] > min &&
+                                (pos == -1 || wrongcol[k] < wrongcol[pos]))
+                                pos = k;
+                        if (pos >= 0) {
+                            int v = wrongcol[pos];
+                            wrongcol[pos] = wrongcol[j];
+                            wrongcol[j++] = v;
+                        } else
+                            break;
+                    }
+                    nwrong = j;
+
+                    /*
+                     * If no colour will go here, stop trying.
+                     */
+                    if (nwrong == nc)
+                        continue;
+
+                    /*
+                     * Otherwise, pick a colour from the remaining
+                     * ones.
+                     */
+                    c = 1 + random_upto(rs, nc - nwrong);
+                    for (i = 0; i < nwrong; i++) {
+                        if (c >= wrongcol[i])
+                            c++;
+                        else
+                            break;
+                    }
+                }
+
+                /*
+                 * Place the new square.
+                 * 
+                 * Although I've _chosen_ the new region's colour
+                 * (so that we can check adjacency), I'm going to
+                 * actually place it as an invalid colour (tc)
+                 * until I'm sure it's viable. This is so that I
+                 * can conveniently check that I really have made a
+                 * _valid_ inverse move later on.
+                 */
+#ifdef GENERATION_DIAGNOSTICS
+                printf("picked colour %d\n", c);
+#endif
+                grid2[y*w+x] = tc;
+
+                /*
+                 * Now attempt to extend it in one of three ways: left,
+                 * right or up.
+                 */
+                ndirs = 0;
+                if (x > 0 &&
+                    grid2[y*w+(x-1)] != c &&
+                    grid2[x-1] == 0 &&
+                    (y+1 >= h || grid2[(y+1)*w+(x-1)] != c) &&
+                    (y+1 >= h || grid2[(y+1)*w+(x-1)] != 0) &&
+                    (x <= 1 || grid2[y*w+(x-2)] != c))
+                    dirs[ndirs++] = -1;    /* left */
+                if (x+1 < w &&
+                    grid2[y*w+(x+1)] != c &&
+                    grid2[x+1] == 0 &&
+                    (y+1 >= h || grid2[(y+1)*w+(x+1)] != c) &&
+                    (y+1 >= h || grid2[(y+1)*w+(x+1)] != 0) &&
+                    (x+2 >= w || grid2[y*w+(x+2)] != c))
+                    dirs[ndirs++] = +1;    /* right */
+                if (y > 0 &&
+                    grid2[x] == 0 &&
+                    (x <= 0 || grid2[(y-1)*w+(x-1)] != c) &&
+                    (x+1 >= w || grid2[(y-1)*w+(x+1)] != c)) {
+                    /*
+                     * We add this possibility _twice_, so that the
+                     * probability of placing a vertical domino is
+                     * about the same as that of a horizontal. This
+                     * should yield less bias in the generated
+                     * grids.
+                     */
+                    dirs[ndirs++] = 0;     /* up */
+                    dirs[ndirs++] = 0;     /* up */
+                }
+
+                if (ndirs == 0)
+                    continue;
+
+                dir = dirs[random_upto(rs, ndirs)];
+
+#ifdef GENERATION_DIAGNOSTICS
+                printf("picked dir %d\n", dir);
+#endif
+
+                /*
+                 * Insert a square within column (x+dir) at position y.
+                 */
+                for (i = 0; i+1 <= y; i++)
+                    grid2[i*w+x+dir] = grid2[(i+1)*w+x+dir];
+                grid2[y*w+x+dir] = tc;
+
+                /*
+                 * See if we've divided the remaining grid squares
+                 * into sub-areas. If so, we need every sub-area to
+                 * have an even area or we won't be able to
+                 * complete generation.
+                 * 
+                 * If the height is odd and not all columns are
+                 * present, we can increase the area of a subarea
+                 * by adding a new column in it, so in that
+                 * situation we don't mind having as many odd
+                 * subareas as there are spare columns.
+                 * 
+                 * If the height is even, we can't fix it at all.
+                 */
+                {
+                    int nerrs = 0, nfix = 0;
+                    k = 0;             /* current subarea size */
+                    for (i = 0; i < w; i++) {
+                        if (grid2[(h-1)*w+i] == 0) {
+                            if (h % 2)
+                                nfix++;
+                            continue;
+                        }
+                        for (j = 0; j < h && grid2[j*w+i] == 0; j++);
+                        assert(j < h);
+                        if (j == 0) {
+                            /*
+                             * End of previous subarea.
+                             */
+                            if (k % 2)
+                                nerrs++;
+                            k = 0;
+                        } else {
+                            k += j;
+                        }
+                    }
+                    if (k % 2)
+                        nerrs++;
+                    if (nerrs > nfix)
+                        continue;      /* try a different placement */
+                }
+
+                /*
+                 * We've made a move. Verify that it is a valid
+                 * move and that if made it would indeed yield the
+                 * previous grid state. The criteria are:
+                 * 
+                 *  (a) removing all the squares of colour tc (and
+                 *      shuffling the columns up etc) from grid2
+                 *      would yield grid
+                 *  (b) no square of colour tc is adjacent to one
+                 *      of colour c
+                 *  (c) all the squares of colour tc form a single
+                 *      connected component
+                 * 
+                 * We verify the latter property at the same time
+                 * as checking that removing all the tc squares
+                 * would yield the previous grid. Then we colour
+                 * the tc squares in colour c by breadth-first
+                 * search, which conveniently permits us to test
+                 * that they're all connected.
+                 */
+                {
+                    int x1, x2, y1, y2;
+                    int ok = TRUE;
+                    int fillstart = -1, ntc = 0;
+
+#ifdef GENERATION_DIAGNOSTICS
+                    {
+                        int x,y;
+                        printf("testing move (new, old):\n");
+                        for (y = 0; y < h; y++) {
+                            for (x = 0; x < w; x++) {
+                                if (grid2[y*w+x] == 0)
+                                    printf("-");
+                                else if (grid2[y*w+x] <= nc)
+                                    printf("%d", grid2[y*w+x]);
+                                else
+                                    printf("*");
+                            }
+                            printf("   ");
+                            for (x = 0; x < w; x++) {
+                                if (grid[y*w+x] == 0)
+                                    printf("-");
+                                else
+                                    printf("%d", grid[y*w+x]);
+                            }
+                            printf("\n");
+                        }
+                    }
+#endif
+
+                    for (x1 = x2 = 0; x2 < w; x2++) {
+                        int usedcol = FALSE;
+
+                        for (y1 = y2 = h-1; y2 >= 0; y2--) {
+                            if (grid2[y2*w+x2] == tc) {
+                                ntc++;
+                                if (fillstart == -1)
+                                    fillstart = y2*w+x2;
+                                if ((y2+1 < h && grid2[(y2+1)*w+x2] == c) ||
+                                    (y2-1 >= 0 && grid2[(y2-1)*w+x2] == c) ||
+                                    (x2+1 < w && grid2[y2*w+x2+1] == c) ||
+                                    (x2-1 >= 0 && grid2[y2*w+x2-1] == c)) {
+#ifdef GENERATION_DIAGNOSTICS
+                                    printf("adjacency failure at %d,%d\n",
+                                           x2, y2);
+#endif
+                                    ok = FALSE;
+                                }
+                                continue;
+                            }
+                            if (grid2[y2*w+x2] == 0)
+                                break;
+                            usedcol = TRUE;
+                            if (grid2[y2*w+x2] != grid[y1*w+x1]) {
+#ifdef GENERATION_DIAGNOSTICS
+                                printf("matching failure at %d,%d vs %d,%d\n",
+                                       x2, y2, x1, y1);
+#endif
+                                ok = FALSE;
+                            }
+                            y1--;
+                        }
+
+                        /*
+                         * If we've reached the top of the column
+                         * in grid2, verify that we've also reached
+                         * the top of the column in `grid'.
+                         */
+                        if (usedcol) {
+                            while (y1 >= 0) {
+                                if (grid[y1*w+x1] != 0) {
+#ifdef GENERATION_DIAGNOSTICS
+                                    printf("junk at column top (%d,%d)\n",
+                                           x1, y1);
+#endif
+                                    ok = FALSE;
+                                }
+                                y1--;
+                            }
+                        }
+
+                        if (!ok)
+                            break;
+
+                        if (usedcol)
+                            x1++;
+                    }
+
+                    if (!ok) {
+                        assert(!"This should never happen");
+
+                        /*
+                         * If this game is compiled NDEBUG so that
+                         * the assertion doesn't bring it to a
+                         * crashing halt, the only thing we can do
+                         * is to give up, loop round again, and
+                         * hope to randomly avoid making whatever
+                         * type of move just caused this failure.
+                         */
+                        continue;
+                    }
+
+                    /*
+                     * Now use bfs to fill in the tc section as
+                     * colour c. We use `list' to store the set of
+                     * squares we have to process.
+                     */
+                    i = j = 0;
+                    assert(fillstart >= 0);
+                    list[i++] = fillstart;
+#ifdef OUTPUT_SOLUTION
+                    printf("M");
+#endif
+                    while (j < i) {
+                        k = list[j];
+                        x = k % w;
+                        y = k / w;
+#ifdef OUTPUT_SOLUTION
+                        printf("%s%d", j ? "," : "", k);
+#endif
+                        j++;
+
+                        assert(grid2[k] == tc);
+                        grid2[k] = c;
+
+                        if (x > 0 && grid2[k-1] == tc)
+                            list[i++] = k-1;
+                        if (x+1 < w && grid2[k+1] == tc)
+                            list[i++] = k+1;
+                        if (y > 0 && grid2[k-w] == tc)
+                            list[i++] = k-w;
+                        if (y+1 < h && grid2[k+w] == tc)
+                            list[i++] = k+w;
+                    }
+#ifdef OUTPUT_SOLUTION
+                    printf("\n");
+#endif
+
+                    /*
+                     * Check that we've filled the same number of
+                     * tc squares as we originally found.
+                     */
+                    assert(j == ntc);
+                }
+
+                memcpy(grid, grid2, wh * sizeof(int));
+
+                break;                /* done it! */
+            }
+
+#ifdef GENERATION_DIAGNOSTICS
+            {
+                int x,y;
+                printf("n=%d\n", n);
+                for (y = 0; y < h; y++) {
+                    for (x = 0; x < w; x++) {
+                        if (grid[y*w+x] == 0)
+                            printf("-");
+                        else
+                            printf("%d", grid[y*w+x]);
+                    }
+                    printf("\n");
+                }
+            }
+#endif
+
+            if (n < 0)
+                break;
+        }
+
+        ok = TRUE;
+        for (i = 0; i < wh; i++)
+            if (grid[i] == 0) {
+                ok = FALSE;
+                failures++;
+#if defined GENERATION_DIAGNOSTICS || defined SHOW_INCOMPLETE
+                {
+                    int x,y;
+                    printf("incomplete grid:\n");
+                    for (y = 0; y < h; y++) {
+                        for (x = 0; x < w; x++) {
+                            if (grid[y*w+x] == 0)
+                                printf("-");
+                            else
+                                printf("%d", grid[y*w+x]);
+                        }
+                        printf("\n");
+                    }
+                }
+#endif
+                break;
+            }
+
+    } while (!ok);
+
+#if defined GENERATION_DIAGNOSTICS || defined COUNT_FAILURES
+    printf("%d failures\n", failures);
+#endif
+#ifdef GENERATION_DIAGNOSTICS
+    {
+        int x,y;
+        printf("final grid:\n");
+        for (y = 0; y < h; y++) {
+            for (x = 0; x < w; x++) {
+                printf("%d", grid[y*w+x]);
+            }
+            printf("\n");
+        }
+    }
+#endif
+
+    sfree(grid2);
+    sfree(list);
+}
+
+/*
+ * Not-guaranteed-soluble grid generator; kept as a legacy, and in
+ * case someone finds the slightly odd quality of the guaranteed-
+ * soluble grids to be aesthetically displeasing or finds its CPU
+ * utilisation to be excessive.
+ */
+static void gen_grid_random(int w, int h, int nc, int *grid, random_state *rs)
+{
+    int i, j, c;
+    int n = w * h;
+
+    for (i = 0; i < n; i++)
+       grid[i] = 0;
+
+    /*
+     * Our sole concession to not gratuitously generating insoluble
+     * grids is to ensure we have at least two of every colour.
+     */
+    for (c = 1; c <= nc; c++) {
+       for (j = 0; j < 2; j++) {
+           do {
+               i = (int)random_upto(rs, n);
+           } while (grid[i] != 0);
+           grid[i] = c;
+       }
+    }
+
+    /*
+     * Fill in the rest of the grid at random.
+     */
+    for (i = 0; i < n; i++) {
+       if (grid[i] == 0)
+           grid[i] = (int)random_upto(rs, nc)+1;
+    }
+}
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                          char **aux, int interactive)
+{
+    char *ret;
+    int n, i, retlen, *tiles;
+
+    n = params->w * params->h;
+    tiles = snewn(n, int);
+
+    if (params->soluble)
+       gen_grid(params->w, params->h, params->ncols, tiles, rs);
+    else
+       gen_grid_random(params->w, params->h, params->ncols, tiles, rs);
+
+    ret = NULL;
+    retlen = 0;
+    for (i = 0; i < n; i++) {
+       char buf[80];
+       int k;
+
+       k = sprintf(buf, "%d,", tiles[i]);
+       ret = sresize(ret, retlen + k + 1, char);
+       strcpy(ret + retlen, buf);
+       retlen += k;
+    }
+    ret[retlen-1] = '\0'; /* delete last comma */
+
+    sfree(tiles);
+    return ret;
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    int area = params->w * params->h, i;
+    const char *p = desc;
+
+    for (i = 0; i < area; i++) {
+       const char *q = p;
+       int n;
+
+       if (!isdigit((unsigned char)*p))
+           return "Not enough numbers in string";
+       while (isdigit((unsigned char)*p)) p++;
+
+       if (i < area-1 && *p != ',')
+           return "Expected comma after number";
+       else if (i == area-1 && *p)
+           return "Excess junk at end of string";
+
+       n = atoi(q);
+       if (n < 0 || n > params->ncols)
+           return "Colour out of range";
+
+       if (*p) p++; /* eat comma */
+    }
+    return NULL;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    game_state *state = snew(game_state);
+    const char *p = desc;
+    int i;
+
+    state->params = *params; /* struct copy */
+    state->n = state->params.w * state->params.h;
+    state->tiles = snewn(state->n, int);
+
+    for (i = 0; i < state->n; i++) {
+       assert(*p);
+       state->tiles[i] = atoi(p);
+       while (*p && *p != ',')
+            p++;
+        if (*p) p++;                   /* eat comma */
+    }
+    state->complete = state->impossible = 0;
+    state->score = 0;
+
+    return state;
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    game_state *ret = snew(game_state);
+
+    *ret = *state; /* structure copy, except... */
+
+    ret->tiles = snewn(state->n, int);
+    memcpy(ret->tiles, state->tiles, state->n * sizeof(int));
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    sfree(state->tiles);
+    sfree(state);
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    return NULL;
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    char *ret, *p;
+    int x, y, maxlen;
+
+    maxlen = state->params.h * (state->params.w + 1);
+    ret = snewn(maxlen+1, char);
+    p = ret;
+
+    for (y = 0; y < state->params.h; y++) {
+       for (x = 0; x < state->params.w; x++) {
+           int t = TILE(state,x,y);
+           if (t <= 0)      *p++ = ' ';
+           else if (t < 10) *p++ = '0'+t;
+           else             *p++ = 'a'+(t-10);
+       }
+       *p++ = '\n';
+    }
+    assert(p - ret == maxlen);
+    *p = '\0';
+    return ret;
+}
+
+struct game_ui {
+    struct game_params params;
+    int *tiles; /* selected-ness only */
+    int nselected;
+    int xsel, ysel, displaysel;
+};
+
+static game_ui *new_ui(const game_state *state)
+{
+    game_ui *ui = snew(game_ui);
+
+    ui->params = state->params; /* structure copy */
+    ui->tiles = snewn(state->n, int);
+    memset(ui->tiles, 0, state->n*sizeof(int));
+    ui->nselected = 0;
+
+    ui->xsel = ui->ysel = ui->displaysel = 0;
+
+    return ui;
+}
+
+static void free_ui(game_ui *ui)
+{
+    sfree(ui->tiles);
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+}
+
+static void sel_clear(game_ui *ui, const game_state *state)
+{
+    int i;
+
+    for (i = 0; i < state->n; i++)
+       ui->tiles[i] &= ~TILE_SELECTED;
+    ui->nselected = 0;
+}
+
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+    sel_clear(ui, newstate);
+
+    /*
+     * If the game state has just changed into an unplayable one
+     * (either completed or impossible), we vanish the keyboard-
+     * control cursor.
+     */
+    if (newstate->complete || newstate->impossible)
+       ui->displaysel = 0;
+}
+
+static char *sel_movedesc(game_ui *ui, const game_state *state)
+{
+    int i;
+    char *ret, *sep, buf[80];
+    int retlen, retsize;
+
+    retsize = 256;
+    ret = snewn(retsize, char);
+    retlen = 0;
+    ret[retlen++] = 'M';
+    sep = "";
+
+    for (i = 0; i < state->n; i++) {
+       if (ui->tiles[i] & TILE_SELECTED) {
+           sprintf(buf, "%s%d", sep, i);
+           sep = ",";
+           if (retlen + (int)strlen(buf) >= retsize) {
+               retsize = retlen + strlen(buf) + 256;
+               ret = sresize(ret, retsize, char);
+           }
+           strcpy(ret + retlen, buf);
+           retlen += strlen(buf);
+
+           ui->tiles[i] &= ~TILE_SELECTED;
+       }
+    }
+    ui->nselected = 0;
+
+    assert(retlen < retsize);
+    ret[retlen++] = '\0';
+    return sresize(ret, retlen, char);
+}
+
+static void sel_expand(game_ui *ui, const game_state *state, int tx, int ty)
+{
+    int ns = 1, nadded, x, y, c;
+
+    TILE(ui,tx,ty) |= TILE_SELECTED;
+    do {
+       nadded = 0;
+
+       for (x = 0; x < state->params.w; x++) {
+           for (y = 0; y < state->params.h; y++) {
+               if (x == tx && y == ty) continue;
+               if (ISSEL(ui,x,y)) continue;
+
+               c = COL(state,x,y);
+               if ((x > 0) &&
+                   ISSEL(ui,x-1,y) && COL(state,x-1,y) == c) {
+                   TILE(ui,x,y) |= TILE_SELECTED;
+                   nadded++;
+                   continue;
+               }
+
+               if ((x+1 < state->params.w) &&
+                   ISSEL(ui,x+1,y) && COL(state,x+1,y) == c) {
+                   TILE(ui,x,y) |= TILE_SELECTED;
+                   nadded++;
+                   continue;
+               }
+
+               if ((y > 0) &&
+                   ISSEL(ui,x,y-1) && COL(state,x,y-1) == c) {
+                   TILE(ui,x,y) |= TILE_SELECTED;
+                   nadded++;
+                   continue;
+               }
+
+               if ((y+1 < state->params.h) &&
+                   ISSEL(ui,x,y+1) && COL(state,x,y+1) == c) {
+                   TILE(ui,x,y) |= TILE_SELECTED;
+                   nadded++;
+                   continue;
+               }
+           }
+       }
+       ns += nadded;
+    } while (nadded > 0);
+
+    if (ns > 1) {
+       ui->nselected = ns;
+    } else {
+       sel_clear(ui, state);
+    }
+}
+
+static int sg_emptycol(game_state *ret, int x)
+{
+    int y;
+    for (y = 0; y < ret->params.h; y++) {
+       if (COL(ret,x,y)) return 0;
+    }
+    return 1;
+}
+
+
+static void sg_snuggle(game_state *ret)
+{
+    int x,y, ndone;
+
+    /* make all unsupported tiles fall down. */
+    do {
+       ndone = 0;
+       for (x = 0; x < ret->params.w; x++) {
+           for (y = ret->params.h-1; y > 0; y--) {
+               if (COL(ret,x,y) != 0) continue;
+               if (COL(ret,x,y-1) != 0) {
+                   SWAPTILE(ret,x,y,x,y-1);
+                   ndone++;
+               }
+           }
+       }
+    } while (ndone);
+
+    /* shuffle all columns as far left as they can go. */
+    do {
+       ndone = 0;
+       for (x = 0; x < ret->params.w-1; x++) {
+           if (sg_emptycol(ret,x) && !sg_emptycol(ret,x+1)) {
+               ndone++;
+               for (y = 0; y < ret->params.h; y++) {
+                   SWAPTILE(ret,x,y,x+1,y);
+               }
+           }
+       }
+    } while (ndone);
+}
+
+static void sg_check(game_state *ret)
+{
+    int x,y, complete = 1, impossible = 1;
+
+    for (x = 0; x < ret->params.w; x++) {
+       for (y = 0; y < ret->params.h; y++) {
+           if (COL(ret,x,y) == 0)
+               continue;
+           complete = 0;
+           if (x+1 < ret->params.w) {
+               if (COL(ret,x,y) == COL(ret,x+1,y))
+                   impossible = 0;
+           }
+           if (y+1 < ret->params.h) {
+               if (COL(ret,x,y) == COL(ret,x,y+1))
+                   impossible = 0;
+           }
+       }
+    }
+    ret->complete = complete;
+    ret->impossible = impossible;
+}
+
+struct game_drawstate {
+    int started, bgcolour;
+    int tileinner, tilegap;
+    int *tiles; /* contains colour and SELECTED. */
+};
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    int tx, ty;
+    char *ret = "";
+
+    ui->displaysel = 0;
+
+    if (button == RIGHT_BUTTON || button == LEFT_BUTTON) {
+       tx = FROMCOORD(x); ty= FROMCOORD(y);
+    } else if (IS_CURSOR_MOVE(button)) {
+       int dx = 0, dy = 0;
+       ui->displaysel = 1;
+       dx = (button == CURSOR_LEFT) ? -1 : ((button == CURSOR_RIGHT) ? +1 : 0);
+       dy = (button == CURSOR_DOWN) ? +1 : ((button == CURSOR_UP)    ? -1 : 0);
+       ui->xsel = (ui->xsel + state->params.w + dx) % state->params.w;
+       ui->ysel = (ui->ysel + state->params.h + dy) % state->params.h;
+       return ret;
+    } else if (IS_CURSOR_SELECT(button)) {
+       ui->displaysel = 1;
+       tx = ui->xsel;
+       ty = ui->ysel;
+    } else
+       return NULL;
+
+    if (tx < 0 || tx >= state->params.w || ty < 0 || ty >= state->params.h)
+       return NULL;
+    if (COL(state, tx, ty) == 0) return NULL;
+
+    if (ISSEL(ui,tx,ty)) {
+       if (button == RIGHT_BUTTON || button == CURSOR_SELECT2)
+           sel_clear(ui, state);
+       else
+           ret = sel_movedesc(ui, state);
+    } else {
+       sel_clear(ui, state); /* might be no-op */
+       sel_expand(ui, state, tx, ty);
+    }
+
+    return ret;
+}
+
+static game_state *execute_move(const game_state *from, const char *move)
+{
+    int i, n;
+    game_state *ret;
+
+    if (move[0] == 'M') {
+       ret = dup_game(from);
+
+       n = 0;
+       move++;
+
+       while (*move) {
+           i = atoi(move);
+           if (i < 0 || i >= ret->n) {
+               free_game(ret);
+               return NULL;
+           }
+           n++;
+           ret->tiles[i] = 0;
+
+           while (*move && isdigit((unsigned char)*move)) move++;
+           if (*move == ',') move++;
+       }
+
+       ret->score += npoints(&ret->params, n);
+
+       sg_snuggle(ret); /* shifts blanks down and to the left */
+       sg_check(ret);   /* checks for completeness or impossibility */
+
+       return ret;
+    } else
+       return NULL;                   /* couldn't parse move string */
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilegap = 2;
+    ds->tileinner = tilesize - ds->tilegap;
+}
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    /* Ick: fake up tile size variables for macro expansion purposes */
+    game_drawstate ads, *ds = &ads;
+    game_set_size(NULL, ds, params, tilesize);
+
+    *x = TILE_SIZE * params->w + 2 * BORDER - TILE_GAP;
+    *y = TILE_SIZE * params->h + 2 * BORDER - TILE_GAP;
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+
+    frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
+
+    ret[COL_1 * 3 + 0] = 0.0F;
+    ret[COL_1 * 3 + 1] = 0.0F;
+    ret[COL_1 * 3 + 2] = 1.0F;
+
+    ret[COL_2 * 3 + 0] = 0.0F;
+    ret[COL_2 * 3 + 1] = 0.5F;
+    ret[COL_2 * 3 + 2] = 0.0F;
+
+    ret[COL_3 * 3 + 0] = 1.0F;
+    ret[COL_3 * 3 + 1] = 0.0F;
+    ret[COL_3 * 3 + 2] = 0.0F;
+
+    ret[COL_4 * 3 + 0] = 1.0F;
+    ret[COL_4 * 3 + 1] = 1.0F;
+    ret[COL_4 * 3 + 2] = 0.0F;
+
+    ret[COL_5 * 3 + 0] = 1.0F;
+    ret[COL_5 * 3 + 1] = 0.0F;
+    ret[COL_5 * 3 + 2] = 1.0F;
+
+    ret[COL_6 * 3 + 0] = 0.0F;
+    ret[COL_6 * 3 + 1] = 1.0F;
+    ret[COL_6 * 3 + 2] = 1.0F;
+
+    ret[COL_7 * 3 + 0] = 0.5F;
+    ret[COL_7 * 3 + 1] = 0.5F;
+    ret[COL_7 * 3 + 2] = 1.0F;
+
+    ret[COL_8 * 3 + 0] = 0.5F;
+    ret[COL_8 * 3 + 1] = 1.0F;
+    ret[COL_8 * 3 + 2] = 0.5F;
+
+    ret[COL_9 * 3 + 0] = 1.0F;
+    ret[COL_9 * 3 + 1] = 0.5F;
+    ret[COL_9 * 3 + 2] = 0.5F;
+
+    ret[COL_IMPOSSIBLE * 3 + 0] = 0.0F;
+    ret[COL_IMPOSSIBLE * 3 + 1] = 0.0F;
+    ret[COL_IMPOSSIBLE * 3 + 2] = 0.0F;
+
+    ret[COL_SEL * 3 + 0] = 1.0F;
+    ret[COL_SEL * 3 + 1] = 1.0F;
+    ret[COL_SEL * 3 + 2] = 1.0F;
+
+    ret[COL_HIGHLIGHT * 3 + 0] = 1.0F;
+    ret[COL_HIGHLIGHT * 3 + 1] = 1.0F;
+    ret[COL_HIGHLIGHT * 3 + 2] = 1.0F;
+
+    ret[COL_LOWLIGHT * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 2.0F / 3.0F;
+    ret[COL_LOWLIGHT * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 2.0F / 3.0F;
+    ret[COL_LOWLIGHT * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] * 2.0F / 3.0F;
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    struct game_drawstate *ds = snew(struct game_drawstate);
+    int i;
+
+    ds->started = 0;
+    ds->tileinner = ds->tilegap = 0;   /* not decided yet */
+    ds->tiles = snewn(state->n, int);
+    ds->bgcolour = -1;
+    for (i = 0; i < state->n; i++)
+       ds->tiles[i] = -1;
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds->tiles);
+    sfree(ds);
+}
+
+/* Drawing routing for the tile at (x,y) is responsible for drawing
+ * itself and the gaps to its right and below. If we're the same colour
+ * as the tile to our right, then we fill in the gap; ditto below, and if
+ * both then we fill the teeny tiny square in the corner as well.
+ */
+
+static void tile_redraw(drawing *dr, game_drawstate *ds,
+                       int x, int y, int dright, int dbelow,
+                        int tile, int bgcolour)
+{
+    int outer = bgcolour, inner = outer, col = tile & TILE_COLMASK;
+
+    if (col) {
+       if (tile & TILE_IMPOSSIBLE) {
+           outer = col;
+           inner = COL_IMPOSSIBLE;
+       } else if (tile & TILE_SELECTED) {
+           outer = COL_SEL;
+           inner = col;
+       } else {
+           outer = inner = col;
+       }
+    }
+    draw_rect(dr, COORD(x), COORD(y), TILE_INNER, TILE_INNER, outer);
+    draw_rect(dr, COORD(x)+TILE_INNER/4, COORD(y)+TILE_INNER/4,
+             TILE_INNER/2, TILE_INNER/2, inner);
+
+    if (dright)
+       draw_rect(dr, COORD(x)+TILE_INNER, COORD(y), TILE_GAP, TILE_INNER,
+                 (tile & TILE_JOINRIGHT) ? outer : bgcolour);
+    if (dbelow)
+       draw_rect(dr, COORD(x), COORD(y)+TILE_INNER, TILE_INNER, TILE_GAP,
+                 (tile & TILE_JOINDOWN) ? outer : bgcolour);
+    if (dright && dbelow)
+       draw_rect(dr, COORD(x)+TILE_INNER, COORD(y)+TILE_INNER, TILE_GAP, TILE_GAP,
+                 (tile & TILE_JOINDIAG) ? outer : bgcolour);
+
+    if (tile & TILE_HASSEL) {
+       int sx = COORD(x)+2, sy = COORD(y)+2, ssz = TILE_INNER-5;
+       int scol = (outer == COL_SEL) ? COL_LOWLIGHT : COL_HIGHLIGHT;
+       draw_line(dr, sx,     sy,     sx+ssz, sy,     scol);
+       draw_line(dr, sx+ssz, sy,     sx+ssz, sy+ssz, scol);
+       draw_line(dr, sx+ssz, sy+ssz, sx,     sy+ssz, scol);
+       draw_line(dr, sx,     sy+ssz, sx,     sy,     scol);
+    }
+
+    draw_update(dr, COORD(x), COORD(y), TILE_SIZE, TILE_SIZE);
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int bgcolour, x, y;
+
+    /* This was entirely cloned from fifteen.c; it should probably be
+     * moved into some generic 'draw-recessed-rectangle' utility fn. */
+    if (!ds->started) {
+       int coords[10];
+
+       draw_rect(dr, 0, 0,
+                 TILE_SIZE * state->params.w + 2 * BORDER,
+                 TILE_SIZE * state->params.h + 2 * BORDER, COL_BACKGROUND);
+       draw_update(dr, 0, 0,
+                   TILE_SIZE * state->params.w + 2 * BORDER,
+                   TILE_SIZE * state->params.h + 2 * BORDER);
+
+       /*
+        * Recessed area containing the whole puzzle.
+        */
+       coords[0] = COORD(state->params.w) + HIGHLIGHT_WIDTH - 1 - TILE_GAP;
+       coords[1] = COORD(state->params.h) + HIGHLIGHT_WIDTH - 1 - TILE_GAP;
+       coords[2] = COORD(state->params.w) + HIGHLIGHT_WIDTH - 1 - TILE_GAP;
+       coords[3] = COORD(0) - HIGHLIGHT_WIDTH;
+       coords[4] = coords[2] - TILE_SIZE;
+       coords[5] = coords[3] + TILE_SIZE;
+       coords[8] = COORD(0) - HIGHLIGHT_WIDTH;
+       coords[9] = COORD(state->params.h) + HIGHLIGHT_WIDTH - 1 - TILE_GAP;
+       coords[6] = coords[8] + TILE_SIZE;
+       coords[7] = coords[9] - TILE_SIZE;
+       draw_polygon(dr, coords, 5, COL_HIGHLIGHT, COL_HIGHLIGHT);
+
+       coords[1] = COORD(0) - HIGHLIGHT_WIDTH;
+       coords[0] = COORD(0) - HIGHLIGHT_WIDTH;
+       draw_polygon(dr, coords, 5, COL_LOWLIGHT, COL_LOWLIGHT);
+
+       ds->started = 1;
+    }
+
+    if (flashtime > 0.0) {
+       int frame = (int)(flashtime / FLASH_FRAME);
+       bgcolour = (frame % 2 ? COL_LOWLIGHT : COL_HIGHLIGHT);
+    } else
+       bgcolour = COL_BACKGROUND;
+
+    for (x = 0; x < state->params.w; x++) {
+       for (y = 0; y < state->params.h; y++) {
+           int i = (state->params.w * y) + x;
+           int col = COL(state,x,y), tile = col;
+           int dright = (x+1 < state->params.w);
+           int dbelow = (y+1 < state->params.h);
+
+           tile |= ISSEL(ui,x,y);
+           if (state->impossible)
+               tile |= TILE_IMPOSSIBLE;
+           if (dright && COL(state,x+1,y) == col)
+               tile |= TILE_JOINRIGHT;
+           if (dbelow && COL(state,x,y+1) == col)
+               tile |= TILE_JOINDOWN;
+           if ((tile & TILE_JOINRIGHT) && (tile & TILE_JOINDOWN) &&
+               COL(state,x+1,y+1) == col)
+               tile |= TILE_JOINDIAG;
+
+           if (ui->displaysel && ui->xsel == x && ui->ysel == y)
+               tile |= TILE_HASSEL;
+
+           /* For now we're never expecting oldstate at all (because we have
+            * no animation); when we do we might well want to be looking
+            * at the tile colours from oldstate, not state. */
+           if ((oldstate && COL(oldstate,x,y) != col) ||
+               (ds->bgcolour != bgcolour) ||
+               (tile != ds->tiles[i])) {
+               tile_redraw(dr, ds, x, y, dright, dbelow, tile, bgcolour);
+               ds->tiles[i] = tile;
+           }
+       }
+    }
+    ds->bgcolour = bgcolour;
+
+    {
+       char status[255], score[80];
+
+       sprintf(score, "Score: %d", state->score);
+
+       if (state->complete)
+           sprintf(status, "COMPLETE! %s", score);
+       else if (state->impossible)
+           sprintf(status, "Cannot move! %s", score);
+       else if (ui->nselected)
+           sprintf(status, "%s  Selected: %d (%d)",
+                   score, ui->nselected, npoints(&state->params, ui->nselected));
+       else
+           sprintf(status, "%s", score);
+       status_bar(dr, status);
+    }
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return 0.0F;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    if ((!oldstate->complete && newstate->complete) ||
+        (!oldstate->impossible && newstate->impossible))
+       return 2 * FLASH_FRAME;
+    else
+       return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    /*
+     * Dead-end situations are assumed to be rescuable by Undo, so we
+     * don't bother to identify them and return -1.
+     */
+    return state->complete ? +1 : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+}
+
+#ifdef COMBINED
+#define thegame samegame
+#endif
+
+const struct game thegame = {
+    "Same Game", "games.samegame", "samegame",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    FALSE, solve_game,
+    TRUE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    FALSE, FALSE, game_print_size, game_print,
+    TRUE,                             /* wants_statusbar */
+    FALSE, game_timing_state,
+    0,                                /* flags */
+};
diff --git a/signpost.R b/signpost.R
new file mode 100644 (file)
index 0000000..09ea367
--- /dev/null
@@ -0,0 +1,23 @@
+# -*- makefile -*-
+
+SIGNPOST_EXTRA = dsf
+
+signpost : [X] GTK COMMON signpost SIGNPOST_EXTRA signpost-icon|no-icon
+signpost : [G] WINDOWS COMMON signpost SIGNPOST_EXTRA signpost.res|noicon.res
+
+signpostsolver : [U] signpost[STANDALONE_SOLVER] SIGNPOST_EXTRA STANDALONE m.lib
+signpostsolver : [C] signpost[STANDALONE_SOLVER] SIGNPOST_EXTRA STANDALONE
+
+ALL += signpost[COMBINED] SIGNPOST_EXTRA
+
+!begin am gtk
+GAMES += signpost
+!end
+
+!begin >list.c
+    A(signpost) \
+!end
+
+!begin >gamedesc.txt
+signpost:signpost.exe:Signpost:Square-connecting puzzle:Connect the squares into a path following the arrows.
+!end
diff --git a/signpost.c b/signpost.c
new file mode 100644 (file)
index 0000000..2e2dff2
--- /dev/null
@@ -0,0 +1,2480 @@
+/*
+ * signpost.c: implementation of the janko game 'arrow path'
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+
+#define PREFERRED_TILE_SIZE 48
+#define TILE_SIZE (ds->tilesize)
+#define BLITTER_SIZE TILE_SIZE
+#define BORDER    (TILE_SIZE / 2)
+
+#define COORD(x)  ( (x) * TILE_SIZE + BORDER )
+#define FROMCOORD(x)  ( ((x) - BORDER + TILE_SIZE) / TILE_SIZE - 1 )
+
+#define INGRID(s,x,y) ((x) >= 0 && (x) < (s)->w && (y) >= 0 && (y) < (s)->h)
+
+#define FLASH_SPIN 0.7F
+
+#define NBACKGROUNDS 16
+
+enum {
+    COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT,
+    COL_GRID, COL_CURSOR, COL_ERROR, COL_DRAG_ORIGIN,
+    COL_ARROW, COL_ARROW_BG_DIM,
+    COL_NUMBER, COL_NUMBER_SET, COL_NUMBER_SET_MID,
+    COL_B0,                             /* background colours */
+    COL_M0 =   COL_B0 + 1*NBACKGROUNDS, /* mid arrow colours */
+    COL_D0 =   COL_B0 + 2*NBACKGROUNDS, /* dim arrow colours */
+    COL_X0 =   COL_B0 + 3*NBACKGROUNDS, /* dim arrow colours */
+    NCOLOURS = COL_B0 + 4*NBACKGROUNDS
+};
+
+struct game_params {
+    int w, h;
+    int force_corner_start;
+};
+
+enum { DIR_N = 0, DIR_NE, DIR_E, DIR_SE, DIR_S, DIR_SW, DIR_W, DIR_NW, DIR_MAX };
+static const char *dirstrings[8] = { "N ", "NE", "E ", "SE", "S ", "SW", "W ", "NW" };
+
+static const int dxs[DIR_MAX] = {  0,  1, 1, 1, 0, -1, -1, -1 };
+static const int dys[DIR_MAX] = { -1, -1, 0, 1, 1,  1,  0, -1 };
+
+#define DIR_OPPOSITE(d) ((d+4)%8)
+
+struct game_state {
+    int w, h, n;
+    int completed, used_solve, impossible;
+    int *dirs;                  /* direction enums, size n */
+    int *nums;                  /* numbers, size n */
+    unsigned int *flags;        /* flags, size n */
+    int *next, *prev;           /* links to other cell indexes, size n (-1 absent) */
+    int *dsf;                   /* connects regions with a dsf. */
+    int *numsi;                 /* for each number, which index is it in? (-1 absent) */
+};
+
+#define FLAG_IMMUTABLE  1
+#define FLAG_ERROR      2
+
+/* --- Generally useful functions --- */
+
+#define ISREALNUM(state, num) ((num) > 0 && (num) <= (state)->n)
+
+static int whichdir(int fromx, int fromy, int tox, int toy)
+{
+    int i, dx, dy;
+
+    dx = tox - fromx;
+    dy = toy - fromy;
+
+    if (dx && dy && abs(dx) != abs(dy)) return -1;
+
+    if (dx) dx = dx / abs(dx); /* limit to (-1, 0, 1) */
+    if (dy) dy = dy / abs(dy); /* ditto */
+
+    for (i = 0; i < DIR_MAX; i++) {
+        if (dx == dxs[i] && dy == dys[i]) return i;
+    }
+    return -1;
+}
+
+static int whichdiri(game_state *state, int fromi, int toi)
+{
+    int w = state->w;
+    return whichdir(fromi%w, fromi/w, toi%w, toi/w);
+}
+
+static int ispointing(const game_state *state, int fromx, int fromy,
+                      int tox, int toy)
+{
+    int w = state->w, dir = state->dirs[fromy*w+fromx];
+
+    /* (by convention) squares do not point to themselves. */
+    if (fromx == tox && fromy == toy) return 0;
+
+    /* the final number points to nothing. */
+    if (state->nums[fromy*w + fromx] == state->n) return 0;
+
+    while (1) {
+        if (!INGRID(state, fromx, fromy)) return 0;
+        if (fromx == tox && fromy == toy) return 1;
+        fromx += dxs[dir]; fromy += dys[dir];
+    }
+    return 0; /* not reached */
+}
+
+static int ispointingi(game_state *state, int fromi, int toi)
+{
+    int w = state->w;
+    return ispointing(state, fromi%w, fromi/w, toi%w, toi/w);
+}
+
+/* Taking the number 'num', work out the gap between it and the next
+ * available number up or down (depending on d). Return 1 if the region
+ * at (x,y) will fit in that gap, or 0 otherwise. */
+static int move_couldfit(const game_state *state, int num, int d, int x, int y)
+{
+    int n, gap, i = y*state->w+x, sz;
+
+    assert(d != 0);
+    /* The 'gap' is the number of missing numbers in the grid between
+     * our number and the next one in the sequence (up or down), or
+     * the end of the sequence (if we happen not to have 1/n present) */
+    for (n = num + d, gap = 0;
+         ISREALNUM(state, n) && state->numsi[n] == -1;
+         n += d, gap++) ; /* empty loop */
+
+    if (gap == 0) {
+        /* no gap, so the only allowable move is that that directly
+         * links the two numbers. */
+        n = state->nums[i];
+        return (n == num+d) ? 0 : 1;
+    }
+    if (state->prev[i] == -1 && state->next[i] == -1)
+        return 1; /* single unconnected square, always OK */
+
+    sz = dsf_size(state->dsf, i);
+    return (sz > gap) ? 0 : 1;
+}
+
+static int isvalidmove(const game_state *state, int clever,
+                       int fromx, int fromy, int tox, int toy)
+{
+    int w = state->w, from = fromy*w+fromx, to = toy*w+tox;
+    int nfrom, nto;
+
+    if (!INGRID(state, fromx, fromy) || !INGRID(state, tox, toy))
+        return 0;
+
+    /* can only move where we point */
+    if (!ispointing(state, fromx, fromy, tox, toy))
+        return 0;
+
+    nfrom = state->nums[from]; nto = state->nums[to];
+
+    /* can't move _from_ the preset final number, or _to_ the preset 1. */
+    if (((nfrom == state->n) && (state->flags[from] & FLAG_IMMUTABLE)) ||
+        ((nto   == 1)        && (state->flags[to]   & FLAG_IMMUTABLE)))
+        return 0;
+
+    /* can't create a new connection between cells in the same region
+     * as that would create a loop. */
+    if (dsf_canonify(state->dsf, from) == dsf_canonify(state->dsf, to))
+        return 0;
+
+    /* if both cells are actual numbers, can't drag if we're not
+     * one digit apart. */
+    if (ISREALNUM(state, nfrom) && ISREALNUM(state, nto)) {
+        if (nfrom != nto-1)
+            return 0;
+    } else if (clever && ISREALNUM(state, nfrom)) {
+        if (!move_couldfit(state, nfrom, +1, tox, toy))
+            return 0;
+    } else if (clever && ISREALNUM(state, nto)) {
+        if (!move_couldfit(state, nto, -1, fromx, fromy))
+            return 0;
+    }
+
+    return 1;
+}
+
+static void makelink(game_state *state, int from, int to)
+{
+    if (state->next[from] != -1)
+        state->prev[state->next[from]] = -1;
+    state->next[from] = to;
+
+    if (state->prev[to] != -1)
+        state->next[state->prev[to]] = -1;
+    state->prev[to] = from;
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    if (params->w * params->h >= 100) return 0;
+    return 1;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    int len = state->h * 2 * (4*state->w + 1) + state->h + 2;
+    int x, y, i, num, n, set;
+    char *ret, *p;
+
+    p = ret = snewn(len, char);
+
+    for (y = 0; y < state->h; y++) {
+        for (x = 0; x < state->h; x++) {
+            i = y*state->w+x;
+            *p++ = dirstrings[state->dirs[i]][0];
+            *p++ = dirstrings[state->dirs[i]][1];
+            *p++ = (state->flags[i] & FLAG_IMMUTABLE) ? 'I' : ' ';
+            *p++ = ' ';
+        }
+        *p++ = '\n';
+        for (x = 0; x < state->h; x++) {
+            i = y*state->w+x;
+            num = state->nums[i];
+            if (num == 0) {
+                *p++ = ' ';
+                *p++ = ' ';
+                *p++ = ' ';
+            } else {
+                n = num % (state->n+1);
+                set = num / (state->n+1);
+
+                assert(n <= 99); /* two digits only! */
+
+                if (set != 0)
+                    *p++ = set+'a'-1;
+
+                *p++ = (n >= 10) ? ('0' + (n/10)) : ' ';
+                *p++ = '0' + (n%10);
+
+                if (set == 0)
+                    *p++ = ' ';
+            }
+            *p++ = ' ';
+        }
+        *p++ = '\n';
+        *p++ = '\n';
+    }
+    *p++ = '\0';
+
+    return ret;
+}
+
+static void debug_state(const char *desc, game_state *state)
+{
+#ifdef DEBUGGING
+    char *dbg;
+    if (state->n >= 100) {
+        debug(("[ no game_text_format for this size ]"));
+        return;
+    }
+    dbg = game_text_format(state);
+    debug(("%s\n%s", desc, dbg));
+    sfree(dbg);
+#endif
+}
+
+
+static void strip_nums(game_state *state) {
+    int i;
+    for (i = 0; i < state->n; i++) {
+        if (!(state->flags[i] & FLAG_IMMUTABLE))
+            state->nums[i] = 0;
+    }
+    memset(state->next, -1, state->n*sizeof(int));
+    memset(state->prev, -1, state->n*sizeof(int));
+    memset(state->numsi, -1, (state->n+1)*sizeof(int));
+    dsf_init(state->dsf, state->n);
+}
+
+static int check_nums(game_state *orig, game_state *copy, int only_immutable)
+{
+    int i, ret = 1;
+    assert(copy->n == orig->n);
+    for (i = 0; i < copy->n; i++) {
+        if (only_immutable && !(copy->flags[i] & FLAG_IMMUTABLE)) continue;
+        assert(copy->nums[i] >= 0);
+        assert(copy->nums[i] <= copy->n);
+        if (copy->nums[i] != orig->nums[i]) {
+            debug(("check_nums: (%d,%d) copy=%d, orig=%d.",
+                   i%orig->w, i/orig->w, copy->nums[i], orig->nums[i]));
+            ret = 0;
+        }
+    }
+    return ret;
+}
+
+/* --- Game parameter/presets functions --- */
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+    ret->w = ret->h = 4;
+    ret->force_corner_start = 1;
+
+    return ret;
+}
+
+static const struct game_params signpost_presets[] = {
+  { 4, 4, 1 },
+  { 4, 4, 0 },
+  { 5, 5, 1 },
+  { 5, 5, 0 },
+  { 6, 6, 1 },
+  { 7, 7, 1 }
+};
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params *ret;
+    char buf[80];
+
+    if (i < 0 || i >= lenof(signpost_presets))
+        return FALSE;
+
+    ret = default_params();
+    *ret = signpost_presets[i];
+    *params = ret;
+
+    sprintf(buf, "%dx%d%s", ret->w, ret->h,
+            ret->force_corner_start ? "" : ", free ends");
+    *name = dupstr(buf);
+
+    return TRUE;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+static void decode_params(game_params *ret, char const *string)
+{
+    ret->w = ret->h = atoi(string);
+    while (*string && isdigit((unsigned char)*string)) string++;
+    if (*string == 'x') {
+        string++;
+        ret->h = atoi(string);
+        while (*string && isdigit((unsigned char)*string)) string++;
+    }
+    ret->force_corner_start = 0;
+    if (*string == 'c') {
+        string++;
+        ret->force_corner_start = 1;
+    }
+
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char data[256];
+
+    if (full)
+        sprintf(data, "%dx%d%s", params->w, params->h,
+                params->force_corner_start ? "c" : "");
+    else
+        sprintf(data, "%dx%d", params->w, params->h);
+
+    return dupstr(data);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+    char buf[80];
+
+    ret = snewn(4, config_item);
+
+    ret[0].name = "Width";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->w);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Height";
+    ret[1].type = C_STRING;
+    sprintf(buf, "%d", params->h);
+    ret[1].sval = dupstr(buf);
+    ret[1].ival = 0;
+
+    ret[2].name = "Start and end in corners";
+    ret[2].type = C_BOOLEAN;
+    ret[2].sval = NULL;
+    ret[2].ival = params->force_corner_start;
+
+    ret[3].name = NULL;
+    ret[3].type = C_END;
+    ret[3].sval = NULL;
+    ret[3].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = atoi(cfg[0].sval);
+    ret->h = atoi(cfg[1].sval);
+    ret->force_corner_start = cfg[2].ival;
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    if (params->w < 1) return "Width must be at least one";
+    if (params->h < 1) return "Height must be at least one";
+    if (full && params->w == 1 && params->h == 1)
+       /* The UI doesn't let us move these from unsolved to solved,
+        * so we disallow generating (but not playing) them. */
+       return "Width and height cannot both be one";
+    return NULL;
+}
+
+/* --- Game description string generation and unpicking --- */
+
+static void blank_game_into(game_state *state)
+{
+    memset(state->dirs, 0, state->n*sizeof(int));
+    memset(state->nums, 0, state->n*sizeof(int));
+    memset(state->flags, 0, state->n*sizeof(unsigned int));
+    memset(state->next, -1, state->n*sizeof(int));
+    memset(state->prev, -1, state->n*sizeof(int));
+    memset(state->numsi, -1, (state->n+1)*sizeof(int));
+}
+
+static game_state *blank_game(int w, int h)
+{
+    game_state *state = snew(game_state);
+
+    memset(state, 0, sizeof(game_state));
+    state->w = w;
+    state->h = h;
+    state->n = w*h;
+
+    state->dirs  = snewn(state->n, int);
+    state->nums  = snewn(state->n, int);
+    state->flags = snewn(state->n, unsigned int);
+    state->next  = snewn(state->n, int);
+    state->prev  = snewn(state->n, int);
+    state->dsf = snew_dsf(state->n);
+    state->numsi  = snewn(state->n+1, int);
+
+    blank_game_into(state);
+
+    return state;
+}
+
+static void dup_game_to(game_state *to, const game_state *from)
+{
+    to->completed = from->completed;
+    to->used_solve = from->used_solve;
+    to->impossible = from->impossible;
+
+    memcpy(to->dirs, from->dirs, to->n*sizeof(int));
+    memcpy(to->flags, from->flags, to->n*sizeof(unsigned int));
+    memcpy(to->nums, from->nums, to->n*sizeof(int));
+
+    memcpy(to->next, from->next, to->n*sizeof(int));
+    memcpy(to->prev, from->prev, to->n*sizeof(int));
+
+    memcpy(to->dsf, from->dsf, to->n*sizeof(int));
+    memcpy(to->numsi, from->numsi, (to->n+1)*sizeof(int));
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    game_state *ret = blank_game(state->w, state->h);
+    dup_game_to(ret, state);
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    sfree(state->dirs);
+    sfree(state->nums);
+    sfree(state->flags);
+    sfree(state->next);
+    sfree(state->prev);
+    sfree(state->dsf);
+    sfree(state->numsi);
+    sfree(state);
+}
+
+static void unpick_desc(const game_params *params, const char *desc,
+                        game_state **sout, char **mout)
+{
+    game_state *state = blank_game(params->w, params->h);
+    char *msg = NULL, c;
+    int num = 0, i = 0;
+
+    while (*desc) {
+        if (i >= state->n) {
+            msg = "Game description longer than expected";
+            goto done;
+        }
+
+        c = *desc;
+        if (isdigit((unsigned char)c)) {
+            num = (num*10) + (int)(c-'0');
+            if (num > state->n) {
+                msg = "Number too large";
+                goto done;
+            }
+        } else if ((c-'a') >= 0 && (c-'a') < DIR_MAX) {
+            state->nums[i] = num;
+            state->flags[i] = num ? FLAG_IMMUTABLE : 0;
+            num = 0;
+
+            state->dirs[i] = c - 'a';
+            i++;
+        } else if (!*desc) {
+            msg = "Game description shorter than expected";
+            goto done;
+        } else {
+            msg = "Game description contains unexpected characters";
+            goto done;
+        }
+        desc++;
+    }
+    if (i < state->n) {
+        msg = "Game description shorter than expected";
+        goto done;
+    }
+
+done:
+    if (msg) { /* sth went wrong. */
+        if (mout) *mout = msg;
+        free_game(state);
+    } else {
+        if (mout) *mout = NULL;
+        if (sout) *sout = state;
+        else free_game(state);
+    }
+}
+
+static char *generate_desc(game_state *state, int issolve)
+{
+    char *ret, buf[80];
+    int retlen, i, k;
+
+    ret = NULL; retlen = 0;
+    if (issolve) {
+        ret = sresize(ret, 2, char);
+        ret[0] = 'S'; ret[1] = '\0';
+        retlen += 1;
+    }
+    for (i = 0; i < state->n; i++) {
+        if (state->nums[i])
+            k = sprintf(buf, "%d%c", state->nums[i], (int)(state->dirs[i]+'a'));
+        else
+            k = sprintf(buf, "%c", (int)(state->dirs[i]+'a'));
+        ret = sresize(ret, retlen + k + 1, char);
+        strcpy(ret + retlen, buf);
+        retlen += k;
+    }
+    return ret;
+}
+
+/* --- Game generation --- */
+
+/* Fills in preallocated arrays ai (indices) and ad (directions)
+ * showing all non-numbered cells adjacent to index i, returns length */
+/* This function has been somewhat optimised... */
+static int cell_adj(game_state *state, int i, int *ai, int *ad)
+{
+    int n = 0, a, x, y, sx, sy, dx, dy, newi;
+    int w = state->w, h = state->h;
+
+    sx = i % w; sy = i / w;
+
+    for (a = 0; a < DIR_MAX; a++) {
+        x = sx; y = sy;
+        dx = dxs[a]; dy = dys[a];
+        while (1) {
+            x += dx; y += dy;
+            if (x < 0 || y < 0 || x >= w || y >= h) break;
+
+            newi = y*w + x;
+            if (state->nums[newi] == 0) {
+                ai[n] = newi;
+                ad[n] = a;
+                n++;
+            }
+        }
+    }
+    return n;
+}
+
+static int new_game_fill(game_state *state, random_state *rs,
+                         int headi, int taili)
+{
+    int nfilled, an, ret = 0, j;
+    int *aidx, *adir;
+
+    aidx = snewn(state->n, int);
+    adir = snewn(state->n, int);
+
+    debug(("new_game_fill: headi=%d, taili=%d.", headi, taili));
+
+    memset(state->nums, 0, state->n*sizeof(int));
+
+    state->nums[headi] = 1;
+    state->nums[taili] = state->n;
+
+    state->dirs[taili] = 0;
+    nfilled = 2;
+    assert(state->n > 1);
+
+    while (nfilled < state->n) {
+        /* Try and expand _from_ headi; keep going if there's only one
+         * place to go to. */
+        an = cell_adj(state, headi, aidx, adir);
+        do {
+            if (an == 0) goto done;
+            j = random_upto(rs, an);
+            state->dirs[headi] = adir[j];
+            state->nums[aidx[j]] = state->nums[headi] + 1;
+            nfilled++;
+            headi = aidx[j];
+            an = cell_adj(state, headi, aidx, adir);
+        } while (an == 1);
+
+       if (nfilled == state->n) break;
+
+        /* Try and expand _to_ taili; keep going if there's only one
+         * place to go to. */
+        an = cell_adj(state, taili, aidx, adir);
+        do {
+            if (an == 0) goto done;
+            j = random_upto(rs, an);
+            state->dirs[aidx[j]] = DIR_OPPOSITE(adir[j]);
+            state->nums[aidx[j]] = state->nums[taili] - 1;
+            nfilled++;
+            taili = aidx[j];
+            an = cell_adj(state, taili, aidx, adir);
+        } while (an == 1);
+    }
+    /* If we get here we have headi and taili set but unconnected
+     * by direction: we need to set headi's direction so as to point
+     * at taili. */
+    state->dirs[headi] = whichdiri(state, headi, taili);
+
+    /* it could happen that our last two weren't in line; if that's the
+     * case, we have to start again. */
+    if (state->dirs[headi] != -1) ret = 1;
+
+done:
+    sfree(aidx);
+    sfree(adir);
+    return ret;
+}
+
+/* Better generator: with the 'generate, sprinkle numbers, solve,
+ * repeat' algorithm we're _never_ generating anything greater than
+ * 6x6, and spending all of our time in new_game_fill (and very little
+ * in solve_state).
+ *
+ * So, new generator steps:
+   * generate the grid, at random (same as now). Numbers 1 and N get
+      immutable flag immediately.
+   * squirrel that away for the solved state.
+   *
+   * (solve:) Try and solve it.
+   * If we solved it, we're done:
+     * generate the description from current immutable numbers,
+     * free stuff that needs freeing,
+     * return description + solved state.
+   * If we didn't solve it:
+     * count #tiles in state we've made deductions about.
+     * while (1):
+       * randomise a scratch array.
+       * for each index in scratch (in turn):
+         * if the cell isn't empty, continue (through scratch array)
+         * set number + immutable in state.
+         * try and solve state.
+         * if we've solved it, we're done.
+         * otherwise, count #tiles. If it's more than we had before:
+           * good, break from this loop and re-randomise.
+         * otherwise (number didn't help):
+           * remove number and try next in scratch array.
+       * if we've got to the end of the scratch array, no luck:
+          free everything we need to, and go back to regenerate the grid.
+   */
+
+static int solve_state(game_state *state);
+
+static void debug_desc(const char *what, game_state *state)
+{
+#if DEBUGGING
+    {
+        char *desc = generate_desc(state, 0);
+        debug(("%s game state: %dx%d:%s", what, state->w, state->h, desc));
+        sfree(desc);
+    }
+#endif
+}
+
+/* Expects a fully-numbered game_state on input, and makes sure
+ * FLAG_IMMUTABLE is only set on those numbers we need to solve
+ * (as for a real new-game); returns 1 if it managed
+ * this (such that it could solve it), or 0 if not. */
+static int new_game_strip(game_state *state, random_state *rs)
+{
+    int *scratch, i, j, ret = 1;
+    game_state *copy = dup_game(state);
+
+    debug(("new_game_strip."));
+
+    strip_nums(copy);
+    debug_desc("Stripped", copy);
+
+    if (solve_state(copy) > 0) {
+        debug(("new_game_strip: soluble immediately after strip."));
+        free_game(copy);
+        return 1;
+    }
+
+    scratch = snewn(state->n, int);
+    for (i = 0; i < state->n; i++) scratch[i] = i;
+    shuffle(scratch, state->n, sizeof(int), rs);
+
+    /* This is scungy. It might just be quick enough.
+     * It goes through, adding set numbers in empty squares
+     * until either we run out of empty squares (in the one
+     * we're half-solving) or else we solve it properly.
+     * NB that we run the entire solver each time, which
+     * strips the grid beforehand; we will save time if we
+     * avoid that. */
+    for (i = 0; i < state->n; i++) {
+        j = scratch[i];
+        if (copy->nums[j] > 0 && copy->nums[j] <= state->n)
+            continue; /* already solved to a real number here. */
+        assert(state->nums[j] <= state->n);
+        debug(("new_game_strip: testing add IMMUTABLE number %d at square (%d,%d).",
+               state->nums[j], j%state->w, j/state->w));
+        copy->nums[j] = state->nums[j];
+        copy->flags[j] |= FLAG_IMMUTABLE;
+        state->flags[j] |= FLAG_IMMUTABLE;
+        debug_state("Copy of state: ", copy);
+        strip_nums(copy);
+        if (solve_state(copy) > 0) goto solved;
+        assert(check_nums(state, copy, 1));
+    }
+    ret = 0;
+    goto done;
+
+solved:
+    debug(("new_game_strip: now solved."));
+    /* Since we added basically at random, try now to remove numbers
+     * and see if we can still solve it; if we can (still), really
+     * remove the number. Make sure we don't remove the anchor numbers
+     * 1 and N. */
+    for (i = 0; i < state->n; i++) {
+        j = scratch[i];
+        if ((state->flags[j] & FLAG_IMMUTABLE) &&
+            (state->nums[j] != 1 && state->nums[j] != state->n)) {
+            debug(("new_game_strip: testing remove IMMUTABLE number %d at square (%d,%d).",
+                  state->nums[j], j%state->w, j/state->w));
+            state->flags[j] &= ~FLAG_IMMUTABLE;
+            dup_game_to(copy, state);
+            strip_nums(copy);
+            if (solve_state(copy) > 0) {
+                assert(check_nums(state, copy, 0));
+                debug(("new_game_strip: OK, removing number"));
+            } else {
+                assert(state->nums[j] <= state->n);
+                debug(("new_game_strip: cannot solve, putting IMMUTABLE back."));
+                copy->nums[j] = state->nums[j];
+                state->flags[j] |= FLAG_IMMUTABLE;
+            }
+        }
+    }
+
+done:
+    debug(("new_game_strip: %ssuccessful.", ret ? "" : "not "));
+    sfree(scratch);
+    free_game(copy);
+    return ret;
+}
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                          char **aux, int interactive)
+{
+    game_state *state = blank_game(params->w, params->h);
+    char *ret;
+    int headi, taili;
+
+    /* this shouldn't happen (validate_params), but let's play it safe */
+    if (params->w == 1 && params->h == 1) return dupstr("1a");
+
+generate:
+    blank_game_into(state);
+
+    /* keep trying until we fill successfully. */
+    do {
+        if (params->force_corner_start) {
+            headi = 0;
+            taili = state->n-1;
+        } else {
+            do {
+                headi = random_upto(rs, state->n);
+                taili = random_upto(rs, state->n);
+            } while (headi == taili);
+        }
+    } while (!new_game_fill(state, rs, headi, taili));
+
+    debug_state("Filled game:", state);
+
+    assert(state->nums[headi] <= state->n);
+    assert(state->nums[taili] <= state->n);
+
+    state->flags[headi] |= FLAG_IMMUTABLE;
+    state->flags[taili] |= FLAG_IMMUTABLE;
+
+    /* This will have filled in directions and _all_ numbers.
+     * Store the game definition for this, as the solved-state. */
+    if (!new_game_strip(state, rs)) {
+        goto generate;
+    }
+    strip_nums(state);
+    {
+        game_state *tosolve = dup_game(state);
+        assert(solve_state(tosolve) > 0);
+        free_game(tosolve);
+    }
+    ret = generate_desc(state, 0);
+    free_game(state);
+    return ret;
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    char *ret = NULL;
+
+    unpick_desc(params, desc, NULL, &ret);
+    return ret;
+}
+
+/* --- Linked-list and numbers array --- */
+
+/* Assuming numbers are always up-to-date, there are only four possibilities
+ * for regions changing after a single valid move:
+ *
+ * 1) two differently-coloured regions being combined (the resulting colouring
+ *     should be based on the larger of the two regions)
+ * 2) a numbered region having a single number added to the start (the
+ *     region's colour will remain, and the numbers will shift by 1)
+ * 3) a numbered region having a single number added to the end (the
+ *     region's colour and numbering remains as-is)
+ * 4) two unnumbered squares being joined (will pick the smallest unused set
+ *     of colours to use for the new region).
+ *
+ * There should never be any complications with regions containing 3 colours
+ * being combined, since two of those colours should have been merged on a
+ * previous move.
+ *
+ * Most of the complications are in ensuring we don't accidentally set two
+ * regions with the same colour (e.g. if a region was split). If this happens
+ * we always try and give the largest original portion the original colour.
+ */
+
+#define COLOUR(a) ((a) / (state->n+1))
+#define START(c) ((c) * (state->n+1))
+
+struct head_meta {
+    int i;      /* position */
+    int sz;     /* size of region */
+    int start;  /* region start number preferred, or 0 if !preference */
+    int preference; /* 0 if we have no preference (and should just pick one) */
+    const char *why;
+};
+
+static void head_number(game_state *state, int i, struct head_meta *head)
+{
+    int off = 0, ss, j = i, c, n, sz;
+
+    /* Insist we really were passed the head of a chain. */
+    assert(state->prev[i] == -1 && state->next[i] != -1);
+
+    head->i = i;
+    head->sz = dsf_size(state->dsf, i);
+    head->why = NULL;
+
+    /* Search through this chain looking for real numbers, checking that
+     * they match up (if there are more than one). */
+    head->preference = 0;
+    while (j != -1) {
+        if (state->flags[j] & FLAG_IMMUTABLE) {
+            ss = state->nums[j] - off;
+            if (!head->preference) {
+                head->start = ss;
+                head->preference = 1;
+                head->why = "contains cell with immutable number";
+            } else if (head->start != ss) {
+                debug(("head_number: chain with non-sequential numbers!"));
+                state->impossible = 1;
+            }
+        }
+        off++;
+        j = state->next[j];
+        assert(j != i); /* we have created a loop, obviously wrong */
+    }
+    if (head->preference) goto done;
+
+    if (state->nums[i] == 0 && state->nums[state->next[i]] > state->n) {
+        /* (probably) empty cell onto the head of a coloured region:
+         * make sure we start at a 0 offset. */
+        head->start = START(COLOUR(state->nums[state->next[i]]));
+        head->preference = 1;
+        head->why = "adding blank cell to head of numbered region";
+    } else if (state->nums[i] <= state->n) {
+        /* if we're 0 we're probably just blank -- but even if we're a
+         * (real) numbered region, we don't have an immutable number
+         * in it (any more) otherwise it'd have been caught above, so
+         * reassign the colour. */
+        head->start = 0;
+        head->preference = 0;
+        head->why = "lowest available colour group";
+    } else {
+        c = COLOUR(state->nums[i]);
+        n = 1;
+        sz = dsf_size(state->dsf, i);
+        j = i;
+        while (state->next[j] != -1) {
+            j = state->next[j];
+            if (state->nums[j] == 0 && state->next[j] == -1) {
+                head->start = START(c);
+                head->preference = 1;
+                head->why = "adding blank cell to end of numbered region";
+                goto done;
+            }
+            if (COLOUR(state->nums[j]) == c)
+                n++;
+            else {
+                int start_alternate = START(COLOUR(state->nums[j]));
+                if (n < (sz - n)) {
+                    head->start = start_alternate;
+                    head->preference = 1;
+                    head->why = "joining two coloured regions, swapping to larger colour";
+                } else {
+                    head->start = START(c);
+                    head->preference = 1;
+                    head->why = "joining two coloured regions, taking largest";
+                }
+                goto done;
+            }
+        }
+        /* If we got here then we may have split a region into
+         * two; make sure we don't assign a colour we've already used. */
+        if (c == 0) {
+            /* not convinced this shouldn't be an assertion failure here. */
+            head->start = 0;
+            head->preference = 0;
+        } else {
+            head->start = START(c);
+            head->preference = 1;
+        }
+        head->why = "got to end of coloured region";
+    }
+
+done:
+    assert(head->why != NULL);
+    if (head->preference)
+        debug(("Chain at (%d,%d) numbered for preference at %d (colour %d): %s.",
+               head->i%state->w, head->i/state->w,
+               head->start, COLOUR(head->start), head->why));
+    else
+        debug(("Chain at (%d,%d) using next available colour: %s.",
+               head->i%state->w, head->i/state->w,
+               head->why));
+}
+
+#if 0
+static void debug_numbers(game_state *state)
+{
+    int i, w=state->w;
+
+    for (i = 0; i < state->n; i++) {
+        debug(("(%d,%d) --> (%d,%d) --> (%d,%d)",
+               state->prev[i]==-1 ? -1 : state->prev[i]%w,
+               state->prev[i]==-1 ? -1 : state->prev[i]/w,
+               i%w, i/w,
+               state->next[i]==-1 ? -1 : state->next[i]%w,
+               state->next[i]==-1 ? -1 : state->next[i]/w));
+    }
+    w = w+1;
+}
+#endif
+
+static void connect_numbers(game_state *state)
+{
+    int i, di, dni;
+
+    dsf_init(state->dsf, state->n);
+    for (i = 0; i < state->n; i++) {
+        if (state->next[i] != -1) {
+            assert(state->prev[state->next[i]] == i);
+            di = dsf_canonify(state->dsf, i);
+            dni = dsf_canonify(state->dsf, state->next[i]);
+            if (di == dni) {
+                debug(("connect_numbers: chain forms a loop."));
+                state->impossible = 1;
+            }
+            dsf_merge(state->dsf, di, dni);
+        }
+    }
+}
+
+static int compare_heads(const void *a, const void *b)
+{
+    struct head_meta *ha = (struct head_meta *)a;
+    struct head_meta *hb = (struct head_meta *)b;
+
+    /* Heads with preferred colours first... */
+    if (ha->preference && !hb->preference) return -1;
+    if (hb->preference && !ha->preference) return 1;
+
+    /* ...then heads with low colours first... */
+    if (ha->start < hb->start) return -1;
+    if (ha->start > hb->start) return 1;
+
+    /* ... then large regions first... */
+    if (ha->sz > hb->sz) return -1;
+    if (ha->sz < hb->sz) return 1;
+
+    /* ... then position. */
+    if (ha->i > hb->i) return -1;
+    if (ha->i < hb->i) return 1;
+
+    return 0;
+}
+
+static int lowest_start(game_state *state, struct head_meta *heads, int nheads)
+{
+    int n, c;
+
+    /* NB start at 1: colour 0 is real numbers */
+    for (c = 1; c < state->n; c++) {
+        for (n = 0; n < nheads; n++) {
+            if (COLOUR(heads[n].start) == c)
+                goto used;
+        }
+        return c;
+used:
+        ;
+    }
+    assert(!"No available colours!");
+    return 0;
+}
+
+static void update_numbers(game_state *state)
+{
+    int i, j, n, nnum, nheads;
+    struct head_meta *heads = snewn(state->n, struct head_meta);
+
+    for (n = 0; n < state->n; n++)
+        state->numsi[n] = -1;
+
+    for (i = 0; i < state->n; i++) {
+        if (state->flags[i] & FLAG_IMMUTABLE) {
+            assert(state->nums[i] > 0);
+            assert(state->nums[i] <= state->n);
+            state->numsi[state->nums[i]] = i;
+        }
+        else if (state->prev[i] == -1 && state->next[i] == -1)
+            state->nums[i] = 0;
+    }
+    connect_numbers(state);
+
+    /* Construct an array of the heads of all current regions, together
+     * with their preferred colours. */
+    nheads = 0;
+    for (i = 0; i < state->n; i++) {
+        /* Look for a cell that is the start of a chain
+         * (has a next but no prev). */
+        if (state->prev[i] != -1 || state->next[i] == -1) continue;
+
+        head_number(state, i, &heads[nheads++]);
+    }
+
+    /* Sort that array:
+     * - heads with preferred colours first, then
+     * - heads with low colours first, then
+     * - large regions first
+     */
+    qsort(heads, nheads, sizeof(struct head_meta), compare_heads);
+
+    /* Remove duplicate-coloured regions. */
+    for (n = nheads-1; n >= 0; n--) { /* order is important! */
+        if ((n != 0) && (heads[n].start == heads[n-1].start)) {
+            /* We have a duplicate-coloured region: since we're
+             * sorted in size order and this is not the first
+             * of its colour it's not the largest: recolour it. */
+            heads[n].start = START(lowest_start(state, heads, nheads));
+            heads[n].preference = -1; /* '-1' means 'was duplicate' */
+        }
+        else if (!heads[n].preference) {
+            assert(heads[n].start == 0);
+            heads[n].start = START(lowest_start(state, heads, nheads));
+        }
+    }
+
+    debug(("Region colouring after duplicate removal:"));
+
+    for (n = 0; n < nheads; n++) {
+        debug(("  Chain at (%d,%d) sz %d numbered at %d (colour %d): %s%s",
+               heads[n].i % state->w, heads[n].i / state->w, heads[n].sz,
+               heads[n].start, COLOUR(heads[n].start), heads[n].why,
+               heads[n].preference == 0 ? " (next available)" :
+               heads[n].preference < 0 ? " (duplicate, next available)" : ""));
+
+        nnum = heads[n].start;
+        j = heads[n].i;
+        while (j != -1) {
+            if (!(state->flags[j] & FLAG_IMMUTABLE)) {
+                if (nnum > 0 && nnum <= state->n)
+                    state->numsi[nnum] = j;
+                state->nums[j] = nnum;
+            }
+            nnum++;
+            j = state->next[j];
+            assert(j != heads[n].i); /* loop?! */
+        }
+    }
+    /*debug_numbers(state);*/
+    sfree(heads);
+}
+
+static int check_completion(game_state *state, int mark_errors)
+{
+    int n, j, k, error = 0, complete;
+
+    /* NB This only marks errors that are possible to perpetrate with
+     * the current UI in interpret_move. Things like forming loops in
+     * linked sections and having numbers not add up should be forbidden
+     * by the code elsewhere, so we don't bother marking those (because
+     * it would add lots of tricky drawing code for very little gain). */
+    if (mark_errors) {
+        for (j = 0; j < state->n; j++)
+            state->flags[j] &= ~FLAG_ERROR;
+    }
+
+    /* Search for repeated numbers. */
+    for (j = 0; j < state->n; j++) {
+        if (state->nums[j] > 0 && state->nums[j] <= state->n) {
+            for (k = j+1; k < state->n; k++) {
+                if (state->nums[k] == state->nums[j]) {
+                    if (mark_errors) {
+                        state->flags[j] |= FLAG_ERROR;
+                        state->flags[k] |= FLAG_ERROR;
+                    }
+                    error = 1;
+                }
+            }
+        }
+    }
+
+    /* Search and mark numbers n not pointing to n+1; if any numbers
+     * are missing we know we've not completed. */
+    complete = 1;
+    for (n = 1; n < state->n; n++) {
+        if (state->numsi[n] == -1 || state->numsi[n+1] == -1)
+            complete = 0;
+        else if (!ispointingi(state, state->numsi[n], state->numsi[n+1])) {
+            if (mark_errors) {
+                state->flags[state->numsi[n]] |= FLAG_ERROR;
+                state->flags[state->numsi[n+1]] |= FLAG_ERROR;
+            }
+            error = 1;
+        } else {
+            /* make sure the link is explicitly made here; for instance, this
+             * is nice if the user drags from 2 out (making 3) and a 4 is also
+             * visible; this ensures that the link from 3 to 4 is also made. */
+            if (mark_errors)
+                makelink(state, state->numsi[n], state->numsi[n+1]);
+        }
+    }
+
+    /* Search and mark numbers less than 0, or 0 with links. */
+    for (n = 1; n < state->n; n++) {
+        if ((state->nums[n] < 0) ||
+            (state->nums[n] == 0 &&
+             (state->next[n] != -1 || state->prev[n] != -1))) {
+            error = 1;
+            if (mark_errors)
+                state->flags[n] |= FLAG_ERROR;
+        }
+    }
+
+    if (error) return 0;
+    return complete;
+}
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    game_state *state = NULL;
+
+    unpick_desc(params, desc, &state, NULL);
+    if (!state) assert(!"new_game failed to unpick");
+
+    update_numbers(state);
+    check_completion(state, 1); /* update any auto-links */
+
+    return state;
+}
+
+/* --- Solver --- */
+
+/* If a tile has a single tile it can link _to_, or there's only a single
+ * location that can link to a given tile, fill that link in. */
+static int solve_single(game_state *state, game_state *copy, int *from)
+{
+    int i, j, sx, sy, x, y, d, poss, w=state->w, nlinks = 0;
+
+    /* The from array is a list of 'which square can link _to_ us';
+     * we start off with from as '-1' (meaning 'not found'); if we find
+     * something that can link to us it is set to that index, and then if
+     * we find another we set it to -2. */
+
+    memset(from, -1, state->n*sizeof(int));
+
+    /* poss is 'can I link to anything' with the same meanings. */
+
+    for (i = 0; i < state->n; i++) {
+        if (state->next[i] != -1) continue;
+        if (state->nums[i] == state->n) continue; /* no next from last no. */
+
+        d = state->dirs[i];
+        poss = -1;
+        sx = x = i%w; sy = y = i/w;
+        while (1) {
+            x += dxs[d]; y += dys[d];
+            if (!INGRID(state, x, y)) break;
+            if (!isvalidmove(state, 1, sx, sy, x, y)) continue;
+
+            /* can't link to somewhere with a back-link we would have to
+             * break (the solver just doesn't work like this). */
+            j = y*w+x;
+            if (state->prev[j] != -1) continue;
+
+            if (state->nums[i] > 0 && state->nums[j] > 0 &&
+                state->nums[i] <= state->n && state->nums[j] <= state->n &&
+                state->nums[j] == state->nums[i]+1) {
+                debug(("Solver: forcing link through existing consecutive numbers."));
+                poss = j;
+                from[j] = i;
+                break;
+            }
+
+            /* if there's been a valid move already, we have to move on;
+             * we can't make any deductions here. */
+            poss = (poss == -1) ? j : -2;
+
+            /* Modify the from array as described above (which is enumerating
+             * what points to 'j' in a similar way). */
+            from[j] = (from[j] == -1) ? i : -2;
+        }
+        if (poss == -2) {
+            /*debug(("Solver: (%d,%d) has multiple possible next squares.", sx, sy));*/
+            ;
+        } else if (poss == -1) {
+            debug(("Solver: nowhere possible for (%d,%d) to link to.", sx, sy));
+            copy->impossible = 1;
+            return -1;
+        } else {
+            debug(("Solver: linking (%d,%d) to only possible next (%d,%d).",
+                   sx, sy, poss%w, poss/w));
+            makelink(copy, i, poss);
+            nlinks++;
+        }
+    }
+
+    for (i = 0; i < state->n; i++) {
+        if (state->prev[i] != -1) continue;
+        if (state->nums[i] == 1) continue; /* no prev from 1st no. */
+
+        x = i%w; y = i/w;
+        if (from[i] == -1) {
+            debug(("Solver: nowhere possible to link to (%d,%d)", x, y));
+            copy->impossible = 1;
+            return -1;
+        } else if (from[i] == -2) {
+            /*debug(("Solver: (%d,%d) has multiple possible prev squares.", x, y));*/
+            ;
+        } else {
+            debug(("Solver: linking only possible prev (%d,%d) to (%d,%d).",
+                   from[i]%w, from[i]/w, x, y));
+            makelink(copy, from[i], i);
+            nlinks++;
+        }
+    }
+
+    return nlinks;
+}
+
+/* Returns 1 if we managed to solve it, 0 otherwise. */
+static int solve_state(game_state *state)
+{
+    game_state *copy = dup_game(state);
+    int *scratch = snewn(state->n, int), ret;
+
+    debug_state("Before solver: ", state);
+
+    while (1) {
+        update_numbers(state);
+
+        if (solve_single(state, copy, scratch)) {
+            dup_game_to(state, copy);
+            if (state->impossible) break; else continue;
+        }
+        break;
+    }
+    free_game(copy);
+    sfree(scratch);
+
+    update_numbers(state);
+    ret = state->impossible ? -1 : check_completion(state, 0);
+    debug(("Solver finished: %s",
+           ret < 0 ? "impossible" : ret > 0 ? "solved" : "not solved"));
+    debug_state("After solver: ", state);
+    return ret;
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    game_state *tosolve;
+    char *ret = NULL;
+    int result;
+
+    tosolve = dup_game(currstate);
+    result = solve_state(tosolve);
+    if (result > 0)
+        ret = generate_desc(tosolve, 1);
+    free_game(tosolve);
+    if (ret) return ret;
+
+    tosolve = dup_game(state);
+    result = solve_state(tosolve);
+    if (result < 0)
+        *error = "Puzzle is impossible.";
+    else if (result == 0)
+        *error = "Unable to solve puzzle.";
+    else
+        ret = generate_desc(tosolve, 1);
+
+    free_game(tosolve);
+
+    return ret;
+}
+
+/* --- UI and move routines. --- */
+
+
+struct game_ui {
+    int cx, cy, cshow;
+
+    int dragging, drag_is_from;
+    int sx, sy;         /* grid coords of start cell */
+    int dx, dy;         /* pixel coords of drag posn */
+};
+
+static game_ui *new_ui(const game_state *state)
+{
+    game_ui *ui = snew(game_ui);
+
+    /* NB: if this is ever changed to as to require more than a structure
+     * copy to clone, there's code that needs fixing in game_redraw too. */
+
+    ui->cx = ui->cy = ui->cshow = 0;
+
+    ui->dragging = 0;
+    ui->sx = ui->sy = ui->dx = ui->dy = 0;
+
+    return ui;
+}
+
+static void free_ui(game_ui *ui)
+{
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+    if (!oldstate->completed && newstate->completed)
+        ui->cshow = ui->dragging = 0;
+}
+
+struct game_drawstate {
+    int tilesize, started, solved;
+    int w, h, n;
+    int *nums, *dirp;
+    unsigned int *f;
+    double angle_offset;
+
+    int dragging, dx, dy;
+    blitter *dragb;
+};
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int mx, int my, int button)
+{
+    int x = FROMCOORD(mx), y = FROMCOORD(my), w = state->w;
+    char buf[80];
+
+    if (IS_CURSOR_MOVE(button)) {
+        move_cursor(button, &ui->cx, &ui->cy, state->w, state->h, 0);
+        ui->cshow = 1;
+        if (ui->dragging) {
+            ui->dx = COORD(ui->cx) + TILE_SIZE/2;
+            ui->dy = COORD(ui->cy) + TILE_SIZE/2;
+        }
+        return "";
+    } else if (IS_CURSOR_SELECT(button)) {
+        if (!ui->cshow)
+            ui->cshow = 1;
+        else if (ui->dragging) {
+            ui->dragging = FALSE;
+            if (ui->sx == ui->cx && ui->sy == ui->cy) return "";
+            if (ui->drag_is_from) {
+                if (!isvalidmove(state, 0, ui->sx, ui->sy, ui->cx, ui->cy)) return "";
+                sprintf(buf, "L%d,%d-%d,%d", ui->sx, ui->sy, ui->cx, ui->cy);
+            } else {
+                if (!isvalidmove(state, 0, ui->cx, ui->cy, ui->sx, ui->sy)) return "";
+                sprintf(buf, "L%d,%d-%d,%d", ui->cx, ui->cy, ui->sx, ui->sy);
+            }
+            return dupstr(buf);
+        } else {
+            ui->dragging = TRUE;
+            ui->sx = ui->cx;
+            ui->sy = ui->cy;
+            ui->dx = COORD(ui->cx) + TILE_SIZE/2;
+            ui->dy = COORD(ui->cy) + TILE_SIZE/2;
+            ui->drag_is_from = (button == CURSOR_SELECT) ? 1 : 0;
+        }
+        return "";
+    }
+    if (IS_MOUSE_DOWN(button)) {
+        if (ui->cshow) {
+            ui->cshow = ui->dragging = 0;
+        }
+        assert(!ui->dragging);
+        if (!INGRID(state, x, y)) return NULL;
+
+        if (button == LEFT_BUTTON) {
+            /* disallow dragging from the final number. */
+            if ((state->nums[y*w+x] == state->n) &&
+                (state->flags[y*w+x] & FLAG_IMMUTABLE))
+                return NULL;
+        } else if (button == RIGHT_BUTTON) {
+            /* disallow dragging to the first number. */
+            if ((state->nums[y*w+x] == 1) &&
+                (state->flags[y*w+x] & FLAG_IMMUTABLE))
+                return NULL;
+        }
+
+        ui->dragging = TRUE;
+        ui->drag_is_from = (button == LEFT_BUTTON) ? 1 : 0;
+        ui->sx = x;
+        ui->sy = y;
+        ui->dx = mx;
+        ui->dy = my;
+        ui->cshow = 0;
+        return "";
+    } else if (IS_MOUSE_DRAG(button) && ui->dragging) {
+        ui->dx = mx;
+        ui->dy = my;
+        return "";
+    } else if (IS_MOUSE_RELEASE(button) && ui->dragging) {
+        ui->dragging = FALSE;
+        if (ui->sx == x && ui->sy == y) return ""; /* single click */
+
+        if (!INGRID(state, x, y)) {
+            int si = ui->sy*w+ui->sx;
+            if (state->prev[si] == -1 && state->next[si] == -1)
+                return "";
+            sprintf(buf, "%c%d,%d",
+                    (int)(ui->drag_is_from ? 'C' : 'X'), ui->sx, ui->sy);
+            return dupstr(buf);
+        }
+
+        if (ui->drag_is_from) {
+            if (!isvalidmove(state, 0, ui->sx, ui->sy, x, y)) return "";
+            sprintf(buf, "L%d,%d-%d,%d", ui->sx, ui->sy, x, y);
+        } else {
+            if (!isvalidmove(state, 0, x, y, ui->sx, ui->sy)) return "";
+            sprintf(buf, "L%d,%d-%d,%d", x, y, ui->sx, ui->sy);
+        }
+        return dupstr(buf);
+    } /* else if (button == 'H' || button == 'h')
+        return dupstr("H"); */
+    else if ((button == 'x' || button == 'X') && ui->cshow) {
+        int si = ui->cy*w + ui->cx;
+        if (state->prev[si] == -1 && state->next[si] == -1)
+            return "";
+        sprintf(buf, "%c%d,%d",
+                (int)((button == 'x') ? 'C' : 'X'), ui->cx, ui->cy);
+        return dupstr(buf);
+    }
+
+    return NULL;
+}
+
+static void unlink_cell(game_state *state, int si)
+{
+    debug(("Unlinking (%d,%d).", si%state->w, si/state->w));
+    if (state->prev[si] != -1) {
+        debug((" ... removing prev link from (%d,%d).",
+               state->prev[si]%state->w, state->prev[si]/state->w));
+        state->next[state->prev[si]] = -1;
+        state->prev[si] = -1;
+    }
+    if (state->next[si] != -1) {
+        debug((" ... removing next link to (%d,%d).",
+               state->next[si]%state->w, state->next[si]/state->w));
+        state->prev[state->next[si]] = -1;
+        state->next[si] = -1;
+    }
+}
+
+static game_state *execute_move(const game_state *state, const char *move)
+{
+    game_state *ret = NULL;
+    int sx, sy, ex, ey, si, ei, w = state->w;
+    char c;
+
+    debug(("move: %s", move));
+
+    if (move[0] == 'S') {
+        game_params p;
+       game_state *tmp;
+        char *valid;
+       int i;
+
+        p.w = state->w; p.h = state->h;
+        valid = validate_desc(&p, move+1);
+        if (valid) {
+            debug(("execute_move: move not valid: %s", valid));
+            return NULL;
+        }
+       ret = dup_game(state);
+        tmp = new_game(NULL, &p, move+1);
+       for (i = 0; i < state->n; i++) {
+           ret->prev[i] = tmp->prev[i];
+           ret->next[i] = tmp->next[i];
+       }
+       free_game(tmp);
+        ret->used_solve = 1;
+    } else if (sscanf(move, "L%d,%d-%d,%d", &sx, &sy, &ex, &ey) == 4) {
+        if (!isvalidmove(state, 0, sx, sy, ex, ey)) return NULL;
+
+        ret = dup_game(state);
+
+        si = sy*w+sx; ei = ey*w+ex;
+        makelink(ret, si, ei);
+    } else if (sscanf(move, "%c%d,%d", &c, &sx, &sy) == 3) {
+        int sset;
+
+        if (c != 'C' && c != 'X') return NULL;
+        if (!INGRID(state, sx, sy)) return NULL;
+        si = sy*w+sx;
+        if (state->prev[si] == -1 && state->next[si] == -1)
+            return NULL;
+
+        ret = dup_game(state);
+
+        sset = state->nums[si] / (state->n+1);
+        if (c == 'C' || (c == 'X' && sset == 0)) {
+            /* Unlink the single cell we dragged from the board. */
+            unlink_cell(ret, si);
+        } else {
+            int i, set;
+            for (i = 0; i < state->n; i++) {
+                /* Unlink all cells in the same set as the one we dragged
+                 * from the board. */
+
+                if (state->nums[i] == 0) continue;
+                set = state->nums[i] / (state->n+1);
+                if (set != sset) continue;
+
+                unlink_cell(ret, i);
+            }
+        }
+    } else if (strcmp(move, "H") == 0) {
+        ret = dup_game(state);
+        solve_state(ret);
+    }
+    if (ret) {
+        update_numbers(ret);
+        if (check_completion(ret, 1)) ret->completed = 1;
+    }
+
+    return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    struct { int tilesize, order; } ads, *ds = &ads;
+    ads.tilesize = tilesize;
+
+    *x = TILE_SIZE * params->w + 2 * BORDER;
+    *y = TILE_SIZE * params->h + 2 * BORDER;
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+    assert(TILE_SIZE > 0);
+
+    assert(!ds->dragb);
+    ds->dragb = blitter_new(dr, BLITTER_SIZE, BLITTER_SIZE);
+}
+
+/* Colours chosen from the webby palette to work as a background to black text,
+ * W then some plausible approximation to pastelly ROYGBIV; we then interpolate
+ * between consecutive pairs to give another 8 (and then the drawing routine
+ * will reuse backgrounds). */
+static const unsigned long bgcols[8] = {
+    0xffffff, /* white */
+    0xffa07a, /* lightsalmon */
+    0x98fb98, /* green */
+    0x7fffd4, /* aquamarine */
+    0x9370db, /* medium purple */
+    0xffa500, /* orange */
+    0x87cefa, /* lightskyblue */
+    0xffff00, /* yellow */
+};
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+    int c, i;
+
+    game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT);
+
+    for (i = 0; i < 3; i++) {
+        ret[COL_NUMBER * 3 + i] = 0.0F;
+        ret[COL_ARROW * 3 + i] = 0.0F;
+        ret[COL_CURSOR * 3 + i] = ret[COL_BACKGROUND * 3 + i] / 2.0F;
+        ret[COL_GRID * 3 + i] = ret[COL_BACKGROUND * 3 + i] / 1.3F;
+    }
+    ret[COL_NUMBER_SET * 3 + 0] = 0.0F;
+    ret[COL_NUMBER_SET * 3 + 1] = 0.0F;
+    ret[COL_NUMBER_SET * 3 + 2] = 0.9F;
+
+    ret[COL_ERROR * 3 + 0] = 1.0F;
+    ret[COL_ERROR * 3 + 1] = 0.0F;
+    ret[COL_ERROR * 3 + 2] = 0.0F;
+
+    ret[COL_DRAG_ORIGIN * 3 + 0] = 0.2F;
+    ret[COL_DRAG_ORIGIN * 3 + 1] = 1.0F;
+    ret[COL_DRAG_ORIGIN * 3 + 2] = 0.2F;
+
+    for (c = 0; c < 8; c++) {
+         ret[(COL_B0 + c) * 3 + 0] = (float)((bgcols[c] & 0xff0000) >> 16) / 256.0F;
+         ret[(COL_B0 + c) * 3 + 1] = (float)((bgcols[c] & 0xff00) >> 8) / 256.0F;
+         ret[(COL_B0 + c) * 3 + 2] = (float)((bgcols[c] & 0xff)) / 256.0F;
+    }
+    for (c = 0; c < 8; c++) {
+        for (i = 0; i < 3; i++) {
+           ret[(COL_B0 + 8 + c) * 3 + i] =
+               (ret[(COL_B0 + c) * 3 + i] + ret[(COL_B0 + c + 1) * 3 + i]) / 2.0F;
+        }
+    }
+
+#define average(r,a,b,w) do { \
+    for (i = 0; i < 3; i++) \
+       ret[(r)*3+i] = ret[(a)*3+i] + w * (ret[(b)*3+i] - ret[(a)*3+i]); \
+} while (0)
+    average(COL_ARROW_BG_DIM, COL_BACKGROUND, COL_ARROW, 0.1F);
+    average(COL_NUMBER_SET_MID, COL_B0, COL_NUMBER_SET, 0.3F);
+    for (c = 0; c < NBACKGROUNDS; c++) {
+       /* I assume here that COL_ARROW and COL_NUMBER are the same.
+        * Otherwise I'd need two sets of COL_M*. */
+       average(COL_M0 + c, COL_B0 + c, COL_NUMBER, 0.3F);
+       average(COL_D0 + c, COL_B0 + c, COL_NUMBER, 0.1F);
+       average(COL_X0 + c, COL_BACKGROUND, COL_B0 + c, 0.5F);
+    }
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    struct game_drawstate *ds = snew(struct game_drawstate);
+    int i;
+
+    ds->tilesize = ds->started = ds->solved = 0;
+    ds->w = state->w;
+    ds->h = state->h;
+    ds->n = state->n;
+
+    ds->nums = snewn(state->n, int);
+    ds->dirp = snewn(state->n, int);
+    ds->f = snewn(state->n, unsigned int);
+    for (i = 0; i < state->n; i++) {
+        ds->nums[i] = 0;
+        ds->dirp[i] = -1;
+        ds->f[i] = 0;
+    }
+
+    ds->angle_offset = 0.0F;
+
+    ds->dragging = ds->dx = ds->dy = 0;
+    ds->dragb = NULL;
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds->nums);
+    sfree(ds->dirp);
+    sfree(ds->f);
+    if (ds->dragb) blitter_free(dr, ds->dragb);
+
+    sfree(ds);
+}
+
+/* cx, cy are top-left corner. sz is the 'radius' of the arrow.
+ * ang is in radians, clockwise from 0 == straight up. */
+static void draw_arrow(drawing *dr, int cx, int cy, int sz, double ang,
+                       int cfill, int cout)
+{
+    int coords[14];
+    int xdx, ydx, xdy, ydy, xdx3, xdy3;
+    double s = sin(ang), c = cos(ang);
+
+    xdx3 = (int)(sz * (c/3 + 1) + 0.5) - sz;
+    xdy3 = (int)(sz * (s/3 + 1) + 0.5) - sz;
+    xdx = (int)(sz * (c + 1) + 0.5) - sz;
+    xdy = (int)(sz * (s + 1) + 0.5) - sz;
+    ydx = -xdy;
+    ydy = xdx;
+
+
+    coords[2*0 + 0] = cx - ydx;
+    coords[2*0 + 1] = cy - ydy;
+    coords[2*1 + 0] = cx + xdx;
+    coords[2*1 + 1] = cy + xdy;
+    coords[2*2 + 0] = cx + xdx3;
+    coords[2*2 + 1] = cy + xdy3;
+    coords[2*3 + 0] = cx + xdx3 + ydx;
+    coords[2*3 + 1] = cy + xdy3 + ydy;
+    coords[2*4 + 0] = cx - xdx3 + ydx;
+    coords[2*4 + 1] = cy - xdy3 + ydy;
+    coords[2*5 + 0] = cx - xdx3;
+    coords[2*5 + 1] = cy - xdy3;
+    coords[2*6 + 0] = cx - xdx;
+    coords[2*6 + 1] = cy - xdy;
+
+    draw_polygon(dr, coords, 7, cfill, cout);
+}
+
+static void draw_arrow_dir(drawing *dr, int cx, int cy, int sz, int dir,
+                           int cfill, int cout, double angle_offset)
+{
+    double ang = 2.0 * PI * (double)dir / 8.0 + angle_offset;
+    draw_arrow(dr, cx, cy, sz, ang, cfill, cout);
+}
+
+/* cx, cy are centre coordinates.. */
+static void draw_star(drawing *dr, int cx, int cy, int rad, int npoints,
+                      int cfill, int cout, double angle_offset)
+{
+    int *coords, n;
+    double a, r;
+
+    assert(npoints > 0);
+
+    coords = snewn(npoints * 2 * 2, int);
+
+    for (n = 0; n < npoints * 2; n++) {
+        a = 2.0 * PI * ((double)n / ((double)npoints * 2.0)) + angle_offset;
+        r = (n % 2) ? (double)rad/2.0 : (double)rad;
+
+        /* We're rotating the point at (0, -r) by a degrees */
+        coords[2*n+0] = cx + (int)( r * sin(a));
+        coords[2*n+1] = cy + (int)(-r * cos(a));
+    }
+    draw_polygon(dr, coords, npoints*2, cfill, cout);
+    sfree(coords);
+}
+
+static int num2col(game_drawstate *ds, int num)
+{
+    int set = num / (ds->n+1);
+
+    if (num <= 0 || set == 0) return COL_B0;
+    return COL_B0 + 1 + ((set-1) % 15);
+}
+
+#define ARROW_HALFSZ (7 * TILE_SIZE / 32)
+
+#define F_CUR           0x001   /* Cursor on this tile. */
+#define F_DRAG_SRC      0x002   /* Tile is source of a drag. */
+#define F_ERROR         0x004   /* Tile marked in error. */
+#define F_IMMUTABLE     0x008   /* Tile (number) is immutable. */
+#define F_ARROW_POINT   0x010   /* Tile points to other tile */
+#define F_ARROW_INPOINT 0x020   /* Other tile points in here. */
+#define F_DIM           0x040   /* Tile is dim */
+
+static void tile_redraw(drawing *dr, game_drawstate *ds, int tx, int ty,
+                        int dir, int dirp, int num, unsigned int f,
+                        double angle_offset, int print_ink)
+{
+    int cb = TILE_SIZE / 16, textsz;
+    char buf[20];
+    int arrowcol, sarrowcol, setcol, textcol;
+    int acx, acy, asz, empty = 0;
+
+    if (num == 0 && !(f & F_ARROW_POINT) && !(f & F_ARROW_INPOINT)) {
+        empty = 1;
+        /*
+         * We don't display text in empty cells: typically these are
+         * signified by num=0. However, in some cases a cell could
+         * have had the number 0 assigned to it if the user made an
+         * error (e.g. tried to connect a chain of length 5 to the
+         * immutable number 4) so we _do_ display the 0 if the cell
+         * has a link in or a link out.
+         */
+    }
+
+    /* Calculate colours. */
+
+    if (print_ink >= 0) {
+       /*
+        * We're printing, so just do everything in black.
+        */
+       arrowcol = textcol = print_ink;
+       setcol = sarrowcol = -1;       /* placate optimiser */
+    } else {
+
+       setcol = empty ? COL_BACKGROUND : num2col(ds, num);
+
+#define dim(fg,bg) ( \
+      (bg)==COL_BACKGROUND ? COL_ARROW_BG_DIM : \
+      (bg) + COL_D0 - COL_B0 \
+    )
+
+#define mid(fg,bg) ( \
+      (fg)==COL_NUMBER_SET ? COL_NUMBER_SET_MID : \
+      (bg) + COL_M0 - COL_B0 \
+    )
+
+#define dimbg(bg) ( \
+      (bg)==COL_BACKGROUND ? COL_BACKGROUND : \
+      (bg) + COL_X0 - COL_B0 \
+    )
+
+       if (f & F_DRAG_SRC) arrowcol = COL_DRAG_ORIGIN;
+       else if (f & F_DIM) arrowcol = dim(COL_ARROW, setcol);
+       else if (f & F_ARROW_POINT) arrowcol = mid(COL_ARROW, setcol);
+       else arrowcol = COL_ARROW;
+
+       if ((f & F_ERROR) && !(f & F_IMMUTABLE)) textcol = COL_ERROR;
+       else {
+           if (f & F_IMMUTABLE) textcol = COL_NUMBER_SET;
+           else textcol = COL_NUMBER;
+
+           if (f & F_DIM) textcol = dim(textcol, setcol);
+           else if (((f & F_ARROW_POINT) || num==ds->n) &&
+                    ((f & F_ARROW_INPOINT) || num==1))
+               textcol = mid(textcol, setcol);
+       }
+
+       if (f & F_DIM) sarrowcol = dim(COL_ARROW, setcol);
+       else sarrowcol = COL_ARROW;
+    }
+
+    /* Clear tile background */
+
+    if (print_ink < 0) {
+       draw_rect(dr, tx, ty, TILE_SIZE, TILE_SIZE,
+                 (f & F_DIM) ? dimbg(setcol) : setcol);
+    }
+
+    /* Draw large (outwards-pointing) arrow. */
+
+    asz = ARROW_HALFSZ;         /* 'radius' of arrow/star. */
+    acx = tx+TILE_SIZE/2+asz;   /* centre x */
+    acy = ty+TILE_SIZE/2+asz;   /* centre y */
+
+    if (num == ds->n && (f & F_IMMUTABLE))
+        draw_star(dr, acx, acy, asz, 5, arrowcol, arrowcol, angle_offset);
+    else
+        draw_arrow_dir(dr, acx, acy, asz, dir, arrowcol, arrowcol, angle_offset);
+    if (print_ink < 0 && (f & F_CUR))
+        draw_rect_corners(dr, acx, acy, asz+1, COL_CURSOR);
+
+    /* Draw dot iff this tile requires a predecessor and doesn't have one. */
+
+    if (print_ink < 0) {
+       acx = tx+TILE_SIZE/2-asz;
+       acy = ty+TILE_SIZE/2+asz;
+
+       if (!(f & F_ARROW_INPOINT) && num != 1) {
+           draw_circle(dr, acx, acy, asz / 4, sarrowcol, sarrowcol);
+       }
+    }
+
+    /* Draw text (number or set). */
+
+    if (!empty) {
+        int set = (num <= 0) ? 0 : num / (ds->n+1);
+
+        char *p = buf;
+        if (set == 0 || num <= 0) {
+            sprintf(buf, "%d", num);
+        } else {
+            int n = num % (ds->n+1);
+            p += sizeof(buf) - 1;
+
+            if (n != 0) {
+                sprintf(buf, "+%d", n);  /* Just to get the length... */
+                p -= strlen(buf);
+                sprintf(p, "+%d", n);
+            } else {
+                *p = '\0';
+            }
+            do {
+                set--;
+                p--;
+                *p = (char)((set % 26)+'a');
+                set /= 26;
+            } while (set);
+        }
+        textsz = min(2*asz, (TILE_SIZE - 2 * cb) / (int)strlen(p));
+        draw_text(dr, tx + cb, ty + TILE_SIZE/4, FONT_VARIABLE, textsz,
+                  ALIGN_VCENTRE | ALIGN_HLEFT, textcol, p);
+    }
+
+    if (print_ink < 0) {
+       draw_rect_outline(dr, tx, ty, TILE_SIZE, TILE_SIZE, COL_GRID);
+       draw_update(dr, tx, ty, TILE_SIZE, TILE_SIZE);
+    }
+}
+
+static void draw_drag_indicator(drawing *dr, game_drawstate *ds,
+                                const game_state *state, const game_ui *ui,
+                                int validdrag)
+{
+    int dir, w = ds->w, acol = COL_ARROW;
+    int fx = FROMCOORD(ui->dx), fy = FROMCOORD(ui->dy);
+    double ang;
+
+    if (validdrag) {
+        /* If we could move here, lock the arrow to the appropriate direction. */
+        dir = ui->drag_is_from ? state->dirs[ui->sy*w+ui->sx] : state->dirs[fy*w+fx];
+
+        ang = (2.0 * PI * dir) / 8.0; /* similar to calculation in draw_arrow_dir. */
+    } else {
+        /* Draw an arrow pointing away from/towards the origin cell. */
+        int ox = COORD(ui->sx) + TILE_SIZE/2, oy = COORD(ui->sy) + TILE_SIZE/2;
+        double tana, offset;
+        double xdiff = abs(ox - ui->dx), ydiff = abs(oy - ui->dy);
+
+        if (xdiff == 0) {
+            ang = (oy > ui->dy) ? 0.0F : PI;
+        } else if (ydiff == 0) {
+            ang = (ox > ui->dx) ? 3.0F*PI/2.0F : PI/2.0F;
+        } else {
+            if (ui->dx > ox && ui->dy < oy) {
+                tana = xdiff / ydiff;
+                offset = 0.0F;
+            } else if (ui->dx > ox && ui->dy > oy) {
+                tana = ydiff / xdiff;
+                offset = PI/2.0F;
+            } else if (ui->dx < ox && ui->dy > oy) {
+                tana = xdiff / ydiff;
+                offset = PI;
+            } else {
+                tana = ydiff / xdiff;
+                offset = 3.0F * PI / 2.0F;
+            }
+            ang = atan(tana) + offset;
+        }
+
+        if (!ui->drag_is_from) ang += PI; /* point to origin, not away from. */
+
+    }
+    draw_arrow(dr, ui->dx, ui->dy, ARROW_HALFSZ, ang, acol, acol);
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int x, y, i, w = ds->w, dirp, force = 0;
+    unsigned int f;
+    double angle_offset = 0.0;
+    game_state *postdrop = NULL;
+
+    if (flashtime > 0.0F)
+        angle_offset = 2.0 * PI * (flashtime / FLASH_SPIN);
+    if (angle_offset != ds->angle_offset) {
+        ds->angle_offset = angle_offset;
+        force = 1;
+    }
+
+    if (ds->dragging) {
+        assert(ds->dragb);
+        blitter_load(dr, ds->dragb, ds->dx, ds->dy);
+        draw_update(dr, ds->dx, ds->dy, BLITTER_SIZE, BLITTER_SIZE);
+        ds->dragging = FALSE;
+    }
+
+    /* If an in-progress drag would make a valid move if finished, we
+     * reflect that move in the board display. We let interpret_move do
+     * most of the heavy lifting for us: we have to copy the game_ui so
+     * as not to stomp on the real UI's drag state. */
+    if (ui->dragging) {
+        game_ui uicopy = *ui;
+        char *movestr = interpret_move(state, &uicopy, ds, ui->dx, ui->dy, LEFT_RELEASE);
+
+        if (movestr != NULL && strcmp(movestr, "") != 0) {
+            postdrop = execute_move(state, movestr);
+            sfree(movestr);
+
+            state = postdrop;
+        }
+    }
+
+    if (!ds->started) {
+        int aw = TILE_SIZE * state->w;
+        int ah = TILE_SIZE * state->h;
+        draw_rect(dr, 0, 0, aw + 2 * BORDER, ah + 2 * BORDER, COL_BACKGROUND);
+        draw_rect_outline(dr, BORDER - 1, BORDER - 1, aw + 2, ah + 2, COL_GRID);
+        draw_update(dr, 0, 0, aw + 2 * BORDER, ah + 2 * BORDER);
+    }
+    for (x = 0; x < state->w; x++) {
+        for (y = 0; y < state->h; y++) {
+            i = y*w + x;
+            f = 0;
+            dirp = -1;
+
+            if (ui->cshow && x == ui->cx && y == ui->cy)
+                f |= F_CUR;
+
+            if (ui->dragging) {
+                if (x == ui->sx && y == ui->sy)
+                    f |= F_DRAG_SRC;
+                else if (ui->drag_is_from) {
+                    if (!ispointing(state, ui->sx, ui->sy, x, y))
+                        f |= F_DIM;
+                } else {
+                    if (!ispointing(state, x, y, ui->sx, ui->sy))
+                        f |= F_DIM;
+                }
+            }
+
+            if (state->impossible ||
+                state->nums[i] < 0 || state->flags[i] & FLAG_ERROR)
+                f |= F_ERROR;
+            if (state->flags[i] & FLAG_IMMUTABLE)
+                f |= F_IMMUTABLE;
+
+            if (state->next[i] != -1)
+                f |= F_ARROW_POINT;
+
+            if (state->prev[i] != -1) {
+                /* Currently the direction here is from our square _back_
+                 * to its previous. We could change this to give the opposite
+                 * sense to the direction. */
+                f |= F_ARROW_INPOINT;
+                dirp = whichdir(x, y, state->prev[i]%w, state->prev[i]/w);
+            }
+
+            if (state->nums[i] != ds->nums[i] ||
+                f != ds->f[i] || dirp != ds->dirp[i] ||
+                force || !ds->started) {
+                int sign;
+                {
+                    /*
+                     * Trivial and foolish configurable option done on
+                     * purest whim. With this option enabled, the
+                     * victory flash is done by rotating each square
+                     * in the opposite direction from its immediate
+                     * neighbours, so that they behave like a field of
+                     * interlocking gears. With it disabled, they all
+                     * rotate in the same direction. Choose for
+                     * yourself which is more brain-twisting :-)
+                     */
+                    static int gear_mode = -1;
+                    if (gear_mode < 0) {
+                        char *env = getenv("SIGNPOST_GEARS");
+                        gear_mode = (env && (env[0] == 'y' || env[0] == 'Y'));
+                    }
+                    if (gear_mode)
+                        sign = 1 - 2 * ((x ^ y) & 1);
+                    else
+                        sign = 1;
+                }
+                tile_redraw(dr, ds,
+                            BORDER + x * TILE_SIZE,
+                            BORDER + y * TILE_SIZE,
+                            state->dirs[i], dirp, state->nums[i], f,
+                            sign * angle_offset, -1);
+                ds->nums[i] = state->nums[i];
+                ds->f[i] = f;
+                ds->dirp[i] = dirp;
+            }
+        }
+    }
+    if (ui->dragging) {
+        ds->dragging = TRUE;
+        ds->dx = ui->dx - BLITTER_SIZE/2;
+        ds->dy = ui->dy - BLITTER_SIZE/2;
+        blitter_save(dr, ds->dragb, ds->dx, ds->dy);
+
+        draw_drag_indicator(dr, ds, state, ui, postdrop ? 1 : 0);
+    }
+    if (postdrop) free_game(postdrop);
+    if (!ds->started) ds->started = TRUE;
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return 0.0F;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    if (!oldstate->completed &&
+        newstate->completed && !newstate->used_solve)
+        return FLASH_SPIN;
+    else
+        return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    return state->completed ? +1 : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+    int pw, ph;
+
+    game_compute_size(params, 1300, &pw, &ph);
+    *x = pw / 100.0F;
+    *y = ph / 100.0F;
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+    int ink = print_mono_colour(dr, 0);
+    int x, y;
+
+    /* Fake up just enough of a drawstate */
+    game_drawstate ads, *ds = &ads;
+    ds->tilesize = tilesize;
+    ds->n = state->n;
+
+    /*
+     * Border and grid.
+     */
+    print_line_width(dr, TILE_SIZE / 40);
+    for (x = 1; x < state->w; x++)
+       draw_line(dr, COORD(x), COORD(0), COORD(x), COORD(state->h), ink);
+    for (y = 1; y < state->h; y++)
+       draw_line(dr, COORD(0), COORD(y), COORD(state->w), COORD(y), ink);
+    print_line_width(dr, 2*TILE_SIZE / 40);
+    draw_rect_outline(dr, COORD(0), COORD(0), TILE_SIZE*state->w,
+                     TILE_SIZE*state->h, ink);
+
+    /*
+     * Arrows and numbers.
+     */
+    print_line_width(dr, 0);
+    for (y = 0; y < state->h; y++)
+       for (x = 0; x < state->w; x++)
+           tile_redraw(dr, ds, COORD(x), COORD(y), state->dirs[y*state->w+x],
+                       0, state->nums[y*state->w+x], 0, 0.0, ink);
+}
+
+#ifdef COMBINED
+#define thegame signpost
+#endif
+
+const struct game thegame = {
+    "Signpost", "games.signpost", "signpost",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    TRUE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    TRUE, FALSE, game_print_size, game_print,
+    FALSE,                            /* wants_statusbar */
+    FALSE, game_timing_state,
+    REQUIRE_RBUTTON,                  /* flags */
+};
+
+#ifdef STANDALONE_SOLVER
+
+#include <time.h>
+#include <stdarg.h>
+
+const char *quis = NULL;
+int verbose = 0;
+
+void usage(FILE *out) {
+    fprintf(out, "usage: %s [--stdin] [--soak] [--seed SEED] <params>|<game id>\n", quis);
+}
+
+static void cycle_seed(char **seedstr, random_state *rs)
+{
+    char newseed[16];
+    int j;
+
+    newseed[15] = '\0';
+    newseed[0] = '1' + (char)random_upto(rs, 9);
+    for (j = 1; j < 15; j++)
+        newseed[j] = '0' + (char)random_upto(rs, 10);
+    sfree(*seedstr);
+    *seedstr = dupstr(newseed);
+}
+
+static void start_soak(game_params *p, char *seedstr)
+{
+    time_t tt_start, tt_now, tt_last;
+    char *desc, *aux;
+    random_state *rs;
+    long n = 0, nnums = 0, i;
+    game_state *state;
+
+    tt_start = tt_now = time(NULL);
+    printf("Soak-generating a %dx%d grid.\n", p->w, p->h);
+
+    while (1) {
+       rs = random_new(seedstr, strlen(seedstr));
+       desc = thegame.new_desc(p, rs, &aux, 0);
+
+       state = thegame.new_game(NULL, p, desc);
+       for (i = 0; i < state->n; i++) {
+           if (state->flags[i] & FLAG_IMMUTABLE)
+               nnums++;
+       }
+       thegame.free_game(state);
+
+       sfree(desc);
+       cycle_seed(&seedstr, rs);
+       random_free(rs);
+
+       n++;
+       tt_last = time(NULL);
+       if (tt_last > tt_now) {
+           tt_now = tt_last;
+           printf("%ld total, %3.1f/s, %3.1f nums/grid (%3.1f%%).\n",
+                  n,
+                  (double)n / ((double)tt_now - tt_start),
+                  (double)nnums / (double)n,
+                  ((double)nnums * 100.0) / ((double)n * (double)p->w * (double)p->h) );
+       }
+    }
+}
+
+static void process_desc(char *id)
+{
+    char *desc, *err, *solvestr;
+    game_params *p;
+    game_state *s;
+
+    printf("%s\n  ", id);
+
+    desc = strchr(id, ':');
+    if (!desc) {
+        fprintf(stderr, "%s: expecting game description.", quis);
+        exit(1);
+    }
+
+    *desc++ = '\0';
+
+    p = thegame.default_params();
+    thegame.decode_params(p, id);
+    err = thegame.validate_params(p, 1);
+    if (err) {
+        fprintf(stderr, "%s: %s", quis, err);
+        thegame.free_params(p);
+        return;
+    }
+
+    err = thegame.validate_desc(p, desc);
+    if (err) {
+        fprintf(stderr, "%s: %s\nDescription: %s\n", quis, err, desc);
+        thegame.free_params(p);
+        return;
+    }
+
+    s = thegame.new_game(NULL, p, desc);
+
+    solvestr = thegame.solve(s, s, NULL, &err);
+    if (!solvestr)
+        fprintf(stderr, "%s\n", err);
+    else
+        printf("Puzzle is soluble.\n");
+
+    thegame.free_game(s);
+    thegame.free_params(p);
+}
+
+int main(int argc, const char *argv[])
+{
+    char *id = NULL, *desc, *err, *aux = NULL;
+    int soak = 0, verbose = 0, stdin_desc = 0, n = 1, i;
+    char *seedstr = NULL, newseed[16];
+
+    setvbuf(stdout, NULL, _IONBF, 0);
+
+    quis = argv[0];
+    while (--argc > 0) {
+        char *p = (char*)(*++argv);
+        if (!strcmp(p, "-v") || !strcmp(p, "--verbose"))
+            verbose = 1;
+        else if (!strcmp(p, "--stdin"))
+            stdin_desc = 1;
+        else if (!strcmp(p, "-e") || !strcmp(p, "--seed")) {
+            seedstr = dupstr(*++argv);
+            argc--;
+        } else if (!strcmp(p, "-n") || !strcmp(p, "--number")) {
+            n = atoi(*++argv);
+            argc--;
+        } else if (!strcmp(p, "-s") || !strcmp(p, "--soak")) {
+            soak = 1;
+        } else if (*p == '-') {
+            fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p);
+            usage(stderr);
+            exit(1);
+        } else {
+            id = p;
+        }
+    }
+
+    sprintf(newseed, "%lu", (unsigned long) time(NULL));
+    seedstr = dupstr(newseed);
+
+    if (id || !stdin_desc) {
+        if (id && strchr(id, ':')) {
+            /* Parameters and description passed on cmd-line:
+             * try and solve it. */
+            process_desc(id);
+        } else {
+            /* No description passed on cmd-line: decode parameters
+             * (with optional seed too) */
+
+            game_params *p = thegame.default_params();
+
+            if (id) {
+                char *cmdseed = strchr(id, '#');
+                if (cmdseed) {
+                    *cmdseed++ = '\0';
+                    sfree(seedstr);
+                    seedstr = dupstr(cmdseed);
+                }
+
+                thegame.decode_params(p, id);
+            }
+
+            err = thegame.validate_params(p, 1);
+            if (err) {
+                fprintf(stderr, "%s: %s", quis, err);
+                thegame.free_params(p);
+                exit(1);
+            }
+
+            /* We have a set of valid parameters; either soak with it
+             * or generate a single game description and print to stdout. */
+            if (soak)
+                start_soak(p, seedstr);
+            else {
+                char *pstring = thegame.encode_params(p, 0);
+
+                for (i = 0; i < n; i++) {
+                    random_state *rs = random_new(seedstr, strlen(seedstr));
+
+                    if (verbose) printf("%s#%s\n", pstring, seedstr);
+                    desc = thegame.new_desc(p, rs, &aux, 0);
+                    printf("%s:%s\n", pstring, desc);
+                    sfree(desc);
+
+                    cycle_seed(&seedstr, rs);
+
+                    random_free(rs);
+                }
+
+                sfree(pstring);
+            }
+            thegame.free_params(p);
+        }
+    }
+
+    if (stdin_desc) {
+        char buf[4096];
+
+        while (fgets(buf, sizeof(buf), stdin)) {
+           buf[strcspn(buf, "\r\n")] = '\0';
+            process_desc(buf);
+        }
+    }
+    sfree(seedstr);
+
+    return 0;
+}
+
+#endif
+
+
+/* vim: set shiftwidth=4 tabstop=8: */
diff --git a/singles.R b/singles.R
new file mode 100644 (file)
index 0000000..2d10c4b
--- /dev/null
+++ b/singles.R
@@ -0,0 +1,23 @@
+# -*- makefile -*-
+
+SINGLES_EXTRA = dsf latin maxflow tree234
+
+singles : [X] GTK COMMON singles SINGLES_EXTRA singles-icon|no-icon
+singles : [G] WINDOWS COMMON singles SINGLES_EXTRA singles.res|noicon.res
+
+ALL += singles[COMBINED] SINGLES_EXTRA
+
+singlessolver : [U] singles[STANDALONE_SOLVER] SINGLES_EXTRA STANDALONE
+singlessolver : [C] singles[STANDALONE_SOLVER] SINGLES_EXTRA STANDALONE
+
+!begin am gtk
+GAMES += singles
+!end
+
+!begin >list.c
+    A(singles) \
+!end
+
+!begin >gamedesc.txt
+singles:singles.exe:Singles:Number-removing puzzle:Black out the right set of duplicate numbers.
+!end
diff --git a/singles.c b/singles.c
new file mode 100644 (file)
index 0000000..a9b1d9d
--- /dev/null
+++ b/singles.c
@@ -0,0 +1,2004 @@
+/*
+ * singles.c: implementation of Hitori ('let me alone') from Nikoli.
+ *
+ * Make single-get able to fetch a specific puzzle ID from menneske.no?
+ *
+ * www.menneske.no solving methods:
+ *
+ * Done:
+ * SC: if you circle a cell, any cells in same row/col with same no --> black
+ *  -- solver_op_circle
+ * SB: if you make a cell black, any cells around it --> white
+ *  -- solver_op_blacken
+ * ST: 3 identical cells in row, centre is white and outer two black.
+ * SP: 2 identical cells with single-cell gap, middle cell is white.
+ *  -- solver_singlesep (both ST and SP)
+ * PI: if you have a pair of same number in row/col, any other
+ *      cells of same number must be black.
+ *  -- solve_doubles
+ * CC: if you have a black on edge one cell away from corner, cell
+ *       on edge diag. adjacent must be white.
+ * CE: if you have 2 black cells of triangle on edge, third cell must
+ *      be white.
+ * QM: if you have 3 black cells of diagonal square in middle, fourth
+ *      cell must be white.
+ *  -- solve_allblackbutone (CC, CE, and QM).
+ * QC: a corner with 4 identical numbers (or 2 and 2) must have the
+ *      corner cell (and cell diagonal to that) black.
+ * TC: a corner with 3 identical numbers (with the L either way)
+ *      must have the apex of L black, and other two white.
+ * DC: a corner with 2 identical numbers in domino can set a white
+ *      cell along wall.
+ *  -- solve_corners (QC, TC, DC)
+ * IP: pair with one-offset-pair force whites by offset pair
+ *  -- solve_offsetpair
+ * MC: any cells diag. adjacent to black cells that would split board
+ *      into separate white regions must be white.
+ *  -- solve_removesplits
+ *
+ * Still to do:
+ *
+ * TEP: 3 pairs of dominos parallel to side, can mark 4 white cells
+ *       alongside.
+ * DEP: 2 pairs of dominos parallel to side, can mark 2 white cells.
+ * FI: if you have two sets of double-cells packed together, singles
+ *      in that row/col must be white (qv. PI)
+ * QuM: four identical cells (or 2 and 2) in middle of grid only have
+ *       two possible solutions each.
+ * FDE: doubles one row/column away from edge can force a white cell.
+ * FDM: doubles in centre (next to bits of diag. square) can force a white cell.
+ * MP: two pairs with same number between force number to black.
+ * CnC: if circling a cell leads to impossible board, cell is black.
+ * MC: if we have two possiblilities, can we force a white circle?
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+#include "latin.h"
+
+#ifdef STANDALONE_SOLVER
+int verbose = 0;
+#endif
+
+#define PREFERRED_TILE_SIZE 32
+#define TILE_SIZE (ds->tilesize)
+#define BORDER    (TILE_SIZE / 2)
+
+#define CRAD      ((TILE_SIZE / 2) - 1)
+#define TEXTSZ    ((14*CRAD/10) - 1) /* 2 * sqrt(2) of CRAD */
+
+#define COORD(x)  ( (x) * TILE_SIZE + BORDER )
+#define FROMCOORD(x)  ( ((x) - BORDER + TILE_SIZE) / TILE_SIZE - 1 )
+
+#define INGRID(s,x,y) ((x) >= 0 && (x) < (s)->w && (y) >= 0 && (y) < (s)->h)
+
+#define FLASH_TIME 0.7F
+
+enum {
+    COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT,
+    COL_BLACK, COL_WHITE, COL_BLACKNUM, COL_GRID,
+    COL_CURSOR, COL_ERROR,
+    NCOLOURS
+};
+
+struct game_params {
+    int w, h, diff;
+};
+
+#define F_BLACK         0x1
+#define F_CIRCLE        0x2
+#define F_ERROR         0x4
+#define F_SCRATCH       0x8
+
+struct game_state {
+    int w, h, n, o;             /* n = w*h; o = max(w, h) */
+    int completed, used_solve, impossible;
+    int *nums;                  /* size w*h */
+    unsigned int *flags;        /* size w*h */
+};
+
+/* top, right, bottom, left */
+static const int dxs[4] = { 0, 1, 0, -1 };
+static const int dys[4] = { -1, 0, 1, 0 };
+
+/* --- Game parameters and preset functions --- */
+
+#define DIFFLIST(A)             \
+    A(EASY,Easy,e)              \
+    A(TRICKY,Tricky,k)
+
+#define ENUM(upper,title,lower) DIFF_ ## upper,
+#define TITLE(upper,title,lower) #title,
+#define ENCODE(upper,title,lower) #lower
+#define CONFIG(upper,title,lower) ":" #title
+
+enum { DIFFLIST(ENUM) DIFF_MAX, DIFF_ANY };
+static char const *const singles_diffnames[] = { DIFFLIST(TITLE) };
+static char const singles_diffchars[] = DIFFLIST(ENCODE);
+#define DIFFCOUNT lenof(singles_diffchars)
+#define DIFFCONFIG DIFFLIST(CONFIG)
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+    ret->w = ret->h = 5;
+    ret->diff = DIFF_EASY;
+
+    return ret;
+}
+
+static const struct game_params singles_presets[] = {
+  {  5,  5, DIFF_EASY },
+  {  5,  5, DIFF_TRICKY },
+  {  6,  6, DIFF_EASY },
+  {  6,  6, DIFF_TRICKY },
+  {  8,  8, DIFF_EASY },
+  {  8,  8, DIFF_TRICKY },
+  { 10, 10, DIFF_EASY },
+  { 10, 10, DIFF_TRICKY },
+  { 12, 12, DIFF_EASY },
+  { 12, 12, DIFF_TRICKY }
+};
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params *ret;
+    char buf[80];
+
+    if (i < 0 || i >= lenof(singles_presets))
+        return FALSE;
+
+    ret = default_params();
+    *ret = singles_presets[i];
+    *params = ret;
+
+    sprintf(buf, "%dx%d %s", ret->w, ret->h, singles_diffnames[ret->diff]);
+    *name = dupstr(buf);
+
+    return TRUE;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+static void decode_params(game_params *ret, char const *string)
+{
+    char const *p = string;
+    int i;
+
+    ret->w = ret->h = atoi(p);
+    while (*p && isdigit((unsigned char)*p)) p++;
+    if (*p == 'x') {
+        p++;
+        ret->h = atoi(p);
+       while (*p && isdigit((unsigned char)*p)) p++;
+    }
+    if (*p == 'd') {
+        ret->diff = DIFF_MAX; /* which is invalid */
+        p++;
+        for (i = 0; i < DIFFCOUNT; i++) {
+            if (*p == singles_diffchars[i])
+                ret->diff = i;
+        }
+        p++;
+    }
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char data[256];
+
+    if (full)
+        sprintf(data, "%dx%dd%c", params->w, params->h, singles_diffchars[params->diff]);
+    else
+        sprintf(data, "%dx%d", params->w, params->h);
+
+    return dupstr(data);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+    char buf[80];
+
+    ret = snewn(4, config_item);
+
+    ret[0].name = "Width";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->w);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Height";
+    ret[1].type = C_STRING;
+    sprintf(buf, "%d", params->h);
+    ret[1].sval = dupstr(buf);
+    ret[1].ival = 0;
+
+    ret[2].name = "Difficulty";
+    ret[2].type = C_CHOICES;
+    ret[2].sval = DIFFCONFIG;
+    ret[2].ival = params->diff;
+
+    ret[3].name = NULL;
+    ret[3].type = C_END;
+    ret[3].sval = NULL;
+    ret[3].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = atoi(cfg[0].sval);
+    ret->h = atoi(cfg[1].sval);
+    ret->diff = cfg[2].ival;
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    if (params->w < 2 || params->h < 2)
+       return "Width and neight must be at least two";
+    if (params->w > 10+26+26 || params->h > 10+26+26)
+        return "Puzzle is too large";
+    if (full) {
+        if (params->diff < 0 || params->diff >= DIFF_MAX)
+            return "Unknown difficulty rating";
+    }
+
+    return NULL;
+}
+
+/* --- Game description string generation and unpicking --- */
+
+static game_state *blank_game(int w, int h)
+{
+    game_state *state = snew(game_state);
+
+    memset(state, 0, sizeof(game_state));
+    state->w = w;
+    state->h = h;
+    state->n = w*h;
+    state->o = max(w,h);
+
+    state->completed = state->used_solve = state->impossible = 0;
+
+    state->nums  = snewn(state->n, int);
+    state->flags = snewn(state->n, unsigned int);
+
+    memset(state->nums, 0, state->n*sizeof(int));
+    memset(state->flags, 0, state->n*sizeof(unsigned int));
+
+    return state;
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    game_state *ret = blank_game(state->w, state->h);
+
+    ret->completed = state->completed;
+    ret->used_solve = state->used_solve;
+    ret->impossible = state->impossible;
+
+    memcpy(ret->nums, state->nums, state->n*sizeof(int));
+    memcpy(ret->flags, state->flags, state->n*sizeof(unsigned int));
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    sfree(state->nums);
+    sfree(state->flags);
+    sfree(state);
+}
+
+static char n2c(int num) {
+    if (num < 10)
+        return '0' + num;
+    else if (num < 10+26)
+        return 'a' + num - 10;
+    else
+        return 'A' + num - 10 - 26;
+    return '?';
+}
+
+static int c2n(char c) {
+    if (isdigit((unsigned char)c))
+        return (int)(c - '0');
+    else if (c >= 'a' && c <= 'z')
+        return (int)(c - 'a' + 10);
+    else if (c >= 'A' && c <= 'Z')
+        return (int)(c - 'A' + 10 + 26);
+    return -1;
+}
+
+static void unpick_desc(const game_params *params, const char *desc,
+                        game_state **sout, char **mout)
+{
+    game_state *state = blank_game(params->w, params->h);
+    char *msg = NULL;
+    int num = 0, i = 0;
+
+    if (strlen(desc) != state->n) {
+        msg = "Game description is wrong length";
+        goto done;
+    }
+    for (i = 0; i < state->n; i++) {
+        num = c2n(desc[i]);
+        if (num <= 0 || num > state->o) {
+            msg = "Game description contains unexpected characters";
+            goto done;
+        }
+        state->nums[i] = num;
+    }
+done:
+    if (msg) { /* sth went wrong. */
+        if (mout) *mout = msg;
+        free_game(state);
+    } else {
+        if (mout) *mout = NULL;
+        if (sout) *sout = state;
+        else free_game(state);
+    }
+}
+
+static char *generate_desc(game_state *state, int issolve)
+{
+    char *ret = snewn(state->n+1+(issolve?1:0), char);
+    int i, p=0;
+
+    if (issolve)
+        ret[p++] = 'S';
+    for (i = 0; i < state->n; i++)
+        ret[p++] = n2c(state->nums[i]);
+    ret[p] = '\0';
+    return ret;
+}
+
+/* --- Useful game functions (completion, etc.) --- */
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    int len, x, y, i;
+    char *ret, *p;
+
+    len = (state->w)*2;       /* one row ... */
+    len = len * (state->h*2); /* ... h rows, including gaps ... */
+    len += 1;              /* ... final NL */
+    p = ret = snewn(len, char);
+
+    for (y = 0; y < state->h; y++) {
+        for (x = 0; x < state->w; x++) {
+            i = y*state->w + x;
+            if (x > 0) *p++ = ' ';
+            *p++ = (state->flags[i] & F_BLACK) ? '*' : n2c(state->nums[i]);
+        }
+        *p++ = '\n';
+        for (x = 0; x < state->w; x++) {
+            i = y*state->w + x;
+            if (x > 0) *p++ = ' ';
+            *p++ = (state->flags[i] & F_CIRCLE) ? '~' : ' ';
+        }
+        *p++ = '\n';
+    }
+    *p++ = '\0';
+    assert(p - ret == len);
+
+    return ret;
+}
+
+static void debug_state(const char *desc, game_state *state) {
+    char *dbg = game_text_format(state);
+    debug(("%s:\n%s", desc, dbg));
+    sfree(dbg);
+}
+
+static void connect_if_same(game_state *state, int *dsf, int i1, int i2)
+{
+    int c1, c2;
+
+    if ((state->flags[i1] & F_BLACK) != (state->flags[i2] & F_BLACK))
+        return;
+
+    c1 = dsf_canonify(dsf, i1);
+    c2 = dsf_canonify(dsf, i2);
+    dsf_merge(dsf, c1, c2);
+}
+
+static void connect_dsf(game_state *state, int *dsf)
+{
+    int x, y, i;
+
+    /* Construct a dsf array for connected blocks; connections
+     * tracked to right and down. */
+    dsf_init(dsf, state->n);
+    for (x = 0; x < state->w; x++) {
+        for (y = 0; y < state->h; y++) {
+            i = y*state->w + x;
+
+            if (x < state->w-1)
+                connect_if_same(state, dsf, i, i+1); /* right */
+            if (y < state->h-1)
+                connect_if_same(state, dsf, i, i+state->w); /* down */
+        }
+    }
+}
+
+#define CC_MARK_ERRORS  1
+#define CC_MUST_FILL    2
+
+static int check_rowcol(game_state *state, int starti, int di, int sz, unsigned flags)
+{
+    int nerr = 0, n, m, i, j;
+
+    /* if any circled numbers have identical non-circled numbers on
+     *     same row/column, error (non-circled)
+     * if any circled numbers in same column are same number, highlight them.
+     * if any rows/columns have >1 of same number, not complete. */
+
+    for (n = 0, i = starti; n < sz; n++, i += di) {
+        if (state->flags[i] & F_BLACK) continue;
+        for (m = n+1, j = i+di; m < sz; m++, j += di) {
+            if (state->flags[j] & F_BLACK) continue;
+            if (state->nums[i] != state->nums[j]) continue;
+
+            nerr++; /* ok, we have two numbers the same in a row. */
+            if (!(flags & CC_MARK_ERRORS)) continue;
+
+            /* If we have two circles in the same row around
+             * two identical numbers, they are _both_ wrong. */
+            if ((state->flags[i] & F_CIRCLE) &&
+                (state->flags[j] & F_CIRCLE)) {
+                state->flags[i] |= F_ERROR;
+                state->flags[j] |= F_ERROR;
+            }
+            /* Otherwise, if we have a circle, any other identical
+             * numbers in that row are obviously wrong. We don't
+             * highlight this, however, since it makes the process
+             * of solving the puzzle too easy (you circle a number
+             * and it promptly tells you which numbers to blacken! */
+#if 0
+            else if (state->flags[i] & F_CIRCLE)
+                state->flags[j] |= F_ERROR;
+            else if (state->flags[j] & F_CIRCLE)
+                state->flags[i] |= F_ERROR;
+#endif
+        }
+    }
+    return nerr;
+}
+
+static int check_complete(game_state *state, unsigned flags)
+{
+    int *dsf = snewn(state->n, int);
+    int x, y, i, error = 0, nwhite, w = state->w, h = state->h;
+
+    if (flags & CC_MARK_ERRORS) {
+        for (i = 0; i < state->n; i++)
+            state->flags[i] &= ~F_ERROR;
+    }
+    connect_dsf(state, dsf);
+
+    /* If we're the solver we need the grid all to be definitively
+     * black or definitively white (i.e. circled) otherwise the solver
+     * has found an ambiguous grid. */
+    if (flags & CC_MUST_FILL) {
+        for (i = 0; i < state->n; i++) {
+            if (!(state->flags[i] & F_BLACK) && !(state->flags[i] & F_CIRCLE))
+                error += 1;
+        }
+    }
+
+    /* Mark any black squares in groups of >1 as errors.
+     * Count number of white squares. */
+    nwhite = 0;
+    for (i = 0; i < state->n; i++) {
+        if (state->flags[i] & F_BLACK) {
+            if (dsf_size(dsf, i) > 1) {
+                error += 1;
+                if (flags & CC_MARK_ERRORS)
+                    state->flags[i] |= F_ERROR;
+            }
+        } else
+            nwhite += 1;
+    }
+
+    /* Check attributes of white squares, row- and column-wise. */
+    for (x = 0; x < w; x++) /* check cols from (x,0) */
+        error += check_rowcol(state, x,   w, h, flags);
+    for (y = 0; y < h; y++) /* check rows from (0,y) */
+        error += check_rowcol(state, y*w, 1, w, flags);
+
+    /* If there's more than one white region, pick the largest one to
+     * be the canonical one (arbitrarily tie-breaking towards lower
+     * array indices), and mark all the others as erroneous. */
+    {
+        int largest = 0, canonical = -1;
+        for (i = 0; i < state->n; i++)
+            if (!(state->flags[i] & F_BLACK)) {
+                int size = dsf_size(dsf, i);
+                if (largest < size) {
+                    largest = size;
+                    canonical = i;
+                }
+            }
+
+        if (largest < nwhite) {
+            for (i = 0; i < state->n; i++)
+                if (!(state->flags[i] & F_BLACK) &&
+                    dsf_canonify(dsf, i) != canonical) {
+                    error += 1;
+                    if (flags & CC_MARK_ERRORS)
+                        state->flags[i] |= F_ERROR;
+                }
+        }
+    }
+
+    sfree(dsf);
+    return (error > 0) ? 0 : 1;
+}
+
+static char *game_state_diff(const game_state *src, const game_state *dst,
+                             int issolve)
+{
+    char *ret = NULL, buf[80], c;
+    int retlen = 0, x, y, i, k;
+    unsigned int fmask = F_BLACK | F_CIRCLE;
+
+    assert(src->n == dst->n);
+
+    if (issolve) {
+        ret = sresize(ret, 3, char);
+        ret[0] = 'S'; ret[1] = ';'; ret[2] = '\0';
+        retlen += 2;
+    }
+
+    for (x = 0; x < dst->w; x++) {
+        for (y = 0; y < dst->h; y++) {
+            i = y*dst->w + x;
+            if ((src->flags[i] & fmask) != (dst->flags[i] & fmask)) {
+                assert((dst->flags[i] & fmask) != fmask);
+                if (dst->flags[i] & F_BLACK)
+                    c = 'B';
+                else if (dst->flags[i] & F_CIRCLE)
+                    c = 'C';
+                else
+                    c = 'E';
+                k = sprintf(buf, "%c%d,%d;", (int)c, x, y);
+                ret = sresize(ret, retlen + k + 1, char);
+                strcpy(ret + retlen, buf);
+                retlen += k;
+            }
+        }
+    }
+    return ret;
+}
+
+/* --- Solver --- */
+
+enum { BLACK, CIRCLE };
+
+struct solver_op {
+    int x, y, op; /* op one of BLACK or CIRCLE. */
+    const char *desc; /* must be non-malloced. */
+};
+
+struct solver_state {
+    struct solver_op *ops;
+    int n_ops, n_alloc;
+    int *scratch;
+};
+
+static struct solver_state *solver_state_new(game_state *state)
+{
+    struct solver_state *ss = snew(struct solver_state);
+
+    ss->ops = NULL;
+    ss->n_ops = ss->n_alloc = 0;
+    ss->scratch = snewn(state->n, int);
+
+    return ss;
+}
+
+static void solver_state_free(struct solver_state *ss)
+{
+    sfree(ss->scratch);
+    if (ss->ops) sfree(ss->ops);
+    sfree(ss);
+}
+
+static void solver_op_add(struct solver_state *ss, int x, int y, int op, const char *desc)
+{
+    struct solver_op *sop;
+
+    if (ss->n_alloc < ss->n_ops + 1) {
+        ss->n_alloc = (ss->n_alloc + 1) * 2;
+        ss->ops = sresize(ss->ops, ss->n_alloc, struct solver_op);
+    }
+    sop = &(ss->ops[ss->n_ops++]);
+    sop->x = x; sop->y = y; sop->op = op; sop->desc = desc;
+    debug(("added solver op %s ('%s') at (%d,%d)\n",
+           op == BLACK ? "BLACK" : "CIRCLE", desc, x, y));
+}
+
+static void solver_op_circle(game_state *state, struct solver_state *ss,
+                             int x, int y)
+{
+    int i = y*state->w + x;
+
+    if (!INGRID(state, x, y)) return;
+    if (state->flags[i] & F_BLACK) {
+        debug(("... solver wants to add auto-circle on black (%d,%d)\n", x, y));
+        state->impossible = 1;
+        return;
+    }
+    /* Only add circle op if it's not already circled. */
+    if (!(state->flags[i] & F_CIRCLE)) {
+        solver_op_add(ss, x, y, CIRCLE, "SB - adjacent to black square");
+    }
+}
+
+static void solver_op_blacken(game_state *state, struct solver_state *ss,
+                              int x, int y, int num)
+{
+    int i = y*state->w + x;
+
+    if (!INGRID(state, x, y)) return;
+    if (state->nums[i] != num) return;
+    if (state->flags[i] & F_CIRCLE) {
+        debug(("... solver wants to add auto-black on circled(%d,%d)\n", x, y));
+        state->impossible = 1;
+        return;
+    }
+    /* Only add black op if it's not already black. */
+    if (!(state->flags[i] & F_BLACK)) {
+        solver_op_add(ss, x, y, BLACK, "SC - number on same row/col as circled");
+    }
+}
+
+static int solver_ops_do(game_state *state, struct solver_state *ss)
+{
+    int next_op = 0, i, x, y, n_ops = 0;
+    struct solver_op op;
+
+    /* Care here: solver_op_* may call solver_op_add which may extend the
+     * ss->n_ops. */
+
+    while (next_op < ss->n_ops) {
+        op = ss->ops[next_op++]; /* copy this away, it may get reallocated. */
+        i = op.y*state->w + op.x;
+
+        if (op.op == BLACK) {
+            if (state->flags[i] & F_CIRCLE) {
+                debug(("Solver wants to blacken circled square (%d,%d)!\n", op.x, op.y));
+                state->impossible = 1;
+                return n_ops;
+            }
+            if (!(state->flags[i] & F_BLACK)) {
+                debug(("... solver adding black at (%d,%d): %s\n", op.x, op.y, op.desc));
+#ifdef STANDALONE_SOLVER
+                if (verbose)
+                    printf("Adding black at (%d,%d): %s\n", op.x, op.y, op.desc);
+#endif
+                state->flags[i] |= F_BLACK;
+                /*debug_state("State after adding black", state);*/
+                n_ops++;
+                solver_op_circle(state, ss, op.x-1, op.y);
+                solver_op_circle(state, ss, op.x+1, op.y);
+                solver_op_circle(state, ss, op.x,   op.y-1);
+                solver_op_circle(state, ss, op.x,   op.y+1);
+                }
+        } else {
+            if (state->flags[i] & F_BLACK) {
+                debug(("Solver wants to circle blackened square (%d,%d)!\n", op.x, op.y));
+                state->impossible = 1;
+                return n_ops;
+            }
+            if (!(state->flags[i] & F_CIRCLE)) {
+                debug(("... solver adding circle at (%d,%d): %s\n", op.x, op.y, op.desc));
+#ifdef STANDALONE_SOLVER
+                if (verbose)
+                    printf("Adding circle at (%d,%d): %s\n", op.x, op.y, op.desc);
+#endif
+                state->flags[i] |= F_CIRCLE;
+                /*debug_state("State after adding circle", state);*/
+                n_ops++;
+                for (x = 0; x < state->w; x++) {
+                    if (x != op.x)
+                        solver_op_blacken(state, ss, x, op.y, state->nums[i]);
+                }
+                for (y = 0; y < state->h; y++) {
+                    if (y != op.y)
+                        solver_op_blacken(state, ss, op.x, y, state->nums[i]);
+                }
+            }
+        }
+    }
+    ss->n_ops = 0;
+    return n_ops;
+}
+
+/* If the grid has two identical numbers with one cell between them, the inner
+ * cell _must_ be white (and thus circled); (at least) one of the two must be
+ * black (since they're in the same column or row) and thus the middle cell is
+ * next to a black cell. */
+static int solve_singlesep(game_state *state, struct solver_state *ss)
+{
+    int x, y, i, ir, irr, id, idd, n_ops = ss->n_ops;
+
+    for (x = 0; x < state->w; x++) {
+        for (y = 0; y < state->h; y++) {
+            i = y*state->w + x;
+
+            /* Cell two to our right? */
+            ir = i + 1; irr = ir + 1;
+            if (x < (state->w-2) &&
+                state->nums[i] == state->nums[irr] &&
+                !(state->flags[ir] & F_CIRCLE)) {
+                solver_op_add(ss, x+1, y, CIRCLE, "SP/ST - between identical nums");
+            }
+            /* Cell two below us? */
+            id = i + state->w; idd = id + state->w;
+            if (y < (state->h-2) &&
+                state->nums[i] == state->nums[idd] &&
+                !(state->flags[id] & F_CIRCLE)) {
+                solver_op_add(ss, x, y+1, CIRCLE, "SP/ST - between identical nums");
+            }
+        }
+    }
+    return ss->n_ops - n_ops;
+}
+
+/* If we have two identical numbers next to each other (in a row or column),
+ * any other identical numbers in that column must be black. */
+static int solve_doubles(game_state *state, struct solver_state *ss)
+{
+    int x, y, i, ii, n_ops = ss->n_ops, xy;
+
+    for (y = 0, i = 0; y < state->h; y++) {
+        for (x = 0; x < state->w; x++, i++) {
+            assert(i == y*state->w+x);
+            if (state->flags[i] & F_BLACK) continue;
+
+            ii = i+1; /* check cell to our right. */
+            if (x < (state->w-1) &&
+                !(state->flags[ii] & F_BLACK) &&
+                state->nums[i] == state->nums[ii]) {
+                for (xy = 0; xy < state->w; xy++) {
+                    if (xy == x || xy == (x+1)) continue;
+                    if (state->nums[y*state->w + xy] == state->nums[i] &&
+                        !(state->flags[y*state->w + xy] & F_BLACK))
+                        solver_op_add(ss, xy, y, BLACK, "PI - same row as pair");
+                }
+            }
+
+            ii = i+state->w; /* check cell below us */
+            if (y < (state->h-1) &&
+                !(state->flags[ii] & F_BLACK) &&
+                state->nums[i] == state->nums[ii]) {
+                for (xy = 0; xy < state->h; xy++) {
+                    if (xy == y || xy == (y+1)) continue;
+                    if (state->nums[xy*state->w + x] == state->nums[i] &&
+                        !(state->flags[xy*state->w + x] & F_BLACK))
+                        solver_op_add(ss, x, xy, BLACK, "PI - same col as pair");
+                }
+            }
+        }
+    }
+    return ss->n_ops - n_ops;
+}
+
+/* If a white square has all-but-one possible adjacent squares black, the
+ * one square left over must be white. */
+static int solve_allblackbutone(game_state *state, struct solver_state *ss)
+{
+    int x, y, i, n_ops = ss->n_ops, xd, yd, id, ifree;
+    int dis[4], d;
+
+    dis[0] = -state->w;
+    dis[1] = 1;
+    dis[2] = state->w;
+    dis[3] = -1;
+
+    for (y = 0, i = 0; y < state->h; y++) {
+        for (x = 0; x < state->w; x++, i++) {
+            assert(i == y*state->w+x);
+            if (state->flags[i] & F_BLACK) continue;
+
+            ifree = -1;
+            for (d = 0; d < 4; d++) {
+                xd = x + dxs[d]; yd = y + dys[d]; id = i + dis[d];
+                if (!INGRID(state, xd, yd)) continue;
+
+                if (state->flags[id] & F_CIRCLE)
+                    goto skip; /* this cell already has a way out */
+                if (!(state->flags[id] & F_BLACK)) {
+                    if (ifree != -1)
+                        goto skip; /* this cell has >1 white cell around it. */
+                    ifree = id;
+                }
+            }
+            if (ifree != -1)
+                solver_op_add(ss, ifree%state->w, ifree/state->w, CIRCLE,
+                              "CC/CE/QM: white cell with single non-black around it");
+            else {
+                debug(("White cell with no escape at (%d,%d)\n", x, y));
+                state->impossible = 1;
+                return 0;
+            }
+skip: ;
+        }
+    }
+    return ss->n_ops - n_ops;
+}
+
+/* If we have 4 numbers the same in a 2x2 corner, the far corner and the
+ * diagonally-adjacent square must both be black.
+ * If we have 3 numbers the same in a 2x2 corner, the apex of the L
+ * thus formed must be black.
+ * If we have 2 numbers the same in a 2x2 corner, the non-same cell
+ * one away from the corner must be white. */
+static void solve_corner(game_state *state, struct solver_state *ss,
+                        int x, int y, int dx, int dy)
+{
+    int is[4], ns[4], xx, yy, w = state->w;
+
+    for (yy = 0; yy < 2; yy++) {
+        for (xx = 0; xx < 2; xx++) {
+            is[yy*2+xx] = (y + dy*yy) * w + (x + dx*xx);
+            ns[yy*2+xx] = state->nums[is[yy*2+xx]];
+        }
+    } /* order is now (corner, side 1, side 2, inner) */
+
+    if (ns[0] == ns[1] && ns[0] == ns[2] && ns[0] == ns[3]) {
+        solver_op_add(ss, is[0]%w, is[0]/w, BLACK, "QC: corner with 4 matching");
+        solver_op_add(ss, is[3]%w, is[3]/w, BLACK, "QC: corner with 4 matching");
+    } else if (ns[0] == ns[1] && ns[0] == ns[2]) {
+        /* corner and 2 sides: apex is corner. */
+        solver_op_add(ss, is[0]%w, is[0]/w, BLACK, "TC: corner apex from 3 matching");
+    } else if (ns[1] == ns[2] && ns[1] == ns[3]) {
+        /* side, side, fourth: apex is fourth. */
+        solver_op_add(ss, is[3]%w, is[3]/w, BLACK, "TC: inside apex from 3 matching");
+    } else if (ns[0] == ns[1] || ns[1] == ns[3]) {
+        /* either way here we match the non-identical side. */
+        solver_op_add(ss, is[2]%w, is[2]/w, CIRCLE, "DC: corner with 2 matching");
+    } else if (ns[0] == ns[2] || ns[2] == ns[3]) {
+        /* ditto */
+        solver_op_add(ss, is[1]%w, is[1]/w, CIRCLE, "DC: corner with 2 matching");
+    }
+}
+
+static int solve_corners(game_state *state, struct solver_state *ss)
+{
+    int n_ops = ss->n_ops;
+
+    solve_corner(state, ss, 0,          0,           1,  1);
+    solve_corner(state, ss, state->w-1, 0,          -1,  1);
+    solve_corner(state, ss, state->w-1, state->h-1, -1, -1);
+    solve_corner(state, ss, 0,          state->h-1,  1, -1);
+
+    return ss->n_ops - n_ops;
+}
+
+/* If you have the following situation:
+ * ...
+ * ...x A x x y A x...
+ * ...x B x x B y x...
+ * ...
+ * then both squares marked 'y' must be white. One of the left-most A or B must
+ * be white (since two side-by-side black cells are disallowed), which means
+ * that the corresponding right-most A or B must be black (since you can't
+ * have two of the same number on one line); thus, the adjacent squares
+ * to that right-most A or B must be white, which include the two marked 'y'
+ * in either case.
+ * Obviously this works in any row or column. It also works if A == B.
+ * It doesn't work for the degenerate case:
+ * ...x A A x x
+ * ...x B y x x
+ * where the square marked 'y' isn't necessarily white (consider the left-most A
+ * is black).
+ *
+ * */
+static void solve_offsetpair_pair(game_state *state, struct solver_state *ss,
+                                  int x1, int y1, int x2, int y2)
+{
+    int ox, oy, w = state->w, ax, ay, an, d, dx[2], dy[2], dn, xd, yd;
+
+    if (x1 == x2) { /* same column */
+        ox = 1; oy = 0;
+    } else {
+        assert(y1 == y2);
+        ox = 0; oy = 1;
+    }
+
+    /* We try adjacent to (x1,y1) and the two diag. adjacent to (x2, y2).
+     * We expect to be called twice, once each way around. */
+    ax = x1+ox; ay = y1+oy;
+    assert(INGRID(state, ax, ay));
+    an = state->nums[ay*w + ax];
+
+    dx[0] = x2 + ox + oy; dx[1] = x2 + ox - oy;
+    dy[0] = y2 + oy + ox; dy[1] = y2 + oy - ox;
+
+    for (d = 0; d < 2; d++) {
+        if (INGRID(state, dx[d], dy[d]) && (dx[d] != ax || dy[d] != ay)) {
+            /* The 'dx != ax || dy != ay' removes the degenerate case,
+             * mentioned above. */
+            dn = state->nums[dy[d]*w + dx[d]];
+            if (an == dn) {
+                /* We have a match; so (WLOG) the 'A' marked above are at
+                 * (x1,y1) and (x2,y2), and the 'B' are at (ax,ay) and (dx,dy). */
+                debug(("Found offset-pair: %d at (%d,%d) and (%d,%d)\n",
+                       state->nums[y1*w + x1], x1, y1, x2, y2));
+                debug(("              and: %d at (%d,%d) and (%d,%d)\n",
+                       an, ax, ay, dx[d], dy[d]));
+
+                xd = dx[d] - x2; yd = dy[d] - y2;
+                solver_op_add(ss, x2 + xd, y2, CIRCLE, "IP: next to offset-pair");
+                solver_op_add(ss, x2, y2 + yd, CIRCLE, "IP: next to offset-pair");
+            }
+        }
+    }
+}
+
+static int solve_offsetpair(game_state *state, struct solver_state *ss)
+{
+    int n_ops = ss->n_ops, x, xx, y, yy, n1, n2;
+
+    for (x = 0; x < state->w-1; x++) {
+        for (y = 0; y < state->h; y++) {
+            n1 = state->nums[y*state->w + x];
+            for (yy = y+1; yy < state->h; yy++) {
+                n2 = state->nums[yy*state->w + x];
+                if (n1 == n2) {
+                    solve_offsetpair_pair(state, ss, x,  y, x, yy);
+                    solve_offsetpair_pair(state, ss, x, yy, x,  y);
+                }
+            }
+        }
+    }
+    for (y = 0; y < state->h-1; y++) {
+        for (x = 0; x < state->w; x++) {
+            n1 = state->nums[y*state->w + x];
+            for (xx = x+1; xx < state->w; xx++) {
+                n2 = state->nums[y*state->w + xx];
+                if (n1 == n2) {
+                    solve_offsetpair_pair(state, ss, x,  y, xx, y);
+                    solve_offsetpair_pair(state, ss, xx, y,  x, y);
+                }
+            }
+        }
+    }
+    return ss->n_ops - n_ops;
+}
+
+static int solve_hassinglewhiteregion(game_state *state, struct solver_state *ss)
+{
+    int i, j, nwhite = 0, lwhite = -1, szwhite, start, end, next, a, d, x, y;
+
+    for (i = 0; i < state->n; i++) {
+        if (!(state->flags[i] & F_BLACK)) {
+            nwhite++;
+            lwhite = i;
+        }
+        state->flags[i] &= ~F_SCRATCH;
+    }
+    if (lwhite == -1) {
+        debug(("solve_hassinglewhite: no white squares found!\n"));
+        state->impossible = 1;
+        return 0;
+    }
+    /* We don't use connect_dsf here; it's too slow, and there's a quicker
+     * algorithm if all we want is the size of one region. */
+    /* Having written this, this algorithm is only about 5% faster than
+     * using a dsf. */
+    memset(ss->scratch, -1, state->n * sizeof(int));
+    ss->scratch[0] = lwhite;
+    state->flags[lwhite] |= F_SCRATCH;
+    start = 0; end = next = 1;
+    while (start < end) {
+        for (a = start; a < end; a++) {
+            i = ss->scratch[a]; assert(i != -1);
+            for (d = 0; d < 4; d++) {
+                x = (i % state->w) + dxs[d];
+                y = (i / state->w) + dys[d];
+                j = y*state->w + x;
+                if (!INGRID(state, x, y)) continue;
+                if (state->flags[j] & (F_BLACK | F_SCRATCH)) continue;
+                ss->scratch[next++] = j;
+                state->flags[j] |= F_SCRATCH;
+            }
+        }
+        start = end; end = next;
+    }
+    szwhite = next;
+    return (szwhite == nwhite) ? 1 : 0;
+}
+
+static void solve_removesplits_check(game_state *state, struct solver_state *ss,
+                                     int x, int y)
+{
+    int i = y*state->w + x, issingle;
+
+    if (!INGRID(state, x, y)) return;
+    if ((state->flags[i] & F_CIRCLE) || (state->flags[i] & F_BLACK))
+        return;
+
+    /* If putting a black square at (x,y) would make the white region
+     * non-contiguous, it must be circled. */
+    state->flags[i] |= F_BLACK;
+    issingle = solve_hassinglewhiteregion(state, ss);
+    state->flags[i] &= ~F_BLACK;
+
+    if (!issingle)
+        solver_op_add(ss, x, y, CIRCLE, "MC: black square here would split white region");
+}
+
+/* For all black squares, search in squares diagonally adjacent to see if
+ * we can rule out putting a black square there (because it would make the
+ * white region non-contiguous). */
+/* This function is likely to be somewhat slow. */
+static int solve_removesplits(game_state *state, struct solver_state *ss)
+{
+    int i, x, y, n_ops = ss->n_ops;
+
+    if (!solve_hassinglewhiteregion(state, ss)) {
+        debug(("solve_removesplits: white region is not contiguous at start!\n"));
+        state->impossible = 1;
+        return 0;
+    }
+
+    for (i = 0; i < state->n; i++) {
+        if (!(state->flags[i] & F_BLACK)) continue;
+
+        x = i%state->w; y = i/state->w;
+        solve_removesplits_check(state, ss, x-1, y-1);
+        solve_removesplits_check(state, ss, x+1, y-1);
+        solve_removesplits_check(state, ss, x+1, y+1);
+        solve_removesplits_check(state, ss, x-1, y+1);
+    }
+    return ss->n_ops - n_ops;
+}
+
+/*
+ * This function performs a solver step that isn't implicit in the rules
+ * of the game and is thus treated somewhat differently.
+ *
+ * It marks cells whose number does not exist elsewhere in its row/column
+ * with circles. As it happens the game generator here does mean that this
+ * is always correct, but it's a solving method that people should not have
+ * to rely upon (except in the hidden 'sneaky' difficulty setting) and so
+ * all grids at 'tricky' and above are checked to make sure that the grid
+ * is no easier if this solving step is performed beforehand.
+ *
+ * Calling with ss=NULL just returns the number of sneaky deductions that
+ * would have been made.
+ */
+static int solve_sneaky(game_state *state, struct solver_state *ss)
+{
+    int i, ii, x, xx, y, yy, nunique = 0;
+
+    /* Clear SCRATCH flags. */
+    for (i = 0; i < state->n; i++) state->flags[i] &= ~F_SCRATCH;
+
+    for (x = 0; x < state->w; x++) {
+        for (y = 0; y < state->h; y++) {
+            i = y*state->w + x;
+
+            /* Check for duplicate numbers on our row, mark (both) if so */
+            for (xx = x; xx < state->w; xx++) {
+                ii = y*state->w + xx;
+                if (i == ii) continue;
+
+                if (state->nums[i] == state->nums[ii]) {
+                    state->flags[i] |= F_SCRATCH;
+                    state->flags[ii] |= F_SCRATCH;
+                }
+            }
+
+            /* Check for duplicate numbers on our col, mark (both) if so */
+            for (yy = y; yy < state->h; yy++) {
+                ii = yy*state->w + x;
+                if (i == ii) continue;
+
+                if (state->nums[i] == state->nums[ii]) {
+                    state->flags[i] |= F_SCRATCH;
+                    state->flags[ii] |= F_SCRATCH;
+                }
+            }
+        }
+    }
+
+    /* Any cell with no marking has no duplicates on its row or column:
+     * set its CIRCLE. */
+    for (i = 0; i < state->n; i++) {
+        if (!(state->flags[i] & F_SCRATCH)) {
+            if (ss) solver_op_add(ss, i%state->w, i/state->w, CIRCLE,
+                                  "SNEAKY: only one of its number in row and col");
+            nunique += 1;
+        } else
+            state->flags[i] &= ~F_SCRATCH;
+    }
+    return nunique;
+}
+
+static int solve_specific(game_state *state, int diff, int sneaky)
+{
+    struct solver_state *ss = solver_state_new(state);
+
+    if (sneaky) solve_sneaky(state, ss);
+
+    /* Some solver operations we only have to perform once --
+     * they're only based on the numbers available, and not black
+     * squares or circles which may be added later. */
+
+    solve_singlesep(state, ss);        /* never sets impossible */
+    solve_doubles(state, ss);          /* ditto */
+    solve_corners(state, ss);          /* ditto */
+
+    if (diff >= DIFF_TRICKY)
+        solve_offsetpair(state, ss);       /* ditto */
+
+    while (1) {
+        if (ss->n_ops > 0) solver_ops_do(state, ss);
+        if (state->impossible) break;
+
+        if (solve_allblackbutone(state, ss) > 0) continue;
+        if (state->impossible) break;
+
+        if (diff >= DIFF_TRICKY) {
+            if (solve_removesplits(state, ss) > 0) continue;
+            if (state->impossible) break;
+        }
+
+        break;
+    }
+
+    solver_state_free(ss);
+    return state->impossible ? -1 : check_complete(state, CC_MUST_FILL);
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    game_state *solved = dup_game(currstate);
+    char *move = NULL;
+
+    if (solve_specific(solved, DIFF_ANY, 0) > 0) goto solved;
+    free_game(solved);
+
+    solved = dup_game(state);
+    if (solve_specific(solved, DIFF_ANY, 0) > 0) goto solved;
+    free_game(solved);
+
+    *error = "Unable to solve puzzle.";
+    return NULL;
+
+solved:
+    move = game_state_diff(currstate, solved, 1);
+    free_game(solved);
+    return move;
+}
+
+/* --- Game generation --- */
+
+/* A correctly completed Hitori board is essentially a latin square
+ * (no duplicated numbers in any row or column) with black squares
+ * added such that no black square touches another, and the white
+ * squares make a contiguous region.
+ *
+ * So we can generate it by:
+   * constructing a latin square
+   * adding black squares at random (minding the constraints)
+   * altering the numbers under the new black squares such that
+      the solver gets a headstart working out where they are.
+ */
+
+static int new_game_is_good(const game_params *params,
+                            game_state *state, game_state *tosolve)
+{
+    int sret, sret_easy = 0;
+
+    memcpy(tosolve->nums, state->nums, state->n * sizeof(int));
+    memset(tosolve->flags, 0, state->n * sizeof(unsigned int));
+    tosolve->completed = tosolve->impossible = 0;
+
+    /*
+     * We try and solve it twice, once at our requested difficulty level
+     * (ensuring it's soluble at all) and once at the level below (if
+     * it exists), which we hope to fail: if you can also solve it at
+     * the level below then it's too easy and we have to try again.
+     *
+     * With this puzzle in particular there's an extra finesse, which is
+     * that we check that the generated puzzle isn't too easy _with
+     * an extra solver step first_, which is the 'sneaky' mode of deductions
+     * (asserting that any number which fulfils the latin-square rules
+     * on its row/column must be white). This is an artefact of the
+     * generation process and not implicit in the rules, so we don't want
+     * people to be able to use it to make the puzzle easier.
+     */
+
+    assert(params->diff < DIFF_MAX);
+    sret = solve_specific(tosolve, params->diff, 0);
+    if (params->diff > DIFF_EASY) {
+        memset(tosolve->flags, 0, state->n * sizeof(unsigned int));
+        tosolve->completed = tosolve->impossible = 0;
+
+        /* this is the only time the 'sneaky' flag is set to 1. */
+        sret_easy = solve_specific(tosolve, params->diff-1, 1);
+    }
+
+    if (sret <= 0 || sret_easy > 0) {
+        debug(("Generated puzzle %s at chosen difficulty %s\n",
+               sret <= 0 ? "insoluble" : "too easy",
+               singles_diffnames[params->diff]));
+        return 0;
+    }
+    return 1;
+}
+
+#define MAXTRIES 20
+
+static int best_black_col(game_state *state, random_state *rs, int *scratch,
+                          int i, int *rownums, int *colnums)
+{
+    int w = state->w, x = i%w, y = i/w, j, o = state->o;
+
+    /* Randomise the list of numbers to try. */
+    for (i = 0; i < o; i++) scratch[i] = i;
+    shuffle(scratch, o, sizeof(int), rs);
+
+    /* Try each number in turn, first giving preference to removing
+     * latin-square characteristics (i.e. those numbers which only
+     * occur once in a row/column). The '&&' here, although intuitively
+     * wrong, results in a smaller number of 'sneaky' deductions on
+     * solvable boards. */
+    for (i = 0; i < o; i++) {
+        j = scratch[i] + 1;
+        if (rownums[y*o + j-1] == 1 && colnums[x*o + j-1] == 1)
+            goto found;
+    }
+
+    /* Then try each number in turn returning the first one that's
+     * not actually unique in its row/column (see comment below) */
+    for (i = 0; i < o; i++) {
+        j = scratch[i] + 1;
+        if (rownums[y*o + j-1] != 0 || colnums[x*o + j-1] != 0)
+            goto found;
+    }
+    assert(!"unable to place number under black cell.");
+    return 0;
+
+found:
+    /* Update column and row counts assuming this number will be placed. */
+    rownums[y*o + j-1] += 1;
+    colnums[x*o + j-1] += 1;
+    return j;
+}
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                          char **aux, int interactive)
+{
+    game_state *state = blank_game(params->w, params->h);
+    game_state *tosolve = blank_game(params->w, params->h);
+    int i, j, *scratch, *rownums, *colnums, x, y, ntries;
+    int w = state->w, h = state->h, o = state->o;
+    char *ret;
+    digit *latin;
+    struct solver_state *ss = solver_state_new(state);
+
+    scratch = snewn(state->n, int);
+    rownums = snewn(h*o, int);
+    colnums = snewn(w*o, int);
+
+generate:
+    ss->n_ops = 0;
+    debug(("Starting game generation, size %dx%d\n", w, h));
+
+    memset(state->flags, 0, state->n*sizeof(unsigned int));
+
+    /* First, generate the latin rectangle.
+     * The order of this, o, is max(w,h). */
+    latin = latin_generate_rect(w, h, rs);
+    for (i = 0; i < state->n; i++)
+        state->nums[i] = (int)latin[i];
+    sfree(latin);
+    debug_state("State after latin square", state);
+
+    /* Add black squares at random, using bits of solver as we go (to lay
+     * white squares), until we can lay no more blacks. */
+    for (i = 0; i < state->n; i++)
+        scratch[i] = i;
+    shuffle(scratch, state->n, sizeof(int), rs);
+    for (j = 0; j < state->n; j++) {
+        i = scratch[j];
+        if ((state->flags[i] & F_CIRCLE) || (state->flags[i] & F_BLACK)) {
+            debug(("generator skipping (%d,%d): %s\n", i%w, i/w,
+                   (state->flags[i] & F_CIRCLE) ? "CIRCLE" : "BLACK"));
+            continue; /* solver knows this must be one or the other already. */
+        }
+
+        /* Add a random black cell... */
+        solver_op_add(ss, i%w, i/w, BLACK, "Generator: adding random black cell");
+        solver_ops_do(state, ss);
+
+        /* ... and do as well as we know how to lay down whites that are now forced. */
+        solve_allblackbutone(state, ss);
+        solver_ops_do(state, ss);
+
+        solve_removesplits(state, ss);
+        solver_ops_do(state, ss);
+
+        if (state->impossible) {
+            debug(("generator made impossible, restarting...\n"));
+            goto generate;
+        }
+    }
+    debug_state("State after adding blacks", state);
+
+    /* Now we know which squares are white and which are black, we lay numbers
+     * under black squares at random, except that the number must appear in
+     * white cells at least once more in the same column or row as that [black]
+     * square. That's necessary to avoid multiple solutions, where blackening
+     * squares in the finished puzzle becomes optional. We use two arrays:
+     *
+     * rownums[ROW * o + NUM-1] is the no. of white cells containing NUM in y=ROW
+     * colnums[COL * o + NUM-1] is the no. of white cells containing NUM in x=COL
+     */
+
+    memset(rownums, 0, h*o * sizeof(int));
+    memset(colnums, 0, w*o * sizeof(int));
+    for (i = 0; i < state->n; i++) {
+        if (state->flags[i] & F_BLACK) continue;
+        j = state->nums[i];
+        x = i%w; y = i/w;
+        rownums[y * o + j-1] += 1;
+        colnums[x * o + j-1] += 1;
+    }
+
+    ntries = 0;
+randomise:
+    for (i = 0; i < state->n; i++) {
+        if (!(state->flags[i] & F_BLACK)) continue;
+        state->nums[i] = best_black_col(state, rs, scratch, i, rownums, colnums);
+    }
+    debug_state("State after adding numbers", state);
+
+    /* DIFF_ANY just returns whatever we first generated, for testing purposes. */
+    if (params->diff != DIFF_ANY &&
+        !new_game_is_good(params, state, tosolve)) {
+        ntries++;
+        if (ntries > MAXTRIES) {
+            debug(("Ran out of randomisation attempts, re-generating.\n"));
+            goto generate;
+        }
+        debug(("Re-randomising numbers under black squares.\n"));
+        goto randomise;
+    }
+
+    ret = generate_desc(state, 0);
+
+    free_game(tosolve);
+    free_game(state);
+    solver_state_free(ss);
+    sfree(scratch);
+    sfree(rownums);
+    sfree(colnums);
+
+    return ret;
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    char *ret = NULL;
+
+    unpick_desc(params, desc, NULL, &ret);
+    return ret;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    game_state *state = NULL;
+
+    unpick_desc(params, desc, &state, NULL);
+    if (!state) assert(!"new_game failed to unpick");
+    return state;
+}
+
+/* --- Game UI and move routines --- */
+
+struct game_ui {
+    int cx, cy, cshow;
+    int show_black_nums;
+};
+
+static game_ui *new_ui(const game_state *state)
+{
+    game_ui *ui = snew(game_ui);
+
+    ui->cx = ui->cy = ui->cshow = 0;
+    ui->show_black_nums = 0;
+
+    return ui;
+}
+
+static void free_ui(game_ui *ui)
+{
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+    if (!oldstate->completed && newstate->completed)
+        ui->cshow = 0;
+}
+
+#define DS_BLACK        0x1
+#define DS_CIRCLE       0x2
+#define DS_CURSOR       0x4
+#define DS_BLACK_NUM    0x8
+#define DS_ERROR        0x10
+#define DS_FLASH        0x20
+#define DS_IMPOSSIBLE   0x40
+
+struct game_drawstate {
+    int tilesize, started, solved;
+    int w, h, n;
+
+    unsigned int *flags;
+};
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int mx, int my, int button)
+{
+    char buf[80], c;
+    int i, x = FROMCOORD(mx), y = FROMCOORD(my);
+    enum { NONE, TOGGLE_BLACK, TOGGLE_CIRCLE, UI } action = NONE;
+
+    if (IS_CURSOR_MOVE(button)) {
+        move_cursor(button, &ui->cx, &ui->cy, state->w, state->h, 1);
+        ui->cshow = 1;
+        action = UI;
+    } else if (IS_CURSOR_SELECT(button)) {
+        x = ui->cx; y = ui->cy;
+        if (!ui->cshow) {
+            action = UI;
+            ui->cshow = 1;
+        }
+        if (button == CURSOR_SELECT) {
+            action = TOGGLE_BLACK;
+        } else if (button == CURSOR_SELECT2) {
+            action = TOGGLE_CIRCLE;
+        }
+    } else if (IS_MOUSE_DOWN(button)) {
+        if (ui->cshow) {
+            ui->cshow = 0;
+            action = UI;
+        }
+        if (!INGRID(state, x, y)) {
+            ui->show_black_nums = 1 - ui->show_black_nums;
+            action = UI; /* this wants to be a per-game option. */
+        } else if (button == LEFT_BUTTON) {
+            action = TOGGLE_BLACK;
+        } else if (button == RIGHT_BUTTON) {
+            action = TOGGLE_CIRCLE;
+        }
+    }
+    if (action == UI) return "";
+
+    if (action == TOGGLE_BLACK || action == TOGGLE_CIRCLE) {
+        i = y * state->w + x;
+        if (state->flags[i] & (F_BLACK | F_CIRCLE))
+            c = 'E';
+        else
+            c = (action == TOGGLE_BLACK) ? 'B' : 'C';
+        sprintf(buf, "%c%d,%d", (int)c, x, y);
+        return dupstr(buf);
+    }
+
+    return NULL;
+}
+
+static game_state *execute_move(const game_state *state, const char *move)
+{
+    game_state *ret = dup_game(state);
+    int x, y, i, n;
+
+    debug(("move: %s\n", move));
+
+    while (*move) {
+        char c = *move;
+        if (c == 'B' || c == 'C' || c == 'E') {
+            move++;
+            if (sscanf(move, "%d,%d%n", &x, &y, &n) != 2 ||
+                !INGRID(state, x, y))
+                goto badmove;
+
+            i = y*ret->w + x;
+            ret->flags[i] &= ~(F_CIRCLE | F_BLACK); /* empty first, always. */
+            if (c == 'B')
+                ret->flags[i] |= F_BLACK;
+            else if (c == 'C')
+                ret->flags[i] |= F_CIRCLE;
+            move += n;
+        } else if (c == 'S') {
+            move++;
+            ret->used_solve = 1;
+        } else
+            goto badmove;
+
+        if (*move == ';')
+            move++;
+        else if (*move)
+            goto badmove;
+    }
+    if (check_complete(ret, CC_MARK_ERRORS)) ret->completed = 1;
+    return ret;
+
+badmove:
+    free_game(ret);
+    return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    struct { int tilesize; } ads, *ds = &ads;
+    ads.tilesize = tilesize;
+
+    *x = TILE_SIZE * params->w + 2 * BORDER;
+    *y = TILE_SIZE * params->h + 2 * BORDER;
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+    int i;
+
+    game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT);
+    for (i = 0; i < 3; i++) {
+        ret[COL_BLACK * 3 + i] = 0.0F;
+        ret[COL_BLACKNUM * 3 + i] = 0.4F;
+        ret[COL_WHITE * 3 + i] = 1.0F;
+        ret[COL_GRID * 3 + i] = ret[COL_LOWLIGHT * 3 + i];
+    }
+    ret[COL_CURSOR * 3 + 0] = 0.2F;
+    ret[COL_CURSOR * 3 + 1] = 0.8F;
+    ret[COL_CURSOR * 3 + 2] = 0.0F;
+
+    ret[COL_ERROR * 3 + 0] = 1.0F;
+    ret[COL_ERROR * 3 + 1] = 0.0F;
+    ret[COL_ERROR * 3 + 2] = 0.0F;
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    struct game_drawstate *ds = snew(struct game_drawstate);
+
+    ds->tilesize = ds->started = ds->solved = 0;
+    ds->w = state->w;
+    ds->h = state->h;
+    ds->n = state->n;
+
+    ds->flags = snewn(state->n, unsigned int);
+
+    memset(ds->flags, 0, state->n*sizeof(unsigned int));
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds->flags);
+    sfree(ds);
+}
+
+static void tile_redraw(drawing *dr, game_drawstate *ds, int x, int y,
+                        int num, unsigned int f)
+{
+    int tcol, bg, dnum, cx, cy, tsz;
+    char buf[32];
+
+    if (f & DS_BLACK) {
+        bg = (f & DS_ERROR) ? COL_ERROR : COL_BLACK;
+        tcol = COL_BLACKNUM;
+        dnum = (f & DS_BLACK_NUM) ? 1 : 0;
+    } else {
+        bg = (f & DS_FLASH) ? COL_LOWLIGHT : COL_BACKGROUND;
+        tcol = (f & DS_ERROR) ? COL_ERROR : COL_BLACK;
+        dnum = 1;
+    }
+
+    cx = x + TILE_SIZE/2; cy = y + TILE_SIZE/2;
+
+    draw_rect(dr, x,    y, TILE_SIZE, TILE_SIZE, bg);
+    draw_rect_outline(dr, x, y, TILE_SIZE, TILE_SIZE,
+                      (f & DS_IMPOSSIBLE) ? COL_ERROR : COL_GRID);
+
+    if (f & DS_CIRCLE) {
+        draw_circle(dr, cx, cy, CRAD, tcol, tcol);
+        draw_circle(dr, cx, cy, CRAD-1, bg, tcol);
+    }
+
+    if (dnum) {
+        sprintf(buf, "%d", num);
+        if (strlen(buf) == 1)
+            tsz = TEXTSZ;
+        else
+            tsz = (CRAD*2 - 1) / strlen(buf);
+        draw_text(dr, cx, cy, FONT_VARIABLE, tsz,
+                  ALIGN_VCENTRE | ALIGN_HCENTRE, tcol, buf);
+    }
+
+    if (f & DS_CURSOR)
+        draw_rect_corners(dr, cx, cy, TEXTSZ/2, COL_CURSOR);
+
+    draw_update(dr, x, y, TILE_SIZE, TILE_SIZE);
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int x, y, i, flash;
+    unsigned int f;
+
+    flash = (int)(flashtime * 5 / FLASH_TIME) % 2;
+
+    if (!ds->started) {
+        int wsz = TILE_SIZE * state->w + 2 * BORDER;
+        int hsz = TILE_SIZE * state->h + 2 * BORDER;
+        draw_rect(dr, 0, 0, wsz, hsz, COL_BACKGROUND);
+        draw_rect_outline(dr, COORD(0)-1, COORD(0)-1,
+                         TILE_SIZE * state->w + 2, TILE_SIZE * state->h + 2,
+                          COL_GRID);
+        draw_update(dr, 0, 0, wsz, hsz);
+    }
+    for (x = 0; x < state->w; x++) {
+        for (y = 0; y < state->h; y++) {
+            i = y*state->w + x;
+            f = 0;
+
+            if (flash) f |= DS_FLASH;
+            if (state->impossible) f |= DS_IMPOSSIBLE;
+
+            if (ui->cshow && x == ui->cx && y == ui->cy)
+                f |= DS_CURSOR;
+            if (state->flags[i] & F_BLACK) {
+                f |= DS_BLACK;
+                if (ui->show_black_nums) f |= DS_BLACK_NUM;
+            }
+            if (state->flags[i] & F_CIRCLE)
+                f |= DS_CIRCLE;
+            if (state->flags[i] & F_ERROR)
+                f |= DS_ERROR;
+
+            if (!ds->started || ds->flags[i] != f) {
+                tile_redraw(dr, ds, COORD(x), COORD(y),
+                            state->nums[i], f);
+                ds->flags[i] = f;
+            }
+        }
+    }
+    ds->started = 1;
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return 0.0F;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    if (!oldstate->completed &&
+        newstate->completed && !newstate->used_solve)
+        return FLASH_TIME;
+    return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    return state->completed ? +1 : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+    int pw, ph;
+
+    /* 8mm squares by default. */
+    game_compute_size(params, 800, &pw, &ph);
+    *x = pw / 100.0F;
+    *y = ph / 100.0F;
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+    int ink = print_mono_colour(dr, 0);
+    int paper = print_mono_colour(dr, 1);
+    int x, y, ox, oy, i;
+    char buf[32];
+
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    game_drawstate ads, *ds = &ads;
+    game_set_size(dr, ds, NULL, tilesize);
+
+    print_line_width(dr, 2 * TILE_SIZE / 40);
+
+    for (x = 0; x < state->w; x++) {
+        for (y = 0; y < state->h; y++) {
+            ox = COORD(x); oy = COORD(y);
+            i = y*state->w+x;
+
+            if (state->flags[i] & F_BLACK) {
+                draw_rect(dr, ox, oy, TILE_SIZE, TILE_SIZE, ink);
+            } else {
+                draw_rect_outline(dr, ox, oy, TILE_SIZE, TILE_SIZE, ink);
+
+                if (state->flags[i] & DS_CIRCLE)
+                    draw_circle(dr, ox+TILE_SIZE/2, oy+TILE_SIZE/2, CRAD,
+                                paper, ink);
+
+                sprintf(buf, "%d", state->nums[i]);
+                draw_text(dr, ox+TILE_SIZE/2, oy+TILE_SIZE/2, FONT_VARIABLE,
+                          TEXTSZ/strlen(buf), ALIGN_VCENTRE | ALIGN_HCENTRE,
+                          ink, buf);
+            }
+        }
+    }
+}
+
+#ifdef COMBINED
+#define thegame singles
+#endif
+
+const struct game thegame = {
+    "Singles", "games.singles", "singles",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    TRUE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    TRUE, FALSE, game_print_size, game_print,
+    FALSE,                            /* wants_statusbar */
+    FALSE, game_timing_state,
+    REQUIRE_RBUTTON,                  /* flags */
+};
+
+#ifdef STANDALONE_SOLVER
+
+#include <time.h>
+#include <stdarg.h>
+
+static void start_soak(game_params *p, random_state *rs)
+{
+    time_t tt_start, tt_now, tt_last;
+    char *desc, *aux;
+    game_state *s;
+    int i, n = 0, ndiff[DIFF_MAX], diff, sret, nblack = 0, nsneaky = 0;
+
+    tt_start = tt_now = time(NULL);
+
+    printf("Soak-testing a %dx%d grid.\n", p->w, p->h);
+    p->diff = DIFF_ANY;
+
+    memset(ndiff, 0, DIFF_MAX * sizeof(int));
+
+    while (1) {
+        n++;
+        desc = new_game_desc(p, rs, &aux, 0);
+        s = new_game(NULL, p, desc);
+        nsneaky += solve_sneaky(s, NULL);
+
+        for (diff = 0; diff < DIFF_MAX; diff++) {
+            memset(s->flags, 0, s->n * sizeof(unsigned int));
+            s->completed = s->impossible = 0;
+            sret = solve_specific(s, diff, 0);
+            if (sret > 0) {
+                ndiff[diff]++;
+                break;
+            } else if (sret < 0)
+                fprintf(stderr, "Impossible! %s\n", desc);
+        }
+        for (i = 0; i < s->n; i++) {
+            if (s->flags[i] & F_BLACK) nblack++;
+        }
+        free_game(s);
+        sfree(desc);
+
+        tt_last = time(NULL);
+        if (tt_last > tt_now) {
+            tt_now = tt_last;
+            printf("%d total, %3.1f/s, bl/sn %3.1f%%/%3.1f%%: ",
+                   n, (double)n / ((double)tt_now - tt_start),
+                   ((double)nblack * 100.0) / (double)(n * p->w * p->h),
+                   ((double)nsneaky * 100.0) / (double)(n * p->w * p->h));
+            for (diff = 0; diff < DIFF_MAX; diff++) {
+                if (diff > 0) printf(", ");
+                printf("%d (%3.1f%%) %s",
+                       ndiff[diff], (double)ndiff[diff] * 100.0 / (double)n,
+                       singles_diffnames[diff]);
+            }
+            printf("\n");
+        }
+    }
+}
+
+int main(int argc, char **argv)
+{
+    char *id = NULL, *desc, *desc_gen = NULL, *tgame, *err, *aux;
+    game_state *s = NULL;
+    game_params *p = NULL;
+    int soln, soak = 0, ret = 1;
+    time_t seed = time(NULL);
+    random_state *rs = NULL;
+
+    setvbuf(stdout, NULL, _IONBF, 0);
+
+    while (--argc > 0) {
+        char *p = *++argv;
+        if (!strcmp(p, "-v")) {
+            verbose = 1;
+        } else if (!strcmp(p, "--soak")) {
+            soak = 1;
+        } else if (!strcmp(p, "--seed")) {
+            if (argc == 0) {
+                fprintf(stderr, "%s: --seed needs an argument", argv[0]);
+                goto done;
+            }
+            seed = (time_t)atoi(*++argv);
+            argc--;
+        } else if (*p == '-') {
+            fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p);
+            return 1;
+        } else {
+            id = p;
+        }
+    }
+
+    rs = random_new((void*)&seed, sizeof(time_t));
+
+    if (!id) {
+        fprintf(stderr, "usage: %s [-v] [--soak] <params> | <game_id>\n", argv[0]);
+        goto done;
+    }
+    desc = strchr(id, ':');
+    if (desc) *desc++ = '\0';
+
+    p = default_params();
+    decode_params(p, id);
+    err = validate_params(p, 1);
+    if (err) {
+        fprintf(stderr, "%s: %s", argv[0], err);
+        goto done;
+    }
+
+    if (soak) {
+        if (desc) {
+            fprintf(stderr, "%s: --soak only needs params, not game desc.\n", argv[0]);
+            goto done;
+        }
+        start_soak(p, rs);
+    } else {
+        if (!desc) desc = desc_gen = new_game_desc(p, rs, &aux, 0);
+
+        err = validate_desc(p, desc);
+        if (err) {
+            fprintf(stderr, "%s: %s\n", argv[0], err);
+            free_params(p);
+            goto done;
+        }
+        s = new_game(NULL, p, desc);
+
+        if (verbose) {
+            tgame = game_text_format(s);
+            fputs(tgame, stdout);
+            sfree(tgame);
+        }
+
+        soln = solve_specific(s, DIFF_ANY, 0);
+        tgame = game_text_format(s);
+        fputs(tgame, stdout);
+        sfree(tgame);
+        printf("Game was %s.\n\n",
+               soln < 0 ? "impossible" : soln > 0 ? "solved" : "not solved");
+    }
+    ret = 0;
+
+done:
+    if (desc_gen) sfree(desc_gen);
+    if (p) free_params(p);
+    if (s) free_game(s);
+    if (rs) random_free(rs);
+
+    return ret;
+}
+
+#endif
+
+
+/* vim: set shiftwidth=4 tabstop=8: */
diff --git a/sixteen.R b/sixteen.R
new file mode 100644 (file)
index 0000000..c63a27c
--- /dev/null
+++ b/sixteen.R
@@ -0,0 +1,19 @@
+# -*- makefile -*-
+
+sixteen  : [X] GTK COMMON sixteen sixteen-icon|no-icon
+
+sixteen  : [G] WINDOWS COMMON sixteen sixteen.res|noicon.res
+
+ALL += sixteen[COMBINED]
+
+!begin am gtk
+GAMES += sixteen
+!end
+
+!begin >list.c
+    A(sixteen) \
+!end
+
+!begin >gamedesc.txt
+sixteen:sixteen.exe:Sixteen:Toroidal sliding block puzzle:Slide a row at a time to arrange the tiles into order.
+!end
diff --git a/sixteen.c b/sixteen.c
new file mode 100644 (file)
index 0000000..06494f5
--- /dev/null
+++ b/sixteen.c
@@ -0,0 +1,1214 @@
+/*
+ * sixteen.c: `16-puzzle', a sliding-tiles jigsaw which differs
+ * from the 15-puzzle in that you toroidally rotate a row or column
+ * at a time.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+
+#define PREFERRED_TILE_SIZE 48
+#define TILE_SIZE (ds->tilesize)
+#define BORDER TILE_SIZE
+#define HIGHLIGHT_WIDTH (TILE_SIZE / 20)
+#define COORD(x)  ( (x) * TILE_SIZE + BORDER )
+#define FROMCOORD(x)  ( ((x) - BORDER + 2*TILE_SIZE) / TILE_SIZE - 2 )
+
+#define ANIM_TIME 0.13F
+#define FLASH_FRAME 0.13F
+
+#define X(state, i) ( (i) % (state)->w )
+#define Y(state, i) ( (i) / (state)->w )
+#define C(state, x, y) ( (y) * (state)->w + (x) )
+
+#define TILE_CURSOR(i, state, x, y) ((i) == C((state), (x), (y)) &&     \
+                                     0 <= (x) && (x) < (state)->w &&    \
+                                     0 <= (y) && (y) < (state)->h)
+enum {
+    COL_BACKGROUND,
+    COL_TEXT,
+    COL_HIGHLIGHT,
+    COL_LOWLIGHT,
+    NCOLOURS
+};
+
+struct game_params {
+    int w, h;
+    int movetarget;
+};
+
+struct game_state {
+    int w, h, n;
+    int *tiles;
+    int completed;
+    int used_solve;                   /* used to suppress completion flash */
+    int movecount, movetarget;
+    int last_movement_sense;
+};
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = ret->h = 4;
+    ret->movetarget = 0;
+
+    return ret;
+}
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params *ret;
+    int w, h;
+    char buf[80];
+
+    switch (i) {
+      case 0: w = 3, h = 3; break;
+      case 1: w = 4, h = 3; break;
+      case 2: w = 4, h = 4; break;
+      case 3: w = 5, h = 4; break;
+      case 4: w = 5, h = 5; break;
+      default: return FALSE;
+    }
+
+    sprintf(buf, "%dx%d", w, h);
+    *name = dupstr(buf);
+    *params = ret = snew(game_params);
+    ret->w = w;
+    ret->h = h;
+    ret->movetarget = 0;
+    return TRUE;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+static void decode_params(game_params *ret, char const *string)
+{
+    ret->w = ret->h = atoi(string);
+    ret->movetarget = 0;
+    while (*string && isdigit((unsigned char)*string)) string++;
+    if (*string == 'x') {
+        string++;
+        ret->h = atoi(string);
+       while (*string && isdigit((unsigned char)*string))
+           string++;
+    }
+    if (*string == 'm') {
+        string++;
+        ret->movetarget = atoi(string);
+       while (*string && isdigit((unsigned char)*string))
+           string++;
+    }
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char data[256];
+
+    sprintf(data, "%dx%d", params->w, params->h);
+    /* Shuffle limit is part of the limited parameters, because we have to
+     * supply the target move count. */
+    if (params->movetarget)
+        sprintf(data + strlen(data), "m%d", params->movetarget);
+
+    return dupstr(data);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+    char buf[80];
+
+    ret = snewn(4, config_item);
+
+    ret[0].name = "Width";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->w);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Height";
+    ret[1].type = C_STRING;
+    sprintf(buf, "%d", params->h);
+    ret[1].sval = dupstr(buf);
+    ret[1].ival = 0;
+
+    ret[2].name = "Number of shuffling moves";
+    ret[2].type = C_STRING;
+    sprintf(buf, "%d", params->movetarget);
+    ret[2].sval = dupstr(buf);
+    ret[2].ival = 0;
+
+    ret[3].name = NULL;
+    ret[3].type = C_END;
+    ret[3].sval = NULL;
+    ret[3].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = atoi(cfg[0].sval);
+    ret->h = atoi(cfg[1].sval);
+    ret->movetarget = atoi(cfg[2].sval);
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    if (params->w < 2 || params->h < 2)
+       return "Width and height must both be at least two";
+
+    return NULL;
+}
+
+static int perm_parity(int *perm, int n)
+{
+    int i, j, ret;
+
+    ret = 0;
+
+    for (i = 0; i < n-1; i++)
+        for (j = i+1; j < n; j++)
+            if (perm[i] > perm[j])
+                ret = !ret;
+
+    return ret;
+}
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                          char **aux, int interactive)
+{
+    int stop, n, i, x;
+    int x1, x2, p1, p2;
+    int *tiles, *used;
+    char *ret;
+    int retlen;
+
+    n = params->w * params->h;
+
+    tiles = snewn(n, int);
+
+    if (params->movetarget) {
+       int prevoffset = -1;
+        int max = (params->w > params->h ? params->w : params->h);
+        int *prevmoves = snewn(max, int);
+
+       /*
+        * Shuffle the old-fashioned way, by making a series of
+        * single moves on the grid.
+        */
+
+       for (i = 0; i < n; i++)
+           tiles[i] = i;
+
+       for (i = 0; i < params->movetarget; i++) {
+           int start, offset, len, direction, index;
+           int j, tmp;
+
+           /*
+            * Choose a move to make. We can choose from any row
+            * or any column.
+            */
+           while (1) {
+               j = random_upto(rs, params->w + params->h);
+
+               if (j < params->w) {
+                   /* Column. */
+                    index = j;
+                   start = j;
+                   offset = params->w;
+                   len = params->h;
+               } else {
+                   /* Row. */
+                    index = j - params->w;
+                   start = index * params->w;
+                   offset = 1;
+                   len = params->w;
+               }
+
+               direction = -1 + 2 * random_upto(rs, 2);
+
+               /*
+                * To at least _try_ to avoid boring cases, check
+                * that this move doesn't directly undo a previous
+                * one, or repeat it so many times as to turn it
+                * into fewer moves in the opposite direction. (For
+                * example, in a row of length 4, we're allowed to
+                * move it the same way twice, but not three
+                * times.)
+                 * 
+                 * We track this for each individual row/column,
+                 * and clear all the counters as soon as a
+                 * perpendicular move is made. This isn't perfect
+                 * (it _can't_ guaranteeably be perfect - there
+                 * will always come a move count beyond which a
+                 * shorter solution will be possible than the one
+                 * which constructed the position) but it should
+                 * sort out all the obvious cases.
+                */
+                if (offset == prevoffset) {
+                    tmp = prevmoves[index] + direction;
+                    if (abs(2*tmp) > len || abs(tmp) < abs(prevmoves[index]))
+                        continue;
+                }
+
+               /* If we didn't `continue', we've found an OK move to make. */
+                if (offset != prevoffset) {
+                    int i;
+                    for (i = 0; i < max; i++)
+                        prevmoves[i] = 0;
+                    prevoffset = offset;
+                }
+                prevmoves[index] += direction;
+               break;
+           }
+
+           /*
+            * Make the move.
+            */
+           if (direction < 0) {
+               start += (len-1) * offset;
+               offset = -offset;
+           }
+           tmp = tiles[start];
+           for (j = 0; j+1 < len; j++)
+               tiles[start + j*offset] = tiles[start + (j+1)*offset];
+           tiles[start + (len-1) * offset] = tmp;
+       }
+
+        sfree(prevmoves);
+
+    } else {
+
+       used = snewn(n, int);
+
+       for (i = 0; i < n; i++) {
+           tiles[i] = -1;
+           used[i] = FALSE;
+       }
+
+       /*
+        * If both dimensions are odd, there is a parity
+        * constraint.
+        */
+       if (params->w & params->h & 1)
+           stop = 2;
+       else
+           stop = 0;
+
+       /*
+        * Place everything except (possibly) the last two tiles.
+        */
+       for (x = 0, i = n; i > stop; i--) {
+           int k = i > 1 ? random_upto(rs, i) : 0;
+           int j;
+
+           for (j = 0; j < n; j++)
+               if (!used[j] && (k-- == 0))
+                   break;
+
+           assert(j < n && !used[j]);
+           used[j] = TRUE;
+
+           while (tiles[x] >= 0)
+               x++;
+           assert(x < n);
+           tiles[x] = j;
+       }
+
+       if (stop) {
+           /*
+            * Find the last two locations, and the last two
+            * pieces.
+            */
+           while (tiles[x] >= 0)
+               x++;
+           assert(x < n);
+           x1 = x;
+           x++;
+           while (tiles[x] >= 0)
+               x++;
+           assert(x < n);
+           x2 = x;
+
+           for (i = 0; i < n; i++)
+               if (!used[i])
+                   break;
+           p1 = i;
+           for (i = p1+1; i < n; i++)
+               if (!used[i])
+                   break;
+           p2 = i;
+
+           /*
+            * Try the last two tiles one way round. If that fails,
+            * swap them.
+            */
+           tiles[x1] = p1;
+           tiles[x2] = p2;
+           if (perm_parity(tiles, n) != 0) {
+               tiles[x1] = p2;
+               tiles[x2] = p1;
+               assert(perm_parity(tiles, n) == 0);
+           }
+       }
+
+       sfree(used);
+    }
+
+    /*
+     * Now construct the game description, by describing the tile
+     * array as a simple sequence of comma-separated integers.
+     */
+    ret = NULL;
+    retlen = 0;
+    for (i = 0; i < n; i++) {
+        char buf[80];
+        int k;
+
+        k = sprintf(buf, "%d,", tiles[i]+1);
+
+        ret = sresize(ret, retlen + k + 1, char);
+        strcpy(ret + retlen, buf);
+        retlen += k;
+    }
+    ret[retlen-1] = '\0';              /* delete last comma */
+
+    sfree(tiles);
+
+    return ret;
+}
+
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    const char *p;
+    char *err;
+    int i, area;
+    int *used;
+
+    area = params->w * params->h;
+    p = desc;
+    err = NULL;
+
+    used = snewn(area, int);
+    for (i = 0; i < area; i++)
+       used[i] = FALSE;
+
+    for (i = 0; i < area; i++) {
+       const char *q = p;
+       int n;
+
+       if (*p < '0' || *p > '9') {
+           err = "Not enough numbers in string";
+           goto leave;
+       }
+       while (*p >= '0' && *p <= '9')
+           p++;
+       if (i < area-1 && *p != ',') {
+           err = "Expected comma after number";
+           goto leave;
+       }
+       else if (i == area-1 && *p) {
+           err = "Excess junk at end of string";
+           goto leave;
+       }
+       n = atoi(q);
+       if (n < 1 || n > area) {
+           err = "Number out of range";
+           goto leave;
+       }
+       if (used[n-1]) {
+           err = "Number used twice";
+           goto leave;
+       }
+       used[n-1] = TRUE;
+
+       if (*p) p++;                   /* eat comma */
+    }
+
+    leave:
+    sfree(used);
+    return err;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    game_state *state = snew(game_state);
+    int i;
+    const char *p;
+
+    state->w = params->w;
+    state->h = params->h;
+    state->n = params->w * params->h;
+    state->tiles = snewn(state->n, int);
+
+    p = desc;
+    i = 0;
+    for (i = 0; i < state->n; i++) {
+        assert(*p);
+        state->tiles[i] = atoi(p);
+        while (*p && *p != ',')
+            p++;
+        if (*p) p++;                   /* eat comma */
+    }
+    assert(!*p);
+
+    state->completed = state->movecount = 0;
+    state->movetarget = params->movetarget;
+    state->used_solve = FALSE;
+    state->last_movement_sense = 0;
+
+    return state;
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    game_state *ret = snew(game_state);
+
+    ret->w = state->w;
+    ret->h = state->h;
+    ret->n = state->n;
+    ret->tiles = snewn(state->w * state->h, int);
+    memcpy(ret->tiles, state->tiles, state->w * state->h * sizeof(int));
+    ret->completed = state->completed;
+    ret->movecount = state->movecount;
+    ret->movetarget = state->movetarget;
+    ret->used_solve = state->used_solve;
+    ret->last_movement_sense = state->last_movement_sense;
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    sfree(state->tiles);
+    sfree(state);
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    return dupstr("S");
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    char *ret, *p, buf[80];
+    int x, y, col, maxlen;
+
+    /*
+     * First work out how many characters we need to display each
+     * number.
+     */
+    col = sprintf(buf, "%d", state->n);
+
+    /*
+     * Now we know the exact total size of the grid we're going to
+     * produce: it's got h rows, each containing w lots of col, w-1
+     * spaces and a trailing newline.
+     */
+    maxlen = state->h * state->w * (col+1);
+
+    ret = snewn(maxlen+1, char);
+    p = ret;
+
+    for (y = 0; y < state->h; y++) {
+       for (x = 0; x < state->w; x++) {
+           int v = state->tiles[state->w*y+x];
+           sprintf(buf, "%*d", col, v);
+           memcpy(p, buf, col);
+           p += col;
+           if (x+1 == state->w)
+               *p++ = '\n';
+           else
+               *p++ = ' ';
+       }
+    }
+
+    assert(p - ret == maxlen);
+    *p = '\0';
+    return ret;
+}
+
+enum cursor_mode { unlocked, lock_tile, lock_position };
+
+struct game_ui {
+    int cur_x, cur_y;
+    int cur_visible;
+    enum cursor_mode cur_mode;
+};
+
+static game_ui *new_ui(const game_state *state)
+{
+    game_ui *ui = snew(game_ui);
+    ui->cur_x = 0;
+    ui->cur_y = 0;
+    ui->cur_visible = FALSE;
+    ui->cur_mode = unlocked;
+
+    return ui;
+}
+
+static void free_ui(game_ui *ui)
+{
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+}
+
+struct game_drawstate {
+    int started;
+    int w, h, bgcolour;
+    int *tiles;
+    int tilesize;
+    int cur_x, cur_y;
+};
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    int cx = -1, cy = -1, dx, dy;
+    char buf[80];
+    int shift = button & MOD_SHFT, control = button & MOD_CTRL,
+        pad = button & MOD_NUM_KEYPAD;
+
+    button &= ~MOD_MASK;
+
+    if (IS_CURSOR_MOVE(button) || pad) {
+        if (!ui->cur_visible) {
+            ui->cur_visible = 1;
+            return "";
+        }
+
+        if (control || shift || ui->cur_mode) {
+            int x = ui->cur_x, y = ui->cur_y, xwrap = x, ywrap = y;
+            if (x < 0 || x >= state->w || y < 0 || y >= state->h)
+                return NULL;
+            move_cursor(button | pad, &x, &y,
+                        state->w, state->h, FALSE);
+            move_cursor(button | pad, &xwrap, &ywrap,
+                        state->w, state->h, TRUE);
+
+            if (x != xwrap) {
+                sprintf(buf, "R%d,%c1", y, x ? '+' : '-');
+            } else if (y != ywrap) {
+                sprintf(buf, "C%d,%c1", x, y ? '+' : '-');
+            } else if (x == ui->cur_x)
+                sprintf(buf, "C%d,%d", x, y - ui->cur_y);
+            else
+                sprintf(buf, "R%d,%d", y, x - ui->cur_x);
+
+            if (control || (!shift && ui->cur_mode == lock_tile)) {
+                ui->cur_x = xwrap;
+                ui->cur_y = ywrap;
+            }
+
+            return dupstr(buf);
+        } else {
+            int x = ui->cur_x + 1, y = ui->cur_y + 1;
+
+            move_cursor(button | pad, &x, &y,
+                        state->w + 2, state->h + 2, FALSE);
+
+            if (x == 0 && y == 0) {
+                int t = ui->cur_x;
+                ui->cur_x = ui->cur_y;
+                ui->cur_y = t;
+            } else if (x == 0 && y == state->h + 1) {
+                int t = ui->cur_x;
+                ui->cur_x = (state->h - 1) - ui->cur_y;
+                ui->cur_y = (state->h - 1) - t;
+            } else if (x == state->w + 1 && y == 0) {
+                int t = ui->cur_x;
+                ui->cur_x = (state->w - 1) - ui->cur_y;
+                ui->cur_y = (state->w - 1) - t;
+            } else if (x == state->w + 1 && y == state->h + 1) {
+                int t = ui->cur_x;
+                ui->cur_x = state->w - state->h + ui->cur_y;
+                ui->cur_y = state->h - state->w + t;
+            } else {
+                ui->cur_x = x - 1;
+                ui->cur_y = y - 1;
+            }
+
+            ui->cur_visible = 1;
+            return "";
+        }
+    }
+
+    if (button == LEFT_BUTTON || button == RIGHT_BUTTON) {
+        cx = FROMCOORD(x);
+        cy = FROMCOORD(y);
+        ui->cur_visible = 0;
+    } else if (IS_CURSOR_SELECT(button)) {
+        if (ui->cur_visible) {
+            if (ui->cur_x == -1 || ui->cur_x == state->w ||
+                ui->cur_y == -1 || ui->cur_y == state->h) {
+                cx = ui->cur_x;
+                cy = ui->cur_y;
+            } else {
+                const enum cursor_mode m = (button == CURSOR_SELECT2 ?
+                                            lock_position : lock_tile);
+                ui->cur_mode = (ui->cur_mode == m ? unlocked : m);
+                return "";
+            }
+        } else {
+            ui->cur_visible = 1;
+            return "";
+        }
+    } else {
+       return NULL;
+    }
+
+    if (cx == -1 && cy >= 0 && cy < state->h)
+        dx = -1, dy = 0;
+    else if (cx == state->w && cy >= 0 && cy < state->h)
+        dx = +1, dy = 0;
+    else if (cy == -1 && cx >= 0 && cx < state->w)
+        dy = -1, dx = 0;
+    else if (cy == state->h && cx >= 0 && cx < state->w)
+        dy = +1, dx = 0;
+    else
+        return "";                   /* invalid click location */
+
+    /* reverse direction if right hand button is pressed */
+    if (button == RIGHT_BUTTON || button == CURSOR_SELECT2) {
+        dx = -dx;
+        dy = -dy;
+    }
+
+    if (dx)
+       sprintf(buf, "R%d,%d", cy, dx);
+    else
+       sprintf(buf, "C%d,%d", cx, dy);
+    return dupstr(buf);
+}
+
+static game_state *execute_move(const game_state *from, const char *move)
+{
+    int cx, cy, dx, dy;
+    int tx, ty, n;
+    game_state *ret;
+
+    if (!strcmp(move, "S")) {
+       int i;
+
+       ret = dup_game(from);
+
+       /*
+        * Simply replace the grid with a solved one. For this game,
+        * this isn't a useful operation for actually telling the user
+        * what they should have done, but it is useful for
+        * conveniently being able to get hold of a clean state from
+        * which to practise manoeuvres.
+        */
+       for (i = 0; i < ret->n; i++)
+           ret->tiles[i] = i+1;
+       ret->used_solve = TRUE;
+       ret->completed = ret->movecount = 1;
+
+       return ret;
+    }
+
+    if (move[0] == 'R' && sscanf(move+1, "%d,%d", &cy, &dx) == 2 &&
+       cy >= 0 && cy < from->h) {
+       cx = dy = 0;
+       n = from->w;
+    } else if (move[0] == 'C' && sscanf(move+1, "%d,%d", &cx, &dy) == 2 &&
+              cx >= 0 && cx < from->w) {
+       cy = dx = 0;
+       n = from->h;
+    } else
+       return NULL;
+
+    ret = dup_game(from);
+
+    do {
+        tx = (cx - dx + from->w) % from->w;
+        ty = (cy - dy + from->h) % from->h;
+        ret->tiles[C(ret, cx, cy)] = from->tiles[C(from, tx, ty)];
+        cx = tx;
+        cy = ty;
+    } while (--n > 0);
+
+    ret->movecount++;
+
+    ret->last_movement_sense = dx+dy;
+
+    /*
+     * See if the game has been completed.
+     */
+    if (!ret->completed) {
+        ret->completed = ret->movecount;
+        for (n = 0; n < ret->n; n++)
+            if (ret->tiles[n] != n+1)
+                ret->completed = FALSE;
+    }
+
+    return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    struct { int tilesize; } ads, *ds = &ads;
+    ads.tilesize = tilesize;
+
+    *x = TILE_SIZE * params->w + 2 * BORDER;
+    *y = TILE_SIZE * params->h + 2 * BORDER;
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+    int i;
+
+    game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT);
+
+    for (i = 0; i < 3; i++)
+        ret[COL_TEXT * 3 + i] = 0.0;
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    struct game_drawstate *ds = snew(struct game_drawstate);
+    int i;
+
+    ds->started = FALSE;
+    ds->w = state->w;
+    ds->h = state->h;
+    ds->bgcolour = COL_BACKGROUND;
+    ds->tiles = snewn(ds->w*ds->h, int);
+    ds->tilesize = 0;                  /* haven't decided yet */
+    for (i = 0; i < ds->w*ds->h; i++)
+        ds->tiles[i] = -1;
+    ds->cur_x = ds->cur_y = -1;
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds->tiles);
+    sfree(ds);
+}
+
+static void draw_tile(drawing *dr, game_drawstate *ds,
+                      const game_state *state, int x, int y,
+                      int tile, int flash_colour)
+{
+    if (tile == 0) {
+        draw_rect(dr, x, y, TILE_SIZE, TILE_SIZE,
+                  flash_colour);
+    } else {
+        int coords[6];
+        char str[40];
+
+        coords[0] = x + TILE_SIZE - 1;
+        coords[1] = y + TILE_SIZE - 1;
+        coords[2] = x + TILE_SIZE - 1;
+        coords[3] = y;
+        coords[4] = x;
+        coords[5] = y + TILE_SIZE - 1;
+        draw_polygon(dr, coords, 3, COL_LOWLIGHT, COL_LOWLIGHT);
+
+        coords[0] = x;
+        coords[1] = y;
+        draw_polygon(dr, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT);
+
+        draw_rect(dr, x + HIGHLIGHT_WIDTH, y + HIGHLIGHT_WIDTH,
+                  TILE_SIZE - 2*HIGHLIGHT_WIDTH, TILE_SIZE - 2*HIGHLIGHT_WIDTH,
+                  flash_colour);
+
+        sprintf(str, "%d", tile);
+        draw_text(dr, x + TILE_SIZE/2, y + TILE_SIZE/2,
+                  FONT_VARIABLE, TILE_SIZE/3, ALIGN_VCENTRE | ALIGN_HCENTRE,
+                  COL_TEXT, str);
+    }
+    draw_update(dr, x, y, TILE_SIZE, TILE_SIZE);
+}
+
+static void draw_arrow(drawing *dr, game_drawstate *ds,
+                       int x, int y, int xdx, int xdy, int cur)
+{
+    int coords[14];
+    int ydy = -xdx, ydx = xdy;
+
+#define POINT(n, xx, yy) ( \
+    coords[2*(n)+0] = x + (xx)*xdx + (yy)*ydx, \
+    coords[2*(n)+1] = y + (xx)*xdy + (yy)*ydy)
+
+    POINT(0, TILE_SIZE / 2, 3 * TILE_SIZE / 4);   /* top of arrow */
+    POINT(1, 3 * TILE_SIZE / 4, TILE_SIZE / 2);   /* right corner */
+    POINT(2, 5 * TILE_SIZE / 8, TILE_SIZE / 2);   /* right concave */
+    POINT(3, 5 * TILE_SIZE / 8, TILE_SIZE / 4);   /* bottom right */
+    POINT(4, 3 * TILE_SIZE / 8, TILE_SIZE / 4);   /* bottom left */
+    POINT(5, 3 * TILE_SIZE / 8, TILE_SIZE / 2);   /* left concave */
+    POINT(6,     TILE_SIZE / 4, TILE_SIZE / 2);   /* left corner */
+
+    draw_polygon(dr, coords, 7, cur ? COL_HIGHLIGHT : COL_LOWLIGHT, COL_TEXT);
+}
+
+static void draw_arrow_for_cursor(drawing *dr, game_drawstate *ds,
+                                  int cur_x, int cur_y, int cur)
+{
+    if (cur_x == -1 && cur_y == -1)
+        return; /* 'no cursur here */
+    else if (cur_x == -1) /* LH column. */
+        draw_arrow(dr, ds, COORD(0), COORD(cur_y+1), 0, -1, cur);
+    else if (cur_x == ds->w) /* RH column */
+        draw_arrow(dr, ds, COORD(ds->w), COORD(cur_y), 0, +1, cur);
+    else if (cur_y == -1) /* Top row */
+        draw_arrow(dr, ds, COORD(cur_x), COORD(0), +1, 0, cur);
+    else if (cur_y == ds->h) /* Bottom row */
+        draw_arrow(dr, ds, COORD(cur_x+1), COORD(ds->h), -1, 0, cur);
+    else
+        return;
+
+    draw_update(dr, COORD(cur_x), COORD(cur_y),
+                TILE_SIZE, TILE_SIZE);
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int i, bgcolour;
+    int cur_x = -1, cur_y = -1;
+
+    if (flashtime > 0) {
+        int frame = (int)(flashtime / FLASH_FRAME);
+        bgcolour = (frame % 2 ? COL_LOWLIGHT : COL_HIGHLIGHT);
+    } else
+        bgcolour = COL_BACKGROUND;
+
+    if (!ds->started) {
+        int coords[10];
+
+       draw_rect(dr, 0, 0,
+                 TILE_SIZE * state->w + 2 * BORDER,
+                 TILE_SIZE * state->h + 2 * BORDER, COL_BACKGROUND);
+       draw_update(dr, 0, 0,
+                   TILE_SIZE * state->w + 2 * BORDER,
+                   TILE_SIZE * state->h + 2 * BORDER);
+
+        /*
+         * Recessed area containing the whole puzzle.
+         */
+        coords[0] = COORD(state->w) + HIGHLIGHT_WIDTH - 1;
+        coords[1] = COORD(state->h) + HIGHLIGHT_WIDTH - 1;
+        coords[2] = COORD(state->w) + HIGHLIGHT_WIDTH - 1;
+        coords[3] = COORD(0) - HIGHLIGHT_WIDTH;
+        coords[4] = coords[2] - TILE_SIZE;
+        coords[5] = coords[3] + TILE_SIZE;
+        coords[8] = COORD(0) - HIGHLIGHT_WIDTH;
+        coords[9] = COORD(state->h) + HIGHLIGHT_WIDTH - 1;
+        coords[6] = coords[8] + TILE_SIZE;
+        coords[7] = coords[9] - TILE_SIZE;
+        draw_polygon(dr, coords, 5, COL_HIGHLIGHT, COL_HIGHLIGHT);
+
+        coords[1] = COORD(0) - HIGHLIGHT_WIDTH;
+        coords[0] = COORD(0) - HIGHLIGHT_WIDTH;
+        draw_polygon(dr, coords, 5, COL_LOWLIGHT, COL_LOWLIGHT);
+
+        /*
+         * Arrows for making moves.
+         */
+        for (i = 0; i < state->w; i++) {
+            draw_arrow(dr, ds, COORD(i), COORD(0), +1, 0, 0);
+            draw_arrow(dr, ds, COORD(i+1), COORD(state->h), -1, 0, 0);
+        }
+        for (i = 0; i < state->h; i++) {
+            draw_arrow(dr, ds, COORD(state->w), COORD(i), 0, +1, 0);
+            draw_arrow(dr, ds, COORD(0), COORD(i+1), 0, -1, 0);
+        }
+
+        ds->started = TRUE;
+    }
+    /*
+     * Cursor (highlighted arrow around edge)
+     */
+    if (ui->cur_visible) {
+        cur_x = ui->cur_x; cur_y = ui->cur_y;
+    }
+
+    if (cur_x != ds->cur_x || cur_y != ds->cur_y) {
+        /* Cursor has changed; redraw two (prev and curr) arrows. */
+        draw_arrow_for_cursor(dr, ds, cur_x, cur_y, 1);
+        draw_arrow_for_cursor(dr, ds, ds->cur_x, ds->cur_y, 0);
+    }
+
+    /*
+     * Now draw each tile.
+     */
+
+    clip(dr, COORD(0), COORD(0), TILE_SIZE*state->w, TILE_SIZE*state->h);
+
+    for (i = 0; i < state->n; i++) {
+       int t, t0;
+       /*
+        * Figure out what should be displayed at this
+        * location. It's either a simple tile, or it's a
+        * transition between two tiles (in which case we say
+        * -1 because it must always be drawn).
+        */
+
+       if (oldstate && oldstate->tiles[i] != state->tiles[i])
+           t = -1;
+       else
+           t = state->tiles[i];
+
+       t0 = t;
+
+       if (ds->bgcolour != bgcolour ||   /* always redraw when flashing */
+            ds->tiles[i] != t || ds->tiles[i] == -1 || t == -1 ||
+            ((ds->cur_x != cur_x || ds->cur_y != cur_y) && /* cursor moved */
+             (TILE_CURSOR(i, state, ds->cur_x, ds->cur_y) ||
+              TILE_CURSOR(i, state, cur_x, cur_y)))) {
+            int x, y, x2, y2;
+
+           /*
+            * Figure out what to _actually_ draw, and where to
+            * draw it.
+            */
+           if (t == -1) {
+               int x0, y0, x1, y1, dx, dy;
+               int j;
+               float c;
+               int sense;
+
+               if (dir < 0) {
+                   assert(oldstate);
+                   sense = -oldstate->last_movement_sense;
+               } else {
+                   sense = state->last_movement_sense;
+               }
+
+               t = state->tiles[i];
+
+               /*
+                * FIXME: must be prepared to draw a double
+                * tile in some situations.
+                */
+
+               /*
+                * Find the coordinates of this tile in the old and
+                * new states.
+                */
+               x1 = COORD(X(state, i));
+               y1 = COORD(Y(state, i));
+               for (j = 0; j < oldstate->n; j++)
+                   if (oldstate->tiles[j] == state->tiles[i])
+                       break;
+               assert(j < oldstate->n);
+               x0 = COORD(X(state, j));
+               y0 = COORD(Y(state, j));
+
+               dx = (x1 - x0);
+               if (dx != 0 &&
+                   dx != TILE_SIZE * sense) {
+                   dx = (dx < 0 ? dx + TILE_SIZE * state->w :
+                         dx - TILE_SIZE * state->w);
+                   assert(abs(dx) == TILE_SIZE);
+               }
+               dy = (y1 - y0);
+               if (dy != 0 &&
+                   dy != TILE_SIZE * sense) {
+                   dy = (dy < 0 ? dy + TILE_SIZE * state->h :
+                         dy - TILE_SIZE * state->h);
+                   assert(abs(dy) == TILE_SIZE);
+               }
+
+               c = (animtime / ANIM_TIME);
+               if (c < 0.0F) c = 0.0F;
+               if (c > 1.0F) c = 1.0F;
+
+               x = x0 + (int)(c * dx);
+               y = y0 + (int)(c * dy);
+               x2 = x1 - dx + (int)(c * dx);
+               y2 = y1 - dy + (int)(c * dy);
+           } else {
+               x = COORD(X(state, i));
+               y = COORD(Y(state, i));
+               x2 = y2 = -1;
+           }
+
+           draw_tile(dr, ds, state, x, y, t,
+                     (x2 == -1 && TILE_CURSOR(i, state, cur_x, cur_y)) ?
+                      COL_LOWLIGHT : bgcolour);
+
+           if (x2 != -1 || y2 != -1)
+               draw_tile(dr, ds, state, x2, y2, t, bgcolour);
+       }
+       ds->tiles[i] = t0;
+    }
+
+    ds->cur_x = cur_x;
+    ds->cur_y = cur_y;
+
+    unclip(dr);
+
+    ds->bgcolour = bgcolour;
+
+    /*
+     * Update the status bar.
+     */
+    {
+       char statusbuf[256];
+
+        /*
+         * Don't show the new status until we're also showing the
+         * new _state_ - after the game animation is complete.
+         */
+        if (oldstate)
+            state = oldstate;
+
+       if (state->used_solve)
+           sprintf(statusbuf, "Moves since auto-solve: %d",
+                   state->movecount - state->completed);
+       else {
+           sprintf(statusbuf, "%sMoves: %d",
+                   (state->completed ? "COMPLETED! " : ""),
+                   (state->completed ? state->completed : state->movecount));
+            if (state->movetarget)
+                sprintf(statusbuf+strlen(statusbuf), " (target %d)",
+                        state->movetarget);
+       }
+
+       status_bar(dr, statusbuf);
+    }
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return ANIM_TIME;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    if (!oldstate->completed && newstate->completed &&
+       !oldstate->used_solve && !newstate->used_solve)
+        return 2 * FLASH_FRAME;
+    else
+        return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    return state->completed ? +1 : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+}
+
+#ifdef COMBINED
+#define thegame sixteen
+#endif
+
+const struct game thegame = {
+    "Sixteen", "games.sixteen", "sixteen",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    TRUE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    FALSE, FALSE, game_print_size, game_print,
+    TRUE,                             /* wants_statusbar */
+    FALSE, game_timing_state,
+    0,                                /* flags */
+};
+
+/* vim: set shiftwidth=4 tabstop=8: */
diff --git a/slant.R b/slant.R
new file mode 100644 (file)
index 0000000..ff0d21f
--- /dev/null
+++ b/slant.R
@@ -0,0 +1,24 @@
+# -*- makefile -*-
+
+SLANT_EXTRA = dsf findloop
+
+slant    : [X] GTK COMMON slant SLANT_EXTRA slant-icon|no-icon
+
+slant    : [G] WINDOWS COMMON slant SLANT_EXTRA slant.res|noicon.res
+
+slantsolver :   [U] slant[STANDALONE_SOLVER] SLANT_EXTRA STANDALONE
+slantsolver :   [C] slant[STANDALONE_SOLVER] SLANT_EXTRA STANDALONE
+
+ALL += slant[COMBINED] SLANT_EXTRA
+
+!begin am gtk
+GAMES += slant
+!end
+
+!begin >list.c
+    A(slant) \
+!end
+
+!begin >gamedesc.txt
+slant:slant.exe:Slant:Maze-drawing puzzle:Draw a maze of slanting lines that matches the clues.
+!end
diff --git a/slant.c b/slant.c
new file mode 100644 (file)
index 0000000..0d3f18c
--- /dev/null
+++ b/slant.c
@@ -0,0 +1,2278 @@
+/*
+ * slant.c: Puzzle from nikoli.co.jp involving drawing a diagonal
+ * line through each square of a grid.
+ */
+
+/*
+ * In this puzzle you have a grid of squares, each of which must
+ * contain a diagonal line; you also have clue numbers placed at
+ * _points_ of that grid, which means there's a (w+1) x (h+1) array
+ * of possible clue positions.
+ * 
+ * I'm therefore going to adopt a rigid convention throughout this
+ * source file of using w and h for the dimensions of the grid of
+ * squares, and W and H for the dimensions of the grid of points.
+ * Thus, W == w+1 and H == h+1 always.
+ * 
+ * Clue arrays will be W*H `signed char's, and the clue at each
+ * point will be a number from 0 to 4, or -1 if there's no clue.
+ * 
+ * Solution arrays will be W*H `signed char's, and the number at
+ * each point will be +1 for a forward slash (/), -1 for a
+ * backslash (\), and 0 for unknown.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+
+enum {
+    COL_BACKGROUND,
+    COL_GRID,
+    COL_INK,
+    COL_SLANT1,
+    COL_SLANT2,
+    COL_ERROR,
+    COL_CURSOR,
+    COL_FILLEDSQUARE,
+    NCOLOURS
+};
+
+/*
+ * In standalone solver mode, `verbose' is a variable which can be
+ * set by command-line option; in debugging mode it's simply always
+ * true.
+ */
+#if defined STANDALONE_SOLVER
+#define SOLVER_DIAGNOSTICS
+int verbose = FALSE;
+#elif defined SOLVER_DIAGNOSTICS
+#define verbose TRUE
+#endif
+
+/*
+ * Difficulty levels. I do some macro ickery here to ensure that my
+ * enum and the various forms of my name list always match up.
+ */
+#define DIFFLIST(A) \
+    A(EASY,Easy,e) \
+    A(HARD,Hard,h)
+#define ENUM(upper,title,lower) DIFF_ ## upper,
+#define TITLE(upper,title,lower) #title,
+#define ENCODE(upper,title,lower) #lower
+#define CONFIG(upper,title,lower) ":" #title
+enum { DIFFLIST(ENUM) DIFFCOUNT };
+static char const *const slant_diffnames[] = { DIFFLIST(TITLE) };
+static char const slant_diffchars[] = DIFFLIST(ENCODE);
+#define DIFFCONFIG DIFFLIST(CONFIG)
+
+struct game_params {
+    int w, h, diff;
+};
+
+typedef struct game_clues {
+    int w, h;
+    signed char *clues;
+    int *tmpdsf;
+    int refcount;
+} game_clues;
+
+#define ERR_VERTEX 1
+#define ERR_SQUARE 2
+
+struct game_state {
+    struct game_params p;
+    game_clues *clues;
+    signed char *soln;
+    unsigned char *errors;
+    int completed;
+    int used_solve;                   /* used to suppress completion flash */
+};
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = ret->h = 8;
+    ret->diff = DIFF_EASY;
+
+    return ret;
+}
+
+static const struct game_params slant_presets[] = {
+    {5, 5, DIFF_EASY},
+    {5, 5, DIFF_HARD},
+    {8, 8, DIFF_EASY},
+    {8, 8, DIFF_HARD},
+    {12, 10, DIFF_EASY},
+    {12, 10, DIFF_HARD},
+};
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params *ret;
+    char str[80];
+
+    if (i < 0 || i >= lenof(slant_presets))
+        return FALSE;
+
+    ret = snew(game_params);
+    *ret = slant_presets[i];
+
+    sprintf(str, "%dx%d %s", ret->w, ret->h, slant_diffnames[ret->diff]);
+
+    *name = dupstr(str);
+    *params = ret;
+    return TRUE;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+static void decode_params(game_params *ret, char const *string)
+{
+    ret->w = ret->h = atoi(string);
+    while (*string && isdigit((unsigned char)*string)) string++;
+    if (*string == 'x') {
+        string++;
+        ret->h = atoi(string);
+       while (*string && isdigit((unsigned char)*string)) string++;
+    }
+    if (*string == 'd') {
+       int i;
+       string++;
+       for (i = 0; i < DIFFCOUNT; i++)
+           if (*string == slant_diffchars[i])
+               ret->diff = i;
+       if (*string) string++;
+    }
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char data[256];
+
+    sprintf(data, "%dx%d", params->w, params->h);
+    if (full)
+       sprintf(data + strlen(data), "d%c", slant_diffchars[params->diff]);
+
+    return dupstr(data);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+    char buf[80];
+
+    ret = snewn(4, config_item);
+
+    ret[0].name = "Width";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->w);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Height";
+    ret[1].type = C_STRING;
+    sprintf(buf, "%d", params->h);
+    ret[1].sval = dupstr(buf);
+    ret[1].ival = 0;
+
+    ret[2].name = "Difficulty";
+    ret[2].type = C_CHOICES;
+    ret[2].sval = DIFFCONFIG;
+    ret[2].ival = params->diff;
+
+    ret[3].name = NULL;
+    ret[3].type = C_END;
+    ret[3].sval = NULL;
+    ret[3].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = atoi(cfg[0].sval);
+    ret->h = atoi(cfg[1].sval);
+    ret->diff = cfg[2].ival;
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    /*
+     * (At least at the time of writing this comment) The grid
+     * generator is actually capable of handling even zero grid
+     * dimensions without crashing. Puzzles with a zero-area grid
+     * are a bit boring, though, because they're already solved :-)
+     * And puzzles with a dimension of 1 can't be made Hard, which
+     * means the simplest thing is to forbid them altogether.
+     */
+
+    if (params->w < 2 || params->h < 2)
+       return "Width and height must both be at least two";
+
+    return NULL;
+}
+
+/*
+ * Scratch space for solver.
+ */
+struct solver_scratch {
+    /*
+     * Disjoint set forest which tracks the connected sets of
+     * points.
+     */
+    int *connected;
+
+    /*
+     * Counts the number of possible exits from each connected set
+     * of points. (That is, the number of possible _simultaneous_
+     * exits: an unconnected point labelled 2 has an exit count of
+     * 2 even if all four possible edges are still under
+     * consideration.)
+     */
+    int *exits;
+
+    /*
+     * Tracks whether each connected set of points includes a
+     * border point.
+     */
+    unsigned char *border;
+
+    /*
+     * Another disjoint set forest. This one tracks _squares_ which
+     * are known to slant in the same direction.
+     */
+    int *equiv;
+
+    /*
+     * Stores slash values which we know for an equivalence class.
+     * When we fill in a square, we set slashval[canonify(x)] to
+     * the same value as soln[x], so that we can then spot other
+     * squares equivalent to it and fill them in immediately via
+     * their known equivalence.
+     */
+    signed char *slashval;
+
+    /*
+     * Stores possible v-shapes. This array is w by h in size, but
+     * not every bit of every entry is meaningful. The bits mean:
+     * 
+     *  - bit 0 for a square means that that square and the one to
+     *    its right might form a v-shape between them
+     *  - bit 1 for a square means that that square and the one to
+     *    its right might form a ^-shape between them
+     *  - bit 2 for a square means that that square and the one
+     *    below it might form a >-shape between them
+     *  - bit 3 for a square means that that square and the one
+     *    below it might form a <-shape between them
+     * 
+     * Any starting 1 or 3 clue rules out four bits in this array
+     * immediately; a 2 clue propagates any ruled-out bit past it
+     * (if the two squares on one side of a 2 cannot be a v-shape,
+     * then neither can the two on the other side be the same
+     * v-shape); we can rule out further bits during play using
+     * partially filled 2 clues; whenever a pair of squares is
+     * known not to be _either_ kind of v-shape, we can mark them
+     * as equivalent.
+     */
+    unsigned char *vbitmap;
+
+    /*
+     * Useful to have this information automatically passed to
+     * solver subroutines. (This pointer is not dynamically
+     * allocated by new_scratch and free_scratch.)
+     */
+    const signed char *clues;
+};
+
+static struct solver_scratch *new_scratch(int w, int h)
+{
+    int W = w+1, H = h+1;
+    struct solver_scratch *ret = snew(struct solver_scratch);
+    ret->connected = snewn(W*H, int);
+    ret->exits = snewn(W*H, int);
+    ret->border = snewn(W*H, unsigned char);
+    ret->equiv = snewn(w*h, int);
+    ret->slashval = snewn(w*h, signed char);
+    ret->vbitmap = snewn(w*h, unsigned char);
+    return ret;
+}
+
+static void free_scratch(struct solver_scratch *sc)
+{
+    sfree(sc->vbitmap);
+    sfree(sc->slashval);
+    sfree(sc->equiv);
+    sfree(sc->border);
+    sfree(sc->exits);
+    sfree(sc->connected);
+    sfree(sc);
+}
+
+/*
+ * Wrapper on dsf_merge() which updates the `exits' and `border'
+ * arrays.
+ */
+static void merge_vertices(int *connected,
+                          struct solver_scratch *sc, int i, int j)
+{
+    int exits = -1, border = FALSE;    /* initialise to placate optimiser */
+
+    if (sc) {
+       i = dsf_canonify(connected, i);
+       j = dsf_canonify(connected, j);
+
+       /*
+        * We have used one possible exit from each of the two
+        * classes. Thus, the viable exit count of the new class is
+        * the sum of the old exit counts minus two.
+        */
+       exits = sc->exits[i] + sc->exits[j] - 2;
+
+       border = sc->border[i] || sc->border[j];
+    }
+
+    dsf_merge(connected, i, j);
+
+    if (sc) {
+       i = dsf_canonify(connected, i);
+       sc->exits[i] = exits;
+       sc->border[i] = border;
+    }
+}
+
+/*
+ * Called when we have just blocked one way out of a particular
+ * point. If that point is a non-clue point (thus has a variable
+ * number of exits), we have therefore decreased its potential exit
+ * count, so we must decrement the exit count for the group as a
+ * whole.
+ */
+static void decr_exits(struct solver_scratch *sc, int i)
+{
+    if (sc->clues[i] < 0) {
+       i = dsf_canonify(sc->connected, i);
+       sc->exits[i]--;
+    }
+}
+
+static void fill_square(int w, int h, int x, int y, int v,
+                       signed char *soln,
+                       int *connected, struct solver_scratch *sc)
+{
+    int W = w+1 /*, H = h+1 */;
+
+    assert(x >= 0 && x < w && y >= 0 && y < h);
+
+    if (soln[y*w+x] != 0) {
+       return;                        /* do nothing */
+    }
+
+#ifdef SOLVER_DIAGNOSTICS
+    if (verbose)
+       printf("  placing %c in %d,%d\n", v == -1 ? '\\' : '/', x, y);
+#endif
+
+    soln[y*w+x] = v;
+
+    if (sc) {
+       int c = dsf_canonify(sc->equiv, y*w+x);
+       sc->slashval[c] = v;
+    }
+
+    if (v < 0) {
+       merge_vertices(connected, sc, y*W+x, (y+1)*W+(x+1));
+       if (sc) {
+           decr_exits(sc, y*W+(x+1));
+           decr_exits(sc, (y+1)*W+x);
+       }
+    } else {
+       merge_vertices(connected, sc, y*W+(x+1), (y+1)*W+x);
+       if (sc) {
+           decr_exits(sc, y*W+x);
+           decr_exits(sc, (y+1)*W+(x+1));
+       }
+    }
+}
+
+static int vbitmap_clear(int w, int h, struct solver_scratch *sc,
+                         int x, int y, int vbits, char *reason, ...)
+{
+    int done_something = FALSE;
+    int vbit;
+
+    for (vbit = 1; vbit <= 8; vbit <<= 1)
+        if (vbits & sc->vbitmap[y*w+x] & vbit) {
+            done_something = TRUE;
+#ifdef SOLVER_DIAGNOSTICS
+            if (verbose) {
+                va_list ap;
+
+                printf("ruling out %c shape at (%d,%d)-(%d,%d) (",
+                       "!v^!>!!!<"[vbit], x, y,
+                       x+((vbit&0x3)!=0), y+((vbit&0xC)!=0));
+
+                va_start(ap, reason);
+                vprintf(reason, ap);
+                va_end(ap);
+
+                printf(")\n");
+            }
+#endif
+            sc->vbitmap[y*w+x] &= ~vbit;
+        }
+
+    return done_something;
+}
+
+/*
+ * Solver. Returns 0 for impossibility, 1 for success, 2 for
+ * ambiguity or failure to converge.
+ */
+static int slant_solve(int w, int h, const signed char *clues,
+                      signed char *soln, struct solver_scratch *sc,
+                      int difficulty)
+{
+    int W = w+1, H = h+1;
+    int x, y, i, j;
+    int done_something;
+
+    /*
+     * Clear the output.
+     */
+    memset(soln, 0, w*h);
+
+    sc->clues = clues;
+
+    /*
+     * Establish a disjoint set forest for tracking connectedness
+     * between grid points.
+     */
+    dsf_init(sc->connected, W*H);
+
+    /*
+     * Establish a disjoint set forest for tracking which squares
+     * are known to slant in the same direction.
+     */
+    dsf_init(sc->equiv, w*h);
+
+    /*
+     * Clear the slashval array.
+     */
+    memset(sc->slashval, 0, w*h);
+
+    /*
+     * Set up the vbitmap array. Initially all types of v are possible.
+     */
+    memset(sc->vbitmap, 0xF, w*h);
+
+    /*
+     * Initialise the `exits' and `border' arrays. These are used
+     * to do second-order loop avoidance: the dual of the no loops
+     * constraint is that every point must be somehow connected to
+     * the border of the grid (otherwise there would be a solid
+     * loop around it which prevented this).
+     * 
+     * I define a `dead end' to be a connected group of points
+     * which contains no border point, and which can form at most
+     * one new connection outside itself. Then I forbid placing an
+     * edge so that it connects together two dead-end groups, since
+     * this would yield a non-border-connected isolated subgraph
+     * with no further scope to extend it.
+     */
+    for (y = 0; y < H; y++)
+       for (x = 0; x < W; x++) {
+           if (y == 0 || y == H-1 || x == 0 || x == W-1)
+               sc->border[y*W+x] = TRUE;
+           else
+               sc->border[y*W+x] = FALSE;
+
+           if (clues[y*W+x] < 0)
+               sc->exits[y*W+x] = 4;
+           else
+               sc->exits[y*W+x] = clues[y*W+x];
+       }
+
+    /*
+     * Repeatedly try to deduce something until we can't.
+     */
+    do {
+       done_something = FALSE;
+
+       /*
+        * Any clue point with the number of remaining lines equal
+        * to zero or to the number of remaining undecided
+        * neighbouring squares can be filled in completely.
+        */
+       for (y = 0; y < H; y++)
+           for (x = 0; x < W; x++) {
+               struct {
+                   int pos, slash;
+               } neighbours[4];
+               int nneighbours;
+               int nu, nl, c, s, eq, eq2, last, meq, mj1, mj2;
+
+               if ((c = clues[y*W+x]) < 0)
+                   continue;
+
+               /*
+                * We have a clue point. Start by listing its
+                * neighbouring squares, in order around the point,
+                * together with the type of slash that would be
+                * required in that square to connect to the point.
+                */
+               nneighbours = 0;
+               if (x > 0 && y > 0) {
+                   neighbours[nneighbours].pos = (y-1)*w+(x-1);
+                   neighbours[nneighbours].slash = -1;
+                   nneighbours++;
+               }
+               if (x > 0 && y < h) {
+                   neighbours[nneighbours].pos = y*w+(x-1);
+                   neighbours[nneighbours].slash = +1;
+                   nneighbours++;
+               }
+               if (x < w && y < h) {
+                   neighbours[nneighbours].pos = y*w+x;
+                   neighbours[nneighbours].slash = -1;
+                   nneighbours++;
+               }
+               if (x < w && y > 0) {
+                   neighbours[nneighbours].pos = (y-1)*w+x;
+                   neighbours[nneighbours].slash = +1;
+                   nneighbours++;
+               }
+
+               /*
+                * Count up the number of undecided neighbours, and
+                * also the number of lines already present.
+                *
+                * If we're not on DIFF_EASY, then in this loop we
+                * also track whether we've seen two adjacent empty
+                * squares belonging to the same equivalence class
+                * (meaning they have the same type of slash). If
+                * so, we count them jointly as one line.
+                */
+               nu = 0;
+               nl = c;
+               last = neighbours[nneighbours-1].pos;
+               if (soln[last] == 0)
+                   eq = dsf_canonify(sc->equiv, last);
+               else
+                   eq = -1;
+               meq = mj1 = mj2 = -1;
+               for (i = 0; i < nneighbours; i++) {
+                   j = neighbours[i].pos;
+                   s = neighbours[i].slash;
+                   if (soln[j] == 0) {
+                       nu++;          /* undecided */
+                       if (meq < 0 && difficulty > DIFF_EASY) {
+                           eq2 = dsf_canonify(sc->equiv, j);
+                           if (eq == eq2 && last != j) {
+                               /*
+                                * We've found an equivalent pair.
+                                * Mark it. This also inhibits any
+                                * further equivalence tracking
+                                * around this square, since we can
+                                * only handle one pair (and in
+                                * particular we want to avoid
+                                * being misled by two overlapping
+                                * equivalence pairs).
+                                */
+                               meq = eq;
+                               mj1 = last;
+                               mj2 = j;
+                               nl--;   /* count one line */
+                               nu -= 2;   /* and lose two undecideds */
+                           } else
+                               eq = eq2;
+                       }
+                   } else {
+                       eq = -1;
+                       if (soln[j] == s)
+                           nl--;      /* here's a line */
+                   }
+                   last = j;
+               }
+
+               /*
+                * Check the counts.
+                */
+               if (nl < 0 || nl > nu) {
+                   /*
+                    * No consistent value for this at all!
+                    */
+#ifdef SOLVER_DIAGNOSTICS
+                   if (verbose)
+                       printf("need %d / %d lines around clue point at %d,%d!\n",
+                              nl, nu, x, y);
+#endif
+                   return 0;          /* impossible */
+               }
+
+               if (nu > 0 && (nl == 0 || nl == nu)) {
+#ifdef SOLVER_DIAGNOSTICS
+                   if (verbose) {
+                       if (meq >= 0)
+                           printf("partially (since %d,%d == %d,%d) ",
+                                  mj1%w, mj1/w, mj2%w, mj2/w);
+                       printf("%s around clue point at %d,%d\n",
+                              nl ? "filling" : "emptying", x, y);
+                   }
+#endif
+                   for (i = 0; i < nneighbours; i++) {
+                       j = neighbours[i].pos;
+                       s = neighbours[i].slash;
+                       if (soln[j] == 0 && j != mj1 && j != mj2)
+                           fill_square(w, h, j%w, j/w, (nl ? s : -s), soln,
+                                       sc->connected, sc);
+                   }
+
+                   done_something = TRUE;
+               } else if (nu == 2 && nl == 1 && difficulty > DIFF_EASY) {
+                   /*
+                    * If we have precisely two undecided squares
+                    * and precisely one line to place between
+                    * them, _and_ those squares are adjacent, then
+                    * we can mark them as equivalent to one
+                    * another.
+                    * 
+                    * This even applies if meq >= 0: if we have a
+                    * 2 clue point and two of its neighbours are
+                    * already marked equivalent, we can indeed
+                    * mark the other two as equivalent.
+                    * 
+                    * We don't bother with this on DIFF_EASY,
+                    * since we wouldn't have used the results
+                    * anyway.
+                    */
+                   last = -1;
+                   for (i = 0; i < nneighbours; i++) {
+                       j = neighbours[i].pos;
+                       if (soln[j] == 0 && j != mj1 && j != mj2) {
+                           if (last < 0)
+                               last = i;
+                           else if (last == i-1 || (last == 0 && i == 3))
+                               break; /* found a pair */
+                       }
+                   }
+                   if (i < nneighbours) {
+                       int sv1, sv2;
+
+                       assert(last >= 0);
+                       /*
+                        * neighbours[last] and neighbours[i] are
+                        * the pair. Mark them equivalent.
+                        */
+#ifdef SOLVER_DIAGNOSTICS
+                       if (verbose) {
+                           if (meq >= 0)
+                               printf("since %d,%d == %d,%d, ",
+                                      mj1%w, mj1/w, mj2%w, mj2/w);
+                       }
+#endif
+                       mj1 = neighbours[last].pos;
+                       mj2 = neighbours[i].pos;
+#ifdef SOLVER_DIAGNOSTICS
+                       if (verbose)
+                           printf("clue point at %d,%d implies %d,%d == %d,"
+                                  "%d\n", x, y, mj1%w, mj1/w, mj2%w, mj2/w);
+#endif
+                       mj1 = dsf_canonify(sc->equiv, mj1);
+                       sv1 = sc->slashval[mj1];
+                       mj2 = dsf_canonify(sc->equiv, mj2);
+                       sv2 = sc->slashval[mj2];
+                       if (sv1 != 0 && sv2 != 0 && sv1 != sv2) {
+#ifdef SOLVER_DIAGNOSTICS
+                           if (verbose)
+                               printf("merged two equivalence classes with"
+                                      " different slash values!\n");
+#endif
+                           return 0;
+                       }
+                       sv1 = sv1 ? sv1 : sv2;
+                       dsf_merge(sc->equiv, mj1, mj2);
+                       mj1 = dsf_canonify(sc->equiv, mj1);
+                       sc->slashval[mj1] = sv1;
+                   }
+               }
+           }
+
+       if (done_something)
+           continue;
+
+       /*
+        * Failing that, we now apply the second condition, which
+        * is that no square may be filled in such a way as to form
+        * a loop. Also in this loop (since it's over squares
+        * rather than points), we check slashval to see if we've
+        * already filled in another square in the same equivalence
+        * class.
+        * 
+        * The slashval check is disabled on DIFF_EASY, as is dead
+        * end avoidance. Only _immediate_ loop avoidance remains.
+        */
+       for (y = 0; y < h; y++)
+           for (x = 0; x < w; x++) {
+               int fs, bs, v;
+               int c1, c2;
+#ifdef SOLVER_DIAGNOSTICS
+               char *reason = "<internal error>";
+#endif
+
+               if (soln[y*w+x])
+                   continue;          /* got this one already */
+
+               fs = FALSE;
+               bs = FALSE;
+
+               if (difficulty > DIFF_EASY)
+                   v = sc->slashval[dsf_canonify(sc->equiv, y*w+x)];
+               else
+                   v = 0;
+
+               /*
+                * Try to rule out connectivity between (x,y) and
+                * (x+1,y+1); if successful, we will deduce that we
+                * must have a forward slash.
+                */
+               c1 = dsf_canonify(sc->connected, y*W+x);
+               c2 = dsf_canonify(sc->connected, (y+1)*W+(x+1));
+               if (c1 == c2) {
+                   fs = TRUE;
+#ifdef SOLVER_DIAGNOSTICS
+                   reason = "simple loop avoidance";
+#endif
+               }
+               if (difficulty > DIFF_EASY &&
+                   !sc->border[c1] && !sc->border[c2] &&
+                   sc->exits[c1] <= 1 && sc->exits[c2] <= 1) {
+                   fs = TRUE;
+#ifdef SOLVER_DIAGNOSTICS
+                   reason = "dead end avoidance";
+#endif
+               }
+               if (v == +1) {
+                   fs = TRUE;
+#ifdef SOLVER_DIAGNOSTICS
+                   reason = "equivalence to an already filled square";
+#endif
+               }
+
+               /*
+                * Now do the same between (x+1,y) and (x,y+1), to
+                * see if we are required to have a backslash.
+                */
+               c1 = dsf_canonify(sc->connected, y*W+(x+1));
+               c2 = dsf_canonify(sc->connected, (y+1)*W+x);
+               if (c1 == c2) {
+                   bs = TRUE;
+#ifdef SOLVER_DIAGNOSTICS
+                   reason = "simple loop avoidance";
+#endif
+               }
+               if (difficulty > DIFF_EASY &&
+                   !sc->border[c1] && !sc->border[c2] &&
+                   sc->exits[c1] <= 1 && sc->exits[c2] <= 1) {
+                   bs = TRUE;
+#ifdef SOLVER_DIAGNOSTICS
+                   reason = "dead end avoidance";
+#endif
+               }
+               if (v == -1) {
+                   bs = TRUE;
+#ifdef SOLVER_DIAGNOSTICS
+                   reason = "equivalence to an already filled square";
+#endif
+               }
+
+               if (fs && bs) {
+                   /*
+                    * No consistent value for this at all!
+                    */
+#ifdef SOLVER_DIAGNOSTICS
+                   if (verbose)
+                       printf("%d,%d has no consistent slash!\n", x, y);
+#endif
+                   return 0;          /* impossible */
+               }
+
+               if (fs) {
+#ifdef SOLVER_DIAGNOSTICS
+                   if (verbose)
+                       printf("employing %s\n", reason);
+#endif
+                   fill_square(w, h, x, y, +1, soln, sc->connected, sc);
+                   done_something = TRUE;
+               } else if (bs) {
+#ifdef SOLVER_DIAGNOSTICS
+                   if (verbose)
+                       printf("employing %s\n", reason);
+#endif
+                   fill_square(w, h, x, y, -1, soln, sc->connected, sc);
+                   done_something = TRUE;
+               }
+           }
+
+       if (done_something)
+           continue;
+
+        /*
+         * Now see what we can do with the vbitmap array. All
+         * vbitmap deductions are disabled at Easy level.
+         */
+        if (difficulty <= DIFF_EASY)
+            continue;
+
+       for (y = 0; y < h; y++)
+           for (x = 0; x < w; x++) {
+                int s, c;
+
+                /*
+                 * Any line already placed in a square must rule
+                 * out any type of v which contradicts it.
+                 */
+                if ((s = soln[y*w+x]) != 0) {
+                    if (x > 0)
+                        done_something |=
+                        vbitmap_clear(w, h, sc, x-1, y, (s < 0 ? 0x1 : 0x2),
+                                      "contradicts known edge at (%d,%d)",x,y);
+                    if (x+1 < w)
+                        done_something |=
+                        vbitmap_clear(w, h, sc, x, y, (s < 0 ? 0x2 : 0x1),
+                                      "contradicts known edge at (%d,%d)",x,y);
+                    if (y > 0)
+                        done_something |=
+                        vbitmap_clear(w, h, sc, x, y-1, (s < 0 ? 0x4 : 0x8),
+                                      "contradicts known edge at (%d,%d)",x,y);
+                    if (y+1 < h)
+                        done_something |=
+                        vbitmap_clear(w, h, sc, x, y, (s < 0 ? 0x8 : 0x4),
+                                      "contradicts known edge at (%d,%d)",x,y);
+                }
+
+                /*
+                 * If both types of v are ruled out for a pair of
+                 * adjacent squares, mark them as equivalent.
+                 */
+                if (x+1 < w && !(sc->vbitmap[y*w+x] & 0x3)) {
+                    int n1 = y*w+x, n2 = y*w+(x+1);
+                    if (dsf_canonify(sc->equiv, n1) !=
+                        dsf_canonify(sc->equiv, n2)) {
+                        dsf_merge(sc->equiv, n1, n2);
+                        done_something = TRUE;
+#ifdef SOLVER_DIAGNOSTICS
+                        if (verbose)
+                            printf("(%d,%d) and (%d,%d) must be equivalent"
+                                   " because both v-shapes are ruled out\n",
+                                   x, y, x+1, y);
+#endif
+                    }
+                }
+                if (y+1 < h && !(sc->vbitmap[y*w+x] & 0xC)) {
+                    int n1 = y*w+x, n2 = (y+1)*w+x;
+                    if (dsf_canonify(sc->equiv, n1) !=
+                        dsf_canonify(sc->equiv, n2)) {
+                        dsf_merge(sc->equiv, n1, n2);
+                        done_something = TRUE;
+#ifdef SOLVER_DIAGNOSTICS
+                        if (verbose)
+                            printf("(%d,%d) and (%d,%d) must be equivalent"
+                                   " because both v-shapes are ruled out\n",
+                                   x, y, x, y+1);
+#endif
+                    }
+                }
+
+                /*
+                 * The remaining work in this loop only works
+                 * around non-edge clue points.
+                 */
+                if (y == 0 || x == 0)
+                    continue;
+               if ((c = clues[y*W+x]) < 0)
+                   continue;
+
+                /*
+                 * x,y marks a clue point not on the grid edge. See
+                 * if this clue point allows us to rule out any v
+                 * shapes.
+                 */
+
+                if (c == 1) {
+                    /*
+                     * A 1 clue can never have any v shape pointing
+                     * at it.
+                     */
+                    done_something |=
+                        vbitmap_clear(w, h, sc, x-1, y-1, 0x5,
+                                      "points at 1 clue at (%d,%d)", x, y);
+                    done_something |=
+                        vbitmap_clear(w, h, sc, x-1, y, 0x2,
+                                      "points at 1 clue at (%d,%d)", x, y);
+                    done_something |=
+                        vbitmap_clear(w, h, sc, x, y-1, 0x8,
+                                      "points at 1 clue at (%d,%d)", x, y);
+                } else if (c == 3) {
+                    /*
+                     * A 3 clue can never have any v shape pointing
+                     * away from it.
+                     */
+                    done_something |=
+                        vbitmap_clear(w, h, sc, x-1, y-1, 0xA,
+                                      "points away from 3 clue at (%d,%d)", x, y);
+                    done_something |=
+                        vbitmap_clear(w, h, sc, x-1, y, 0x1,
+                                      "points away from 3 clue at (%d,%d)", x, y);
+                    done_something |=
+                        vbitmap_clear(w, h, sc, x, y-1, 0x4,
+                                      "points away from 3 clue at (%d,%d)", x, y);
+                } else if (c == 2) {
+                    /*
+                     * If a 2 clue has any kind of v ruled out on
+                     * one side of it, the same v is ruled out on
+                     * the other side.
+                     */
+                    done_something |=
+                        vbitmap_clear(w, h, sc, x-1, y-1,
+                                      (sc->vbitmap[(y  )*w+(x-1)] & 0x3) ^ 0x3,
+                                      "propagated by 2 clue at (%d,%d)", x, y);
+                    done_something |=
+                        vbitmap_clear(w, h, sc, x-1, y-1,
+                                      (sc->vbitmap[(y-1)*w+(x  )] & 0xC) ^ 0xC,
+                                      "propagated by 2 clue at (%d,%d)", x, y);
+                    done_something |=
+                        vbitmap_clear(w, h, sc, x-1, y,
+                                      (sc->vbitmap[(y-1)*w+(x-1)] & 0x3) ^ 0x3,
+                                      "propagated by 2 clue at (%d,%d)", x, y);
+                    done_something |=
+                        vbitmap_clear(w, h, sc, x, y-1,
+                                      (sc->vbitmap[(y-1)*w+(x-1)] & 0xC) ^ 0xC,
+                                      "propagated by 2 clue at (%d,%d)", x, y);
+                }
+
+#undef CLEARBITS
+
+            }
+
+    } while (done_something);
+
+    /*
+     * Solver can make no more progress. See if the grid is full.
+     */
+    for (i = 0; i < w*h; i++)
+       if (!soln[i])
+           return 2;                  /* failed to converge */
+    return 1;                         /* success */
+}
+
+/*
+ * Filled-grid generator.
+ */
+static void slant_generate(int w, int h, signed char *soln, random_state *rs)
+{
+    int W = w+1, H = h+1;
+    int x, y, i;
+    int *connected, *indices;
+
+    /*
+     * Clear the output.
+     */
+    memset(soln, 0, w*h);
+
+    /*
+     * Establish a disjoint set forest for tracking connectedness
+     * between grid points.
+     */
+    connected = snew_dsf(W*H);
+
+    /*
+     * Prepare a list of the squares in the grid, and fill them in
+     * in a random order.
+     */
+    indices = snewn(w*h, int);
+    for (i = 0; i < w*h; i++)
+       indices[i] = i;
+    shuffle(indices, w*h, sizeof(*indices), rs);
+
+    /*
+     * Fill in each one in turn.
+     */
+    for (i = 0; i < w*h; i++) {
+       int fs, bs, v;
+
+       y = indices[i] / w;
+       x = indices[i] % w;
+
+       fs = (dsf_canonify(connected, y*W+x) ==
+             dsf_canonify(connected, (y+1)*W+(x+1)));
+       bs = (dsf_canonify(connected, (y+1)*W+x) ==
+             dsf_canonify(connected, y*W+(x+1)));
+
+       /*
+        * It isn't possible to get into a situation where we
+        * aren't allowed to place _either_ type of slash in a
+        * square. Thus, filled-grid generation never has to
+        * backtrack.
+        * 
+        * Proof (thanks to Gareth Taylor):
+        * 
+        * If it were possible, it would have to be because there
+        * was an existing path (not using this square) between the
+        * top-left and bottom-right corners of this square, and
+        * another between the other two. These two paths would
+        * have to cross at some point.
+        * 
+        * Obviously they can't cross in the middle of a square, so
+        * they must cross by sharing a point in common. But this
+        * isn't possible either: if you chessboard-colour all the
+        * points on the grid, you find that any continuous
+        * diagonal path is entirely composed of points of the same
+        * colour. And one of our two hypothetical paths is between
+        * two black points, and the other is between two white
+        * points - therefore they can have no point in common. []
+        */
+       assert(!(fs && bs));
+
+       v = fs ? +1 : bs ? -1 : 2 * random_upto(rs, 2) - 1;
+       fill_square(w, h, x, y, v, soln, connected, NULL);
+    }
+
+    sfree(indices);
+    sfree(connected);
+}
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                          char **aux, int interactive)
+{
+    int w = params->w, h = params->h, W = w+1, H = h+1;
+    signed char *soln, *tmpsoln, *clues;
+    int *clueindices;
+    struct solver_scratch *sc;
+    int x, y, v, i, j;
+    char *desc;
+
+    soln = snewn(w*h, signed char);
+    tmpsoln = snewn(w*h, signed char);
+    clues = snewn(W*H, signed char);
+    clueindices = snewn(W*H, int);
+    sc = new_scratch(w, h);
+
+    do {
+       /*
+        * Create the filled grid.
+        */
+       slant_generate(w, h, soln, rs);
+
+       /*
+        * Fill in the complete set of clues.
+        */
+       for (y = 0; y < H; y++)
+           for (x = 0; x < W; x++) {
+               v = 0;
+
+               if (x > 0 && y > 0 && soln[(y-1)*w+(x-1)] == -1) v++;
+               if (x > 0 && y < h && soln[y*w+(x-1)] == +1) v++;
+               if (x < w && y > 0 && soln[(y-1)*w+x] == +1) v++;
+               if (x < w && y < h && soln[y*w+x] == -1) v++;
+
+               clues[y*W+x] = v;
+           }
+
+       /*
+        * With all clue points filled in, all puzzles are easy: we can
+        * simply process the clue points in lexicographic order, and
+        * at each clue point we will always have at most one square
+        * undecided, which we can then fill in uniquely.
+        */
+       assert(slant_solve(w, h, clues, tmpsoln, sc, DIFF_EASY) == 1);
+
+       /*
+        * Remove as many clues as possible while retaining solubility.
+        *
+        * In DIFF_HARD mode, we prioritise the removal of obvious
+        * starting points (4s, 0s, border 2s and corner 1s), on
+        * the grounds that having as few of these as possible
+        * seems like a good thing. In particular, we can often get
+        * away without _any_ completely obvious starting points,
+        * which is even better.
+        */
+       for (i = 0; i < W*H; i++)
+           clueindices[i] = i;
+       shuffle(clueindices, W*H, sizeof(*clueindices), rs);
+       for (j = 0; j < 2; j++) {
+           for (i = 0; i < W*H; i++) {
+               int pass, yb, xb;
+
+               y = clueindices[i] / W;
+               x = clueindices[i] % W;
+               v = clues[y*W+x];
+
+               /*
+                * Identify which pass we should process this point
+                * in. If it's an obvious start point, _or_ we're
+                * in DIFF_EASY, then it goes in pass 0; otherwise
+                * pass 1.
+                */
+               xb = (x == 0 || x == W-1);
+               yb = (y == 0 || y == H-1);
+               if (params->diff == DIFF_EASY || v == 4 || v == 0 ||
+                   (v == 2 && (xb||yb)) || (v == 1 && xb && yb))
+                   pass = 0;
+               else
+                   pass = 1;
+
+               if (pass == j) {
+                   clues[y*W+x] = -1;
+                   if (slant_solve(w, h, clues, tmpsoln, sc,
+                                   params->diff) != 1)
+                       clues[y*W+x] = v;              /* put it back */
+               }
+           }
+       }
+
+       /*
+        * And finally, verify that the grid is of _at least_ the
+        * requested difficulty, by running the solver one level
+        * down and verifying that it can't manage it.
+        */
+    } while (params->diff > 0 &&
+            slant_solve(w, h, clues, tmpsoln, sc, params->diff - 1) <= 1);
+
+    /*
+     * Now we have the clue set as it will be presented to the
+     * user. Encode it in a game desc.
+     */
+    {
+       char *p;
+       int run, i;
+
+       desc = snewn(W*H+1, char);
+       p = desc;
+       run = 0;
+       for (i = 0; i <= W*H; i++) {
+           int n = (i < W*H ? clues[i] : -2);
+
+           if (n == -1)
+               run++;
+           else {
+               if (run) {
+                   while (run > 0) {
+                       int c = 'a' - 1 + run;
+                       if (run > 26)
+                           c = 'z';
+                       *p++ = c;
+                       run -= c - ('a' - 1);
+                   }
+               }
+               if (n >= 0)
+                   *p++ = '0' + n;
+               run = 0;
+           }
+       }
+       assert(p - desc <= W*H);
+       *p++ = '\0';
+       desc = sresize(desc, p - desc, char);
+    }
+
+    /*
+     * Encode the solution as an aux_info.
+     */
+    {
+       char *auxbuf;
+       *aux = auxbuf = snewn(w*h+1, char);
+       for (i = 0; i < w*h; i++)
+           auxbuf[i] = soln[i] < 0 ? '\\' : '/';
+       auxbuf[w*h] = '\0';
+    }
+
+    free_scratch(sc);
+    sfree(clueindices);
+    sfree(clues);
+    sfree(tmpsoln);
+    sfree(soln);
+
+    return desc;
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    int w = params->w, h = params->h, W = w+1, H = h+1;
+    int area = W*H;
+    int squares = 0;
+
+    while (*desc) {
+        int n = *desc++;
+        if (n >= 'a' && n <= 'z') {
+            squares += n - 'a' + 1;
+        } else if (n >= '0' && n <= '4') {
+            squares++;
+        } else
+            return "Invalid character in game description";
+    }
+
+    if (squares < area)
+        return "Not enough data to fill grid";
+
+    if (squares > area)
+        return "Too much data to fit in grid";
+
+    return NULL;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    int w = params->w, h = params->h, W = w+1, H = h+1;
+    game_state *state = snew(game_state);
+    int area = W*H;
+    int squares = 0;
+
+    state->p = *params;
+    state->soln = snewn(w*h, signed char);
+    memset(state->soln, 0, w*h);
+    state->completed = state->used_solve = FALSE;
+    state->errors = snewn(W*H, unsigned char);
+    memset(state->errors, 0, W*H);
+
+    state->clues = snew(game_clues);
+    state->clues->w = w;
+    state->clues->h = h;
+    state->clues->clues = snewn(W*H, signed char);
+    state->clues->refcount = 1;
+    state->clues->tmpdsf = snewn(W*H*2+W+H, int);
+    memset(state->clues->clues, -1, W*H);
+    while (*desc) {
+        int n = *desc++;
+        if (n >= 'a' && n <= 'z') {
+            squares += n - 'a' + 1;
+        } else if (n >= '0' && n <= '4') {
+            state->clues->clues[squares++] = n - '0';
+        } else
+           assert(!"can't get here");
+    }
+    assert(squares == area);
+
+    return state;
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    int w = state->p.w, h = state->p.h, W = w+1, H = h+1;
+    game_state *ret = snew(game_state);
+
+    ret->p = state->p;
+    ret->clues = state->clues;
+    ret->clues->refcount++;
+    ret->completed = state->completed;
+    ret->used_solve = state->used_solve;
+
+    ret->soln = snewn(w*h, signed char);
+    memcpy(ret->soln, state->soln, w*h);
+
+    ret->errors = snewn(W*H, unsigned char);
+    memcpy(ret->errors, state->errors, W*H);
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    sfree(state->errors);
+    sfree(state->soln);
+    assert(state->clues);
+    if (--state->clues->refcount <= 0) {
+        sfree(state->clues->clues);
+        sfree(state->clues->tmpdsf);
+        sfree(state->clues);
+    }
+    sfree(state);
+}
+
+/*
+ * Utility function to return the current degree of a vertex. If
+ * `anti' is set, it returns the number of filled-in edges
+ * surrounding the point which _don't_ connect to it; thus 4 minus
+ * its anti-degree is the maximum degree it could have if all the
+ * empty spaces around it were filled in.
+ * 
+ * (Yes, _4_ minus its anti-degree even if it's a border vertex.)
+ * 
+ * If ret > 0, *sx and *sy are set to the coordinates of one of the
+ * squares that contributed to it.
+ */
+static int vertex_degree(int w, int h, signed char *soln, int x, int y,
+                         int anti, int *sx, int *sy)
+{
+    int ret = 0;
+
+    assert(x >= 0 && x <= w && y >= 0 && y <= h);
+    if (x > 0 && y > 0 && soln[(y-1)*w+(x-1)] - anti < 0) {
+        if (sx) *sx = x-1;
+        if (sy) *sy = y-1;
+        ret++;
+    }
+    if (x > 0 && y < h && soln[y*w+(x-1)] + anti > 0) {
+        if (sx) *sx = x-1;
+        if (sy) *sy = y;
+        ret++;
+    }
+    if (x < w && y > 0 && soln[(y-1)*w+x] + anti > 0) {
+        if (sx) *sx = x;
+        if (sy) *sy = y-1;
+        ret++;
+    }
+    if (x < w && y < h && soln[y*w+x] - anti < 0) {
+        if (sx) *sx = x;
+        if (sy) *sy = y;
+        ret++;
+    }
+
+    return anti ? 4 - ret : ret;
+}
+
+struct slant_neighbour_ctx {
+    const game_state *state;
+    int i, n, neighbours[4];
+};
+static int slant_neighbour(int vertex, void *vctx)
+{
+    struct slant_neighbour_ctx *ctx = (struct slant_neighbour_ctx *)vctx;
+
+    if (vertex >= 0) {
+        int w = ctx->state->p.w, h = ctx->state->p.h, W = w+1;
+        int x = vertex % W, y = vertex / W;
+        ctx->n = ctx->i = 0;
+        if (x < w && y < h && ctx->state->soln[y*w+x] < 0)
+            ctx->neighbours[ctx->n++] = (y+1)*W+(x+1);
+        if (x > 0 && y > 0 && ctx->state->soln[(y-1)*w+(x-1)] < 0)
+            ctx->neighbours[ctx->n++] = (y-1)*W+(x-1);
+        if (x > 0 && y < h && ctx->state->soln[y*w+(x-1)] > 0)
+            ctx->neighbours[ctx->n++] = (y+1)*W+(x-1);
+        if (x < w && y > 0 && ctx->state->soln[(y-1)*w+x] > 0)
+            ctx->neighbours[ctx->n++] = (y-1)*W+(x+1);
+    }
+
+    if (ctx->i < ctx->n)
+        return ctx->neighbours[ctx->i++];
+    else
+        return -1;
+}
+
+static int check_completion(game_state *state)
+{
+    int w = state->p.w, h = state->p.h, W = w+1, H = h+1;
+    int x, y, err = FALSE;
+
+    memset(state->errors, 0, W*H);
+
+    /*
+     * Detect and error-highlight loops in the grid.
+     */
+    {
+        struct findloopstate *fls = findloop_new_state(W*H);
+        struct slant_neighbour_ctx ctx;
+        ctx.state = state;
+
+        if (findloop_run(fls, W*H, slant_neighbour, &ctx))
+            err = TRUE;
+        for (y = 0; y < h; y++) {
+            for (x = 0; x < w; x++) {
+                int u, v;
+                if (state->soln[y*w+x] == 0) {
+                    continue;
+                } else if (state->soln[y*w+x] > 0) {
+                    u = y*W+(x+1);
+                    v = (y+1)*W+x;
+                } else {
+                    u = (y+1)*W+(x+1);
+                    v = y*W+x;
+                }
+                if (findloop_is_loop_edge(fls, u, v))
+                    state->errors[y*W+x] |= ERR_SQUARE;
+           }
+        }
+
+        findloop_free_state(fls);
+    }
+
+    /*
+     * Now go through and check the degree of each clue vertex, and
+     * mark it with ERR_VERTEX if it cannot be fulfilled.
+     */
+    for (y = 0; y < H; y++)
+        for (x = 0; x < W; x++) {
+            int c;
+
+           if ((c = state->clues->clues[y*W+x]) < 0)
+               continue;
+
+            /*
+             * Check to see if there are too many connections to
+             * this vertex _or_ too many non-connections. Either is
+             * grounds for marking the vertex as erroneous.
+             */
+            if (vertex_degree(w, h, state->soln, x, y,
+                              FALSE, NULL, NULL) > c ||
+                vertex_degree(w, h, state->soln, x, y,
+                              TRUE, NULL, NULL) > 4-c) {
+                state->errors[y*W+x] |= ERR_VERTEX;
+                err = TRUE;
+            }
+        }
+
+    /*
+     * Now our actual victory condition is that (a) none of the
+     * above code marked anything as erroneous, and (b) every
+     * square has an edge in it.
+     */
+
+    if (err)
+        return FALSE;
+
+    for (y = 0; y < h; y++)
+       for (x = 0; x < w; x++)
+           if (state->soln[y*w+x] == 0)
+               return FALSE;
+
+    return TRUE;
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    int w = state->p.w, h = state->p.h;
+    signed char *soln;
+    int bs, ret;
+    int free_soln = FALSE;
+    char *move, buf[80];
+    int movelen, movesize;
+    int x, y;
+
+    if (aux) {
+       /*
+        * If we already have the solution, save ourselves some
+        * time.
+        */
+       soln = (signed char *)aux;
+       bs = (signed char)'\\';
+       free_soln = FALSE;
+    } else {
+       struct solver_scratch *sc = new_scratch(w, h);
+       soln = snewn(w*h, signed char);
+       bs = -1;
+       ret = slant_solve(w, h, state->clues->clues, soln, sc, DIFF_HARD);
+       free_scratch(sc);
+       if (ret != 1) {
+           sfree(soln);
+           if (ret == 0)
+               *error = "This puzzle is not self-consistent";
+           else
+               *error = "Unable to find a unique solution for this puzzle";
+            return NULL;
+       }
+       free_soln = TRUE;
+    }
+
+    /*
+     * Construct a move string which turns the current state into
+     * the solved state.
+     */
+    movesize = 256;
+    move = snewn(movesize, char);
+    movelen = 0;
+    move[movelen++] = 'S';
+    move[movelen] = '\0';
+    for (y = 0; y < h; y++)
+       for (x = 0; x < w; x++) {
+           int v = (soln[y*w+x] == bs ? -1 : +1);
+           if (state->soln[y*w+x] != v) {
+               int len = sprintf(buf, ";%c%d,%d", (int)(v < 0 ? '\\' : '/'), x, y);
+               if (movelen + len >= movesize) {
+                   movesize = movelen + len + 256;
+                   move = sresize(move, movesize, char);
+               }
+               strcpy(move + movelen, buf);
+               movelen += len;
+           }
+       }
+
+    if (free_soln)
+       sfree(soln);
+
+    return move;
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    int w = state->p.w, h = state->p.h, W = w+1, H = h+1;
+    int x, y, len;
+    char *ret, *p;
+
+    /*
+     * There are h+H rows of w+W columns.
+     */
+    len = (h+H) * (w+W+1) + 1;
+    ret = snewn(len, char);
+    p = ret;
+
+    for (y = 0; y < H; y++) {
+       for (x = 0; x < W; x++) {
+           if (state->clues->clues[y*W+x] >= 0)
+               *p++ = state->clues->clues[y*W+x] + '0';
+           else
+               *p++ = '+';
+           if (x < w)
+               *p++ = '-';
+       }
+       *p++ = '\n';
+       if (y < h) {
+           for (x = 0; x < W; x++) {
+               *p++ = '|';
+               if (x < w) {
+                   if (state->soln[y*w+x] != 0)
+                       *p++ = (state->soln[y*w+x] < 0 ? '\\' : '/');
+                   else
+                       *p++ = ' ';
+               }
+           }
+           *p++ = '\n';
+       }
+    }
+    *p++ = '\0';
+
+    assert(p - ret == len);
+    return ret;
+}
+
+struct game_ui {
+    int cur_x, cur_y, cur_visible;
+};
+
+static game_ui *new_ui(const game_state *state)
+{
+    game_ui *ui = snew(game_ui);
+    ui->cur_x = ui->cur_y = ui->cur_visible = 0;
+    return ui;
+}
+
+static void free_ui(game_ui *ui)
+{
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+}
+
+#define PREFERRED_TILESIZE 32
+#define TILESIZE (ds->tilesize)
+#define BORDER TILESIZE
+#define CLUE_RADIUS (TILESIZE / 3)
+#define CLUE_TEXTSIZE (TILESIZE / 2)
+#define COORD(x)  ( (x) * TILESIZE + BORDER )
+#define FROMCOORD(x)  ( ((x) - BORDER + TILESIZE) / TILESIZE - 1 )
+
+#define FLASH_TIME 0.30F
+
+/*
+ * Bit fields in the `grid' and `todraw' elements of the drawstate.
+ */
+#define BACKSLASH 0x00000001L
+#define FORWSLASH 0x00000002L
+#define L_T       0x00000004L
+#define ERR_L_T   0x00000008L
+#define L_B       0x00000010L
+#define ERR_L_B   0x00000020L
+#define T_L       0x00000040L
+#define ERR_T_L   0x00000080L
+#define T_R       0x00000100L
+#define ERR_T_R   0x00000200L
+#define C_TL      0x00000400L
+#define ERR_C_TL  0x00000800L
+#define FLASH     0x00001000L
+#define ERRSLASH  0x00002000L
+#define ERR_TL    0x00004000L
+#define ERR_TR    0x00008000L
+#define ERR_BL    0x00010000L
+#define ERR_BR    0x00020000L
+#define CURSOR    0x00040000L
+
+struct game_drawstate {
+    int tilesize;
+    int started;
+    long *grid;
+    long *todraw;
+};
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    int w = state->p.w, h = state->p.h;
+    int v;
+    char buf[80];
+    enum { CLOCKWISE, ANTICLOCKWISE, NONE } action = NONE;
+
+    if (button == LEFT_BUTTON || button == RIGHT_BUTTON) {
+       /*
+        * This is an utterly awful hack which I should really sort out
+        * by means of a proper configuration mechanism. One Slant
+        * player has observed that they prefer the mouse buttons to
+        * function exactly the opposite way round, so here's a
+        * mechanism for environment-based configuration. I cache the
+        * result in a global variable - yuck! - to avoid repeated
+        * lookups.
+        */
+       {
+           static int swap_buttons = -1;
+           if (swap_buttons < 0) {
+               char *env = getenv("SLANT_SWAP_BUTTONS");
+               swap_buttons = (env && (env[0] == 'y' || env[0] == 'Y'));
+           }
+           if (swap_buttons) {
+               if (button == LEFT_BUTTON)
+                   button = RIGHT_BUTTON;
+               else
+                   button = LEFT_BUTTON;
+           }
+       }
+        action = (button == LEFT_BUTTON) ? CLOCKWISE : ANTICLOCKWISE;
+
+        x = FROMCOORD(x);
+        y = FROMCOORD(y);
+        if (x < 0 || y < 0 || x >= w || y >= h)
+            return NULL;
+        ui->cur_visible = 0;
+    } else if (IS_CURSOR_SELECT(button)) {
+        if (!ui->cur_visible) {
+            ui->cur_visible = 1;
+            return "";
+        }
+        x = ui->cur_x;
+        y = ui->cur_y;
+
+        action = (button == CURSOR_SELECT2) ? ANTICLOCKWISE : CLOCKWISE;
+    } else if (IS_CURSOR_MOVE(button)) {
+        move_cursor(button, &ui->cur_x, &ui->cur_y, w, h, 0);
+        ui->cur_visible = 1;
+        return "";
+    } else if (button == '\\' || button == '\b' || button == '/') {
+       int x = ui->cur_x, y = ui->cur_y;
+       if (button == ("\\" "\b" "/")[state->soln[y*w + x] + 1]) return NULL;
+       sprintf(buf, "%c%d,%d", button == '\b' ? 'C' : button, x, y);
+       return dupstr(buf);
+    }
+
+    if (action != NONE) {
+        if (action == CLOCKWISE) {
+            /*
+             * Left-clicking cycles blank -> \ -> / -> blank.
+             */
+            v = state->soln[y*w+x] - 1;
+            if (v == -2)
+                v = +1;
+        } else {
+            /*
+             * Right-clicking cycles blank -> / -> \ -> blank.
+             */
+            v = state->soln[y*w+x] + 1;
+            if (v == +2)
+                v = -1;
+        }
+
+        sprintf(buf, "%c%d,%d", (int)(v==-1 ? '\\' : v==+1 ? '/' : 'C'), x, y);
+        return dupstr(buf);
+    }
+
+    return NULL;
+}
+
+static game_state *execute_move(const game_state *state, const char *move)
+{
+    int w = state->p.w, h = state->p.h;
+    char c;
+    int x, y, n;
+    game_state *ret = dup_game(state);
+
+    while (*move) {
+        c = *move;
+       if (c == 'S') {
+           ret->used_solve = TRUE;
+           move++;
+       } else if (c == '\\' || c == '/' || c == 'C') {
+            move++;
+            if (sscanf(move, "%d,%d%n", &x, &y, &n) != 2 ||
+                x < 0 || y < 0 || x >= w || y >= h) {
+                free_game(ret);
+                return NULL;
+            }
+            ret->soln[y*w+x] = (c == '\\' ? -1 : c == '/' ? +1 : 0);
+            move += n;
+        } else {
+            free_game(ret);
+            return NULL;
+        }
+        if (*move == ';')
+            move++;
+        else if (*move) {
+            free_game(ret);
+            return NULL;
+        }
+    }
+
+    /*
+     * We never clear the `completed' flag, but we must always
+     * re-run the completion check because it also highlights
+     * errors in the grid.
+     */
+    ret->completed = check_completion(ret) || ret->completed;
+
+    return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    /* fool the macros */
+    struct dummy { int tilesize; } dummy, *ds = &dummy;
+    dummy.tilesize = tilesize;
+
+    *x = 2 * BORDER + params->w * TILESIZE + 1;
+    *y = 2 * BORDER + params->h * TILESIZE + 1;
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+
+    /* CURSOR colour is a background highlight. */
+    game_mkhighlight(fe, ret, COL_BACKGROUND, COL_CURSOR, -1);
+
+    ret[COL_FILLEDSQUARE * 3 + 0] = ret[COL_BACKGROUND * 3 + 0];
+    ret[COL_FILLEDSQUARE * 3 + 1] = ret[COL_BACKGROUND * 3 + 1];
+    ret[COL_FILLEDSQUARE * 3 + 2] = ret[COL_BACKGROUND * 3 + 2];
+
+    ret[COL_GRID * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 0.7F;
+    ret[COL_GRID * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 0.7F;
+    ret[COL_GRID * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] * 0.7F;
+
+    ret[COL_INK * 3 + 0] = 0.0F;
+    ret[COL_INK * 3 + 1] = 0.0F;
+    ret[COL_INK * 3 + 2] = 0.0F;
+
+    ret[COL_SLANT1 * 3 + 0] = 0.0F;
+    ret[COL_SLANT1 * 3 + 1] = 0.0F;
+    ret[COL_SLANT1 * 3 + 2] = 0.0F;
+
+    ret[COL_SLANT2 * 3 + 0] = 0.0F;
+    ret[COL_SLANT2 * 3 + 1] = 0.0F;
+    ret[COL_SLANT2 * 3 + 2] = 0.0F;
+
+    ret[COL_ERROR * 3 + 0] = 1.0F;
+    ret[COL_ERROR * 3 + 1] = 0.0F;
+    ret[COL_ERROR * 3 + 2] = 0.0F;
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    int w = state->p.w, h = state->p.h;
+    int i;
+    struct game_drawstate *ds = snew(struct game_drawstate);
+
+    ds->tilesize = 0;
+    ds->started = FALSE;
+    ds->grid = snewn((w+2)*(h+2), long);
+    ds->todraw = snewn((w+2)*(h+2), long);
+    for (i = 0; i < (w+2)*(h+2); i++)
+       ds->grid[i] = ds->todraw[i] = -1;
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds->todraw);
+    sfree(ds->grid);
+    sfree(ds);
+}
+
+static void draw_clue(drawing *dr, game_drawstate *ds,
+                     int x, int y, long v, long err, int bg, int colour)
+{
+    char p[2];
+    int ccol = colour >= 0 ? colour : ((x ^ y) & 1) ? COL_SLANT1 : COL_SLANT2;
+    int tcol = colour >= 0 ? colour : err ? COL_ERROR : COL_INK;
+
+    if (v < 0)
+       return;
+
+    p[0] = (char)v + '0';
+    p[1] = '\0';
+    draw_circle(dr, COORD(x), COORD(y), CLUE_RADIUS,
+               bg >= 0 ? bg : COL_BACKGROUND, ccol);
+    draw_text(dr, COORD(x), COORD(y), FONT_VARIABLE,
+             CLUE_TEXTSIZE, ALIGN_VCENTRE|ALIGN_HCENTRE, tcol, p);
+}
+
+static void draw_tile(drawing *dr, game_drawstate *ds, game_clues *clues,
+                     int x, int y, long v)
+{
+    int w = clues->w, h = clues->h, W = w+1 /*, H = h+1 */;
+    int chesscolour = (x ^ y) & 1;
+    int fscol = chesscolour ? COL_SLANT2 : COL_SLANT1;
+    int bscol = chesscolour ? COL_SLANT1 : COL_SLANT2;
+
+    clip(dr, COORD(x), COORD(y), TILESIZE, TILESIZE);
+
+    draw_rect(dr, COORD(x), COORD(y), TILESIZE, TILESIZE,
+             (v & FLASH) ? COL_GRID :
+              (v & CURSOR) ? COL_CURSOR :
+             (v & (BACKSLASH | FORWSLASH)) ? COL_FILLEDSQUARE :
+             COL_BACKGROUND);
+
+    /*
+     * Draw the grid lines.
+     */
+    if (x >= 0 && x < w && y >= 0)
+        draw_rect(dr, COORD(x), COORD(y), TILESIZE+1, 1, COL_GRID);
+    if (x >= 0 && x < w && y < h)
+        draw_rect(dr, COORD(x), COORD(y+1), TILESIZE+1, 1, COL_GRID);
+    if (y >= 0 && y < h && x >= 0)
+        draw_rect(dr, COORD(x), COORD(y), 1, TILESIZE+1, COL_GRID);
+    if (y >= 0 && y < h && x < w)
+        draw_rect(dr, COORD(x+1), COORD(y), 1, TILESIZE+1, COL_GRID);
+    if (x == -1 && y == -1)
+        draw_rect(dr, COORD(x+1), COORD(y+1), 1, 1, COL_GRID);
+    if (x == -1 && y == h)
+        draw_rect(dr, COORD(x+1), COORD(y), 1, 1, COL_GRID);
+    if (x == w && y == -1)
+        draw_rect(dr, COORD(x), COORD(y+1), 1, 1, COL_GRID);
+    if (x == w && y == h)
+        draw_rect(dr, COORD(x), COORD(y), 1, 1, COL_GRID);
+
+    /*
+     * Draw the slash.
+     */
+    if (v & BACKSLASH) {
+        int scol = (v & ERRSLASH) ? COL_ERROR : bscol;
+       draw_line(dr, COORD(x), COORD(y), COORD(x+1), COORD(y+1), scol);
+       draw_line(dr, COORD(x)+1, COORD(y), COORD(x+1), COORD(y+1)-1,
+                 scol);
+       draw_line(dr, COORD(x), COORD(y)+1, COORD(x+1)-1, COORD(y+1),
+                 scol);
+    } else if (v & FORWSLASH) {
+        int scol = (v & ERRSLASH) ? COL_ERROR : fscol;
+       draw_line(dr, COORD(x+1), COORD(y), COORD(x), COORD(y+1), scol);
+       draw_line(dr, COORD(x+1)-1, COORD(y), COORD(x), COORD(y+1)-1,
+                 scol);
+       draw_line(dr, COORD(x+1), COORD(y)+1, COORD(x)+1, COORD(y+1),
+                 scol);
+    }
+
+    /*
+     * Draw dots on the grid corners that appear if a slash is in a
+     * neighbouring cell.
+     */
+    if (v & (L_T | BACKSLASH))
+       draw_rect(dr, COORD(x), COORD(y)+1, 1, 1,
+                  (v & ERR_L_T ? COL_ERROR : bscol));
+    if (v & (L_B | FORWSLASH))
+       draw_rect(dr, COORD(x), COORD(y+1)-1, 1, 1,
+                  (v & ERR_L_B ? COL_ERROR : fscol));
+    if (v & (T_L | BACKSLASH))
+       draw_rect(dr, COORD(x)+1, COORD(y), 1, 1,
+                  (v & ERR_T_L ? COL_ERROR : bscol));
+    if (v & (T_R | FORWSLASH))
+       draw_rect(dr, COORD(x+1)-1, COORD(y), 1, 1,
+                  (v & ERR_T_R ? COL_ERROR : fscol));
+    if (v & (C_TL | BACKSLASH))
+       draw_rect(dr, COORD(x), COORD(y), 1, 1,
+                  (v & ERR_C_TL ? COL_ERROR : bscol));
+
+    /*
+     * And finally the clues at the corners.
+     */
+    if (x >= 0 && y >= 0)
+        draw_clue(dr, ds, x, y, clues->clues[y*W+x], v & ERR_TL, -1, -1);
+    if (x < w && y >= 0)
+        draw_clue(dr, ds, x+1, y, clues->clues[y*W+(x+1)], v & ERR_TR, -1, -1);
+    if (x >= 0 && y < h)
+        draw_clue(dr, ds, x, y+1, clues->clues[(y+1)*W+x], v & ERR_BL, -1, -1);
+    if (x < w && y < h)
+        draw_clue(dr, ds, x+1, y+1, clues->clues[(y+1)*W+(x+1)], v & ERR_BR,
+                 -1, -1);
+
+    unclip(dr);
+    draw_update(dr, COORD(x), COORD(y), TILESIZE, TILESIZE);
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int w = state->p.w, h = state->p.h, W = w+1, H = h+1;
+    int x, y;
+    int flashing;
+
+    if (flashtime > 0)
+       flashing = (int)(flashtime * 3 / FLASH_TIME) != 1;
+    else
+       flashing = FALSE;
+
+    if (!ds->started) {
+       int ww, wh;
+       game_compute_size(&state->p, TILESIZE, &ww, &wh);
+       draw_rect(dr, 0, 0, ww, wh, COL_BACKGROUND);
+       draw_update(dr, 0, 0, ww, wh);
+       ds->started = TRUE;
+    }
+
+    /*
+     * Loop over the grid and work out where all the slashes are.
+     * We need to do this because a slash in one square affects the
+     * drawing of the next one along.
+     */
+    for (y = -1; y <= h; y++)
+       for (x = -1; x <= w; x++) {
+            if (x >= 0 && x < w && y >= 0 && y < h)
+                ds->todraw[(y+1)*(w+2)+(x+1)] = flashing ? FLASH : 0;
+            else
+                ds->todraw[(y+1)*(w+2)+(x+1)] = 0;
+        }
+
+    for (y = 0; y < h; y++) {
+       for (x = 0; x < w; x++) {
+            int err = state->errors[y*W+x] & ERR_SQUARE;
+
+           if (state->soln[y*w+x] < 0) {
+               ds->todraw[(y+1)*(w+2)+(x+1)] |= BACKSLASH;
+                ds->todraw[(y+2)*(w+2)+(x+1)] |= T_R;
+                ds->todraw[(y+1)*(w+2)+(x+2)] |= L_B;
+                ds->todraw[(y+2)*(w+2)+(x+2)] |= C_TL;
+                if (err) {
+                    ds->todraw[(y+1)*(w+2)+(x+1)] |= ERRSLASH | 
+                       ERR_T_L | ERR_L_T | ERR_C_TL;
+                    ds->todraw[(y+2)*(w+2)+(x+1)] |= ERR_T_R;
+                    ds->todraw[(y+1)*(w+2)+(x+2)] |= ERR_L_B;
+                    ds->todraw[(y+2)*(w+2)+(x+2)] |= ERR_C_TL;
+                }
+           } else if (state->soln[y*w+x] > 0) {
+               ds->todraw[(y+1)*(w+2)+(x+1)] |= FORWSLASH;
+                ds->todraw[(y+1)*(w+2)+(x+2)] |= L_T | C_TL;
+                ds->todraw[(y+2)*(w+2)+(x+1)] |= T_L | C_TL;
+                if (err) {
+                    ds->todraw[(y+1)*(w+2)+(x+1)] |= ERRSLASH |
+                       ERR_L_B | ERR_T_R;
+                    ds->todraw[(y+1)*(w+2)+(x+2)] |= ERR_L_T | ERR_C_TL;
+                    ds->todraw[(y+2)*(w+2)+(x+1)] |= ERR_T_L | ERR_C_TL;
+                }
+           }
+            if (ui->cur_visible && ui->cur_x == x && ui->cur_y == y)
+                ds->todraw[(y+1)*(w+2)+(x+1)] |= CURSOR;
+       }
+    }
+
+    for (y = 0; y < H; y++)
+        for (x = 0; x < W; x++)
+            if (state->errors[y*W+x] & ERR_VERTEX) {
+                ds->todraw[y*(w+2)+x] |= ERR_BR;
+                ds->todraw[y*(w+2)+(x+1)] |= ERR_BL;
+                ds->todraw[(y+1)*(w+2)+x] |= ERR_TR;
+                ds->todraw[(y+1)*(w+2)+(x+1)] |= ERR_TL;
+            }
+
+    /*
+     * Now go through and draw the grid squares.
+     */
+    for (y = -1; y <= h; y++) {
+       for (x = -1; x <= w; x++) {
+           if (ds->todraw[(y+1)*(w+2)+(x+1)] != ds->grid[(y+1)*(w+2)+(x+1)]) {
+               draw_tile(dr, ds, state->clues, x, y,
+                          ds->todraw[(y+1)*(w+2)+(x+1)]);
+               ds->grid[(y+1)*(w+2)+(x+1)] = ds->todraw[(y+1)*(w+2)+(x+1)];
+           }
+       }
+    }
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return 0.0F;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    if (!oldstate->completed && newstate->completed &&
+       !oldstate->used_solve && !newstate->used_solve)
+        return FLASH_TIME;
+
+    return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    return state->completed ? +1 : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+    int pw, ph;
+
+    /*
+     * I'll use 6mm squares by default.
+     */
+    game_compute_size(params, 600, &pw, &ph);
+    *x = pw / 100.0F;
+    *y = ph / 100.0F;
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+    int w = state->p.w, h = state->p.h, W = w+1;
+    int ink = print_mono_colour(dr, 0);
+    int paper = print_mono_colour(dr, 1);
+    int x, y;
+
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    game_drawstate ads, *ds = &ads;
+    game_set_size(dr, ds, NULL, tilesize);
+
+    /*
+     * Border.
+     */
+    print_line_width(dr, TILESIZE / 16);
+    draw_rect_outline(dr, COORD(0), COORD(0), w*TILESIZE, h*TILESIZE, ink);
+
+    /*
+     * Grid.
+     */
+    print_line_width(dr, TILESIZE / 24);
+    for (x = 1; x < w; x++)
+       draw_line(dr, COORD(x), COORD(0), COORD(x), COORD(h), ink);
+    for (y = 1; y < h; y++)
+       draw_line(dr, COORD(0), COORD(y), COORD(w), COORD(y), ink);
+
+    /*
+     * Solution.
+     */
+    print_line_width(dr, TILESIZE / 12);
+    for (y = 0; y < h; y++)
+       for (x = 0; x < w; x++)
+           if (state->soln[y*w+x]) {
+               int ly, ry;
+               /*
+                * To prevent nasty line-ending artefacts at
+                * corners, I'll do something slightly cunning
+                * here.
+                */
+               clip(dr, COORD(x), COORD(y), TILESIZE, TILESIZE);
+               if (state->soln[y*w+x] < 0)
+                   ly = y-1, ry = y+2;
+               else
+                   ry = y-1, ly = y+2;
+               draw_line(dr, COORD(x-1), COORD(ly), COORD(x+2), COORD(ry),
+                         ink);
+               unclip(dr);
+           }
+
+    /*
+     * Clues.
+     */
+    print_line_width(dr, TILESIZE / 24);
+    for (y = 0; y <= h; y++)
+       for (x = 0; x <= w; x++)
+           draw_clue(dr, ds, x, y, state->clues->clues[y*W+x],
+                     FALSE, paper, ink);
+}
+
+#ifdef COMBINED
+#define thegame slant
+#endif
+
+const struct game thegame = {
+    "Slant", "games.slant", "slant",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    TRUE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILESIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    TRUE, FALSE, game_print_size, game_print,
+    FALSE,                            /* wants_statusbar */
+    FALSE, game_timing_state,
+    0,                                /* flags */
+};
+
+#ifdef STANDALONE_SOLVER
+
+#include <stdarg.h>
+
+int main(int argc, char **argv)
+{
+    game_params *p;
+    game_state *s;
+    char *id = NULL, *desc, *err;
+    int grade = FALSE;
+    int ret, diff, really_verbose = FALSE;
+    struct solver_scratch *sc;
+
+    while (--argc > 0) {
+        char *p = *++argv;
+        if (!strcmp(p, "-v")) {
+            really_verbose = TRUE;
+        } else if (!strcmp(p, "-g")) {
+            grade = TRUE;
+        } else if (*p == '-') {
+            fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p);
+            return 1;
+        } else {
+            id = p;
+        }
+    }
+
+    if (!id) {
+        fprintf(stderr, "usage: %s [-g | -v] <game_id>\n", argv[0]);
+        return 1;
+    }
+
+    desc = strchr(id, ':');
+    if (!desc) {
+        fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]);
+        return 1;
+    }
+    *desc++ = '\0';
+
+    p = default_params();
+    decode_params(p, id);
+    err = validate_desc(p, desc);
+    if (err) {
+        fprintf(stderr, "%s: %s\n", argv[0], err);
+        return 1;
+    }
+    s = new_game(NULL, p, desc);
+
+    sc = new_scratch(p->w, p->h);
+
+    /*
+     * When solving an Easy puzzle, we don't want to bother the
+     * user with Hard-level deductions. For this reason, we grade
+     * the puzzle internally before doing anything else.
+     */
+    ret = -1;                         /* placate optimiser */
+    for (diff = 0; diff < DIFFCOUNT; diff++) {
+       ret = slant_solve(p->w, p->h, s->clues->clues,
+                         s->soln, sc, diff);
+       if (ret < 2)
+           break;
+    }
+
+    if (diff == DIFFCOUNT) {
+       if (grade)
+           printf("Difficulty rating: harder than Hard, or ambiguous\n");
+       else
+           printf("Unable to find a unique solution\n");
+    } else {
+       if (grade) {
+           if (ret == 0)
+               printf("Difficulty rating: impossible (no solution exists)\n");
+           else if (ret == 1)
+               printf("Difficulty rating: %s\n", slant_diffnames[diff]);
+       } else {
+           verbose = really_verbose;
+           ret = slant_solve(p->w, p->h, s->clues->clues,
+                             s->soln, sc, diff);
+           if (ret == 0)
+               printf("Puzzle is inconsistent\n");
+           else
+               fputs(game_text_format(s), stdout);
+       }
+    }
+
+    return 0;
+}
+
+#endif
+
+/* vim: set shiftwidth=4 tabstop=8: */
diff --git a/solo.R b/solo.R
new file mode 100644 (file)
index 0000000..081a761
--- /dev/null
+++ b/solo.R
@@ -0,0 +1,24 @@
+# -*- makefile -*-
+
+SOLO_EXTRA = divvy dsf
+
+solo     : [X] GTK COMMON solo SOLO_EXTRA solo-icon|no-icon
+
+solo     : [G] WINDOWS COMMON solo SOLO_EXTRA solo.res|noicon.res
+
+solosolver :    [U] solo[STANDALONE_SOLVER] SOLO_EXTRA STANDALONE
+solosolver :    [C] solo[STANDALONE_SOLVER] SOLO_EXTRA STANDALONE
+
+ALL += solo[COMBINED] SOLO_EXTRA
+
+!begin am gtk
+GAMES += solo
+!end
+
+!begin >list.c
+    A(solo) \
+!end
+
+!begin >gamedesc.txt
+solo:solo.exe:Solo:Number placement puzzle:Fill in the grid so that each row, column and square block contains one of every digit.
+!end
diff --git a/solo.c b/solo.c
new file mode 100644 (file)
index 0000000..a8a67e8
--- /dev/null
+++ b/solo.c
@@ -0,0 +1,5656 @@
+/*
+ * solo.c: the number-placing puzzle most popularly known as `Sudoku'.
+ *
+ * TODO:
+ *
+ *  - reports from users are that `Trivial'-mode puzzles are still
+ *    rather hard compared to newspapers' easy ones, so some better
+ *    low-end difficulty grading would be nice
+ *     + it's possible that really easy puzzles always have
+ *       _several_ things you can do, so don't make you hunt too
+ *       hard for the one deduction you can currently make
+ *     + it's also possible that easy puzzles require fewer
+ *       cross-eliminations: perhaps there's a higher incidence of
+ *       things you can deduce by looking only at (say) rows,
+ *       rather than things you have to check both rows and columns
+ *       for
+ *     + but really, what I need to do is find some really easy
+ *       puzzles and _play_ them, to see what's actually easy about
+ *       them
+ *     + while I'm revamping this area, filling in the _last_
+ *       number in a nearly-full row or column should certainly be
+ *       permitted even at the lowest difficulty level.
+ *     + also Owen noticed that `Basic' grids requiring numeric
+ *       elimination are actually very hard, so I wonder if a
+ *       difficulty gradation between that and positional-
+ *       elimination-only might be in order
+ *     + but it's not good to have _too_ many difficulty levels, or
+ *       it'll take too long to randomly generate a given level.
+ * 
+ *  - it might still be nice to do some prioritisation on the
+ *    removal of numbers from the grid
+ *     + one possibility is to try to minimise the maximum number
+ *      of filled squares in any block, which in particular ought
+ *      to enforce never leaving a completely filled block in the
+ *      puzzle as presented.
+ *
+ *  - alternative interface modes
+ *     + sudoku.com's Windows program has a palette of possible
+ *      entries; you select a palette entry first and then click
+ *      on the square you want it to go in, thus enabling
+ *      mouse-only play. Useful for PDAs! I don't think it's
+ *      actually incompatible with the current highlight-then-type
+ *      approach: you _either_ highlight a palette entry and then
+ *      click, _or_ you highlight a square and then type. At most
+ *      one thing is ever highlighted at a time, so there's no way
+ *      to confuse the two.
+ *     + then again, I don't actually like sudoku.com's interface;
+ *       it's too much like a paint package whereas I prefer to
+ *       think of Solo as a text editor.
+ *     + another PDA-friendly possibility is a drag interface:
+ *       _drag_ numbers from the palette into the grid squares.
+ *       Thought experiments suggest I'd prefer that to the
+ *       sudoku.com approach, but I haven't actually tried it.
+ */
+
+/*
+ * Solo puzzles need to be square overall (since each row and each
+ * column must contain one of every digit), but they need not be
+ * subdivided the same way internally. I am going to adopt a
+ * convention whereby I _always_ refer to `r' as the number of rows
+ * of _big_ divisions, and `c' as the number of columns of _big_
+ * divisions. Thus, a 2c by 3r puzzle looks something like this:
+ *
+ *   4 5 1 | 2 6 3
+ *   6 3 2 | 5 4 1
+ *   ------+------     (Of course, you can't subdivide it the other way
+ *   1 4 5 | 6 3 2     or you'll get clashes; observe that the 4 in the
+ *   3 2 6 | 4 1 5     top left would conflict with the 4 in the second
+ *   ------+------     box down on the left-hand side.)
+ *   5 1 4 | 3 2 6
+ *   2 6 3 | 1 5 4
+ *
+ * The need for a strong naming convention should now be clear:
+ * each small box is two rows of digits by three columns, while the
+ * overall puzzle has three rows of small boxes by two columns. So
+ * I will (hopefully) consistently use `r' to denote the number of
+ * rows _of small boxes_ (here 3), which is also the number of
+ * columns of digits in each small box; and `c' vice versa (here
+ * 2).
+ *
+ * I'm also going to choose arbitrarily to list c first wherever
+ * possible: the above is a 2x3 puzzle, not a 3x2 one.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#ifdef STANDALONE_SOLVER
+#include <stdarg.h>
+int solver_show_working, solver_recurse_depth;
+#endif
+
+#include "puzzles.h"
+
+/*
+ * To save space, I store digits internally as unsigned char. This
+ * imposes a hard limit of 255 on the order of the puzzle. Since
+ * even a 5x5 takes unacceptably long to generate, I don't see this
+ * as a serious limitation unless something _really_ impressive
+ * happens in computing technology; but here's a typedef anyway for
+ * general good practice.
+ */
+typedef unsigned char digit;
+#define ORDER_MAX 255
+
+#define PREFERRED_TILE_SIZE 48
+#define TILE_SIZE (ds->tilesize)
+#define BORDER (TILE_SIZE / 2)
+#define GRIDEXTRA max((TILE_SIZE / 32),1)
+
+#define FLASH_TIME 0.4F
+
+enum { SYMM_NONE, SYMM_ROT2, SYMM_ROT4, SYMM_REF2, SYMM_REF2D, SYMM_REF4,
+       SYMM_REF4D, SYMM_REF8 };
+
+enum { DIFF_BLOCK,
+       DIFF_SIMPLE, DIFF_INTERSECT, DIFF_SET, DIFF_EXTREME, DIFF_RECURSIVE,
+       DIFF_AMBIGUOUS, DIFF_IMPOSSIBLE };
+
+enum { DIFF_KSINGLE, DIFF_KMINMAX, DIFF_KSUMS, DIFF_KINTERSECT };
+
+enum {
+    COL_BACKGROUND,
+    COL_XDIAGONALS,
+    COL_GRID,
+    COL_CLUE,
+    COL_USER,
+    COL_HIGHLIGHT,
+    COL_ERROR,
+    COL_PENCIL,
+    COL_KILLER,
+    NCOLOURS
+};
+
+/*
+ * To determine all possible ways to reach a given sum by adding two or
+ * three numbers from 1..9, each of which occurs exactly once in the sum,
+ * these arrays contain a list of bitmasks for each sum value, where if
+ * bit N is set, it means that N occurs in the sum.  Each list is
+ * terminated by a zero if it is shorter than the size of the array.
+ */
+#define MAX_2SUMS 5
+#define MAX_3SUMS 8
+#define MAX_4SUMS 12
+unsigned long sum_bits2[18][MAX_2SUMS];
+unsigned long sum_bits3[25][MAX_3SUMS];
+unsigned long sum_bits4[31][MAX_4SUMS];
+
+static int find_sum_bits(unsigned long *array, int idx, int value_left,
+                        int addends_left, int min_addend,
+                        unsigned long bitmask_so_far)
+{
+    int i;
+    assert(addends_left >= 2);
+
+    for (i = min_addend; i < value_left; i++) {
+       unsigned long new_bitmask = bitmask_so_far | (1L << i);
+       assert(bitmask_so_far != new_bitmask);
+
+       if (addends_left == 2) {
+           int j = value_left - i;
+           if (j <= i)
+               break;
+           if (j > 9)
+               continue;
+           array[idx++] = new_bitmask | (1L << j);
+       } else
+           idx = find_sum_bits(array, idx, value_left - i,
+                               addends_left - 1, i + 1,
+                               new_bitmask);
+    }
+    return idx;
+}
+
+static void precompute_sum_bits(void)
+{
+    int i;
+    for (i = 3; i < 31; i++) {
+       int j;
+       if (i < 18) {
+           j = find_sum_bits(sum_bits2[i], 0, i, 2, 1, 0);
+           assert (j <= MAX_2SUMS);
+           if (j < MAX_2SUMS)
+               sum_bits2[i][j] = 0;
+       }
+       if (i < 25) {
+           j = find_sum_bits(sum_bits3[i], 0, i, 3, 1, 0);
+           assert (j <= MAX_3SUMS);
+           if (j < MAX_3SUMS)
+               sum_bits3[i][j] = 0;
+       }
+       j = find_sum_bits(sum_bits4[i], 0, i, 4, 1, 0);
+       assert (j <= MAX_4SUMS);
+       if (j < MAX_4SUMS)
+           sum_bits4[i][j] = 0;
+    }
+}
+
+struct game_params {
+    /*
+     * For a square puzzle, `c' and `r' indicate the puzzle
+     * parameters as described above.
+     * 
+     * A jigsaw-style puzzle is indicated by r==1, in which case c
+     * can be whatever it likes (there is no constraint on
+     * compositeness - a 7x7 jigsaw sudoku makes perfect sense).
+     */
+    int c, r, symm, diff, kdiff;
+    int xtype;                        /* require all digits in X-diagonals */
+    int killer;
+};
+
+struct block_structure {
+    int refcount;
+
+    /*
+     * For text formatting, we do need c and r here.
+     */
+    int c, r, area;
+
+    /*
+     * For any square index, whichblock[i] gives its block index.
+     *
+     * For 0 <= b,i < cr, blocks[b][i] gives the index of the ith
+     * square in block b.  nr_squares[b] gives the number of squares
+     * in block b (also the number of valid elements in blocks[b]).
+     *
+     * blocks_data holds the data pointed to by blocks.
+     *
+     * nr_squares may be NULL for block structures where all blocks are
+     * the same size.
+     */
+    int *whichblock, **blocks, *nr_squares, *blocks_data;
+    int nr_blocks, max_nr_squares;
+
+#ifdef STANDALONE_SOLVER
+    /*
+     * Textual descriptions of each block. For normal Sudoku these
+     * are of the form "(1,3)"; for jigsaw they are "starting at
+     * (5,7)". So the sensible usage in both cases is to say
+     * "elimination within block %s" with one of these strings.
+     * 
+     * Only blocknames itself needs individually freeing; it's all
+     * one block.
+     */
+    char **blocknames;
+#endif
+};
+
+struct game_state {
+    /*
+     * For historical reasons, I use `cr' to denote the overall
+     * width/height of the puzzle. It was a natural notation when
+     * all puzzles were divided into blocks in a grid, but doesn't
+     * really make much sense given jigsaw puzzles. However, the
+     * obvious `n' is heavily used in the solver to describe the
+     * index of a number being placed, so `cr' will have to stay.
+     */
+    int cr;
+    struct block_structure *blocks;
+    struct block_structure *kblocks;   /* Blocks for killer puzzles.  */
+    int xtype, killer;
+    digit *grid, *kgrid;
+    unsigned char *pencil;             /* c*r*c*r elements */
+    unsigned char *immutable;         /* marks which digits are clues */
+    int completed, cheated;
+};
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+
+    ret->c = ret->r = 3;
+    ret->xtype = FALSE;
+    ret->killer = FALSE;
+    ret->symm = SYMM_ROT2;            /* a plausible default */
+    ret->diff = DIFF_BLOCK;           /* so is this */
+    ret->kdiff = DIFF_KINTERSECT;      /* so is this */
+
+    return ret;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    static struct {
+        char *title;
+        game_params params;
+    } presets[] = {
+        { "2x2 Trivial", { 2, 2, SYMM_ROT2, DIFF_BLOCK, DIFF_KMINMAX, FALSE, FALSE } },
+        { "2x3 Basic", { 2, 3, SYMM_ROT2, DIFF_SIMPLE, DIFF_KMINMAX, FALSE, FALSE } },
+        { "3x3 Trivial", { 3, 3, SYMM_ROT2, DIFF_BLOCK, DIFF_KMINMAX, FALSE, FALSE } },
+        { "3x3 Basic", { 3, 3, SYMM_ROT2, DIFF_SIMPLE, DIFF_KMINMAX, FALSE, FALSE } },
+        { "3x3 Basic X", { 3, 3, SYMM_ROT2, DIFF_SIMPLE, DIFF_KMINMAX, TRUE } },
+        { "3x3 Intermediate", { 3, 3, SYMM_ROT2, DIFF_INTERSECT, DIFF_KMINMAX, FALSE, FALSE } },
+        { "3x3 Advanced", { 3, 3, SYMM_ROT2, DIFF_SET, DIFF_KMINMAX, FALSE, FALSE } },
+        { "3x3 Advanced X", { 3, 3, SYMM_ROT2, DIFF_SET, DIFF_KMINMAX, TRUE } },
+        { "3x3 Extreme", { 3, 3, SYMM_ROT2, DIFF_EXTREME, DIFF_KMINMAX, FALSE, FALSE } },
+        { "3x3 Unreasonable", { 3, 3, SYMM_ROT2, DIFF_RECURSIVE, DIFF_KMINMAX, FALSE, FALSE } },
+        { "3x3 Killer", { 3, 3, SYMM_NONE, DIFF_BLOCK, DIFF_KINTERSECT, FALSE, TRUE } },
+        { "9 Jigsaw Basic", { 9, 1, SYMM_ROT2, DIFF_SIMPLE, DIFF_KMINMAX, FALSE, FALSE } },
+        { "9 Jigsaw Basic X", { 9, 1, SYMM_ROT2, DIFF_SIMPLE, DIFF_KMINMAX, TRUE } },
+        { "9 Jigsaw Advanced", { 9, 1, SYMM_ROT2, DIFF_SET, DIFF_KMINMAX, FALSE, FALSE } },
+#ifndef SLOW_SYSTEM
+        { "3x4 Basic", { 3, 4, SYMM_ROT2, DIFF_SIMPLE, DIFF_KMINMAX, FALSE, FALSE } },
+        { "4x4 Basic", { 4, 4, SYMM_ROT2, DIFF_SIMPLE, DIFF_KMINMAX, FALSE, FALSE } },
+#endif
+    };
+
+    if (i < 0 || i >= lenof(presets))
+        return FALSE;
+
+    *name = dupstr(presets[i].title);
+    *params = dup_params(&presets[i].params);
+
+    return TRUE;
+}
+
+static void decode_params(game_params *ret, char const *string)
+{
+    int seen_r = FALSE;
+
+    ret->c = ret->r = atoi(string);
+    ret->xtype = FALSE;
+    ret->killer = FALSE;
+    while (*string && isdigit((unsigned char)*string)) string++;
+    if (*string == 'x') {
+        string++;
+        ret->r = atoi(string);
+       seen_r = TRUE;
+       while (*string && isdigit((unsigned char)*string)) string++;
+    }
+    while (*string) {
+        if (*string == 'j') {
+           string++;
+           if (seen_r)
+               ret->c *= ret->r;
+           ret->r = 1;
+       } else if (*string == 'x') {
+           string++;
+           ret->xtype = TRUE;
+       } else if (*string == 'k') {
+           string++;
+           ret->killer = TRUE;
+       } else if (*string == 'r' || *string == 'm' || *string == 'a') {
+            int sn, sc, sd;
+            sc = *string++;
+            if (sc == 'm' && *string == 'd') {
+                sd = TRUE;
+                string++;
+            } else {
+                sd = FALSE;
+            }
+            sn = atoi(string);
+            while (*string && isdigit((unsigned char)*string)) string++;
+            if (sc == 'm' && sn == 8)
+                ret->symm = SYMM_REF8;
+            if (sc == 'm' && sn == 4)
+                ret->symm = sd ? SYMM_REF4D : SYMM_REF4;
+            if (sc == 'm' && sn == 2)
+                ret->symm = sd ? SYMM_REF2D : SYMM_REF2;
+            if (sc == 'r' && sn == 4)
+                ret->symm = SYMM_ROT4;
+            if (sc == 'r' && sn == 2)
+                ret->symm = SYMM_ROT2;
+            if (sc == 'a')
+                ret->symm = SYMM_NONE;
+        } else if (*string == 'd') {
+            string++;
+            if (*string == 't')        /* trivial */
+                string++, ret->diff = DIFF_BLOCK;
+            else if (*string == 'b')   /* basic */
+                string++, ret->diff = DIFF_SIMPLE;
+            else if (*string == 'i')   /* intermediate */
+                string++, ret->diff = DIFF_INTERSECT;
+            else if (*string == 'a')   /* advanced */
+                string++, ret->diff = DIFF_SET;
+            else if (*string == 'e')   /* extreme */
+                string++, ret->diff = DIFF_EXTREME;
+            else if (*string == 'u')   /* unreasonable */
+                string++, ret->diff = DIFF_RECURSIVE;
+        } else
+            string++;                  /* eat unknown character */
+    }
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char str[80];
+
+    if (params->r > 1)
+       sprintf(str, "%dx%d", params->c, params->r);
+    else
+       sprintf(str, "%dj", params->c);
+    if (params->xtype)
+       strcat(str, "x");
+    if (params->killer)
+       strcat(str, "k");
+
+    if (full) {
+        switch (params->symm) {
+          case SYMM_REF8: strcat(str, "m8"); break;
+          case SYMM_REF4: strcat(str, "m4"); break;
+          case SYMM_REF4D: strcat(str, "md4"); break;
+          case SYMM_REF2: strcat(str, "m2"); break;
+          case SYMM_REF2D: strcat(str, "md2"); break;
+          case SYMM_ROT4: strcat(str, "r4"); break;
+          /* case SYMM_ROT2: strcat(str, "r2"); break; [default] */
+          case SYMM_NONE: strcat(str, "a"); break;
+        }
+        switch (params->diff) {
+          /* case DIFF_BLOCK: strcat(str, "dt"); break; [default] */
+          case DIFF_SIMPLE: strcat(str, "db"); break;
+          case DIFF_INTERSECT: strcat(str, "di"); break;
+          case DIFF_SET: strcat(str, "da"); break;
+          case DIFF_EXTREME: strcat(str, "de"); break;
+          case DIFF_RECURSIVE: strcat(str, "du"); break;
+        }
+    }
+    return dupstr(str);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+    char buf[80];
+
+    ret = snewn(8, config_item);
+
+    ret[0].name = "Columns of sub-blocks";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->c);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Rows of sub-blocks";
+    ret[1].type = C_STRING;
+    sprintf(buf, "%d", params->r);
+    ret[1].sval = dupstr(buf);
+    ret[1].ival = 0;
+
+    ret[2].name = "\"X\" (require every number in each main diagonal)";
+    ret[2].type = C_BOOLEAN;
+    ret[2].sval = NULL;
+    ret[2].ival = params->xtype;
+
+    ret[3].name = "Jigsaw (irregularly shaped sub-blocks)";
+    ret[3].type = C_BOOLEAN;
+    ret[3].sval = NULL;
+    ret[3].ival = (params->r == 1);
+
+    ret[4].name = "Killer (digit sums)";
+    ret[4].type = C_BOOLEAN;
+    ret[4].sval = NULL;
+    ret[4].ival = params->killer;
+
+    ret[5].name = "Symmetry";
+    ret[5].type = C_CHOICES;
+    ret[5].sval = ":None:2-way rotation:4-way rotation:2-way mirror:"
+        "2-way diagonal mirror:4-way mirror:4-way diagonal mirror:"
+        "8-way mirror";
+    ret[5].ival = params->symm;
+
+    ret[6].name = "Difficulty";
+    ret[6].type = C_CHOICES;
+    ret[6].sval = ":Trivial:Basic:Intermediate:Advanced:Extreme:Unreasonable";
+    ret[6].ival = params->diff;
+
+    ret[7].name = NULL;
+    ret[7].type = C_END;
+    ret[7].sval = NULL;
+    ret[7].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->c = atoi(cfg[0].sval);
+    ret->r = atoi(cfg[1].sval);
+    ret->xtype = cfg[2].ival;
+    if (cfg[3].ival) {
+       ret->c *= ret->r;
+       ret->r = 1;
+    }
+    ret->killer = cfg[4].ival;
+    ret->symm = cfg[5].ival;
+    ret->diff = cfg[6].ival;
+    ret->kdiff = DIFF_KINTERSECT;
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    if (params->c < 2)
+       return "Both dimensions must be at least 2";
+    if (params->c > ORDER_MAX || params->r > ORDER_MAX)
+       return "Dimensions greater than "STR(ORDER_MAX)" are not supported";
+    if ((params->c * params->r) > 31)
+        return "Unable to support more than 31 distinct symbols in a puzzle";
+    if (params->killer && params->c * params->r > 9)
+       return "Killer puzzle dimensions must be smaller than 10.";
+    return NULL;
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * Block structure functions.
+ */
+
+static struct block_structure *alloc_block_structure(int c, int r, int area,
+                                                    int max_nr_squares,
+                                                    int nr_blocks)
+{
+    int i;
+    struct block_structure *b = snew(struct block_structure);
+
+    b->refcount = 1;
+    b->nr_blocks = nr_blocks;
+    b->max_nr_squares = max_nr_squares;
+    b->c = c; b->r = r; b->area = area;
+    b->whichblock = snewn(area, int);
+    b->blocks_data = snewn(nr_blocks * max_nr_squares, int);
+    b->blocks = snewn(nr_blocks, int *);
+    b->nr_squares = snewn(nr_blocks, int);
+
+    for (i = 0; i < nr_blocks; i++)
+       b->blocks[i] = b->blocks_data + i*max_nr_squares;
+
+#ifdef STANDALONE_SOLVER
+    b->blocknames = (char **)smalloc(c*r*(sizeof(char *)+80));
+    for (i = 0; i < c * r; i++)
+       b->blocknames[i] = NULL;
+#endif
+    return b;
+}
+
+static void free_block_structure(struct block_structure *b)
+{
+    if (--b->refcount == 0) {
+       sfree(b->whichblock);
+       sfree(b->blocks);
+       sfree(b->blocks_data);
+#ifdef STANDALONE_SOLVER
+       sfree(b->blocknames);
+#endif
+       sfree(b->nr_squares);
+       sfree(b);
+    }
+}
+
+static struct block_structure *dup_block_structure(struct block_structure *b)
+{
+    struct block_structure *nb;
+    int i;
+
+    nb = alloc_block_structure(b->c, b->r, b->area, b->max_nr_squares,
+                              b->nr_blocks);
+    memcpy(nb->nr_squares, b->nr_squares, b->nr_blocks * sizeof *b->nr_squares);
+    memcpy(nb->whichblock, b->whichblock, b->area * sizeof *b->whichblock);
+    memcpy(nb->blocks_data, b->blocks_data,
+          b->nr_blocks * b->max_nr_squares * sizeof *b->blocks_data);
+    for (i = 0; i < b->nr_blocks; i++)
+       nb->blocks[i] = nb->blocks_data + i*nb->max_nr_squares;
+
+#ifdef STANDALONE_SOLVER
+    memcpy(nb->blocknames, b->blocknames, b->c * b->r *(sizeof(char *)+80));
+    {
+       int i;
+       for (i = 0; i < b->c * b->r; i++)
+           if (b->blocknames[i] == NULL)
+               nb->blocknames[i] = NULL;
+           else
+               nb->blocknames[i] = ((char *)nb->blocknames) + (b->blocknames[i] - (char *)b->blocknames);
+    }
+#endif
+    return nb;
+}
+
+static void split_block(struct block_structure *b, int *squares, int nr_squares)
+{
+    int i, j;
+    int previous_block = b->whichblock[squares[0]];
+    int newblock = b->nr_blocks;
+
+    assert(b->max_nr_squares >= nr_squares);
+    assert(b->nr_squares[previous_block] > nr_squares);
+
+    b->nr_blocks++;
+    b->blocks_data = sresize(b->blocks_data,
+                            b->nr_blocks * b->max_nr_squares, int);
+    b->nr_squares = sresize(b->nr_squares, b->nr_blocks, int);
+    sfree(b->blocks);
+    b->blocks = snewn(b->nr_blocks, int *);
+    for (i = 0; i < b->nr_blocks; i++)
+       b->blocks[i] = b->blocks_data + i*b->max_nr_squares;
+    for (i = 0; i < nr_squares; i++) {
+       assert(b->whichblock[squares[i]] == previous_block);
+       b->whichblock[squares[i]] = newblock;
+       b->blocks[newblock][i] = squares[i];
+    }
+    for (i = j = 0; i < b->nr_squares[previous_block]; i++) {
+       int k;
+       int sq = b->blocks[previous_block][i];
+       for (k = 0; k < nr_squares; k++)
+           if (squares[k] == sq)
+               break;
+       if (k == nr_squares)
+           b->blocks[previous_block][j++] = sq;
+    }
+    b->nr_squares[previous_block] -= nr_squares;
+    b->nr_squares[newblock] = nr_squares;
+}
+
+static void remove_from_block(struct block_structure *blocks, int b, int n)
+{
+    int i, j;
+    blocks->whichblock[n] = -1;
+    for (i = j = 0; i < blocks->nr_squares[b]; i++)
+       if (blocks->blocks[b][i] != n)
+           blocks->blocks[b][j++] = blocks->blocks[b][i];
+    assert(j+1 == i);
+    blocks->nr_squares[b]--;
+}
+
+/* ----------------------------------------------------------------------
+ * Solver.
+ * 
+ * This solver is used for two purposes:
+ *  + to check solubility of a grid as we gradually remove numbers
+ *    from it
+ *  + to solve an externally generated puzzle when the user selects
+ *    `Solve'.
+ * 
+ * It supports a variety of specific modes of reasoning. By
+ * enabling or disabling subsets of these modes we can arrange a
+ * range of difficulty levels.
+ */
+
+/*
+ * Modes of reasoning currently supported:
+ *
+ *  - Positional elimination: a number must go in a particular
+ *    square because all the other empty squares in a given
+ *    row/col/blk are ruled out.
+ *
+ *  - Killer minmax elimination: for killer-type puzzles, a number
+ *    is impossible if choosing it would cause the sum in a killer
+ *    region to be guaranteed to be too large or too small.
+ *
+ *  - Numeric elimination: a square must have a particular number
+ *    in because all the other numbers that could go in it are
+ *    ruled out.
+ *
+ *  - Intersectional analysis: given two domains which overlap
+ *    (hence one must be a block, and the other can be a row or
+ *    col), if the possible locations for a particular number in
+ *    one of the domains can be narrowed down to the overlap, then
+ *    that number can be ruled out everywhere but the overlap in
+ *    the other domain too.
+ *
+ *  - Set elimination: if there is a subset of the empty squares
+ *    within a domain such that the union of the possible numbers
+ *    in that subset has the same size as the subset itself, then
+ *    those numbers can be ruled out everywhere else in the domain.
+ *    (For example, if there are five empty squares and the
+ *    possible numbers in each are 12, 23, 13, 134 and 1345, then
+ *    the first three empty squares form such a subset: the numbers
+ *    1, 2 and 3 _must_ be in those three squares in some
+ *    permutation, and hence we can deduce none of them can be in
+ *    the fourth or fifth squares.)
+ *     + You can also see this the other way round, concentrating
+ *       on numbers rather than squares: if there is a subset of
+ *       the unplaced numbers within a domain such that the union
+ *       of all their possible positions has the same size as the
+ *       subset itself, then all other numbers can be ruled out for
+ *       those positions. However, it turns out that this is
+ *       exactly equivalent to the first formulation at all times:
+ *       there is a 1-1 correspondence between suitable subsets of
+ *       the unplaced numbers and suitable subsets of the unfilled
+ *       places, found by taking the _complement_ of the union of
+ *       the numbers' possible positions (or the spaces' possible
+ *       contents).
+ * 
+ *  - Forcing chains (see comment for solver_forcing().)
+ * 
+ *  - Recursion. If all else fails, we pick one of the currently
+ *    most constrained empty squares and take a random guess at its
+ *    contents, then continue solving on that basis and see if we
+ *    get any further.
+ */
+
+struct solver_usage {
+    int cr;
+    struct block_structure *blocks, *kblocks, *extra_cages;
+    /*
+     * We set up a cubic array, indexed by x, y and digit; each
+     * element of this array is TRUE or FALSE according to whether
+     * or not that digit _could_ in principle go in that position.
+     *
+     * The way to index this array is cube[(y*cr+x)*cr+n-1]; there
+     * are macros below to help with this.
+     */
+    unsigned char *cube;
+    /*
+     * This is the grid in which we write down our final
+     * deductions. y-coordinates in here are _not_ transformed.
+     */
+    digit *grid;
+    /*
+     * For killer-type puzzles, kclues holds the secondary clue for
+     * each cage.  For derived cages, the clue is in extra_clues.
+     */
+    digit *kclues, *extra_clues;
+    /*
+     * Now we keep track, at a slightly higher level, of what we
+     * have yet to work out, to prevent doing the same deduction
+     * many times.
+     */
+    /* row[y*cr+n-1] TRUE if digit n has been placed in row y */
+    unsigned char *row;
+    /* col[x*cr+n-1] TRUE if digit n has been placed in row x */
+    unsigned char *col;
+    /* blk[i*cr+n-1] TRUE if digit n has been placed in block i */
+    unsigned char *blk;
+    /* diag[i*cr+n-1] TRUE if digit n has been placed in diagonal i */
+    unsigned char *diag;              /* diag 0 is \, 1 is / */
+
+    int *regions;
+    int nr_regions;
+    int **sq2region;
+};
+#define cubepos2(xy,n) ((xy)*usage->cr+(n)-1)
+#define cubepos(x,y,n) cubepos2((y)*usage->cr+(x),n)
+#define cube(x,y,n) (usage->cube[cubepos(x,y,n)])
+#define cube2(xy,n) (usage->cube[cubepos2(xy,n)])
+
+#define ondiag0(xy) ((xy) % (cr+1) == 0)
+#define ondiag1(xy) ((xy) % (cr-1) == 0 && (xy) > 0 && (xy) < cr*cr-1)
+#define diag0(i) ((i) * (cr+1))
+#define diag1(i) ((i+1) * (cr-1))
+
+/*
+ * Function called when we are certain that a particular square has
+ * a particular number in it. The y-coordinate passed in here is
+ * transformed.
+ */
+static void solver_place(struct solver_usage *usage, int x, int y, int n)
+{
+    int cr = usage->cr;
+    int sqindex = y*cr+x;
+    int i, bi;
+
+    assert(cube(x,y,n));
+
+    /*
+     * Rule out all other numbers in this square.
+     */
+    for (i = 1; i <= cr; i++)
+       if (i != n)
+           cube(x,y,i) = FALSE;
+
+    /*
+     * Rule out this number in all other positions in the row.
+     */
+    for (i = 0; i < cr; i++)
+       if (i != y)
+           cube(x,i,n) = FALSE;
+
+    /*
+     * Rule out this number in all other positions in the column.
+     */
+    for (i = 0; i < cr; i++)
+       if (i != x)
+           cube(i,y,n) = FALSE;
+
+    /*
+     * Rule out this number in all other positions in the block.
+     */
+    bi = usage->blocks->whichblock[sqindex];
+    for (i = 0; i < cr; i++) {
+       int bp = usage->blocks->blocks[bi][i];
+       if (bp != sqindex)
+           cube2(bp,n) = FALSE;
+    }
+
+    /*
+     * Enter the number in the result grid.
+     */
+    usage->grid[sqindex] = n;
+
+    /*
+     * Cross out this number from the list of numbers left to place
+     * in its row, its column and its block.
+     */
+    usage->row[y*cr+n-1] = usage->col[x*cr+n-1] =
+       usage->blk[bi*cr+n-1] = TRUE;
+
+    if (usage->diag) {
+       if (ondiag0(sqindex)) {
+           for (i = 0; i < cr; i++)
+               if (diag0(i) != sqindex)
+                   cube2(diag0(i),n) = FALSE;
+           usage->diag[n-1] = TRUE;
+       }
+       if (ondiag1(sqindex)) {
+           for (i = 0; i < cr; i++)
+               if (diag1(i) != sqindex)
+                   cube2(diag1(i),n) = FALSE;
+           usage->diag[cr+n-1] = TRUE;
+       }
+    }
+}
+
+#if defined STANDALONE_SOLVER && defined __GNUC__
+/*
+ * Forward-declare the functions taking printf-like format arguments
+ * with __attribute__((format)) so as to ensure the argument syntax
+ * gets debugged.
+ */
+struct solver_scratch;
+static int solver_elim(struct solver_usage *usage, int *indices,
+                       char *fmt, ...) __attribute__((format(printf,3,4)));
+static int solver_intersect(struct solver_usage *usage,
+                            int *indices1, int *indices2, char *fmt, ...)
+    __attribute__((format(printf,4,5)));
+static int solver_set(struct solver_usage *usage,
+                      struct solver_scratch *scratch,
+                      int *indices, char *fmt, ...)
+    __attribute__((format(printf,4,5)));
+#endif
+
+static int solver_elim(struct solver_usage *usage, int *indices
+#ifdef STANDALONE_SOLVER
+                       , char *fmt, ...
+#endif
+                       )
+{
+    int cr = usage->cr;
+    int fpos, m, i;
+
+    /*
+     * Count the number of set bits within this section of the
+     * cube.
+     */
+    m = 0;
+    fpos = -1;
+    for (i = 0; i < cr; i++)
+       if (usage->cube[indices[i]]) {
+           fpos = indices[i];
+           m++;
+       }
+
+    if (m == 1) {
+       int x, y, n;
+       assert(fpos >= 0);
+
+       n = 1 + fpos % cr;
+       x = fpos / cr;
+       y = x / cr;
+       x %= cr;
+
+        if (!usage->grid[y*cr+x]) {
+#ifdef STANDALONE_SOLVER
+            if (solver_show_working) {
+                va_list ap;
+               printf("%*s", solver_recurse_depth*4, "");
+                va_start(ap, fmt);
+                vprintf(fmt, ap);
+                va_end(ap);
+                printf(":\n%*s  placing %d at (%d,%d)\n",
+                       solver_recurse_depth*4, "", n, 1+x, 1+y);
+            }
+#endif
+            solver_place(usage, x, y, n);
+            return +1;
+        }
+    } else if (m == 0) {
+#ifdef STANDALONE_SOLVER
+       if (solver_show_working) {
+           va_list ap;
+           printf("%*s", solver_recurse_depth*4, "");
+           va_start(ap, fmt);
+           vprintf(fmt, ap);
+           va_end(ap);
+           printf(":\n%*s  no possibilities available\n",
+                  solver_recurse_depth*4, "");
+       }
+#endif
+        return -1;
+    }
+
+    return 0;
+}
+
+static int solver_intersect(struct solver_usage *usage,
+                            int *indices1, int *indices2
+#ifdef STANDALONE_SOLVER
+                            , char *fmt, ...
+#endif
+                            )
+{
+    int cr = usage->cr;
+    int ret, i, j;
+
+    /*
+     * Loop over the first domain and see if there's any set bit
+     * not also in the second.
+     */
+    for (i = j = 0; i < cr; i++) {
+        int p = indices1[i];
+       while (j < cr && indices2[j] < p)
+           j++;
+        if (usage->cube[p]) {
+           if (j < cr && indices2[j] == p)
+               continue;              /* both domains contain this index */
+           else
+               return 0;              /* there is, so we can't deduce */
+       }
+    }
+
+    /*
+     * We have determined that all set bits in the first domain are
+     * within its overlap with the second. So loop over the second
+     * domain and remove all set bits that aren't also in that
+     * overlap; return +1 iff we actually _did_ anything.
+     */
+    ret = 0;
+    for (i = j = 0; i < cr; i++) {
+        int p = indices2[i];
+       while (j < cr && indices1[j] < p)
+           j++;
+        if (usage->cube[p] && (j >= cr || indices1[j] != p)) {
+#ifdef STANDALONE_SOLVER
+            if (solver_show_working) {
+                int px, py, pn;
+
+                if (!ret) {
+                    va_list ap;
+                   printf("%*s", solver_recurse_depth*4, "");
+                    va_start(ap, fmt);
+                    vprintf(fmt, ap);
+                    va_end(ap);
+                    printf(":\n");
+                }
+
+                pn = 1 + p % cr;
+                px = p / cr;
+                py = px / cr;
+                px %= cr;
+
+                printf("%*s  ruling out %d at (%d,%d)\n",
+                       solver_recurse_depth*4, "", pn, 1+px, 1+py);
+            }
+#endif
+            ret = +1;                 /* we did something */
+            usage->cube[p] = 0;
+        }
+    }
+
+    return ret;
+}
+
+struct solver_scratch {
+    unsigned char *grid, *rowidx, *colidx, *set;
+    int *neighbours, *bfsqueue;
+    int *indexlist, *indexlist2;
+#ifdef STANDALONE_SOLVER
+    int *bfsprev;
+#endif
+};
+
+static int solver_set(struct solver_usage *usage,
+                      struct solver_scratch *scratch,
+                      int *indices
+#ifdef STANDALONE_SOLVER
+                      , char *fmt, ...
+#endif
+                      )
+{
+    int cr = usage->cr;
+    int i, j, n, count;
+    unsigned char *grid = scratch->grid;
+    unsigned char *rowidx = scratch->rowidx;
+    unsigned char *colidx = scratch->colidx;
+    unsigned char *set = scratch->set;
+
+    /*
+     * We are passed a cr-by-cr matrix of booleans. Our first job
+     * is to winnow it by finding any definite placements - i.e.
+     * any row with a solitary 1 - and discarding that row and the
+     * column containing the 1.
+     */
+    memset(rowidx, TRUE, cr);
+    memset(colidx, TRUE, cr);
+    for (i = 0; i < cr; i++) {
+        int count = 0, first = -1;
+        for (j = 0; j < cr; j++)
+            if (usage->cube[indices[i*cr+j]])
+                first = j, count++;
+
+       /*
+        * If count == 0, then there's a row with no 1s at all and
+        * the puzzle is internally inconsistent. However, we ought
+        * to have caught this already during the simpler reasoning
+        * methods, so we can safely fail an assertion if we reach
+        * this point here.
+        */
+       assert(count > 0);
+        if (count == 1)
+            rowidx[i] = colidx[first] = FALSE;
+    }
+
+    /*
+     * Convert each of rowidx/colidx from a list of 0s and 1s to a
+     * list of the indices of the 1s.
+     */
+    for (i = j = 0; i < cr; i++)
+        if (rowidx[i])
+            rowidx[j++] = i;
+    n = j;
+    for (i = j = 0; i < cr; i++)
+        if (colidx[i])
+            colidx[j++] = i;
+    assert(n == j);
+
+    /*
+     * And create the smaller matrix.
+     */
+    for (i = 0; i < n; i++)
+        for (j = 0; j < n; j++)
+            grid[i*cr+j] = usage->cube[indices[rowidx[i]*cr+colidx[j]]];
+
+    /*
+     * Having done that, we now have a matrix in which every row
+     * has at least two 1s in. Now we search to see if we can find
+     * a rectangle of zeroes (in the set-theoretic sense of
+     * `rectangle', i.e. a subset of rows crossed with a subset of
+     * columns) whose width and height add up to n.
+     */
+
+    memset(set, 0, n);
+    count = 0;
+    while (1) {
+        /*
+         * We have a candidate set. If its size is <=1 or >=n-1
+         * then we move on immediately.
+         */
+        if (count > 1 && count < n-1) {
+            /*
+             * The number of rows we need is n-count. See if we can
+             * find that many rows which each have a zero in all
+             * the positions listed in `set'.
+             */
+            int rows = 0;
+            for (i = 0; i < n; i++) {
+                int ok = TRUE;
+                for (j = 0; j < n; j++)
+                    if (set[j] && grid[i*cr+j]) {
+                        ok = FALSE;
+                        break;
+                    }
+                if (ok)
+                    rows++;
+            }
+
+            /*
+             * We expect never to be able to get _more_ than
+             * n-count suitable rows: this would imply that (for
+             * example) there are four numbers which between them
+             * have at most three possible positions, and hence it
+             * indicates a faulty deduction before this point or
+             * even a bogus clue.
+             */
+            if (rows > n - count) {
+#ifdef STANDALONE_SOLVER
+               if (solver_show_working) {
+                   va_list ap;
+                   printf("%*s", solver_recurse_depth*4,
+                          "");
+                   va_start(ap, fmt);
+                   vprintf(fmt, ap);
+                   va_end(ap);
+                   printf(":\n%*s  contradiction reached\n",
+                          solver_recurse_depth*4, "");
+               }
+#endif
+               return -1;
+           }
+
+            if (rows >= n - count) {
+                int progress = FALSE;
+
+                /*
+                 * We've got one! Now, for each row which _doesn't_
+                 * satisfy the criterion, eliminate all its set
+                 * bits in the positions _not_ listed in `set'.
+                 * Return +1 (meaning progress has been made) if we
+                 * successfully eliminated anything at all.
+                 * 
+                 * This involves referring back through
+                 * rowidx/colidx in order to work out which actual
+                 * positions in the cube to meddle with.
+                 */
+                for (i = 0; i < n; i++) {
+                    int ok = TRUE;
+                    for (j = 0; j < n; j++)
+                        if (set[j] && grid[i*cr+j]) {
+                            ok = FALSE;
+                            break;
+                        }
+                    if (!ok) {
+                        for (j = 0; j < n; j++)
+                            if (!set[j] && grid[i*cr+j]) {
+                                int fpos = indices[rowidx[i]*cr+colidx[j]];
+#ifdef STANDALONE_SOLVER
+                                if (solver_show_working) {
+                                    int px, py, pn;
+
+                                    if (!progress) {
+                                        va_list ap;
+                                       printf("%*s", solver_recurse_depth*4,
+                                              "");
+                                        va_start(ap, fmt);
+                                        vprintf(fmt, ap);
+                                        va_end(ap);
+                                        printf(":\n");
+                                    }
+
+                                    pn = 1 + fpos % cr;
+                                    px = fpos / cr;
+                                    py = px / cr;
+                                    px %= cr;
+
+                                    printf("%*s  ruling out %d at (%d,%d)\n",
+                                          solver_recurse_depth*4, "",
+                                           pn, 1+px, 1+py);
+                                }
+#endif
+                                progress = TRUE;
+                                usage->cube[fpos] = FALSE;
+                            }
+                    }
+                }
+
+                if (progress) {
+                    return +1;
+                }
+            }
+        }
+
+        /*
+         * Binary increment: change the rightmost 0 to a 1, and
+         * change all 1s to the right of it to 0s.
+         */
+        i = n;
+        while (i > 0 && set[i-1])
+            set[--i] = 0, count--;
+        if (i > 0)
+            set[--i] = 1, count++;
+        else
+            break;                     /* done */
+    }
+
+    return 0;
+}
+
+/*
+ * Look for forcing chains. A forcing chain is a path of
+ * pairwise-exclusive squares (i.e. each pair of adjacent squares
+ * in the path are in the same row, column or block) with the
+ * following properties:
+ *
+ *  (a) Each square on the path has precisely two possible numbers.
+ *
+ *  (b) Each pair of squares which are adjacent on the path share
+ *     at least one possible number in common.
+ *
+ *  (c) Each square in the middle of the path shares _both_ of its
+ *     numbers with at least one of its neighbours (not the same
+ *     one with both neighbours).
+ *
+ * These together imply that at least one of the possible number
+ * choices at one end of the path forces _all_ the rest of the
+ * numbers along the path. In order to make real use of this, we
+ * need further properties:
+ *
+ *  (c) Ruling out some number N from the square at one end of the
+ *     path forces the square at the other end to take the same
+ *     number N.
+ *
+ *  (d) The two end squares are both in line with some third
+ *     square.
+ *
+ *  (e) That third square currently has N as a possibility.
+ *
+ * If we can find all of that lot, we can deduce that at least one
+ * of the two ends of the forcing chain has number N, and that
+ * therefore the mutually adjacent third square does not.
+ *
+ * To find forcing chains, we're going to start a bfs at each
+ * suitable square, once for each of its two possible numbers.
+ */
+static int solver_forcing(struct solver_usage *usage,
+                          struct solver_scratch *scratch)
+{
+    int cr = usage->cr;
+    int *bfsqueue = scratch->bfsqueue;
+#ifdef STANDALONE_SOLVER
+    int *bfsprev = scratch->bfsprev;
+#endif
+    unsigned char *number = scratch->grid;
+    int *neighbours = scratch->neighbours;
+    int x, y;
+
+    for (y = 0; y < cr; y++)
+        for (x = 0; x < cr; x++) {
+            int count, t, n;
+
+            /*
+             * If this square doesn't have exactly two candidate
+             * numbers, don't try it.
+             * 
+             * In this loop we also sum the candidate numbers,
+             * which is a nasty hack to allow us to quickly find
+             * `the other one' (since we will shortly know there
+             * are exactly two).
+             */
+            for (count = t = 0, n = 1; n <= cr; n++)
+                if (cube(x, y, n))
+                    count++, t += n;
+            if (count != 2)
+                continue;
+
+            /*
+             * Now attempt a bfs for each candidate.
+             */
+            for (n = 1; n <= cr; n++)
+                if (cube(x, y, n)) {
+                    int orign, currn, head, tail;
+
+                    /*
+                     * Begin a bfs.
+                     */
+                    orign = n;
+
+                    memset(number, cr+1, cr*cr);
+                    head = tail = 0;
+                    bfsqueue[tail++] = y*cr+x;
+#ifdef STANDALONE_SOLVER
+                    bfsprev[y*cr+x] = -1;
+#endif
+                    number[y*cr+x] = t - n;
+
+                    while (head < tail) {
+                        int xx, yy, nneighbours, xt, yt, i;
+
+                        xx = bfsqueue[head++];
+                        yy = xx / cr;
+                        xx %= cr;
+
+                        currn = number[yy*cr+xx];
+
+                        /*
+                         * Find neighbours of yy,xx.
+                         */
+                        nneighbours = 0;
+                        for (yt = 0; yt < cr; yt++)
+                            neighbours[nneighbours++] = yt*cr+xx;
+                        for (xt = 0; xt < cr; xt++)
+                            neighbours[nneighbours++] = yy*cr+xt;
+                        xt = usage->blocks->whichblock[yy*cr+xx];
+                        for (yt = 0; yt < cr; yt++)
+                           neighbours[nneighbours++] = usage->blocks->blocks[xt][yt];
+                       if (usage->diag) {
+                           int sqindex = yy*cr+xx;
+                           if (ondiag0(sqindex)) {
+                               for (i = 0; i < cr; i++)
+                                   neighbours[nneighbours++] = diag0(i);
+                           }
+                           if (ondiag1(sqindex)) {
+                               for (i = 0; i < cr; i++)
+                                   neighbours[nneighbours++] = diag1(i);
+                           }
+                       }
+
+                        /*
+                         * Try visiting each of those neighbours.
+                         */
+                        for (i = 0; i < nneighbours; i++) {
+                            int cc, tt, nn;
+
+                            xt = neighbours[i] % cr;
+                            yt = neighbours[i] / cr;
+
+                            /*
+                             * We need this square to not be
+                             * already visited, and to include
+                             * currn as a possible number.
+                             */
+                            if (number[yt*cr+xt] <= cr)
+                                continue;
+                            if (!cube(xt, yt, currn))
+                                continue;
+
+                            /*
+                             * Don't visit _this_ square a second
+                             * time!
+                             */
+                            if (xt == xx && yt == yy)
+                                continue;
+
+                            /*
+                             * To continue with the bfs, we need
+                             * this square to have exactly two
+                             * possible numbers.
+                             */
+                            for (cc = tt = 0, nn = 1; nn <= cr; nn++)
+                                if (cube(xt, yt, nn))
+                                    cc++, tt += nn;
+                            if (cc == 2) {
+                                bfsqueue[tail++] = yt*cr+xt;
+#ifdef STANDALONE_SOLVER
+                                bfsprev[yt*cr+xt] = yy*cr+xx;
+#endif
+                                number[yt*cr+xt] = tt - currn;
+                            }
+
+                            /*
+                             * One other possibility is that this
+                             * might be the square in which we can
+                             * make a real deduction: if it's
+                             * adjacent to x,y, and currn is equal
+                             * to the original number we ruled out.
+                             */
+                            if (currn == orign &&
+                                (xt == x || yt == y ||
+                                 (usage->blocks->whichblock[yt*cr+xt] == usage->blocks->whichblock[y*cr+x]) ||
+                                (usage->diag && ((ondiag0(yt*cr+xt) && ondiag0(y*cr+x)) ||
+                                                 (ondiag1(yt*cr+xt) && ondiag1(y*cr+x)))))) {
+#ifdef STANDALONE_SOLVER
+                                if (solver_show_working) {
+                                    char *sep = "";
+                                    int xl, yl;
+                                    printf("%*sforcing chain, %d at ends of ",
+                                           solver_recurse_depth*4, "", orign);
+                                    xl = xx;
+                                    yl = yy;
+                                    while (1) {
+                                        printf("%s(%d,%d)", sep, 1+xl,
+                                               1+yl);
+                                        xl = bfsprev[yl*cr+xl];
+                                        if (xl < 0)
+                                            break;
+                                        yl = xl / cr;
+                                        xl %= cr;
+                                        sep = "-";
+                                    }
+                                    printf("\n%*s  ruling out %d at (%d,%d)\n",
+                                           solver_recurse_depth*4, "",
+                                           orign, 1+xt, 1+yt);
+                                }
+#endif
+                                cube(xt, yt, orign) = FALSE;
+                                return 1;
+                            }
+                        }
+                    }
+                }
+        }
+
+    return 0;
+}
+
+static int solver_killer_minmax(struct solver_usage *usage,
+                               struct block_structure *cages, digit *clues,
+                               int b
+#ifdef STANDALONE_SOLVER
+                               , const char *extra
+#endif
+                               )
+{
+    int cr = usage->cr;
+    int i;
+    int ret = 0;
+    int nsquares = cages->nr_squares[b];
+
+    if (clues[b] == 0)
+       return 0;
+
+    for (i = 0; i < nsquares; i++) {
+       int n, x = cages->blocks[b][i];
+
+       for (n = 1; n <= cr; n++)
+           if (cube2(x, n)) {
+               int maxval = 0, minval = 0;
+               int j;
+               for (j = 0; j < nsquares; j++) {
+                   int m;
+                   int y = cages->blocks[b][j];
+                   if (i == j)
+                       continue;
+                   for (m = 1; m <= cr; m++)
+                       if (cube2(y, m)) {
+                           minval += m;
+                           break;
+                       }
+                   for (m = cr; m > 0; m--)
+                       if (cube2(y, m)) {
+                           maxval += m;
+                           break;
+                       }
+               }
+               if (maxval + n < clues[b]) {
+                   cube2(x, n) = FALSE;
+                   ret = 1;
+#ifdef STANDALONE_SOLVER
+                   if (solver_show_working)
+                       printf("%*s  ruling out %d at (%d,%d) as too low %s\n",
+                              solver_recurse_depth*4, "killer minmax analysis",
+                              n, 1 + x%cr, 1 + x/cr, extra);
+#endif
+               }
+               if (minval + n > clues[b]) {
+                   cube2(x, n) = FALSE;
+                   ret = 1;
+#ifdef STANDALONE_SOLVER
+                   if (solver_show_working)
+                       printf("%*s  ruling out %d at (%d,%d) as too high %s\n",
+                              solver_recurse_depth*4, "killer minmax analysis",
+                              n, 1 + x%cr, 1 + x/cr, extra);
+#endif
+               }
+           }
+    }
+    return ret;
+}
+
+static int solver_killer_sums(struct solver_usage *usage, int b,
+                             struct block_structure *cages, int clue,
+                             int cage_is_region
+#ifdef STANDALONE_SOLVER
+                             , const char *cage_type
+#endif
+                             )
+{
+    int cr = usage->cr;
+    int i, ret, max_sums;
+    int nsquares = cages->nr_squares[b];
+    unsigned long *sumbits, possible_addends;
+
+    if (clue == 0) {
+       assert(nsquares == 0);
+       return 0;
+    }
+    assert(nsquares > 0);
+
+    if (nsquares < 2 || nsquares > 4)
+       return 0;
+
+    if (!cage_is_region) {
+       int known_row = -1, known_col = -1, known_block = -1;
+       /*
+        * Verify that the cage lies entirely within one region,
+        * so that using the precomputed sums is valid.
+        */
+       for (i = 0; i < nsquares; i++) {
+           int x = cages->blocks[b][i];
+
+           assert(usage->grid[x] == 0);
+
+           if (i == 0) {
+               known_row = x/cr;
+               known_col = x%cr;
+               known_block = usage->blocks->whichblock[x];
+           } else {
+               if (known_row != x/cr)
+                   known_row = -1;
+               if (known_col != x%cr)
+                   known_col = -1;
+               if (known_block != usage->blocks->whichblock[x])
+                   known_block = -1;
+           }
+       }
+       if (known_block == -1 && known_col == -1 && known_row == -1)
+           return 0;
+    }
+    if (nsquares == 2) {
+       if (clue < 3 || clue > 17)
+           return -1;
+
+       sumbits = sum_bits2[clue];
+       max_sums = MAX_2SUMS;
+    } else if (nsquares == 3) {
+       if (clue < 6 || clue > 24)
+           return -1;
+
+       sumbits = sum_bits3[clue];
+       max_sums = MAX_3SUMS;
+    } else {
+       if (clue < 10 || clue > 30)
+           return -1;
+
+       sumbits = sum_bits4[clue];
+       max_sums = MAX_4SUMS;
+    }
+    /*
+     * For every possible way to get the sum, see if there is
+     * one square in the cage that disallows all the required
+     * addends.  If we find one such square, this way to compute
+     * the sum is impossible.
+     */
+    possible_addends = 0;
+    for (i = 0; i < max_sums; i++) {
+       int j;
+       unsigned long bits = sumbits[i];
+
+       if (bits == 0)
+           break;
+
+       for (j = 0; j < nsquares; j++) {
+           int n;
+           unsigned long square_bits = bits;
+           int x = cages->blocks[b][j];
+           for (n = 1; n <= cr; n++)
+               if (!cube2(x, n))
+                   square_bits &= ~(1L << n);
+           if (square_bits == 0) {
+               break;
+           }
+       }
+       if (j == nsquares)
+           possible_addends |= bits;
+    }
+    /*
+     * Now we know which addends can possibly be used to
+     * compute the sum.  Remove all other digits from the
+     * set of possibilities.
+     */
+    if (possible_addends == 0)
+       return -1;
+
+    ret = 0;
+    for (i = 0; i < nsquares; i++) {
+       int n;
+       int x = cages->blocks[b][i];
+       for (n = 1; n <= cr; n++) {
+           if (!cube2(x, n))
+               continue;
+           if ((possible_addends & (1 << n)) == 0) {
+               cube2(x, n) = FALSE;
+               ret = 1;
+#ifdef STANDALONE_SOLVER
+               if (solver_show_working) {
+                   printf("%*s  using %s\n",
+                          solver_recurse_depth*4, "killer sums analysis",
+                          cage_type);
+                   printf("%*s  ruling out %d at (%d,%d) due to impossible %d-sum\n",
+                          solver_recurse_depth*4, "",
+                          n, 1 + x%cr, 1 + x/cr, nsquares);
+               }
+#endif
+           }
+       }
+    }
+    return ret;
+}
+
+static int filter_whole_cages(struct solver_usage *usage, int *squares, int n,
+                             int *filtered_sum)
+{
+    int b, i, j, off;
+    *filtered_sum = 0;
+
+    /* First, filter squares with a clue.  */
+    for (i = j = 0; i < n; i++)
+       if (usage->grid[squares[i]])
+           *filtered_sum += usage->grid[squares[i]];
+       else
+           squares[j++] = squares[i];
+    n = j;
+
+    /*
+     * Filter all cages that are covered entirely by the list of
+     * squares.
+     */
+    off = 0;
+    for (b = 0; b < usage->kblocks->nr_blocks && off < n; b++) {
+       int b_squares = usage->kblocks->nr_squares[b];
+       int matched = 0;
+
+       if (b_squares == 0)
+           continue;
+
+       /*
+        * Find all squares of block b that lie in our list,
+        * and make them contiguous at off, which is the current position
+        * in the output list.
+        */
+       for (i = 0; i < b_squares; i++) {
+           for (j = off; j < n; j++)
+               if (squares[j] == usage->kblocks->blocks[b][i]) {
+                   int t = squares[off + matched];
+                   squares[off + matched] = squares[j];
+                   squares[j] = t;
+                   matched++;
+                   break;
+               }
+       }
+       /* If so, filter out all squares of b from the list.  */
+       if (matched != usage->kblocks->nr_squares[b]) {
+           off += matched;
+           continue;
+       }
+       memmove(squares + off, squares + off + matched,
+               (n - off - matched) * sizeof *squares);
+       n -= matched;
+
+       *filtered_sum += usage->kclues[b];
+    }
+    assert(off == n);
+    return off;
+}
+
+static struct solver_scratch *solver_new_scratch(struct solver_usage *usage)
+{
+    struct solver_scratch *scratch = snew(struct solver_scratch);
+    int cr = usage->cr;
+    scratch->grid = snewn(cr*cr, unsigned char);
+    scratch->rowidx = snewn(cr, unsigned char);
+    scratch->colidx = snewn(cr, unsigned char);
+    scratch->set = snewn(cr, unsigned char);
+    scratch->neighbours = snewn(5*cr, int);
+    scratch->bfsqueue = snewn(cr*cr, int);
+#ifdef STANDALONE_SOLVER
+    scratch->bfsprev = snewn(cr*cr, int);
+#endif
+    scratch->indexlist = snewn(cr*cr, int);   /* used for set elimination */
+    scratch->indexlist2 = snewn(cr, int);   /* only used for intersect() */
+    return scratch;
+}
+
+static void solver_free_scratch(struct solver_scratch *scratch)
+{
+#ifdef STANDALONE_SOLVER
+    sfree(scratch->bfsprev);
+#endif
+    sfree(scratch->bfsqueue);
+    sfree(scratch->neighbours);
+    sfree(scratch->set);
+    sfree(scratch->colidx);
+    sfree(scratch->rowidx);
+    sfree(scratch->grid);
+    sfree(scratch->indexlist);
+    sfree(scratch->indexlist2);
+    sfree(scratch);
+}
+
+/*
+ * Used for passing information about difficulty levels between the solver
+ * and its callers.
+ */
+struct difficulty {
+    /* Maximum levels allowed.  */
+    int maxdiff, maxkdiff;
+    /* Levels reached by the solver.  */
+    int diff, kdiff;
+};
+
+static void solver(int cr, struct block_structure *blocks,
+                 struct block_structure *kblocks, int xtype,
+                 digit *grid, digit *kgrid, struct difficulty *dlev)
+{
+    struct solver_usage *usage;
+    struct solver_scratch *scratch;
+    int x, y, b, i, n, ret;
+    int diff = DIFF_BLOCK;
+    int kdiff = DIFF_KSINGLE;
+
+    /*
+     * Set up a usage structure as a clean slate (everything
+     * possible).
+     */
+    usage = snew(struct solver_usage);
+    usage->cr = cr;
+    usage->blocks = blocks;
+    if (kblocks) {
+       usage->kblocks = dup_block_structure(kblocks);
+       usage->extra_cages = alloc_block_structure (kblocks->c, kblocks->r,
+                                                   cr * cr, cr, cr * cr);
+       usage->extra_clues = snewn(cr*cr, digit);
+    } else {
+       usage->kblocks = usage->extra_cages = NULL;
+       usage->extra_clues = NULL;
+    }
+    usage->cube = snewn(cr*cr*cr, unsigned char);
+    usage->grid = grid;                       /* write straight back to the input */
+    if (kgrid) {
+       int nclues;
+
+        assert(kblocks);
+        nclues = kblocks->nr_blocks;
+       /*
+        * Allow for expansion of the killer regions, the absolute
+        * limit is obviously one region per square.
+        */
+       usage->kclues = snewn(cr*cr, digit);
+       for (i = 0; i < nclues; i++) {
+           for (n = 0; n < kblocks->nr_squares[i]; n++)
+               if (kgrid[kblocks->blocks[i][n]] != 0)
+                   usage->kclues[i] = kgrid[kblocks->blocks[i][n]];
+           assert(usage->kclues[i] > 0);
+       }
+       memset(usage->kclues + nclues, 0, cr*cr - nclues);
+    } else {
+       usage->kclues = NULL;
+    }
+
+    memset(usage->cube, TRUE, cr*cr*cr);
+
+    usage->row = snewn(cr * cr, unsigned char);
+    usage->col = snewn(cr * cr, unsigned char);
+    usage->blk = snewn(cr * cr, unsigned char);
+    memset(usage->row, FALSE, cr * cr);
+    memset(usage->col, FALSE, cr * cr);
+    memset(usage->blk, FALSE, cr * cr);
+
+    if (xtype) {
+       usage->diag = snewn(cr * 2, unsigned char);
+       memset(usage->diag, FALSE, cr * 2);
+    } else
+       usage->diag = NULL; 
+
+    usage->nr_regions = cr * 3 + (xtype ? 2 : 0);
+    usage->regions = snewn(cr * usage->nr_regions, int);
+    usage->sq2region = snewn(cr * cr * 3, int *);
+
+    for (n = 0; n < cr; n++) {
+       for (i = 0; i < cr; i++) {
+           x = n*cr+i;
+           y = i*cr+n;
+           b = usage->blocks->blocks[n][i];
+           usage->regions[cr*n*3 + i] = x;
+           usage->regions[cr*n*3 + cr + i] = y;
+           usage->regions[cr*n*3 + 2*cr + i] = b;
+           usage->sq2region[x*3] = usage->regions + cr*n*3;
+           usage->sq2region[y*3 + 1] = usage->regions + cr*n*3 + cr;
+           usage->sq2region[b*3 + 2] = usage->regions + cr*n*3 + 2*cr;
+       }
+    }
+
+    scratch = solver_new_scratch(usage);
+
+    /*
+     * Place all the clue numbers we are given.
+     */
+    for (x = 0; x < cr; x++)
+       for (y = 0; y < cr; y++) {
+            int n = grid[y*cr+x];
+           if (n) {
+                if (!cube(x,y,n)) {
+                    diff = DIFF_IMPOSSIBLE;
+                    goto got_result;
+                }
+               solver_place(usage, x, y, grid[y*cr+x]);
+            }
+        }
+
+    /*
+     * Now loop over the grid repeatedly trying all permitted modes
+     * of reasoning. The loop terminates if we complete an
+     * iteration without making any progress; we then return
+     * failure or success depending on whether the grid is full or
+     * not.
+     */
+    while (1) {
+        /*
+         * I'd like to write `continue;' inside each of the
+         * following loops, so that the solver returns here after
+         * making some progress. However, I can't specify that I
+         * want to continue an outer loop rather than the innermost
+         * one, so I'm apologetically resorting to a goto.
+         */
+        cont:
+
+       /*
+        * Blockwise positional elimination.
+        */
+       for (b = 0; b < cr; b++)
+           for (n = 1; n <= cr; n++)
+               if (!usage->blk[b*cr+n-1]) {
+                   for (i = 0; i < cr; i++)
+                       scratch->indexlist[i] = cubepos2(usage->blocks->blocks[b][i],n);
+                   ret = solver_elim(usage, scratch->indexlist
+#ifdef STANDALONE_SOLVER
+                                     , "positional elimination,"
+                                     " %d in block %s", n,
+                                     usage->blocks->blocknames[b]
+#endif
+                                     );
+                   if (ret < 0) {
+                       diff = DIFF_IMPOSSIBLE;
+                       goto got_result;
+                   } else if (ret > 0) {
+                       diff = max(diff, DIFF_BLOCK);
+                       goto cont;
+                   }
+               }
+
+       if (usage->kclues != NULL) {
+           int changed = FALSE;
+
+           /*
+            * First, bring the kblocks into a more useful form: remove
+            * all filled-in squares, and reduce the sum by their values.
+            * Walk in reverse order, since otherwise remove_from_block
+            * can move element past our loop counter.
+            */
+           for (b = 0; b < usage->kblocks->nr_blocks; b++)
+               for (i = usage->kblocks->nr_squares[b] -1; i >= 0; i--) {
+                   int x = usage->kblocks->blocks[b][i];
+                   int t = usage->grid[x];
+
+                   if (t == 0)
+                       continue;
+                   remove_from_block(usage->kblocks, b, x);
+                   if (t > usage->kclues[b]) {
+                       diff = DIFF_IMPOSSIBLE;
+                       goto got_result;
+                   }
+                   usage->kclues[b] -= t;
+                   /*
+                    * Since cages are regions, this tells us something
+                    * about the other squares in the cage.
+                    */
+                   for (n = 0; n < usage->kblocks->nr_squares[b]; n++) {
+                       cube2(usage->kblocks->blocks[b][n], t) = FALSE;
+                   }
+               }
+
+           /*
+            * The most trivial kind of solver for killer puzzles: fill
+            * single-square cages.
+            */
+           for (b = 0; b < usage->kblocks->nr_blocks; b++) {
+               int squares = usage->kblocks->nr_squares[b];
+               if (squares == 1) {
+                   int v = usage->kclues[b];
+                   if (v < 1 || v > cr) {
+                       diff = DIFF_IMPOSSIBLE;
+                       goto got_result;
+                   }
+                   x = usage->kblocks->blocks[b][0] % cr;
+                   y = usage->kblocks->blocks[b][0] / cr;
+                   if (!cube(x, y, v)) {
+                       diff = DIFF_IMPOSSIBLE;
+                       goto got_result;
+                   }
+                   solver_place(usage, x, y, v);
+
+#ifdef STANDALONE_SOLVER
+                   if (solver_show_working) {
+                       printf("%*s  placing %d at (%d,%d)\n",
+                              solver_recurse_depth*4, "killer single-square cage",
+                              v, 1 + x%cr, 1 + x/cr);
+                   }
+#endif
+                   changed = TRUE;
+               }
+           }
+
+           if (changed) {
+               kdiff = max(kdiff, DIFF_KSINGLE);
+               goto cont;
+           }
+       }
+       if (dlev->maxkdiff >= DIFF_KINTERSECT && usage->kclues != NULL) {
+           int changed = FALSE;
+           /*
+            * Now, create the extra_cages information.  Every full region
+            * (row, column, or block) has the same sum total (45 for 3x3
+            * puzzles.  After we try to cover these regions with cages that
+            * lie entirely within them, any squares that remain must bring
+            * the total to this known value, and so they form additional
+            * cages which aren't immediately evident in the displayed form
+            * of the puzzle.
+            */
+           usage->extra_cages->nr_blocks = 0;
+           for (i = 0; i < 3; i++) {
+               for (n = 0; n < cr; n++) {
+                   int *region = usage->regions + cr*n*3 + i*cr;
+                   int sum = cr * (cr + 1) / 2;
+                   int nsquares = cr;
+                   int filtered;
+                   int n_extra = usage->extra_cages->nr_blocks;
+                   int *extra_list = usage->extra_cages->blocks[n_extra];
+                   memcpy(extra_list, region, cr * sizeof *extra_list);
+
+                   nsquares = filter_whole_cages(usage, extra_list, nsquares, &filtered);
+                   sum -= filtered;
+                   if (nsquares == cr || nsquares == 0)
+                       continue;
+                   if (dlev->maxdiff >= DIFF_RECURSIVE) {
+                       if (sum <= 0) {
+                           dlev->diff = DIFF_IMPOSSIBLE;
+                           goto got_result;
+                       }
+                   }
+                   assert(sum > 0);
+
+                   if (nsquares == 1) {
+                       if (sum > cr) {
+                           diff = DIFF_IMPOSSIBLE;
+                           goto got_result;
+                       }
+                       x = extra_list[0] % cr;
+                       y = extra_list[0] / cr;
+                       if (!cube(x, y, sum)) {
+                           diff = DIFF_IMPOSSIBLE;
+                           goto got_result;
+                       }
+                       solver_place(usage, x, y, sum);
+                       changed = TRUE;
+#ifdef STANDALONE_SOLVER
+                       if (solver_show_working) {
+                           printf("%*s  placing %d at (%d,%d)\n",
+                                  solver_recurse_depth*4, "killer single-square deduced cage",
+                                  sum, 1 + x, 1 + y);
+                       }
+#endif
+                   }
+
+                   b = usage->kblocks->whichblock[extra_list[0]];
+                   for (x = 1; x < nsquares; x++)
+                       if (usage->kblocks->whichblock[extra_list[x]] != b)
+                           break;
+                   if (x == nsquares) {
+                       assert(usage->kblocks->nr_squares[b] > nsquares);
+                       split_block(usage->kblocks, extra_list, nsquares);
+                       assert(usage->kblocks->nr_squares[usage->kblocks->nr_blocks - 1] == nsquares);
+                       usage->kclues[usage->kblocks->nr_blocks - 1] = sum;
+                       usage->kclues[b] -= sum;
+                   } else {
+                       usage->extra_cages->nr_squares[n_extra] = nsquares;
+                       usage->extra_cages->nr_blocks++;
+                       usage->extra_clues[n_extra] = sum;
+                   }
+               }
+           }
+           if (changed) {
+               kdiff = max(kdiff, DIFF_KINTERSECT);
+               goto cont;
+           }
+       }
+
+       /*
+        * Another simple killer-type elimination.  For every square in a
+        * cage, find the minimum and maximum possible sums of all the
+        * other squares in the same cage, and rule out possibilities
+        * for the given square based on whether they are guaranteed to
+        * cause the sum to be either too high or too low.
+        * This is a special case of trying all possible sums across a
+        * region, which is a recursive algorithm.  We should probably
+        * implement it for a higher difficulty level.
+        */
+       if (dlev->maxkdiff >= DIFF_KMINMAX && usage->kclues != NULL) {
+           int changed = FALSE;
+           for (b = 0; b < usage->kblocks->nr_blocks; b++) {
+               int ret = solver_killer_minmax(usage, usage->kblocks,
+                                              usage->kclues, b
+#ifdef STANDALONE_SOLVER
+                                            , ""
+#endif
+                                              );
+               if (ret < 0) {
+                   diff = DIFF_IMPOSSIBLE;
+                   goto got_result;
+               } else if (ret > 0)
+                   changed = TRUE;
+           }
+           for (b = 0; b < usage->extra_cages->nr_blocks; b++) {
+               int ret = solver_killer_minmax(usage, usage->extra_cages,
+                                              usage->extra_clues, b
+#ifdef STANDALONE_SOLVER
+                                              , "using deduced cages"
+#endif
+                                              );
+               if (ret < 0) {
+                   diff = DIFF_IMPOSSIBLE;
+                   goto got_result;
+               } else if (ret > 0)
+                   changed = TRUE;
+           }
+           if (changed) {
+               kdiff = max(kdiff, DIFF_KMINMAX);
+               goto cont;
+           }
+       }
+
+       /*
+        * Try to use knowledge of which numbers can be used to generate
+        * a given sum.
+        * This can only be used if a cage lies entirely within a region.
+        */
+       if (dlev->maxkdiff >= DIFF_KSUMS && usage->kclues != NULL) {
+           int changed = FALSE;
+
+           for (b = 0; b < usage->kblocks->nr_blocks; b++) {
+               int ret = solver_killer_sums(usage, b, usage->kblocks,
+                                            usage->kclues[b], TRUE
+#ifdef STANDALONE_SOLVER
+                                            , "regular clues"
+#endif
+                                            );
+               if (ret > 0) {
+                   changed = TRUE;
+                   kdiff = max(kdiff, DIFF_KSUMS);
+               } else if (ret < 0) {
+                   diff = DIFF_IMPOSSIBLE;
+                   goto got_result;
+               }
+           }
+
+           for (b = 0; b < usage->extra_cages->nr_blocks; b++) {
+               int ret = solver_killer_sums(usage, b, usage->extra_cages,
+                                            usage->extra_clues[b], FALSE
+#ifdef STANDALONE_SOLVER
+                                            , "deduced clues"
+#endif
+                                            );
+               if (ret > 0) {
+                   changed = TRUE;
+                   kdiff = max(kdiff, DIFF_KSUMS);
+               } else if (ret < 0) {
+                   diff = DIFF_IMPOSSIBLE;
+                   goto got_result;
+               }
+           }
+
+           if (changed)
+               goto cont;
+       }
+
+       if (dlev->maxdiff <= DIFF_BLOCK)
+           break;
+
+       /*
+        * Row-wise positional elimination.
+        */
+       for (y = 0; y < cr; y++)
+           for (n = 1; n <= cr; n++)
+               if (!usage->row[y*cr+n-1]) {
+                   for (x = 0; x < cr; x++)
+                       scratch->indexlist[x] = cubepos(x, y, n);
+                   ret = solver_elim(usage, scratch->indexlist
+#ifdef STANDALONE_SOLVER
+                                     , "positional elimination,"
+                                     " %d in row %d", n, 1+y
+#endif
+                                     );
+                   if (ret < 0) {
+                       diff = DIFF_IMPOSSIBLE;
+                       goto got_result;
+                   } else if (ret > 0) {
+                       diff = max(diff, DIFF_SIMPLE);
+                       goto cont;
+                   }
+                }
+       /*
+        * Column-wise positional elimination.
+        */
+       for (x = 0; x < cr; x++)
+           for (n = 1; n <= cr; n++)
+               if (!usage->col[x*cr+n-1]) {
+                   for (y = 0; y < cr; y++)
+                       scratch->indexlist[y] = cubepos(x, y, n);
+                   ret = solver_elim(usage, scratch->indexlist
+#ifdef STANDALONE_SOLVER
+                                     , "positional elimination,"
+                                     " %d in column %d", n, 1+x
+#endif
+                                     );
+                   if (ret < 0) {
+                       diff = DIFF_IMPOSSIBLE;
+                       goto got_result;
+                   } else if (ret > 0) {
+                       diff = max(diff, DIFF_SIMPLE);
+                       goto cont;
+                   }
+                }
+
+       /*
+        * X-diagonal positional elimination.
+        */
+       if (usage->diag) {
+           for (n = 1; n <= cr; n++)
+               if (!usage->diag[n-1]) {
+                   for (i = 0; i < cr; i++)
+                       scratch->indexlist[i] = cubepos2(diag0(i), n);
+                   ret = solver_elim(usage, scratch->indexlist
+#ifdef STANDALONE_SOLVER
+                                     , "positional elimination,"
+                                     " %d in \\-diagonal", n
+#endif
+                                     );
+                   if (ret < 0) {
+                       diff = DIFF_IMPOSSIBLE;
+                       goto got_result;
+                   } else if (ret > 0) {
+                       diff = max(diff, DIFF_SIMPLE);
+                       goto cont;
+                   }
+                }
+           for (n = 1; n <= cr; n++)
+               if (!usage->diag[cr+n-1]) {
+                   for (i = 0; i < cr; i++)
+                       scratch->indexlist[i] = cubepos2(diag1(i), n);
+                   ret = solver_elim(usage, scratch->indexlist
+#ifdef STANDALONE_SOLVER
+                                     , "positional elimination,"
+                                     " %d in /-diagonal", n
+#endif
+                                     );
+                   if (ret < 0) {
+                       diff = DIFF_IMPOSSIBLE;
+                       goto got_result;
+                   } else if (ret > 0) {
+                       diff = max(diff, DIFF_SIMPLE);
+                       goto cont;
+                   }
+                }
+       }
+
+       /*
+        * Numeric elimination.
+        */
+       for (x = 0; x < cr; x++)
+           for (y = 0; y < cr; y++)
+               if (!usage->grid[y*cr+x]) {
+                   for (n = 1; n <= cr; n++)
+                       scratch->indexlist[n-1] = cubepos(x, y, n);
+                   ret = solver_elim(usage, scratch->indexlist
+#ifdef STANDALONE_SOLVER
+                                     , "numeric elimination at (%d,%d)",
+                                     1+x, 1+y
+#endif
+                                     );
+                   if (ret < 0) {
+                       diff = DIFF_IMPOSSIBLE;
+                       goto got_result;
+                   } else if (ret > 0) {
+                       diff = max(diff, DIFF_SIMPLE);
+                       goto cont;
+                   }
+                }
+
+       if (dlev->maxdiff <= DIFF_SIMPLE)
+           break;
+
+        /*
+         * Intersectional analysis, rows vs blocks.
+         */
+        for (y = 0; y < cr; y++)
+            for (b = 0; b < cr; b++)
+                for (n = 1; n <= cr; n++) {
+                    if (usage->row[y*cr+n-1] ||
+                        usage->blk[b*cr+n-1])
+                       continue;
+                   for (i = 0; i < cr; i++) {
+                       scratch->indexlist[i] = cubepos(i, y, n);
+                       scratch->indexlist2[i] = cubepos2(usage->blocks->blocks[b][i], n);
+                   }
+                   /*
+                    * solver_intersect() never returns -1.
+                    */
+                   if (solver_intersect(usage, scratch->indexlist,
+                                        scratch->indexlist2
+#ifdef STANDALONE_SOLVER
+                                          , "intersectional analysis,"
+                                          " %d in row %d vs block %s",
+                                          n, 1+y, usage->blocks->blocknames[b]
+#endif
+                                          ) ||
+                         solver_intersect(usage, scratch->indexlist2,
+                                        scratch->indexlist
+#ifdef STANDALONE_SOLVER
+                                          , "intersectional analysis,"
+                                          " %d in block %s vs row %d",
+                                          n, usage->blocks->blocknames[b], 1+y
+#endif
+                                          )) {
+                        diff = max(diff, DIFF_INTERSECT);
+                        goto cont;
+                    }
+               }
+
+        /*
+         * Intersectional analysis, columns vs blocks.
+         */
+        for (x = 0; x < cr; x++)
+            for (b = 0; b < cr; b++)
+                for (n = 1; n <= cr; n++) {
+                    if (usage->col[x*cr+n-1] ||
+                        usage->blk[b*cr+n-1])
+                       continue;
+                   for (i = 0; i < cr; i++) {
+                       scratch->indexlist[i] = cubepos(x, i, n);
+                       scratch->indexlist2[i] = cubepos2(usage->blocks->blocks[b][i], n);
+                   }
+                   if (solver_intersect(usage, scratch->indexlist,
+                                        scratch->indexlist2
+#ifdef STANDALONE_SOLVER
+                                          , "intersectional analysis,"
+                                          " %d in column %d vs block %s",
+                                          n, 1+x, usage->blocks->blocknames[b]
+#endif
+                                          ) ||
+                         solver_intersect(usage, scratch->indexlist2,
+                                        scratch->indexlist
+#ifdef STANDALONE_SOLVER
+                                          , "intersectional analysis,"
+                                          " %d in block %s vs column %d",
+                                          n, usage->blocks->blocknames[b], 1+x
+#endif
+                                          )) {
+                        diff = max(diff, DIFF_INTERSECT);
+                        goto cont;
+                    }
+               }
+
+       if (usage->diag) {
+           /*
+            * Intersectional analysis, \-diagonal vs blocks.
+            */
+            for (b = 0; b < cr; b++)
+                for (n = 1; n <= cr; n++) {
+                    if (usage->diag[n-1] ||
+                        usage->blk[b*cr+n-1])
+                       continue;
+                   for (i = 0; i < cr; i++) {
+                       scratch->indexlist[i] = cubepos2(diag0(i), n);
+                       scratch->indexlist2[i] = cubepos2(usage->blocks->blocks[b][i], n);
+                   }
+                   if (solver_intersect(usage, scratch->indexlist,
+                                        scratch->indexlist2
+#ifdef STANDALONE_SOLVER
+                                          , "intersectional analysis,"
+                                          " %d in \\-diagonal vs block %s",
+                                          n, usage->blocks->blocknames[b]
+#endif
+                                          ) ||
+                         solver_intersect(usage, scratch->indexlist2,
+                                        scratch->indexlist
+#ifdef STANDALONE_SOLVER
+                                          , "intersectional analysis,"
+                                          " %d in block %s vs \\-diagonal",
+                                          n, usage->blocks->blocknames[b]
+#endif
+                                          )) {
+                        diff = max(diff, DIFF_INTERSECT);
+                        goto cont;
+                    }
+               }
+
+           /*
+            * Intersectional analysis, /-diagonal vs blocks.
+            */
+            for (b = 0; b < cr; b++)
+                for (n = 1; n <= cr; n++) {
+                    if (usage->diag[cr+n-1] ||
+                        usage->blk[b*cr+n-1])
+                       continue;
+                   for (i = 0; i < cr; i++) {
+                       scratch->indexlist[i] = cubepos2(diag1(i), n);
+                       scratch->indexlist2[i] = cubepos2(usage->blocks->blocks[b][i], n);
+                   }
+                   if (solver_intersect(usage, scratch->indexlist,
+                                        scratch->indexlist2
+#ifdef STANDALONE_SOLVER
+                                          , "intersectional analysis,"
+                                          " %d in /-diagonal vs block %s",
+                                          n, usage->blocks->blocknames[b]
+#endif
+                                          ) ||
+                         solver_intersect(usage, scratch->indexlist2,
+                                        scratch->indexlist
+#ifdef STANDALONE_SOLVER
+                                          , "intersectional analysis,"
+                                          " %d in block %s vs /-diagonal",
+                                          n, usage->blocks->blocknames[b]
+#endif
+                                          )) {
+                        diff = max(diff, DIFF_INTERSECT);
+                        goto cont;
+                    }
+               }
+       }
+
+       if (dlev->maxdiff <= DIFF_INTERSECT)
+           break;
+
+       /*
+        * Blockwise set elimination.
+        */
+       for (b = 0; b < cr; b++) {
+           for (i = 0; i < cr; i++)
+               for (n = 1; n <= cr; n++)
+                   scratch->indexlist[i*cr+n-1] = cubepos2(usage->blocks->blocks[b][i], n);
+           ret = solver_set(usage, scratch, scratch->indexlist
+#ifdef STANDALONE_SOLVER
+                            , "set elimination, block %s",
+                            usage->blocks->blocknames[b]
+#endif
+                                );
+           if (ret < 0) {
+               diff = DIFF_IMPOSSIBLE;
+               goto got_result;
+           } else if (ret > 0) {
+               diff = max(diff, DIFF_SET);
+               goto cont;
+           }
+       }
+
+       /*
+        * Row-wise set elimination.
+        */
+       for (y = 0; y < cr; y++) {
+           for (x = 0; x < cr; x++)
+               for (n = 1; n <= cr; n++)
+                   scratch->indexlist[x*cr+n-1] = cubepos(x, y, n);
+           ret = solver_set(usage, scratch, scratch->indexlist
+#ifdef STANDALONE_SOLVER
+                            , "set elimination, row %d", 1+y
+#endif
+                            );
+           if (ret < 0) {
+               diff = DIFF_IMPOSSIBLE;
+               goto got_result;
+           } else if (ret > 0) {
+               diff = max(diff, DIFF_SET);
+               goto cont;
+           }
+       }
+
+       /*
+        * Column-wise set elimination.
+        */
+       for (x = 0; x < cr; x++) {
+           for (y = 0; y < cr; y++)
+               for (n = 1; n <= cr; n++)
+                   scratch->indexlist[y*cr+n-1] = cubepos(x, y, n);
+            ret = solver_set(usage, scratch, scratch->indexlist
+#ifdef STANDALONE_SOLVER
+                            , "set elimination, column %d", 1+x
+#endif
+                            );
+           if (ret < 0) {
+               diff = DIFF_IMPOSSIBLE;
+               goto got_result;
+           } else if (ret > 0) {
+               diff = max(diff, DIFF_SET);
+               goto cont;
+           }
+       }
+
+       if (usage->diag) {
+           /*
+            * \-diagonal set elimination.
+            */
+           for (i = 0; i < cr; i++)
+               for (n = 1; n <= cr; n++)
+                   scratch->indexlist[i*cr+n-1] = cubepos2(diag0(i), n);
+            ret = solver_set(usage, scratch, scratch->indexlist
+#ifdef STANDALONE_SOLVER
+                            , "set elimination, \\-diagonal"
+#endif
+                            );
+           if (ret < 0) {
+               diff = DIFF_IMPOSSIBLE;
+               goto got_result;
+           } else if (ret > 0) {
+               diff = max(diff, DIFF_SET);
+               goto cont;
+           }
+
+           /*
+            * /-diagonal set elimination.
+            */
+           for (i = 0; i < cr; i++)
+               for (n = 1; n <= cr; n++)
+                   scratch->indexlist[i*cr+n-1] = cubepos2(diag1(i), n);
+            ret = solver_set(usage, scratch, scratch->indexlist
+#ifdef STANDALONE_SOLVER
+                            , "set elimination, /-diagonal"
+#endif
+                            );
+           if (ret < 0) {
+               diff = DIFF_IMPOSSIBLE;
+               goto got_result;
+           } else if (ret > 0) {
+               diff = max(diff, DIFF_SET);
+               goto cont;
+           }
+       }
+
+       if (dlev->maxdiff <= DIFF_SET)
+           break;
+
+       /*
+        * Row-vs-column set elimination on a single number.
+        */
+       for (n = 1; n <= cr; n++) {
+           for (y = 0; y < cr; y++)
+               for (x = 0; x < cr; x++)
+                   scratch->indexlist[y*cr+x] = cubepos(x, y, n);
+            ret = solver_set(usage, scratch, scratch->indexlist
+#ifdef STANDALONE_SOLVER
+                            , "positional set elimination, number %d", n
+#endif
+                            );
+           if (ret < 0) {
+               diff = DIFF_IMPOSSIBLE;
+               goto got_result;
+           } else if (ret > 0) {
+               diff = max(diff, DIFF_EXTREME);
+               goto cont;
+           }
+       }
+
+        /*
+         * Forcing chains.
+         */
+        if (solver_forcing(usage, scratch)) {
+            diff = max(diff, DIFF_EXTREME);
+            goto cont;
+        }
+
+       /*
+        * If we reach here, we have made no deductions in this
+        * iteration, so the algorithm terminates.
+        */
+       break;
+    }
+
+    /*
+     * Last chance: if we haven't fully solved the puzzle yet, try
+     * recursing based on guesses for a particular square. We pick
+     * one of the most constrained empty squares we can find, which
+     * has the effect of pruning the search tree as much as
+     * possible.
+     */
+    if (dlev->maxdiff >= DIFF_RECURSIVE) {
+       int best, bestcount;
+
+       best = -1;
+       bestcount = cr+1;
+
+       for (y = 0; y < cr; y++)
+           for (x = 0; x < cr; x++)
+               if (!grid[y*cr+x]) {
+                   int count;
+
+                   /*
+                    * An unfilled square. Count the number of
+                    * possible digits in it.
+                    */
+                   count = 0;
+                   for (n = 1; n <= cr; n++)
+                       if (cube(x,y,n))
+                           count++;
+
+                   /*
+                    * We should have found any impossibilities
+                    * already, so this can safely be an assert.
+                    */
+                   assert(count > 1);
+
+                   if (count < bestcount) {
+                       bestcount = count;
+                       best = y*cr+x;
+                   }
+               }
+
+       if (best != -1) {
+           int i, j;
+           digit *list, *ingrid, *outgrid;
+
+           diff = DIFF_IMPOSSIBLE;    /* no solution found yet */
+
+           /*
+            * Attempt recursion.
+            */
+           y = best / cr;
+           x = best % cr;
+
+           list = snewn(cr, digit);
+           ingrid = snewn(cr * cr, digit);
+           outgrid = snewn(cr * cr, digit);
+           memcpy(ingrid, grid, cr * cr);
+
+           /* Make a list of the possible digits. */
+           for (j = 0, n = 1; n <= cr; n++)
+               if (cube(x,y,n))
+                   list[j++] = n;
+
+#ifdef STANDALONE_SOLVER
+           if (solver_show_working) {
+               char *sep = "";
+               printf("%*srecursing on (%d,%d) [",
+                      solver_recurse_depth*4, "", x + 1, y + 1);
+               for (i = 0; i < j; i++) {
+                   printf("%s%d", sep, list[i]);
+                   sep = " or ";
+               }
+               printf("]\n");
+           }
+#endif
+
+           /*
+            * And step along the list, recursing back into the
+            * main solver at every stage.
+            */
+           for (i = 0; i < j; i++) {
+               memcpy(outgrid, ingrid, cr * cr);
+               outgrid[y*cr+x] = list[i];
+
+#ifdef STANDALONE_SOLVER
+               if (solver_show_working)
+                   printf("%*sguessing %d at (%d,%d)\n",
+                          solver_recurse_depth*4, "", list[i], x + 1, y + 1);
+               solver_recurse_depth++;
+#endif
+
+               solver(cr, blocks, kblocks, xtype, outgrid, kgrid, dlev);
+
+#ifdef STANDALONE_SOLVER
+               solver_recurse_depth--;
+               if (solver_show_working) {
+                   printf("%*sretracting %d at (%d,%d)\n",
+                          solver_recurse_depth*4, "", list[i], x + 1, y + 1);
+               }
+#endif
+
+               /*
+                * If we have our first solution, copy it into the
+                * grid we will return.
+                */
+               if (diff == DIFF_IMPOSSIBLE && dlev->diff != DIFF_IMPOSSIBLE)
+                   memcpy(grid, outgrid, cr*cr);
+
+               if (dlev->diff == DIFF_AMBIGUOUS)
+                   diff = DIFF_AMBIGUOUS;
+               else if (dlev->diff == DIFF_IMPOSSIBLE)
+                   /* do not change our return value */;
+               else {
+                   /* the recursion turned up exactly one solution */
+                   if (diff == DIFF_IMPOSSIBLE)
+                       diff = DIFF_RECURSIVE;
+                   else
+                       diff = DIFF_AMBIGUOUS;
+               }
+
+               /*
+                * As soon as we've found more than one solution,
+                * give up immediately.
+                */
+               if (diff == DIFF_AMBIGUOUS)
+                   break;
+           }
+
+           sfree(outgrid);
+           sfree(ingrid);
+           sfree(list);
+       }
+
+    } else {
+        /*
+         * We're forbidden to use recursion, so we just see whether
+         * our grid is fully solved, and return DIFF_IMPOSSIBLE
+         * otherwise.
+         */
+       for (y = 0; y < cr; y++)
+           for (x = 0; x < cr; x++)
+               if (!grid[y*cr+x])
+                    diff = DIFF_IMPOSSIBLE;
+    }
+
+    got_result:
+    dlev->diff = diff;
+    dlev->kdiff = kdiff;
+
+#ifdef STANDALONE_SOLVER
+    if (solver_show_working)
+       printf("%*s%s found\n",
+              solver_recurse_depth*4, "",
+              diff == DIFF_IMPOSSIBLE ? "no solution" :
+              diff == DIFF_AMBIGUOUS ? "multiple solutions" :
+              "one solution");
+#endif
+
+    sfree(usage->sq2region);
+    sfree(usage->regions);
+    sfree(usage->cube);
+    sfree(usage->row);
+    sfree(usage->col);
+    sfree(usage->blk);
+    if (usage->kblocks) {
+       free_block_structure(usage->kblocks);
+       free_block_structure(usage->extra_cages);
+       sfree(usage->extra_clues);
+    }
+    if (usage->kclues) sfree(usage->kclues);
+    sfree(usage);
+
+    solver_free_scratch(scratch);
+}
+
+/* ----------------------------------------------------------------------
+ * End of solver code.
+ */
+
+/* ----------------------------------------------------------------------
+ * Killer set generator.
+ */
+
+/* ----------------------------------------------------------------------
+ * Solo filled-grid generator.
+ *
+ * This grid generator works by essentially trying to solve a grid
+ * starting from no clues, and not worrying that there's more than
+ * one possible solution. Unfortunately, it isn't computationally
+ * feasible to do this by calling the above solver with an empty
+ * grid, because that one needs to allocate a lot of scratch space
+ * at every recursion level. Instead, I have a much simpler
+ * algorithm which I shamelessly copied from a Python solver
+ * written by Andrew Wilkinson (which is GPLed, but I've reused
+ * only ideas and no code). It mostly just does the obvious
+ * recursive thing: pick an empty square, put one of the possible
+ * digits in it, recurse until all squares are filled, backtrack
+ * and change some choices if necessary.
+ *
+ * The clever bit is that every time it chooses which square to
+ * fill in next, it does so by counting the number of _possible_
+ * numbers that can go in each square, and it prioritises so that
+ * it picks a square with the _lowest_ number of possibilities. The
+ * idea is that filling in lots of the obvious bits (particularly
+ * any squares with only one possibility) will cut down on the list
+ * of possibilities for other squares and hence reduce the enormous
+ * search space as much as possible as early as possible.
+ *
+ * The use of bit sets implies that we support puzzles up to a size of
+ * 32x32 (less if anyone finds a 16-bit machine to compile this on).
+ */
+
+/*
+ * Internal data structure used in gridgen to keep track of
+ * progress.
+ */
+struct gridgen_coord { int x, y, r; };
+struct gridgen_usage {
+    int cr;
+    struct block_structure *blocks, *kblocks;
+    /* grid is a copy of the input grid, modified as we go along */
+    digit *grid;
+    /*
+     * Bitsets.  In each of them, bit n is set if digit n has been placed
+     * in the corresponding region.  row, col and blk are used for all
+     * puzzles.  cge is used only for killer puzzles, and diag is used
+     * only for x-type puzzles.
+     * All of these have cr entries, except diag which only has 2,
+     * and cge, which has as many entries as kblocks.
+     */
+    unsigned int *row, *col, *blk, *cge, *diag;
+    /* This lists all the empty spaces remaining in the grid. */
+    struct gridgen_coord *spaces;
+    int nspaces;
+    /* If we need randomisation in the solve, this is our random state. */
+    random_state *rs;
+};
+
+static void gridgen_place(struct gridgen_usage *usage, int x, int y, digit n)
+{
+    unsigned int bit = 1 << n;
+    int cr = usage->cr;
+    usage->row[y] |= bit;
+    usage->col[x] |= bit;
+    usage->blk[usage->blocks->whichblock[y*cr+x]] |= bit;
+    if (usage->cge)
+       usage->cge[usage->kblocks->whichblock[y*cr+x]] |= bit;
+    if (usage->diag) {
+       if (ondiag0(y*cr+x))
+           usage->diag[0] |= bit;
+       if (ondiag1(y*cr+x))
+           usage->diag[1] |= bit;
+    }
+    usage->grid[y*cr+x] = n;
+}
+
+static void gridgen_remove(struct gridgen_usage *usage, int x, int y, digit n)
+{
+    unsigned int mask = ~(1 << n);
+    int cr = usage->cr;
+    usage->row[y] &= mask;
+    usage->col[x] &= mask;
+    usage->blk[usage->blocks->whichblock[y*cr+x]] &= mask;
+    if (usage->cge)
+       usage->cge[usage->kblocks->whichblock[y*cr+x]] &= mask;
+    if (usage->diag) {
+       if (ondiag0(y*cr+x))
+           usage->diag[0] &= mask;
+       if (ondiag1(y*cr+x))
+           usage->diag[1] &= mask;
+    }
+    usage->grid[y*cr+x] = 0;
+}
+
+#define N_SINGLE 32
+
+/*
+ * The real recursive step in the generating function.
+ *
+ * Return values: 1 means solution found, 0 means no solution
+ * found on this branch.
+ */
+static int gridgen_real(struct gridgen_usage *usage, digit *grid, int *steps)
+{
+    int cr = usage->cr;
+    int i, j, n, sx, sy, bestm, bestr, ret;
+    int *digits;
+    unsigned int used;
+
+    /*
+     * Firstly, check for completion! If there are no spaces left
+     * in the grid, we have a solution.
+     */
+    if (usage->nspaces == 0)
+       return TRUE;
+
+    /*
+     * Next, abandon generation if we went over our steps limit.
+     */
+    if (*steps <= 0)
+       return FALSE;
+    (*steps)--;
+
+    /*
+     * Otherwise, there must be at least one space. Find the most
+     * constrained space, using the `r' field as a tie-breaker.
+     */
+    bestm = cr+1;                     /* so that any space will beat it */
+    bestr = 0;
+    used = ~0;
+    i = sx = sy = -1;
+    for (j = 0; j < usage->nspaces; j++) {
+       int x = usage->spaces[j].x, y = usage->spaces[j].y;
+       unsigned int used_xy;
+       int m;
+
+       m = usage->blocks->whichblock[y*cr+x];
+       used_xy = usage->row[y] | usage->col[x] | usage->blk[m];
+       if (usage->cge != NULL)
+           used_xy |= usage->cge[usage->kblocks->whichblock[y*cr+x]];
+       if (usage->cge != NULL)
+           used_xy |= usage->cge[usage->kblocks->whichblock[y*cr+x]];
+       if (usage->diag != NULL) {
+           if (ondiag0(y*cr+x))
+               used_xy |= usage->diag[0];
+           if (ondiag1(y*cr+x))
+               used_xy |= usage->diag[1];
+       }
+
+       /*
+        * Find the number of digits that could go in this space.
+        */
+       m = 0;
+       for (n = 1; n <= cr; n++) {
+           unsigned int bit = 1 << n;
+           if ((used_xy & bit) == 0)
+               m++;
+       }
+       if (m < bestm || (m == bestm && usage->spaces[j].r < bestr)) {
+           bestm = m;
+           bestr = usage->spaces[j].r;
+           sx = x;
+           sy = y;
+           i = j;
+           used = used_xy;
+       }
+    }
+
+    /*
+     * Swap that square into the final place in the spaces array,
+     * so that decrementing nspaces will remove it from the list.
+     */
+    if (i != usage->nspaces-1) {
+       struct gridgen_coord t;
+       t = usage->spaces[usage->nspaces-1];
+       usage->spaces[usage->nspaces-1] = usage->spaces[i];
+       usage->spaces[i] = t;
+    }
+
+    /*
+     * Now we've decided which square to start our recursion at,
+     * simply go through all possible values, shuffling them
+     * randomly first if necessary.
+     */
+    digits = snewn(bestm, int);
+
+    j = 0;
+    for (n = 1; n <= cr; n++) {
+       unsigned int bit = 1 << n;
+
+       if ((used & bit) == 0)
+           digits[j++] = n;
+    }
+
+    if (usage->rs)
+       shuffle(digits, j, sizeof(*digits), usage->rs);
+
+    /* And finally, go through the digit list and actually recurse. */
+    ret = FALSE;
+    for (i = 0; i < j; i++) {
+       n = digits[i];
+
+       /* Update the usage structure to reflect the placing of this digit. */
+       gridgen_place(usage, sx, sy, n);
+       usage->nspaces--;
+
+       /* Call the solver recursively. Stop when we find a solution. */
+       if (gridgen_real(usage, grid, steps)) {
+            ret = TRUE;
+           break;
+       }
+
+       /* Revert the usage structure. */
+       gridgen_remove(usage, sx, sy, n);
+       usage->nspaces++;
+    }
+
+    sfree(digits);
+    return ret;
+}
+
+/*
+ * Entry point to generator. You give it parameters and a starting
+ * grid, which is simply an array of cr*cr digits.
+ */
+static int gridgen(int cr, struct block_structure *blocks,
+                  struct block_structure *kblocks, int xtype,
+                  digit *grid, random_state *rs, int maxsteps)
+{
+    struct gridgen_usage *usage;
+    int x, y, ret;
+
+    /*
+     * Clear the grid to start with.
+     */
+    memset(grid, 0, cr*cr);
+
+    /*
+     * Create a gridgen_usage structure.
+     */
+    usage = snew(struct gridgen_usage);
+
+    usage->cr = cr;
+    usage->blocks = blocks;
+
+    usage->grid = grid;
+
+    usage->row = snewn(cr, unsigned int);
+    usage->col = snewn(cr, unsigned int);
+    usage->blk = snewn(cr, unsigned int);
+    if (kblocks != NULL) {
+       usage->kblocks = kblocks;
+       usage->cge = snewn(usage->kblocks->nr_blocks, unsigned int);
+       memset(usage->cge, FALSE, kblocks->nr_blocks * sizeof *usage->cge);
+    } else {
+       usage->cge = NULL;
+    }
+
+    memset(usage->row, 0, cr * sizeof *usage->row);
+    memset(usage->col, 0, cr * sizeof *usage->col);
+    memset(usage->blk, 0, cr * sizeof *usage->blk);
+
+    if (xtype) {
+       usage->diag = snewn(2, unsigned int);
+       memset(usage->diag, 0, 2 * sizeof *usage->diag);
+    } else {
+       usage->diag = NULL;
+    }
+
+    /*
+     * Begin by filling in the whole top row with randomly chosen
+     * numbers. This cannot introduce any bias or restriction on
+     * the available grids, since we already know those numbers
+     * are all distinct so all we're doing is choosing their
+     * labels.
+     */
+    for (x = 0; x < cr; x++)
+       grid[x] = x+1;
+    shuffle(grid, cr, sizeof(*grid), rs);
+    for (x = 0; x < cr; x++)
+       gridgen_place(usage, x, 0, grid[x]);
+
+    usage->spaces = snewn(cr * cr, struct gridgen_coord);
+    usage->nspaces = 0;
+
+    usage->rs = rs;
+
+    /*
+     * Initialise the list of grid spaces, taking care to leave
+     * out the row I've already filled in above.
+     */
+    for (y = 1; y < cr; y++) {
+       for (x = 0; x < cr; x++) {
+            usage->spaces[usage->nspaces].x = x;
+            usage->spaces[usage->nspaces].y = y;
+            usage->spaces[usage->nspaces].r = random_bits(rs, 31);
+            usage->nspaces++;
+       }
+    }
+
+    /*
+     * Run the real generator function.
+     */
+    ret = gridgen_real(usage, grid, &maxsteps);
+
+    /*
+     * Clean up the usage structure now we have our answer.
+     */
+    sfree(usage->spaces);
+    sfree(usage->cge);
+    sfree(usage->blk);
+    sfree(usage->col);
+    sfree(usage->row);
+    sfree(usage);
+
+    return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * End of grid generator code.
+ */
+
+static int check_killer_cage_sum(struct block_structure *kblocks,
+                                 digit *kgrid, digit *grid, int blk)
+{
+    /*
+     * Returns: -1 if the cage has any empty square; 0 if all squares
+     * are full but the sum is wrong; +1 if all squares are full and
+     * they have the right sum.
+     *
+     * Does not check uniqueness of numbers within the cage; that's
+     * done elsewhere (because in error highlighting it needs to be
+     * detected separately so as to flag the error in a visually
+     * different way).
+     */
+    int n_squares = kblocks->nr_squares[blk];
+    int sum = 0, clue = 0;
+    int i;
+
+    for (i = 0; i < n_squares; i++) {
+        int xy = kblocks->blocks[blk][i];
+
+        if (grid[xy] == 0)
+            return -1;
+        sum += grid[xy];
+
+        if (kgrid[xy]) {
+            assert(clue == 0);
+            clue = kgrid[xy];
+        }
+    }
+
+    assert(clue != 0);
+    return sum == clue;
+}
+
+/*
+ * Check whether a grid contains a valid complete puzzle.
+ */
+static int check_valid(int cr, struct block_structure *blocks,
+                      struct block_structure *kblocks,
+                       digit *kgrid, int xtype, digit *grid)
+{
+    unsigned char *used;
+    int x, y, i, j, n;
+
+    used = snewn(cr, unsigned char);
+
+    /*
+     * Check that each row contains precisely one of everything.
+     */
+    for (y = 0; y < cr; y++) {
+       memset(used, FALSE, cr);
+       for (x = 0; x < cr; x++)
+           if (grid[y*cr+x] > 0 && grid[y*cr+x] <= cr)
+               used[grid[y*cr+x]-1] = TRUE;
+       for (n = 0; n < cr; n++)
+           if (!used[n]) {
+               sfree(used);
+               return FALSE;
+           }
+    }
+
+    /*
+     * Check that each column contains precisely one of everything.
+     */
+    for (x = 0; x < cr; x++) {
+       memset(used, FALSE, cr);
+       for (y = 0; y < cr; y++)
+           if (grid[y*cr+x] > 0 && grid[y*cr+x] <= cr)
+               used[grid[y*cr+x]-1] = TRUE;
+       for (n = 0; n < cr; n++)
+           if (!used[n]) {
+               sfree(used);
+               return FALSE;
+           }
+    }
+
+    /*
+     * Check that each block contains precisely one of everything.
+     */
+    for (i = 0; i < cr; i++) {
+       memset(used, FALSE, cr);
+       for (j = 0; j < cr; j++)
+           if (grid[blocks->blocks[i][j]] > 0 &&
+               grid[blocks->blocks[i][j]] <= cr)
+               used[grid[blocks->blocks[i][j]]-1] = TRUE;
+       for (n = 0; n < cr; n++)
+           if (!used[n]) {
+               sfree(used);
+               return FALSE;
+           }
+    }
+
+    /*
+     * Check that each Killer cage, if any, contains at most one of
+     * everything. If we also know the clues for those cages (which we
+     * might not, when this function is called early in puzzle
+     * generation), we also check that they all have the right sum.
+     */
+    if (kblocks) {
+       for (i = 0; i < kblocks->nr_blocks; i++) {
+           memset(used, FALSE, cr);
+           for (j = 0; j < kblocks->nr_squares[i]; j++)
+               if (grid[kblocks->blocks[i][j]] > 0 &&
+                   grid[kblocks->blocks[i][j]] <= cr) {
+                   if (used[grid[kblocks->blocks[i][j]]-1]) {
+                       sfree(used);
+                       return FALSE;
+                   }
+                   used[grid[kblocks->blocks[i][j]]-1] = TRUE;
+               }
+
+            if (kgrid && check_killer_cage_sum(kblocks, kgrid, grid, i) != 1) {
+                sfree(used);
+                return FALSE;
+            }
+       }
+    }
+
+    /*
+     * Check that each diagonal contains precisely one of everything.
+     */
+    if (xtype) {
+       memset(used, FALSE, cr);
+       for (i = 0; i < cr; i++)
+           if (grid[diag0(i)] > 0 && grid[diag0(i)] <= cr)
+               used[grid[diag0(i)]-1] = TRUE;
+       for (n = 0; n < cr; n++)
+           if (!used[n]) {
+               sfree(used);
+               return FALSE;
+           }
+       for (i = 0; i < cr; i++)
+           if (grid[diag1(i)] > 0 && grid[diag1(i)] <= cr)
+               used[grid[diag1(i)]-1] = TRUE;
+       for (n = 0; n < cr; n++)
+           if (!used[n]) {
+               sfree(used);
+               return FALSE;
+           }
+    }
+
+    sfree(used);
+    return TRUE;
+}
+
+static int symmetries(const game_params *params, int x, int y,
+                      int *output, int s)
+{
+    int c = params->c, r = params->r, cr = c*r;
+    int i = 0;
+
+#define ADD(x,y) (*output++ = (x), *output++ = (y), i++)
+
+    ADD(x, y);
+
+    switch (s) {
+      case SYMM_NONE:
+       break;                         /* just x,y is all we need */
+      case SYMM_ROT2:
+        ADD(cr - 1 - x, cr - 1 - y);
+        break;
+      case SYMM_ROT4:
+        ADD(cr - 1 - y, x);
+        ADD(y, cr - 1 - x);
+        ADD(cr - 1 - x, cr - 1 - y);
+        break;
+      case SYMM_REF2:
+        ADD(cr - 1 - x, y);
+        break;
+      case SYMM_REF2D:
+        ADD(y, x);
+        break;
+      case SYMM_REF4:
+        ADD(cr - 1 - x, y);
+        ADD(x, cr - 1 - y);
+        ADD(cr - 1 - x, cr - 1 - y);
+        break;
+      case SYMM_REF4D:
+        ADD(y, x);
+        ADD(cr - 1 - x, cr - 1 - y);
+        ADD(cr - 1 - y, cr - 1 - x);
+        break;
+      case SYMM_REF8:
+        ADD(cr - 1 - x, y);
+        ADD(x, cr - 1 - y);
+        ADD(cr - 1 - x, cr - 1 - y);
+        ADD(y, x);
+        ADD(y, cr - 1 - x);
+        ADD(cr - 1 - y, x);
+        ADD(cr - 1 - y, cr - 1 - x);
+        break;
+    }
+
+#undef ADD
+
+    return i;
+}
+
+static char *encode_solve_move(int cr, digit *grid)
+{
+    int i, len;
+    char *ret, *p, *sep;
+
+    /*
+     * It's surprisingly easy to work out _exactly_ how long this
+     * string needs to be. To decimal-encode all the numbers from 1
+     * to n:
+     * 
+     *  - every number has a units digit; total is n.
+     *  - all numbers above 9 have a tens digit; total is max(n-9,0).
+     *  - all numbers above 99 have a hundreds digit; total is max(n-99,0).
+     *  - and so on.
+     */
+    len = 0;
+    for (i = 1; i <= cr; i *= 10)
+       len += max(cr - i + 1, 0);
+    len += cr;                /* don't forget the commas */
+    len *= cr;                /* there are cr rows of these */
+
+    /*
+     * Now len is one bigger than the total size of the
+     * comma-separated numbers (because we counted an
+     * additional leading comma). We need to have a leading S
+     * and a trailing NUL, so we're off by one in total.
+     */
+    len++;
+
+    ret = snewn(len, char);
+    p = ret;
+    *p++ = 'S';
+    sep = "";
+    for (i = 0; i < cr*cr; i++) {
+       p += sprintf(p, "%s%d", sep, grid[i]);
+       sep = ",";
+    }
+    *p++ = '\0';
+    assert(p - ret == len);
+
+    return ret;
+}
+
+static void dsf_to_blocks(int *dsf, struct block_structure *blocks,
+                         int min_expected, int max_expected)
+{
+    int cr = blocks->c * blocks->r, area = cr * cr;
+    int i, nb = 0;
+
+    for (i = 0; i < area; i++)
+       blocks->whichblock[i] = -1;
+    for (i = 0; i < area; i++) {
+       int j = dsf_canonify(dsf, i);
+       if (blocks->whichblock[j] < 0)
+           blocks->whichblock[j] = nb++;
+       blocks->whichblock[i] = blocks->whichblock[j];
+    }
+    assert(nb >= min_expected && nb <= max_expected);
+    blocks->nr_blocks = nb;
+}
+
+static void make_blocks_from_whichblock(struct block_structure *blocks)
+{
+    int i;
+
+    for (i = 0; i < blocks->nr_blocks; i++) {
+       blocks->blocks[i][blocks->max_nr_squares-1] = 0;
+       blocks->nr_squares[i] = 0;
+    }
+    for (i = 0; i < blocks->area; i++) {
+       int b = blocks->whichblock[i];
+       int j = blocks->blocks[b][blocks->max_nr_squares-1]++;
+       assert(j < blocks->max_nr_squares);
+       blocks->blocks[b][j] = i;
+       blocks->nr_squares[b]++;
+    }
+}
+
+static char *encode_block_structure_desc(char *p, struct block_structure *blocks)
+{
+    int i, currrun = 0;
+    int c = blocks->c, r = blocks->r, cr = c * r;
+
+    /*
+     * Encode the block structure. We do this by encoding
+     * the pattern of dividing lines: first we iterate
+     * over the cr*(cr-1) internal vertical grid lines in
+     * ordinary reading order, then over the cr*(cr-1)
+     * internal horizontal ones in transposed reading
+     * order.
+     * 
+     * We encode the number of non-lines between the
+     * lines; _ means zero (two adjacent divisions), a
+     * means 1, ..., y means 25, and z means 25 non-lines
+     * _and no following line_ (so that za means 26, zb 27
+     * etc).
+     */
+    for (i = 0; i <= 2*cr*(cr-1); i++) {
+       int x, y, p0, p1, edge;
+
+       if (i == 2*cr*(cr-1)) {
+           edge = TRUE;       /* terminating virtual edge */
+       } else {
+           if (i < cr*(cr-1)) {
+               y = i/(cr-1);
+               x = i%(cr-1);
+               p0 = y*cr+x;
+               p1 = y*cr+x+1;
+           } else {
+               x = i/(cr-1) - cr;
+               y = i%(cr-1);
+               p0 = y*cr+x;
+               p1 = (y+1)*cr+x;
+           }
+           edge = (blocks->whichblock[p0] != blocks->whichblock[p1]);
+       }
+
+       if (edge) {
+           while (currrun > 25)
+               *p++ = 'z', currrun -= 25;
+           if (currrun)
+               *p++ = 'a'-1 + currrun;
+           else
+               *p++ = '_';
+           currrun = 0;
+       } else
+           currrun++;
+    }
+    return p;
+}
+
+static char *encode_grid(char *desc, digit *grid, int area)
+{
+    int run, i;
+    char *p = desc;
+
+    run = 0;
+    for (i = 0; i <= area; i++) {
+       int n = (i < area ? grid[i] : -1);
+
+       if (!n)
+           run++;
+       else {
+           if (run) {
+               while (run > 0) {
+                   int c = 'a' - 1 + run;
+                   if (run > 26)
+                       c = 'z';
+                   *p++ = c;
+                   run -= c - ('a' - 1);
+               }
+           } else {
+               /*
+                * If there's a number in the very top left or
+                * bottom right, there's no point putting an
+                * unnecessary _ before or after it.
+                */
+               if (p > desc && n > 0)
+                   *p++ = '_';
+           }
+           if (n > 0)
+               p += sprintf(p, "%d", n);
+           run = 0;
+       }
+    }
+    return p;
+}
+
+/*
+ * Conservatively stimate the number of characters required for
+ * encoding a grid of a certain area.
+ */
+static int grid_encode_space (int area)
+{
+    int t, count;
+    for (count = 1, t = area; t > 26; t -= 26)
+       count++;
+    return count * area;
+}
+
+/*
+ * Conservatively stimate the number of characters required for
+ * encoding a given blocks structure.
+ */
+static int blocks_encode_space(struct block_structure *blocks)
+{
+    int cr = blocks->c * blocks->r, area = cr * cr;
+    return grid_encode_space(area);
+}
+
+static char *encode_puzzle_desc(const game_params *params, digit *grid,
+                               struct block_structure *blocks,
+                               digit *kgrid,
+                               struct block_structure *kblocks)
+{
+    int c = params->c, r = params->r, cr = c*r;
+    int area = cr*cr;
+    char *p, *desc;
+    int space;
+
+    space = grid_encode_space(area) + 1;
+    if (r == 1)
+       space += blocks_encode_space(blocks) + 1;
+    if (params->killer) {
+       space += blocks_encode_space(kblocks) + 1;
+       space += grid_encode_space(area) + 1;
+    }
+    desc = snewn(space, char);
+    p = encode_grid(desc, grid, area);
+
+    if (r == 1) {
+       *p++ = ',';
+       p = encode_block_structure_desc(p, blocks);
+    }
+    if (params->killer) {
+       *p++ = ',';
+       p = encode_block_structure_desc(p, kblocks);
+       *p++ = ',';
+       p = encode_grid(p, kgrid, area);
+    }
+    assert(p - desc < space);
+    *p++ = '\0';
+    desc = sresize(desc, p - desc, char);
+
+    return desc;
+}
+
+static void merge_blocks(struct block_structure *b, int n1, int n2)
+{
+    int i;
+    /* Move data towards the lower block number.  */
+    if (n2 < n1) {
+       int t = n2;
+       n2 = n1;
+       n1 = t;
+    }
+
+    /* Merge n2 into n1, and move the last block into n2's position.  */
+    for (i = 0; i < b->nr_squares[n2]; i++)
+       b->whichblock[b->blocks[n2][i]] = n1;
+    memcpy(b->blocks[n1] + b->nr_squares[n1], b->blocks[n2],
+          b->nr_squares[n2] * sizeof **b->blocks);
+    b->nr_squares[n1] += b->nr_squares[n2];
+
+    n1 = b->nr_blocks - 1;
+    if (n2 != n1) {
+       memcpy(b->blocks[n2], b->blocks[n1],
+              b->nr_squares[n1] * sizeof **b->blocks);
+       for (i = 0; i < b->nr_squares[n1]; i++)
+           b->whichblock[b->blocks[n1][i]] = n2;
+       b->nr_squares[n2] = b->nr_squares[n1];
+    }
+    b->nr_blocks = n1;
+}
+
+static int merge_some_cages(struct block_structure *b, int cr, int area,
+                            digit *grid, random_state *rs)
+{
+    /*
+     * Make a list of all the pairs of adjacent blocks.
+     */
+    int i, j, k;
+    struct pair {
+       int b1, b2;
+    } *pairs;
+    int npairs;
+
+    pairs = snewn(b->nr_blocks * b->nr_blocks, struct pair);
+    npairs = 0;
+
+    for (i = 0; i < b->nr_blocks; i++) {
+       for (j = i+1; j < b->nr_blocks; j++) {
+
+           /*
+            * Rule the merger out of consideration if it's
+            * obviously not viable.
+            */
+           if (b->nr_squares[i] + b->nr_squares[j] > b->max_nr_squares)
+               continue;              /* we couldn't merge these anyway */
+
+           /*
+            * See if these two blocks have a pair of squares
+            * adjacent to each other.
+            */
+           for (k = 0; k < b->nr_squares[i]; k++) {
+               int xy = b->blocks[i][k];
+               int y = xy / cr, x = xy % cr;
+               if ((y   > 0  && b->whichblock[xy - cr] == j) ||
+                   (y+1 < cr && b->whichblock[xy + cr] == j) ||
+                   (x   > 0  && b->whichblock[xy -  1] == j) ||
+                   (x+1 < cr && b->whichblock[xy +  1] == j)) {
+                   /*
+                    * Yes! Add this pair to our list.
+                    */
+                   pairs[npairs].b1 = i;
+                   pairs[npairs].b2 = j;
+                   break;
+               }
+           }
+       }
+    }
+
+    /*
+     * Now go through that list in random order until we find a pair
+     * of blocks we can merge.
+     */
+    while (npairs > 0) {
+       int n1, n2;
+       unsigned int digits_found;
+
+       /*
+        * Pick a random pair, and remove it from the list.
+        */
+       i = random_upto(rs, npairs);
+       n1 = pairs[i].b1;
+       n2 = pairs[i].b2;
+       if (i != npairs-1)
+           pairs[i] = pairs[npairs-1];
+       npairs--;
+
+       /* Guarantee that the merged cage would still be a region.  */
+       digits_found = 0;
+       for (i = 0; i < b->nr_squares[n1]; i++)
+           digits_found |= 1 << grid[b->blocks[n1][i]];
+       for (i = 0; i < b->nr_squares[n2]; i++)
+           if (digits_found & (1 << grid[b->blocks[n2][i]]))
+               break;
+       if (i != b->nr_squares[n2])
+           continue;
+
+       /*
+        * Got one! Do the merge.
+        */
+       merge_blocks(b, n1, n2);
+       sfree(pairs);
+       return TRUE;
+    }
+
+    sfree(pairs);
+    return FALSE;
+}
+
+static void compute_kclues(struct block_structure *cages, digit *kclues,
+                          digit *grid, int area)
+{
+    int i;
+    memset(kclues, 0, area * sizeof *kclues);
+    for (i = 0; i < cages->nr_blocks; i++) {
+       int j, sum = 0;
+       for (j = 0; j < area; j++)
+           if (cages->whichblock[j] == i)
+               sum += grid[j];
+       for (j = 0; j < area; j++)
+           if (cages->whichblock[j] == i)
+               break;
+       assert (j != area);
+       kclues[j] = sum;
+    }
+}
+
+static struct block_structure *gen_killer_cages(int cr, random_state *rs,
+                                               int remove_singletons)
+{
+    int nr;
+    int x, y, area = cr * cr;
+    int n_singletons = 0;
+    struct block_structure *b = alloc_block_structure (1, cr, area, cr, area);
+
+    for (x = 0; x < area; x++)
+       b->whichblock[x] = -1;
+    nr = 0;
+    for (y = 0; y < cr; y++)
+       for (x = 0; x < cr; x++) {
+           int rnd;
+           int xy = y*cr+x;
+           if (b->whichblock[xy] != -1)
+               continue;
+           b->whichblock[xy] = nr;
+
+           rnd = random_bits(rs, 4);
+           if (xy + 1 < area && (rnd >= 4 || (!remove_singletons && rnd >= 1))) {
+               int xy2 = xy + 1;
+               if (x + 1 == cr || b->whichblock[xy2] != -1 ||
+                   (xy + cr < area && random_bits(rs, 1) == 0))
+                   xy2 = xy + cr;
+               if (xy2 >= area)
+                   n_singletons++;
+               else
+                   b->whichblock[xy2] = nr;
+           } else
+               n_singletons++;
+           nr++;
+       }
+
+    b->nr_blocks = nr;
+    make_blocks_from_whichblock(b);
+
+    for (x = y = 0; x < b->nr_blocks; x++)
+       if (b->nr_squares[x] == 1)
+           y++;
+    assert(y == n_singletons);
+
+    if (n_singletons > 0 && remove_singletons) {
+       int n;
+       for (n = 0; n < b->nr_blocks;) {
+           int xy, x, y, xy2, other;
+           if (b->nr_squares[n] > 1) {
+               n++;
+               continue;
+           }
+           xy = b->blocks[n][0];
+           x = xy % cr;
+           y = xy / cr;
+           if (xy + 1 == area)
+               xy2 = xy - 1;
+           else if (x + 1 < cr && (y + 1 == cr || random_bits(rs, 1) == 0))
+               xy2 = xy + 1;
+           else
+               xy2 = xy + cr;
+           other = b->whichblock[xy2];
+
+           if (b->nr_squares[other] == 1)
+               n_singletons--;
+           n_singletons--;
+           merge_blocks(b, n, other);
+           if (n < other)
+               n++;
+       }
+       assert(n_singletons == 0);
+    }
+    return b;
+}
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                          char **aux, int interactive)
+{
+    int c = params->c, r = params->r, cr = c*r;
+    int area = cr*cr;
+    struct block_structure *blocks, *kblocks;
+    digit *grid, *grid2, *kgrid;
+    struct xy { int x, y; } *locs;
+    int nlocs;
+    char *desc;
+    int coords[16], ncoords;
+    int x, y, i, j;
+    struct difficulty dlev;
+
+    precompute_sum_bits();
+
+    /*
+     * Adjust the maximum difficulty level to be consistent with
+     * the puzzle size: all 2x2 puzzles appear to be Trivial
+     * (DIFF_BLOCK) so we cannot hold out for even a Basic
+     * (DIFF_SIMPLE) one.
+     */
+    dlev.maxdiff = params->diff;
+    dlev.maxkdiff = params->kdiff;
+    if (c == 2 && r == 2)
+        dlev.maxdiff = DIFF_BLOCK;
+
+    grid = snewn(area, digit);
+    locs = snewn(area, struct xy);
+    grid2 = snewn(area, digit);
+
+    blocks = alloc_block_structure (c, r, area, cr, cr);
+
+    kblocks = NULL;
+    kgrid = (params->killer) ? snewn(area, digit) : NULL;
+
+#ifdef STANDALONE_SOLVER
+    assert(!"This should never happen, so we don't need to create blocknames");
+#endif
+
+    /*
+     * Loop until we get a grid of the required difficulty. This is
+     * nasty, but it seems to be unpleasantly hard to generate
+     * difficult grids otherwise.
+     */
+    while (1) {
+        /*
+         * Generate a random solved state, starting by
+         * constructing the block structure.
+         */
+       if (r == 1) {                  /* jigsaw mode */
+           int *dsf = divvy_rectangle(cr, cr, cr, rs);
+
+           dsf_to_blocks (dsf, blocks, cr, cr);
+
+           sfree(dsf);
+       } else {                       /* basic Sudoku mode */
+           for (y = 0; y < cr; y++)
+               for (x = 0; x < cr; x++)
+                   blocks->whichblock[y*cr+x] = (y/c) * c + (x/r);
+       }
+       make_blocks_from_whichblock(blocks);
+
+       if (params->killer) {
+            if (kblocks) free_block_structure(kblocks);
+           kblocks = gen_killer_cages(cr, rs, params->kdiff > DIFF_KSINGLE);
+       }
+
+        if (!gridgen(cr, blocks, kblocks, params->xtype, grid, rs, area*area))
+           continue;
+        assert(check_valid(cr, blocks, kblocks, NULL, params->xtype, grid));
+
+       /*
+        * Save the solved grid in aux.
+        */
+       {
+           /*
+            * We might already have written *aux the last time we
+            * went round this loop, in which case we should free
+            * the old aux before overwriting it with the new one.
+            */
+            if (*aux) {
+               sfree(*aux);
+            }
+
+            *aux = encode_solve_move(cr, grid);
+       }
+
+       /*
+        * Now we have a solved grid. For normal puzzles, we start removing
+        * things from it while preserving solubility.  Killer puzzles are
+        * different: we just pass the empty grid to the solver, and use
+        * the puzzle if it comes back solved.
+        */
+
+       if (params->killer) {
+           struct block_structure *good_cages = NULL;
+           struct block_structure *last_cages = NULL;
+           int ntries = 0;
+
+            memcpy(grid2, grid, area);
+
+           for (;;) {
+               compute_kclues(kblocks, kgrid, grid2, area);
+
+               memset(grid, 0, area * sizeof *grid);
+               solver(cr, blocks, kblocks, params->xtype, grid, kgrid, &dlev);
+               if (dlev.diff == dlev.maxdiff && dlev.kdiff == dlev.maxkdiff) {
+                   /*
+                    * We have one that matches our difficulty.  Store it for
+                    * later, but keep going.
+                    */
+                   if (good_cages)
+                       free_block_structure(good_cages);
+                   ntries = 0;
+                   good_cages = dup_block_structure(kblocks);
+                   if (!merge_some_cages(kblocks, cr, area, grid2, rs))
+                       break;
+               } else if (dlev.diff > dlev.maxdiff || dlev.kdiff > dlev.maxkdiff) {
+                   /*
+                    * Give up after too many tries and either use the good one we
+                    * found, or generate a new grid.
+                    */
+                   if (++ntries > 50)
+                       break;
+                   /*
+                    * The difficulty level got too high.  If we have a good
+                    * one, use it, otherwise go back to the last one that
+                    * was at a lower difficulty and restart the process from
+                    * there.
+                    */
+                   if (good_cages != NULL) {
+                       free_block_structure(kblocks);
+                       kblocks = dup_block_structure(good_cages);
+                       if (!merge_some_cages(kblocks, cr, area, grid2, rs))
+                           break;
+                   } else {
+                       if (last_cages == NULL)
+                           break;
+                       free_block_structure(kblocks);
+                       kblocks = last_cages;
+                       last_cages = NULL;
+                   }
+               } else {
+                   if (last_cages)
+                       free_block_structure(last_cages);
+                   last_cages = dup_block_structure(kblocks);
+                   if (!merge_some_cages(kblocks, cr, area, grid2, rs))
+                       break;
+               }
+           }
+           if (last_cages)
+               free_block_structure(last_cages);
+           if (good_cages != NULL) {
+               free_block_structure(kblocks);
+               kblocks = good_cages;
+               compute_kclues(kblocks, kgrid, grid2, area);
+               memset(grid, 0, area * sizeof *grid);
+               break;
+           }
+           continue;
+       }
+
+        /*
+         * Find the set of equivalence classes of squares permitted
+         * by the selected symmetry. We do this by enumerating all
+         * the grid squares which have no symmetric companion
+         * sorting lower than themselves.
+         */
+        nlocs = 0;
+        for (y = 0; y < cr; y++)
+            for (x = 0; x < cr; x++) {
+                int i = y*cr+x;
+                int j;
+
+                ncoords = symmetries(params, x, y, coords, params->symm);
+                for (j = 0; j < ncoords; j++)
+                    if (coords[2*j+1]*cr+coords[2*j] < i)
+                        break;
+                if (j == ncoords) {
+                    locs[nlocs].x = x;
+                    locs[nlocs].y = y;
+                    nlocs++;
+                }
+            }
+
+        /*
+         * Now shuffle that list.
+         */
+        shuffle(locs, nlocs, sizeof(*locs), rs);
+
+        /*
+         * Now loop over the shuffled list and, for each element,
+         * see whether removing that element (and its reflections)
+         * from the grid will still leave the grid soluble.
+         */
+        for (i = 0; i < nlocs; i++) {
+            x = locs[i].x;
+            y = locs[i].y;
+
+            memcpy(grid2, grid, area);
+            ncoords = symmetries(params, x, y, coords, params->symm);
+            for (j = 0; j < ncoords; j++)
+                grid2[coords[2*j+1]*cr+coords[2*j]] = 0;
+
+            solver(cr, blocks, kblocks, params->xtype, grid2, kgrid, &dlev);
+            if (dlev.diff <= dlev.maxdiff &&
+               (!params->killer || dlev.kdiff <= dlev.maxkdiff)) {
+                for (j = 0; j < ncoords; j++)
+                    grid[coords[2*j+1]*cr+coords[2*j]] = 0;
+            }
+        }
+
+        memcpy(grid2, grid, area);
+
+       solver(cr, blocks, kblocks, params->xtype, grid2, kgrid, &dlev);
+       if (dlev.diff == dlev.maxdiff &&
+           (!params->killer || dlev.kdiff == dlev.maxkdiff))
+           break;                     /* found one! */
+    }
+
+    sfree(grid2);
+    sfree(locs);
+
+    /*
+     * Now we have the grid as it will be presented to the user.
+     * Encode it in a game desc.
+     */
+    desc = encode_puzzle_desc(params, grid, blocks, kgrid, kblocks);
+
+    sfree(grid);
+    free_block_structure(blocks);
+    if (params->killer) {
+        free_block_structure(kblocks);
+        sfree(kgrid);
+    }
+
+    return desc;
+}
+
+static const char *spec_to_grid(const char *desc, digit *grid, int area)
+{
+    int i = 0;
+    while (*desc && *desc != ',') {
+        int n = *desc++;
+        if (n >= 'a' && n <= 'z') {
+            int run = n - 'a' + 1;
+            assert(i + run <= area);
+            while (run-- > 0)
+                grid[i++] = 0;
+        } else if (n == '_') {
+            /* do nothing */;
+        } else if (n > '0' && n <= '9') {
+            assert(i < area);
+            grid[i++] = atoi(desc-1);
+            while (*desc >= '0' && *desc <= '9')
+                desc++;
+        } else {
+            assert(!"We can't get here");
+        }
+    }
+    assert(i == area);
+    return desc;
+}
+
+/*
+ * Create a DSF from a spec found in *pdesc. Update this to point past the
+ * end of the block spec, and return an error string or NULL if everything
+ * is OK. The DSF is stored in *PDSF.
+ */
+static char *spec_to_dsf(const char **pdesc, int **pdsf, int cr, int area)
+{
+    const char *desc = *pdesc;
+    int pos = 0;
+    int *dsf;
+
+    *pdsf = dsf = snew_dsf(area);
+
+    while (*desc && *desc != ',') {
+       int c, adv;
+
+       if (*desc == '_')
+           c = 0;
+       else if (*desc >= 'a' && *desc <= 'z')
+           c = *desc - 'a' + 1;
+       else {
+           sfree(dsf);
+           return "Invalid character in game description";
+       }
+       desc++;
+
+       adv = (c != 26);               /* 'z' is a special case */
+
+       while (c-- > 0) {
+           int p0, p1;
+
+           /*
+            * Non-edge; merge the two dsf classes on either
+            * side of it.
+            */
+           if (pos >= 2*cr*(cr-1)) {
+                sfree(dsf);
+                return "Too much data in block structure specification";
+            }
+
+           if (pos < cr*(cr-1)) {
+               int y = pos/(cr-1);
+               int x = pos%(cr-1);
+               p0 = y*cr+x;
+               p1 = y*cr+x+1;
+           } else {
+               int x = pos/(cr-1) - cr;
+               int y = pos%(cr-1);
+               p0 = y*cr+x;
+               p1 = (y+1)*cr+x;
+           }
+           dsf_merge(dsf, p0, p1);
+
+           pos++;
+       }
+       if (adv)
+           pos++;
+    }
+    *pdesc = desc;
+
+    /*
+     * When desc is exhausted, we expect to have gone exactly
+     * one space _past_ the end of the grid, due to the dummy
+     * edge at the end.
+     */
+    if (pos != 2*cr*(cr-1)+1) {
+       sfree(dsf);
+       return "Not enough data in block structure specification";
+    }
+
+    return NULL;
+}
+
+static char *validate_grid_desc(const char **pdesc, int range, int area)
+{
+    const char *desc = *pdesc;
+    int squares = 0;
+    while (*desc && *desc != ',') {
+        int n = *desc++;
+        if (n >= 'a' && n <= 'z') {
+            squares += n - 'a' + 1;
+        } else if (n == '_') {
+            /* do nothing */;
+        } else if (n > '0' && n <= '9') {
+            int val = atoi(desc-1);
+            if (val < 1 || val > range)
+                return "Out-of-range number in game description";
+            squares++;
+            while (*desc >= '0' && *desc <= '9')
+                desc++;
+        } else
+            return "Invalid character in game description";
+    }
+
+    if (squares < area)
+        return "Not enough data to fill grid";
+
+    if (squares > area)
+        return "Too much data to fit in grid";
+    *pdesc = desc;
+    return NULL;
+}
+
+static char *validate_block_desc(const char **pdesc, int cr, int area,
+                                int min_nr_blocks, int max_nr_blocks,
+                                int min_nr_squares, int max_nr_squares)
+{
+    char *err;
+    int *dsf;
+
+    err = spec_to_dsf(pdesc, &dsf, cr, area);
+    if (err) {
+       return err;
+    }
+
+    if (min_nr_squares == max_nr_squares) {
+       assert(min_nr_blocks == max_nr_blocks);
+       assert(min_nr_blocks * min_nr_squares == area);
+    }
+    /*
+     * Now we've got our dsf. Verify that it matches
+     * expectations.
+     */
+    {
+       int *canons, *counts;
+       int i, j, c, ncanons = 0;
+
+       canons = snewn(max_nr_blocks, int);
+       counts = snewn(max_nr_blocks, int);
+
+       for (i = 0; i < area; i++) {
+           j = dsf_canonify(dsf, i);
+
+           for (c = 0; c < ncanons; c++)
+               if (canons[c] == j) {
+                   counts[c]++;
+                   if (counts[c] > max_nr_squares) {
+                       sfree(dsf);
+                       sfree(canons);
+                       sfree(counts);
+                       return "A jigsaw block is too big";
+                   }
+                   break;
+               }
+
+           if (c == ncanons) {
+               if (ncanons >= max_nr_blocks) {
+                   sfree(dsf);
+                   sfree(canons);
+                   sfree(counts);
+                   return "Too many distinct jigsaw blocks";
+               }
+               canons[ncanons] = j;
+               counts[ncanons] = 1;
+               ncanons++;
+           }
+       }
+
+       if (ncanons < min_nr_blocks) {
+           sfree(dsf);
+           sfree(canons);
+           sfree(counts);
+           return "Not enough distinct jigsaw blocks";
+       }
+       for (c = 0; c < ncanons; c++) {
+           if (counts[c] < min_nr_squares) {
+               sfree(dsf);
+               sfree(canons);
+               sfree(counts);
+               return "A jigsaw block is too small";
+           }
+       }
+       sfree(canons);
+       sfree(counts);
+    }
+
+    sfree(dsf);
+    return NULL;
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    int cr = params->c * params->r, area = cr*cr;
+    char *err;
+
+    err = validate_grid_desc(&desc, cr, area);
+    if (err)
+       return err;
+
+    if (params->r == 1) {
+       /*
+        * Now we expect a suffix giving the jigsaw block
+        * structure. Parse it and validate that it divides the
+        * grid into the right number of regions which are the
+        * right size.
+        */
+       if (*desc != ',')
+           return "Expected jigsaw block structure in game description";
+       desc++;
+       err = validate_block_desc(&desc, cr, area, cr, cr, cr, cr);
+       if (err)
+           return err;
+
+    }
+    if (params->killer) {
+       if (*desc != ',')
+           return "Expected killer block structure in game description";
+       desc++;
+       err = validate_block_desc(&desc, cr, area, cr, area, 2, cr);
+       if (err)
+           return err;
+       if (*desc != ',')
+           return "Expected killer clue grid in game description";
+       desc++;
+       err = validate_grid_desc(&desc, cr * area, area);
+       if (err)
+           return err;
+    }
+    if (*desc)
+       return "Unexpected data at end of game description";
+
+    return NULL;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    game_state *state = snew(game_state);
+    int c = params->c, r = params->r, cr = c*r, area = cr * cr;
+    int i;
+
+    precompute_sum_bits();
+
+    state->cr = cr;
+    state->xtype = params->xtype;
+    state->killer = params->killer;
+
+    state->grid = snewn(area, digit);
+    state->pencil = snewn(area * cr, unsigned char);
+    memset(state->pencil, 0, area * cr);
+    state->immutable = snewn(area, unsigned char);
+    memset(state->immutable, FALSE, area);
+
+    state->blocks = alloc_block_structure (c, r, area, cr, cr);
+
+    if (params->killer) {
+       state->kblocks = alloc_block_structure (c, r, area, cr, area);
+       state->kgrid = snewn(area, digit);
+    } else {
+       state->kblocks = NULL;
+       state->kgrid = NULL;
+    }
+    state->completed = state->cheated = FALSE;
+
+    desc = spec_to_grid(desc, state->grid, area);
+    for (i = 0; i < area; i++)
+       if (state->grid[i] != 0)
+           state->immutable[i] = TRUE;
+
+    if (r == 1) {
+       char *err;
+       int *dsf;
+       assert(*desc == ',');
+       desc++;
+       err = spec_to_dsf(&desc, &dsf, cr, area);
+       assert(err == NULL);
+       dsf_to_blocks(dsf, state->blocks, cr, cr);
+       sfree(dsf);
+    } else {
+       int x, y;
+
+       for (y = 0; y < cr; y++)
+           for (x = 0; x < cr; x++)
+               state->blocks->whichblock[y*cr+x] = (y/c) * c + (x/r);
+    }
+    make_blocks_from_whichblock(state->blocks);
+
+    if (params->killer) {
+       char *err;
+       int *dsf;
+       assert(*desc == ',');
+       desc++;
+       err = spec_to_dsf(&desc, &dsf, cr, area);
+       assert(err == NULL);
+       dsf_to_blocks(dsf, state->kblocks, cr, area);
+       sfree(dsf);
+       make_blocks_from_whichblock(state->kblocks);
+
+       assert(*desc == ',');
+       desc++;
+       desc = spec_to_grid(desc, state->kgrid, area);
+    }
+    assert(!*desc);
+
+#ifdef STANDALONE_SOLVER
+    /*
+     * Set up the block names for solver diagnostic output.
+     */
+    {
+       char *p = (char *)(state->blocks->blocknames + cr);
+
+       if (r == 1) {
+           for (i = 0; i < area; i++) {
+               int j = state->blocks->whichblock[i];
+               if (!state->blocks->blocknames[j]) {
+                   state->blocks->blocknames[j] = p;
+                   p += 1 + sprintf(p, "starting at (%d,%d)",
+                                    1 + i%cr, 1 + i/cr);
+               }
+           }
+       } else {
+           int bx, by;
+           for (by = 0; by < r; by++)
+               for (bx = 0; bx < c; bx++) {
+                   state->blocks->blocknames[by*c+bx] = p;
+                   p += 1 + sprintf(p, "(%d,%d)", bx+1, by+1);
+               }
+       }
+       assert(p - (char *)state->blocks->blocknames < (int)(cr*(sizeof(char *)+80)));
+       for (i = 0; i < cr; i++)
+           assert(state->blocks->blocknames[i]);
+    }
+#endif
+
+    return state;
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    game_state *ret = snew(game_state);
+    int cr = state->cr, area = cr * cr;
+
+    ret->cr = state->cr;
+    ret->xtype = state->xtype;
+    ret->killer = state->killer;
+
+    ret->blocks = state->blocks;
+    ret->blocks->refcount++;
+
+    ret->kblocks = state->kblocks;
+    if (ret->kblocks)
+       ret->kblocks->refcount++;
+
+    ret->grid = snewn(area, digit);
+    memcpy(ret->grid, state->grid, area);
+
+    if (state->killer) {
+       ret->kgrid = snewn(area, digit);
+       memcpy(ret->kgrid, state->kgrid, area);
+    } else
+       ret->kgrid = NULL;
+
+    ret->pencil = snewn(area * cr, unsigned char);
+    memcpy(ret->pencil, state->pencil, area * cr);
+
+    ret->immutable = snewn(area, unsigned char);
+    memcpy(ret->immutable, state->immutable, area);
+
+    ret->completed = state->completed;
+    ret->cheated = state->cheated;
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    free_block_structure(state->blocks);
+    if (state->kblocks)
+       free_block_structure(state->kblocks);
+
+    sfree(state->immutable);
+    sfree(state->pencil);
+    sfree(state->grid);
+    if (state->kgrid) sfree(state->kgrid);
+    sfree(state);
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *ai, char **error)
+{
+    int cr = state->cr;
+    char *ret;
+    digit *grid;
+    struct difficulty dlev;
+
+    /*
+     * If we already have the solution in ai, save ourselves some
+     * time.
+     */
+    if (ai)
+        return dupstr(ai);
+
+    grid = snewn(cr*cr, digit);
+    memcpy(grid, state->grid, cr*cr);
+    dlev.maxdiff = DIFF_RECURSIVE;
+    dlev.maxkdiff = DIFF_KINTERSECT;
+    solver(cr, state->blocks, state->kblocks, state->xtype, grid,
+          state->kgrid, &dlev);
+
+    *error = NULL;
+
+    if (dlev.diff == DIFF_IMPOSSIBLE)
+       *error = "No solution exists for this puzzle";
+    else if (dlev.diff == DIFF_AMBIGUOUS)
+       *error = "Multiple solutions exist for this puzzle";
+
+    if (*error) {
+        sfree(grid);
+        return NULL;
+    }
+
+    ret = encode_solve_move(cr, grid);
+
+    sfree(grid);
+
+    return ret;
+}
+
+static char *grid_text_format(int cr, struct block_structure *blocks,
+                             int xtype, digit *grid)
+{
+    int vmod, hmod;
+    int x, y;
+    int totallen, linelen, nlines;
+    char *ret, *p, ch;
+
+    /*
+     * For non-jigsaw Sudoku, we format in the way we always have,
+     * by having the digits unevenly spaced so that the dividing
+     * lines can fit in:
+     *
+     * . . | . .
+     * . . | . .
+     * ----+----
+     * . . | . .
+     * . . | . .
+     *
+     * For jigsaw puzzles, however, we must leave space between
+     * _all_ pairs of digits for an optional dividing line, so we
+     * have to move to the rather ugly
+     * 
+     * .   .   .   .
+     * ------+------
+     * .   . | .   .
+     *       +---+  
+     * .   . | . | .
+     * ------+   |  
+     * .   .   . | .
+     * 
+     * We deal with both cases using the same formatting code; we
+     * simply invent a vmod value such that there's a vertical
+     * dividing line before column i iff i is divisible by vmod
+     * (so it's r in the first case and 1 in the second), and hmod
+     * likewise for horizontal dividing lines.
+     */
+
+    if (blocks->r != 1) {
+       vmod = blocks->r;
+       hmod = blocks->c;
+    } else {
+       vmod = hmod = 1;
+    }
+
+    /*
+     * Line length: we have cr digits, each with a space after it,
+     * and (cr-1)/vmod dividing lines, each with a space after it.
+     * The final space is replaced by a newline, but that doesn't
+     * affect the length.
+     */
+    linelen = 2*(cr + (cr-1)/vmod);
+
+    /*
+     * Number of lines: we have cr rows of digits, and (cr-1)/hmod
+     * dividing rows.
+     */
+    nlines = cr + (cr-1)/hmod;
+
+    /*
+     * Allocate the space.
+     */
+    totallen = linelen * nlines;
+    ret = snewn(totallen+1, char);     /* leave room for terminating NUL */
+
+    /*
+     * Write the text.
+     */
+    p = ret;
+    for (y = 0; y < cr; y++) {
+       /*
+        * Row of digits.
+        */
+       for (x = 0; x < cr; x++) {
+           /*
+            * Digit.
+            */
+           digit d = grid[y*cr+x];
+
+            if (d == 0) {
+               /*
+                * Empty space: we usually write a dot, but we'll
+                * highlight spaces on the X-diagonals (in X mode)
+                * by using underscores instead.
+                */
+               if (xtype && (ondiag0(y*cr+x) || ondiag1(y*cr+x)))
+                   ch = '_';
+               else
+                   ch = '.';
+           } else if (d <= 9) {
+                ch = '0' + d;
+           } else {
+                ch = 'a' + d-10;
+           }
+
+           *p++ = ch;
+           if (x == cr-1) {
+               *p++ = '\n';
+               continue;
+           }
+           *p++ = ' ';
+
+           if ((x+1) % vmod)
+               continue;
+
+           /*
+            * Optional dividing line.
+            */
+           if (blocks->whichblock[y*cr+x] != blocks->whichblock[y*cr+x+1])
+               ch = '|';
+           else
+               ch = ' ';
+           *p++ = ch;
+           *p++ = ' ';
+       }
+       if (y == cr-1 || (y+1) % hmod)
+           continue;
+
+       /*
+        * Dividing row.
+        */
+       for (x = 0; x < cr; x++) {
+           int dwid;
+           int tl, tr, bl, br;
+
+           /*
+            * Division between two squares. This varies
+            * complicatedly in length.
+            */
+           dwid = 2;                  /* digit and its following space */
+           if (x == cr-1)
+               dwid--;                /* no following space at end of line */
+           if (x > 0 && x % vmod == 0)
+               dwid++;                /* preceding space after a divider */
+
+           if (blocks->whichblock[y*cr+x] != blocks->whichblock[(y+1)*cr+x])
+               ch = '-';
+           else
+               ch = ' ';
+
+           while (dwid-- > 0)
+               *p++ = ch;
+
+           if (x == cr-1) {
+               *p++ = '\n';
+               break;
+           }
+
+           if ((x+1) % vmod)
+               continue;
+
+           /*
+            * Corner square. This is:
+            *  - a space if all four surrounding squares are in
+            *    the same block
+            *  - a vertical line if the two left ones are in one
+            *    block and the two right in another
+            *  - a horizontal line if the two top ones are in one
+            *    block and the two bottom in another
+            *  - a plus sign in all other cases. (If we had a
+            *    richer character set available we could break
+            *    this case up further by doing fun things with
+            *    line-drawing T-pieces.)
+            */
+           tl = blocks->whichblock[y*cr+x];
+           tr = blocks->whichblock[y*cr+x+1];
+           bl = blocks->whichblock[(y+1)*cr+x];
+           br = blocks->whichblock[(y+1)*cr+x+1];
+
+           if (tl == tr && tr == bl && bl == br)
+               ch = ' ';
+           else if (tl == bl && tr == br)
+               ch = '|';
+           else if (tl == tr && bl == br)
+               ch = '-';
+           else
+               ch = '+';
+
+           *p++ = ch;
+       }
+    }
+
+    assert(p - ret == totallen);
+    *p = '\0';
+    return ret;
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    /*
+     * Formatting Killer puzzles as text is currently unsupported. I
+     * can't think of any sensible way of doing it which doesn't
+     * involve expanding the puzzle to such a large scale as to make
+     * it unusable.
+     */
+    if (params->killer)
+        return FALSE;
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    assert(!state->kblocks);
+    return grid_text_format(state->cr, state->blocks, state->xtype,
+                           state->grid);
+}
+
+struct game_ui {
+    /*
+     * These are the coordinates of the currently highlighted
+     * square on the grid, if hshow = 1.
+     */
+    int hx, hy;
+    /*
+     * This indicates whether the current highlight is a
+     * pencil-mark one or a real one.
+     */
+    int hpencil;
+    /*
+     * This indicates whether or not we're showing the highlight
+     * (used to be hx = hy = -1); important so that when we're
+     * using the cursor keys it doesn't keep coming back at a
+     * fixed position. When hshow = 1, pressing a valid number
+     * or letter key or Space will enter that number or letter in the grid.
+     */
+    int hshow;
+    /*
+     * This indicates whether we're using the highlight as a cursor;
+     * it means that it doesn't vanish on a keypress, and that it is
+     * allowed on immutable squares.
+     */
+    int hcursor;
+};
+
+static game_ui *new_ui(const game_state *state)
+{
+    game_ui *ui = snew(game_ui);
+
+    ui->hx = ui->hy = 0;
+    ui->hpencil = ui->hshow = ui->hcursor = 0;
+
+    return ui;
+}
+
+static void free_ui(game_ui *ui)
+{
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+    int cr = newstate->cr;
+    /*
+     * We prevent pencil-mode highlighting of a filled square, unless
+     * we're using the cursor keys. So if the user has just filled in
+     * a square which we had a pencil-mode highlight in (by Undo, or
+     * by Redo, or by Solve), then we cancel the highlight.
+     */
+    if (ui->hshow && ui->hpencil && !ui->hcursor &&
+        newstate->grid[ui->hy * cr + ui->hx] != 0) {
+        ui->hshow = 0;
+    }
+}
+
+struct game_drawstate {
+    int started;
+    int cr, xtype;
+    int tilesize;
+    digit *grid;
+    unsigned char *pencil;
+    unsigned char *hl;
+    /* This is scratch space used within a single call to game_redraw. */
+    int nregions, *entered_items;
+};
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    int cr = state->cr;
+    int tx, ty;
+    char buf[80];
+
+    button &= ~MOD_MASK;
+
+    tx = (x + TILE_SIZE - BORDER) / TILE_SIZE - 1;
+    ty = (y + TILE_SIZE - BORDER) / TILE_SIZE - 1;
+
+    if (tx >= 0 && tx < cr && ty >= 0 && ty < cr) {
+        if (button == LEFT_BUTTON) {
+            if (state->immutable[ty*cr+tx]) {
+                ui->hshow = 0;
+            } else if (tx == ui->hx && ty == ui->hy &&
+                       ui->hshow && ui->hpencil == 0) {
+                ui->hshow = 0;
+            } else {
+                ui->hx = tx;
+                ui->hy = ty;
+                ui->hshow = 1;
+                ui->hpencil = 0;
+            }
+            ui->hcursor = 0;
+            return "";                /* UI activity occurred */
+        }
+        if (button == RIGHT_BUTTON) {
+            /*
+             * Pencil-mode highlighting for non filled squares.
+             */
+            if (state->grid[ty*cr+tx] == 0) {
+                if (tx == ui->hx && ty == ui->hy &&
+                    ui->hshow && ui->hpencil) {
+                    ui->hshow = 0;
+                } else {
+                    ui->hpencil = 1;
+                    ui->hx = tx;
+                    ui->hy = ty;
+                    ui->hshow = 1;
+                }
+            } else {
+                ui->hshow = 0;
+            }
+            ui->hcursor = 0;
+            return "";                /* UI activity occurred */
+        }
+    }
+    if (IS_CURSOR_MOVE(button)) {
+        move_cursor(button, &ui->hx, &ui->hy, cr, cr, 0);
+        ui->hshow = ui->hcursor = 1;
+        return "";
+    }
+    if (ui->hshow &&
+        (button == CURSOR_SELECT)) {
+        ui->hpencil = 1 - ui->hpencil;
+        ui->hcursor = 1;
+        return "";
+    }
+
+    if (ui->hshow &&
+       ((button >= '0' && button <= '9' && button - '0' <= cr) ||
+        (button >= 'a' && button <= 'z' && button - 'a' + 10 <= cr) ||
+        (button >= 'A' && button <= 'Z' && button - 'A' + 10 <= cr) ||
+        button == CURSOR_SELECT2 || button == '\b')) {
+       int n = button - '0';
+       if (button >= 'A' && button <= 'Z')
+           n = button - 'A' + 10;
+       if (button >= 'a' && button <= 'z')
+           n = button - 'a' + 10;
+       if (button == CURSOR_SELECT2 || button == '\b')
+           n = 0;
+
+        /*
+         * Can't overwrite this square. This can only happen here
+         * if we're using the cursor keys.
+         */
+       if (state->immutable[ui->hy*cr+ui->hx])
+           return NULL;
+
+        /*
+         * Can't make pencil marks in a filled square. Again, this
+         * can only become highlighted if we're using cursor keys.
+         */
+        if (ui->hpencil && state->grid[ui->hy*cr+ui->hx])
+            return NULL;
+
+       sprintf(buf, "%c%d,%d,%d",
+               (char)(ui->hpencil && n > 0 ? 'P' : 'R'), ui->hx, ui->hy, n);
+
+        if (!ui->hcursor) ui->hshow = 0;
+
+       return dupstr(buf);
+    }
+
+    if (button == 'M' || button == 'm')
+        return dupstr("M");
+
+    return NULL;
+}
+
+static game_state *execute_move(const game_state *from, const char *move)
+{
+    int cr = from->cr;
+    game_state *ret;
+    int x, y, n;
+
+    if (move[0] == 'S') {
+       const char *p;
+
+       ret = dup_game(from);
+       ret->completed = ret->cheated = TRUE;
+
+       p = move+1;
+       for (n = 0; n < cr*cr; n++) {
+           ret->grid[n] = atoi(p);
+
+           if (!*p || ret->grid[n] < 1 || ret->grid[n] > cr) {
+               free_game(ret);
+               return NULL;
+           }
+
+           while (*p && isdigit((unsigned char)*p)) p++;
+           if (*p == ',') p++;
+       }
+
+       return ret;
+    } else if ((move[0] == 'P' || move[0] == 'R') &&
+       sscanf(move+1, "%d,%d,%d", &x, &y, &n) == 3 &&
+       x >= 0 && x < cr && y >= 0 && y < cr && n >= 0 && n <= cr) {
+
+       ret = dup_game(from);
+        if (move[0] == 'P' && n > 0) {
+            int index = (y*cr+x) * cr + (n-1);
+            ret->pencil[index] = !ret->pencil[index];
+        } else {
+            ret->grid[y*cr+x] = n;
+            memset(ret->pencil + (y*cr+x)*cr, 0, cr);
+
+            /*
+             * We've made a real change to the grid. Check to see
+             * if the game has been completed.
+             */
+            if (!ret->completed && check_valid(
+                    cr, ret->blocks, ret->kblocks, ret->kgrid,
+                    ret->xtype, ret->grid)) {
+                ret->completed = TRUE;
+            }
+        }
+       return ret;
+    } else if (move[0] == 'M') {
+       /*
+        * Fill in absolutely all pencil marks in unfilled squares,
+        * for those who like to play by the rigorous approach of
+        * starting off in that state and eliminating things.
+        */
+       ret = dup_game(from);
+        for (y = 0; y < cr; y++) {
+            for (x = 0; x < cr; x++) {
+                if (!ret->grid[y*cr+x]) {
+                    memset(ret->pencil + (y*cr+x)*cr, 1, cr);
+                }
+            }
+        }
+       return ret;
+    } else
+       return NULL;                   /* couldn't parse move string */
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+#define SIZE(cr) ((cr) * TILE_SIZE + 2*BORDER + 1)
+#define GETTILESIZE(cr, w) ( (double)(w-1) / (double)(cr+1) )
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    struct { int tilesize; } ads, *ds = &ads;
+    ads.tilesize = tilesize;
+
+    *x = SIZE(params->c * params->r);
+    *y = SIZE(params->c * params->r);
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+
+    frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
+
+    ret[COL_XDIAGONALS * 3 + 0] = 0.9F * ret[COL_BACKGROUND * 3 + 0];
+    ret[COL_XDIAGONALS * 3 + 1] = 0.9F * ret[COL_BACKGROUND * 3 + 1];
+    ret[COL_XDIAGONALS * 3 + 2] = 0.9F * ret[COL_BACKGROUND * 3 + 2];
+
+    ret[COL_GRID * 3 + 0] = 0.0F;
+    ret[COL_GRID * 3 + 1] = 0.0F;
+    ret[COL_GRID * 3 + 2] = 0.0F;
+
+    ret[COL_CLUE * 3 + 0] = 0.0F;
+    ret[COL_CLUE * 3 + 1] = 0.0F;
+    ret[COL_CLUE * 3 + 2] = 0.0F;
+
+    ret[COL_USER * 3 + 0] = 0.0F;
+    ret[COL_USER * 3 + 1] = 0.6F * ret[COL_BACKGROUND * 3 + 1];
+    ret[COL_USER * 3 + 2] = 0.0F;
+
+    ret[COL_HIGHLIGHT * 3 + 0] = 0.78F * ret[COL_BACKGROUND * 3 + 0];
+    ret[COL_HIGHLIGHT * 3 + 1] = 0.78F * ret[COL_BACKGROUND * 3 + 1];
+    ret[COL_HIGHLIGHT * 3 + 2] = 0.78F * ret[COL_BACKGROUND * 3 + 2];
+
+    ret[COL_ERROR * 3 + 0] = 1.0F;
+    ret[COL_ERROR * 3 + 1] = 0.0F;
+    ret[COL_ERROR * 3 + 2] = 0.0F;
+
+    ret[COL_PENCIL * 3 + 0] = 0.5F * ret[COL_BACKGROUND * 3 + 0];
+    ret[COL_PENCIL * 3 + 1] = 0.5F * ret[COL_BACKGROUND * 3 + 1];
+    ret[COL_PENCIL * 3 + 2] = ret[COL_BACKGROUND * 3 + 2];
+
+    ret[COL_KILLER * 3 + 0] = 0.5F * ret[COL_BACKGROUND * 3 + 0];
+    ret[COL_KILLER * 3 + 1] = 0.5F * ret[COL_BACKGROUND * 3 + 1];
+    ret[COL_KILLER * 3 + 2] = 0.1F * ret[COL_BACKGROUND * 3 + 2];
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    struct game_drawstate *ds = snew(struct game_drawstate);
+    int cr = state->cr;
+
+    ds->started = FALSE;
+    ds->cr = cr;
+    ds->xtype = state->xtype;
+    ds->grid = snewn(cr*cr, digit);
+    memset(ds->grid, cr+2, cr*cr);
+    ds->pencil = snewn(cr*cr*cr, digit);
+    memset(ds->pencil, 0, cr*cr*cr);
+    ds->hl = snewn(cr*cr, unsigned char);
+    memset(ds->hl, 0, cr*cr);
+    /*
+     * ds->entered_items needs one row of cr entries per entity in
+     * which digits may not be duplicated. That's one for each row,
+     * each column, each block, each diagonal, and each Killer cage.
+     */
+    ds->nregions = cr*3 + 2;
+    if (state->kblocks)
+       ds->nregions += state->kblocks->nr_blocks;
+    ds->entered_items = snewn(cr * ds->nregions, int);
+    ds->tilesize = 0;                  /* not decided yet */
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds->hl);
+    sfree(ds->pencil);
+    sfree(ds->grid);
+    sfree(ds->entered_items);
+    sfree(ds);
+}
+
+static void draw_number(drawing *dr, game_drawstate *ds,
+                        const game_state *state, int x, int y, int hl)
+{
+    int cr = state->cr;
+    int tx, ty, tw, th;
+    int cx, cy, cw, ch;
+    int col_killer = (hl & 32 ? COL_ERROR : COL_KILLER);
+    char str[20];
+
+    if (ds->grid[y*cr+x] == state->grid[y*cr+x] &&
+        ds->hl[y*cr+x] == hl &&
+        !memcmp(ds->pencil+(y*cr+x)*cr, state->pencil+(y*cr+x)*cr, cr))
+       return;                        /* no change required */
+
+    tx = BORDER + x * TILE_SIZE + 1 + GRIDEXTRA;
+    ty = BORDER + y * TILE_SIZE + 1 + GRIDEXTRA;
+
+    cx = tx;
+    cy = ty;
+    cw = tw = TILE_SIZE-1-2*GRIDEXTRA;
+    ch = th = TILE_SIZE-1-2*GRIDEXTRA;
+
+    if (x > 0 && state->blocks->whichblock[y*cr+x] == state->blocks->whichblock[y*cr+x-1])
+       cx -= GRIDEXTRA, cw += GRIDEXTRA;
+    if (x+1 < cr && state->blocks->whichblock[y*cr+x] == state->blocks->whichblock[y*cr+x+1])
+       cw += GRIDEXTRA;
+    if (y > 0 && state->blocks->whichblock[y*cr+x] == state->blocks->whichblock[(y-1)*cr+x])
+       cy -= GRIDEXTRA, ch += GRIDEXTRA;
+    if (y+1 < cr && state->blocks->whichblock[y*cr+x] == state->blocks->whichblock[(y+1)*cr+x])
+       ch += GRIDEXTRA;
+
+    clip(dr, cx, cy, cw, ch);
+
+    /* background needs erasing */
+    draw_rect(dr, cx, cy, cw, ch,
+             ((hl & 15) == 1 ? COL_HIGHLIGHT :
+              (ds->xtype && (ondiag0(y*cr+x) || ondiag1(y*cr+x))) ? COL_XDIAGONALS :
+              COL_BACKGROUND));
+
+    /*
+     * Draw the corners of thick lines in corner-adjacent squares,
+     * which jut into this square by one pixel.
+     */
+    if (x > 0 && y > 0 && state->blocks->whichblock[y*cr+x] != state->blocks->whichblock[(y-1)*cr+x-1])
+       draw_rect(dr, tx-GRIDEXTRA, ty-GRIDEXTRA, GRIDEXTRA, GRIDEXTRA, COL_GRID);
+    if (x+1 < cr && y > 0 && state->blocks->whichblock[y*cr+x] != state->blocks->whichblock[(y-1)*cr+x+1])
+       draw_rect(dr, tx+TILE_SIZE-1-2*GRIDEXTRA, ty-GRIDEXTRA, GRIDEXTRA, GRIDEXTRA, COL_GRID);
+    if (x > 0 && y+1 < cr && state->blocks->whichblock[y*cr+x] != state->blocks->whichblock[(y+1)*cr+x-1])
+       draw_rect(dr, tx-GRIDEXTRA, ty+TILE_SIZE-1-2*GRIDEXTRA, GRIDEXTRA, GRIDEXTRA, COL_GRID);
+    if (x+1 < cr && y+1 < cr && state->blocks->whichblock[y*cr+x] != state->blocks->whichblock[(y+1)*cr+x+1])
+       draw_rect(dr, tx+TILE_SIZE-1-2*GRIDEXTRA, ty+TILE_SIZE-1-2*GRIDEXTRA, GRIDEXTRA, GRIDEXTRA, COL_GRID);
+
+    /* pencil-mode highlight */
+    if ((hl & 15) == 2) {
+        int coords[6];
+        coords[0] = cx;
+        coords[1] = cy;
+        coords[2] = cx+cw/2;
+        coords[3] = cy;
+        coords[4] = cx;
+        coords[5] = cy+ch/2;
+        draw_polygon(dr, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT);
+    }
+
+    if (state->kblocks) {
+       int t = GRIDEXTRA * 3;
+       int kcx, kcy, kcw, kch;
+       int kl, kt, kr, kb;
+       int has_left = 0, has_right = 0, has_top = 0, has_bottom = 0;
+
+       /*
+        * In non-jigsaw mode, the Killer cages are placed at a
+        * fixed offset from the outer edge of the cell dividing
+        * lines, so that they look right whether those lines are
+        * thick or thin. In jigsaw mode, however, doing this will
+        * sometimes cause the cage outlines in adjacent squares to
+        * fail to match up with each other, so we must offset a
+        * fixed amount from the _centre_ of the cell dividing
+        * lines.
+        */
+       if (state->blocks->r == 1) {
+           kcx = tx;
+           kcy = ty;
+           kcw = tw;
+           kch = th;
+       } else {
+           kcx = cx;
+           kcy = cy;
+           kcw = cw;
+           kch = ch;
+       }
+       kl = kcx - 1;
+       kt = kcy - 1;
+       kr = kcx + kcw;
+       kb = kcy + kch;
+
+       /*
+        * First, draw the lines dividing this area from neighbouring
+        * different areas.
+        */
+       if (x == 0 || state->kblocks->whichblock[y*cr+x] != state->kblocks->whichblock[y*cr+x-1])
+           has_left = 1, kl += t;
+       if (x+1 >= cr || state->kblocks->whichblock[y*cr+x] != state->kblocks->whichblock[y*cr+x+1])
+           has_right = 1, kr -= t;
+       if (y == 0 || state->kblocks->whichblock[y*cr+x] != state->kblocks->whichblock[(y-1)*cr+x])
+           has_top = 1, kt += t;
+       if (y+1 >= cr || state->kblocks->whichblock[y*cr+x] != state->kblocks->whichblock[(y+1)*cr+x])
+           has_bottom = 1, kb -= t;
+       if (has_top)
+           draw_line(dr, kl, kt, kr, kt, col_killer);
+       if (has_bottom)
+           draw_line(dr, kl, kb, kr, kb, col_killer);
+       if (has_left)
+           draw_line(dr, kl, kt, kl, kb, col_killer);
+       if (has_right)
+           draw_line(dr, kr, kt, kr, kb, col_killer);
+       /*
+        * Now, take care of the corners (just as for the normal borders).
+        * We only need a corner if there wasn't a full edge.
+        */
+       if (x > 0 && y > 0 && !has_left && !has_top
+           && state->kblocks->whichblock[y*cr+x] != state->kblocks->whichblock[(y-1)*cr+x-1])
+       {
+           draw_line(dr, kl, kt + t, kl + t, kt + t, col_killer);
+           draw_line(dr, kl + t, kt, kl + t, kt + t, col_killer);
+       }
+       if (x+1 < cr && y > 0 && !has_right && !has_top
+           && state->kblocks->whichblock[y*cr+x] != state->kblocks->whichblock[(y-1)*cr+x+1])
+       {
+           draw_line(dr, kcx + kcw - t, kt + t, kcx + kcw, kt + t, col_killer);
+           draw_line(dr, kcx + kcw - t, kt, kcx + kcw - t, kt + t, col_killer);
+       }
+       if (x > 0 && y+1 < cr && !has_left && !has_bottom
+           && state->kblocks->whichblock[y*cr+x] != state->kblocks->whichblock[(y+1)*cr+x-1])
+       {
+           draw_line(dr, kl, kcy + kch - t, kl + t, kcy + kch - t, col_killer);
+           draw_line(dr, kl + t, kcy + kch - t, kl + t, kcy + kch, col_killer);
+       }
+       if (x+1 < cr && y+1 < cr && !has_right && !has_bottom
+           && state->kblocks->whichblock[y*cr+x] != state->kblocks->whichblock[(y+1)*cr+x+1])
+       {
+           draw_line(dr, kcx + kcw - t, kcy + kch - t, kcx + kcw - t, kcy + kch, col_killer);
+           draw_line(dr, kcx + kcw - t, kcy + kch - t, kcx + kcw, kcy + kch - t, col_killer);
+       }
+
+    }
+
+    if (state->killer && state->kgrid[y*cr+x]) {
+       sprintf (str, "%d", state->kgrid[y*cr+x]);
+       draw_text(dr, tx + GRIDEXTRA * 4, ty + GRIDEXTRA * 4 + TILE_SIZE/4,
+                 FONT_VARIABLE, TILE_SIZE/4, ALIGN_VNORMAL | ALIGN_HLEFT,
+                 col_killer, str);
+    }
+
+    /* new number needs drawing? */
+    if (state->grid[y*cr+x]) {
+       str[1] = '\0';
+       str[0] = state->grid[y*cr+x] + '0';
+       if (str[0] > '9')
+           str[0] += 'a' - ('9'+1);
+       draw_text(dr, tx + TILE_SIZE/2, ty + TILE_SIZE/2,
+                 FONT_VARIABLE, TILE_SIZE/2, ALIGN_VCENTRE | ALIGN_HCENTRE,
+                 state->immutable[y*cr+x] ? COL_CLUE : (hl & 16) ? COL_ERROR : COL_USER, str);
+    } else {
+        int i, j, npencil;
+       int pl, pr, pt, pb;
+       float bestsize;
+       int pw, ph, minph, pbest, fontsize;
+
+        /* Count the pencil marks required. */
+        for (i = npencil = 0; i < cr; i++)
+            if (state->pencil[(y*cr+x)*cr+i])
+               npencil++;
+       if (npencil) {
+
+           minph = 2;
+
+           /*
+            * Determine the bounding rectangle within which we're going
+            * to put the pencil marks.
+            */
+           /* Start with the whole square */
+           pl = tx + GRIDEXTRA;
+           pr = pl + TILE_SIZE - GRIDEXTRA;
+           pt = ty + GRIDEXTRA;
+           pb = pt + TILE_SIZE - GRIDEXTRA;
+           if (state->killer) {
+               /*
+                * Make space for the Killer cages. We do this
+                * unconditionally, for uniformity between squares,
+                * rather than making it depend on whether a Killer
+                * cage edge is actually present on any given side.
+                */
+               pl += GRIDEXTRA * 3;
+               pr -= GRIDEXTRA * 3;
+               pt += GRIDEXTRA * 3;
+               pb -= GRIDEXTRA * 3;
+               if (state->kgrid[y*cr+x] != 0) {
+                   /* Make further space for the Killer number. */
+                   pt += TILE_SIZE/4;
+                   /* minph--; */
+               }
+           }
+
+           /*
+            * We arrange our pencil marks in a grid layout, with
+            * the number of rows and columns adjusted to allow the
+            * maximum font size.
+            *
+            * So now we work out what the grid size ought to be.
+            */
+           bestsize = 0.0;
+           pbest = 0;
+           /* Minimum */
+           for (pw = 3; pw < max(npencil,4); pw++) {
+               float fw, fh, fs;
+
+               ph = (npencil + pw - 1) / pw;
+               ph = max(ph, minph);
+               fw = (pr - pl) / (float)pw;
+               fh = (pb - pt) / (float)ph;
+               fs = min(fw, fh);
+               if (fs > bestsize) {
+                   bestsize = fs;
+                   pbest = pw;
+               }
+           }
+           assert(pbest > 0);
+           pw = pbest;
+           ph = (npencil + pw - 1) / pw;
+           ph = max(ph, minph);
+
+           /*
+            * Now we've got our grid dimensions, work out the pixel
+            * size of a grid element, and round it to the nearest
+            * pixel. (We don't want rounding errors to make the
+            * grid look uneven at low pixel sizes.)
+            */
+           fontsize = min((pr - pl) / pw, (pb - pt) / ph);
+
+           /*
+            * Centre the resulting figure in the square.
+            */
+           pl = tx + (TILE_SIZE - fontsize * pw) / 2;
+           pt = ty + (TILE_SIZE - fontsize * ph) / 2;
+
+           /*
+            * And move it down a bit if it's collided with the
+            * Killer cage number.
+            */
+           if (state->killer && state->kgrid[y*cr+x] != 0) {
+               pt = max(pt, ty + GRIDEXTRA * 3 + TILE_SIZE/4);
+           }
+
+           /*
+            * Now actually draw the pencil marks.
+            */
+           for (i = j = 0; i < cr; i++)
+               if (state->pencil[(y*cr+x)*cr+i]) {
+                   int dx = j % pw, dy = j / pw;
+
+                   str[1] = '\0';
+                   str[0] = i + '1';
+                   if (str[0] > '9')
+                       str[0] += 'a' - ('9'+1);
+                   draw_text(dr, pl + fontsize * (2*dx+1) / 2,
+                             pt + fontsize * (2*dy+1) / 2,
+                             FONT_VARIABLE, fontsize,
+                             ALIGN_VCENTRE | ALIGN_HCENTRE, COL_PENCIL, str);
+                   j++;
+               }
+       }
+    }
+
+    unclip(dr);
+
+    draw_update(dr, cx, cy, cw, ch);
+
+    ds->grid[y*cr+x] = state->grid[y*cr+x];
+    memcpy(ds->pencil+(y*cr+x)*cr, state->pencil+(y*cr+x)*cr, cr);
+    ds->hl[y*cr+x] = hl;
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int cr = state->cr;
+    int x, y;
+
+    if (!ds->started) {
+       /*
+        * The initial contents of the window are not guaranteed
+        * and can vary with front ends. To be on the safe side,
+        * all games should start by drawing a big
+        * background-colour rectangle covering the whole window.
+        */
+       draw_rect(dr, 0, 0, SIZE(cr), SIZE(cr), COL_BACKGROUND);
+
+       /*
+        * Draw the grid. We draw it as a big thick rectangle of
+        * COL_GRID initially; individual calls to draw_number()
+        * will poke the right-shaped holes in it.
+        */
+       draw_rect(dr, BORDER-GRIDEXTRA, BORDER-GRIDEXTRA,
+                 cr*TILE_SIZE+1+2*GRIDEXTRA, cr*TILE_SIZE+1+2*GRIDEXTRA,
+                 COL_GRID);
+    }
+
+    /*
+     * This array is used to keep track of rows, columns and boxes
+     * which contain a number more than once.
+     */
+    for (x = 0; x < cr * ds->nregions; x++)
+       ds->entered_items[x] = 0;
+    for (x = 0; x < cr; x++)
+       for (y = 0; y < cr; y++) {
+           digit d = state->grid[y*cr+x];
+           if (d) {
+               int box, kbox;
+
+               /* Rows */
+               ds->entered_items[x*cr+d-1]++;
+
+               /* Columns */
+               ds->entered_items[(y+cr)*cr+d-1]++;
+
+               /* Blocks */
+               box = state->blocks->whichblock[y*cr+x];
+               ds->entered_items[(box+2*cr)*cr+d-1]++;
+
+               /* Diagonals */
+               if (ds->xtype) {
+                   if (ondiag0(y*cr+x))
+                       ds->entered_items[(3*cr)*cr+d-1]++;
+                   if (ondiag1(y*cr+x))
+                       ds->entered_items[(3*cr+1)*cr+d-1]++;
+               }
+
+               /* Killer cages */
+               if (state->kblocks) {
+                   kbox = state->kblocks->whichblock[y*cr+x];
+                   ds->entered_items[(kbox+3*cr+2)*cr+d-1]++;
+               }
+           }
+       }
+
+    /*
+     * Draw any numbers which need redrawing.
+     */
+    for (x = 0; x < cr; x++) {
+       for (y = 0; y < cr; y++) {
+            int highlight = 0;
+            digit d = state->grid[y*cr+x];
+
+            if (flashtime > 0 &&
+                (flashtime <= FLASH_TIME/3 ||
+                 flashtime >= FLASH_TIME*2/3))
+                highlight = 1;
+
+            /* Highlight active input areas. */
+            if (x == ui->hx && y == ui->hy && ui->hshow)
+                highlight = ui->hpencil ? 2 : 1;
+
+           /* Mark obvious errors (ie, numbers which occur more than once
+            * in a single row, column, or box). */
+           if (d && (ds->entered_items[x*cr+d-1] > 1 ||
+                     ds->entered_items[(y+cr)*cr+d-1] > 1 ||
+                     ds->entered_items[(state->blocks->whichblock[y*cr+x]
+                                        +2*cr)*cr+d-1] > 1 ||
+                     (ds->xtype && ((ondiag0(y*cr+x) &&
+                                     ds->entered_items[(3*cr)*cr+d-1] > 1) ||
+                                    (ondiag1(y*cr+x) &&
+                                     ds->entered_items[(3*cr+1)*cr+d-1]>1)))||
+                     (state->kblocks &&
+                      ds->entered_items[(state->kblocks->whichblock[y*cr+x]
+                                         +3*cr+2)*cr+d-1] > 1)))
+               highlight |= 16;
+
+           if (d && state->kblocks) {
+                if (check_killer_cage_sum(
+                        state->kblocks, state->kgrid, state->grid,
+                        state->kblocks->whichblock[y*cr+x]) == 0)
+                    highlight |= 32;
+           }
+
+           draw_number(dr, ds, state, x, y, highlight);
+       }
+    }
+
+    /*
+     * Update the _entire_ grid if necessary.
+     */
+    if (!ds->started) {
+       draw_update(dr, 0, 0, SIZE(cr), SIZE(cr));
+       ds->started = TRUE;
+    }
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return 0.0F;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    if (!oldstate->completed && newstate->completed &&
+       !oldstate->cheated && !newstate->cheated)
+        return FLASH_TIME;
+    return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    return state->completed ? +1 : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    if (state->completed)
+       return FALSE;
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+    int pw, ph;
+
+    /*
+     * I'll use 9mm squares by default. They should be quite big
+     * for this game, because players will want to jot down no end
+     * of pencil marks in the squares.
+     */
+    game_compute_size(params, 900, &pw, &ph);
+    *x = pw / 100.0F;
+    *y = ph / 100.0F;
+}
+
+/*
+ * Subfunction to draw the thick lines between cells. In order to do
+ * this using the line-drawing rather than rectangle-drawing API (so
+ * as to get line thicknesses to scale correctly) and yet have
+ * correctly mitred joins between lines, we must do this by tracing
+ * the boundary of each sub-block and drawing it in one go as a
+ * single polygon.
+ *
+ * This subfunction is also reused with thinner dotted lines to
+ * outline the Killer cages, this time offsetting the outline toward
+ * the interior of the affected squares.
+ */
+static void outline_block_structure(drawing *dr, game_drawstate *ds,
+                                   const game_state *state,
+                                   struct block_structure *blocks,
+                                   int ink, int inset)
+{
+    int cr = state->cr;
+    int *coords;
+    int bi, i, n;
+    int x, y, dx, dy, sx, sy, sdx, sdy;
+
+    /*
+     * Maximum perimeter of a k-omino is 2k+2. (Proof: start
+     * with k unconnected squares, with total perimeter 4k.
+     * Now repeatedly join two disconnected components
+     * together into a larger one; every time you do so you
+     * remove at least two unit edges, and you require k-1 of
+     * these operations to create a single connected piece, so
+     * you must have at most 4k-2(k-1) = 2k+2 unit edges left
+     * afterwards.)
+     */
+    coords = snewn(4*cr+4, int);   /* 2k+2 points, 2 coords per point */
+
+    /*
+     * Iterate over all the blocks.
+     */
+    for (bi = 0; bi < blocks->nr_blocks; bi++) {
+       if (blocks->nr_squares[bi] == 0)
+           continue;
+
+       /*
+        * For each block, find a starting square within it
+        * which has a boundary at the left.
+        */
+       for (i = 0; i < cr; i++) {
+           int j = blocks->blocks[bi][i];
+           if (j % cr == 0 || blocks->whichblock[j-1] != bi)
+               break;
+       }
+       assert(i < cr); /* every block must have _some_ leftmost square */
+       x = blocks->blocks[bi][i] % cr;
+       y = blocks->blocks[bi][i] / cr;
+       dx = -1;
+       dy = 0;
+
+       /*
+        * Now begin tracing round the perimeter. At all
+        * times, (x,y) describes some square within the
+        * block, and (x+dx,y+dy) is some adjacent square
+        * outside it; so the edge between those two squares
+        * is always an edge of the block.
+        */
+       sx = x, sy = y, sdx = dx, sdy = dy;   /* save starting position */
+       n = 0;
+       do {
+           int cx, cy, tx, ty, nin;
+
+           /*
+            * Advance to the next edge, by looking at the two
+            * squares beyond it. If they're both outside the block,
+            * we turn right (by leaving x,y the same and rotating
+            * dx,dy clockwise); if they're both inside, we turn
+            * left (by rotating dx,dy anticlockwise and contriving
+            * to leave x+dx,y+dy unchanged); if one of each, we go
+            * straight on (and may enforce by assertion that
+            * they're one of each the _right_ way round).
+            */
+           nin = 0;
+           tx = x - dy + dx;
+           ty = y + dx + dy;
+           nin += (tx >= 0 && tx < cr && ty >= 0 && ty < cr &&
+                   blocks->whichblock[ty*cr+tx] == bi);
+           tx = x - dy;
+           ty = y + dx;
+           nin += (tx >= 0 && tx < cr && ty >= 0 && ty < cr &&
+                   blocks->whichblock[ty*cr+tx] == bi);
+           if (nin == 0) {
+               /*
+                * Turn right.
+                */
+               int tmp;
+               tmp = dx;
+               dx = -dy;
+               dy = tmp;
+           } else if (nin == 2) {
+               /*
+                * Turn left.
+                */
+               int tmp;
+
+               x += dx;
+               y += dy;
+
+               tmp = dx;
+               dx = dy;
+               dy = -tmp;
+
+               x -= dx;
+               y -= dy;
+           } else {
+               /*
+                * Go straight on.
+                */
+               x -= dy;
+               y += dx;
+           }
+
+           /*
+            * Now enforce by assertion that we ended up
+            * somewhere sensible.
+            */
+           assert(x >= 0 && x < cr && y >= 0 && y < cr &&
+                  blocks->whichblock[y*cr+x] == bi);
+           assert(x+dx < 0 || x+dx >= cr || y+dy < 0 || y+dy >= cr ||
+                  blocks->whichblock[(y+dy)*cr+(x+dx)] != bi);
+
+           /*
+            * Record the point we just went past at one end of the
+            * edge. To do this, we translate (x,y) down and right
+            * by half a unit (so they're describing a point in the
+            * _centre_ of the square) and then translate back again
+            * in a manner rotated by dy and dx.
+            */
+           assert(n < 2*cr+2);
+           cx = ((2*x+1) + dy + dx) / 2;
+           cy = ((2*y+1) - dx + dy) / 2;
+           coords[2*n+0] = BORDER + cx * TILE_SIZE;
+           coords[2*n+1] = BORDER + cy * TILE_SIZE;
+           coords[2*n+0] -= dx * inset;
+           coords[2*n+1] -= dy * inset;
+           if (nin == 0) {
+               /*
+                * We turned right, so inset this corner back along
+                * the edge towards the centre of the square.
+                */
+               coords[2*n+0] -= dy * inset;
+               coords[2*n+1] += dx * inset;
+           } else if (nin == 2) {
+               /*
+                * We turned left, so inset this corner further
+                * _out_ along the edge into the next square.
+                */
+               coords[2*n+0] += dy * inset;
+               coords[2*n+1] -= dx * inset;
+           }
+           n++;
+
+       } while (x != sx || y != sy || dx != sdx || dy != sdy);
+
+       /*
+        * That's our polygon; now draw it.
+        */
+       draw_polygon(dr, coords, n, -1, ink);
+    }
+
+    sfree(coords);
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+    int cr = state->cr;
+    int ink = print_mono_colour(dr, 0);
+    int x, y;
+
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    game_drawstate ads, *ds = &ads;
+    game_set_size(dr, ds, NULL, tilesize);
+
+    /*
+     * Border.
+     */
+    print_line_width(dr, 3 * TILE_SIZE / 40);
+    draw_rect_outline(dr, BORDER, BORDER, cr*TILE_SIZE, cr*TILE_SIZE, ink);
+
+    /*
+     * Highlight X-diagonal squares.
+     */
+    if (state->xtype) {
+       int i;
+       int xhighlight = print_grey_colour(dr, 0.90F);
+
+       for (i = 0; i < cr; i++)
+           draw_rect(dr, BORDER + i*TILE_SIZE, BORDER + i*TILE_SIZE,
+                     TILE_SIZE, TILE_SIZE, xhighlight);
+       for (i = 0; i < cr; i++)
+           if (i*2 != cr-1)  /* avoid redoing centre square, just for fun */
+               draw_rect(dr, BORDER + i*TILE_SIZE,
+                         BORDER + (cr-1-i)*TILE_SIZE,
+                         TILE_SIZE, TILE_SIZE, xhighlight);
+    }
+
+    /*
+     * Main grid.
+     */
+    for (x = 1; x < cr; x++) {
+       print_line_width(dr, TILE_SIZE / 40);
+       draw_line(dr, BORDER+x*TILE_SIZE, BORDER,
+                 BORDER+x*TILE_SIZE, BORDER+cr*TILE_SIZE, ink);
+    }
+    for (y = 1; y < cr; y++) {
+       print_line_width(dr, TILE_SIZE / 40);
+       draw_line(dr, BORDER, BORDER+y*TILE_SIZE,
+                 BORDER+cr*TILE_SIZE, BORDER+y*TILE_SIZE, ink);
+    }
+
+    /*
+     * Thick lines between cells.
+     */
+    print_line_width(dr, 3 * TILE_SIZE / 40);
+    outline_block_structure(dr, ds, state, state->blocks, ink, 0);
+
+    /*
+     * Killer cages and their totals.
+     */
+    if (state->kblocks) {
+       print_line_width(dr, TILE_SIZE / 40);
+       print_line_dotted(dr, TRUE);
+       outline_block_structure(dr, ds, state, state->kblocks, ink,
+                               5 * TILE_SIZE / 40);
+       print_line_dotted(dr, FALSE);
+       for (y = 0; y < cr; y++)
+           for (x = 0; x < cr; x++)
+               if (state->kgrid[y*cr+x]) {
+                   char str[20];
+                   sprintf(str, "%d", state->kgrid[y*cr+x]);
+                   draw_text(dr,
+                             BORDER+x*TILE_SIZE + 7*TILE_SIZE/40,
+                             BORDER+y*TILE_SIZE + 16*TILE_SIZE/40,
+                             FONT_VARIABLE, TILE_SIZE/4,
+                             ALIGN_VNORMAL | ALIGN_HLEFT,
+                             ink, str);
+               }
+    }
+
+    /*
+     * Standard (non-Killer) clue numbers.
+     */
+    for (y = 0; y < cr; y++)
+       for (x = 0; x < cr; x++)
+           if (state->grid[y*cr+x]) {
+               char str[2];
+               str[1] = '\0';
+               str[0] = state->grid[y*cr+x] + '0';
+               if (str[0] > '9')
+                   str[0] += 'a' - ('9'+1);
+               draw_text(dr, BORDER + x*TILE_SIZE + TILE_SIZE/2,
+                         BORDER + y*TILE_SIZE + TILE_SIZE/2,
+                         FONT_VARIABLE, TILE_SIZE/2,
+                         ALIGN_VCENTRE | ALIGN_HCENTRE, ink, str);
+           }
+}
+
+#ifdef COMBINED
+#define thegame solo
+#endif
+
+const struct game thegame = {
+    "Solo", "games.solo", "solo",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    TRUE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    TRUE, FALSE, game_print_size, game_print,
+    FALSE,                            /* wants_statusbar */
+    FALSE, game_timing_state,
+    REQUIRE_RBUTTON | REQUIRE_NUMPAD,  /* flags */
+};
+
+#ifdef STANDALONE_SOLVER
+
+int main(int argc, char **argv)
+{
+    game_params *p;
+    game_state *s;
+    char *id = NULL, *desc, *err;
+    int grade = FALSE;
+    struct difficulty dlev;
+
+    while (--argc > 0) {
+        char *p = *++argv;
+        if (!strcmp(p, "-v")) {
+            solver_show_working = TRUE;
+        } else if (!strcmp(p, "-g")) {
+            grade = TRUE;
+        } else if (*p == '-') {
+            fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p);
+            return 1;
+        } else {
+            id = p;
+        }
+    }
+
+    if (!id) {
+        fprintf(stderr, "usage: %s [-g | -v] <game_id>\n", argv[0]);
+        return 1;
+    }
+
+    desc = strchr(id, ':');
+    if (!desc) {
+        fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]);
+        return 1;
+    }
+    *desc++ = '\0';
+
+    p = default_params();
+    decode_params(p, id);
+    err = validate_desc(p, desc);
+    if (err) {
+        fprintf(stderr, "%s: %s\n", argv[0], err);
+        return 1;
+    }
+    s = new_game(NULL, p, desc);
+
+    dlev.maxdiff = DIFF_RECURSIVE;
+    dlev.maxkdiff = DIFF_KINTERSECT;
+    solver(s->cr, s->blocks, s->kblocks, s->xtype, s->grid, s->kgrid, &dlev);
+    if (grade) {
+       printf("Difficulty rating: %s\n",
+              dlev.diff==DIFF_BLOCK ? "Trivial (blockwise positional elimination only)":
+              dlev.diff==DIFF_SIMPLE ? "Basic (row/column/number elimination required)":
+              dlev.diff==DIFF_INTERSECT ? "Intermediate (intersectional analysis required)":
+              dlev.diff==DIFF_SET ? "Advanced (set elimination required)":
+              dlev.diff==DIFF_EXTREME ? "Extreme (complex non-recursive techniques required)":
+              dlev.diff==DIFF_RECURSIVE ? "Unreasonable (guesswork and backtracking required)":
+              dlev.diff==DIFF_AMBIGUOUS ? "Ambiguous (multiple solutions exist)":
+              dlev.diff==DIFF_IMPOSSIBLE ? "Impossible (no solution exists)":
+              "INTERNAL ERROR: unrecognised difficulty code");
+       if (p->killer)
+           printf("Killer difficulty: %s\n",
+                  dlev.kdiff==DIFF_KSINGLE ? "Trivial (single square cages only)":
+                  dlev.kdiff==DIFF_KMINMAX ? "Simple (maximum sum analysis required)":
+                  dlev.kdiff==DIFF_KSUMS ? "Intermediate (sum possibilities)":
+                  dlev.kdiff==DIFF_KINTERSECT ? "Advanced (sum region intersections)":
+                  "INTERNAL ERROR: unrecognised difficulty code");
+    } else {
+        printf("%s\n", grid_text_format(s->cr, s->blocks, s->xtype, s->grid));
+    }
+
+    return 0;
+}
+
+#endif
+
+/* vim: set shiftwidth=4 tabstop=8: */
diff --git a/tdq.c b/tdq.c
new file mode 100644 (file)
index 0000000..d66f9f4
--- /dev/null
+++ b/tdq.c
@@ -0,0 +1,88 @@
+/*
+ * tdq.c: implement a 'to-do queue', a simple de-duplicating to-do
+ * list mechanism.
+ */
+
+#include <assert.h>
+
+#include "puzzles.h"
+
+/*
+ * Implementation: a tdq consists of a circular buffer of size n
+ * storing the integers currently in the queue, plus an array of n
+ * booleans indicating whether each integer is already there.
+ *
+ * Using a circular buffer of size n to store between 0 and n items
+ * inclusive has an obvious failure mode: if the input and output
+ * pointers are the same, how do you know whether that means the
+ * buffer is full or empty?
+ *
+ * In this application we have a simple way to tell: in the former
+ * case, the flags array is all 1s, and in the latter case it's all
+ * 0s. So we could spot that case and check, say, flags[0].
+ *
+ * However, it's even easier to simply determine whether the queue is
+ * non-empty by testing flags[buffer[op]] - that way we don't even
+ * _have_ to compare ip against op.
+ */
+
+struct tdq {
+    int n;
+    int *queue;
+    int ip, op;                        /* in pointer, out pointer */
+    char *flags;
+};
+
+tdq *tdq_new(int n)
+{
+    int i;
+    tdq *tdq = snew(struct tdq);
+    tdq->queue = snewn(n, int);
+    tdq->flags = snewn(n, char);
+    for (i = 0; i < n; i++) {
+        tdq->queue[i] = 0;
+        tdq->flags[i] = 0;
+    }
+    tdq->n = n;
+    tdq->ip = tdq->op = 0;
+    return tdq;
+}
+
+void tdq_free(tdq *tdq)
+{
+    sfree(tdq->queue);
+    sfree(tdq->flags);
+    sfree(tdq);
+}
+
+void tdq_add(tdq *tdq, int k)
+{
+    assert((unsigned)k < (unsigned)tdq->n);
+    if (!tdq->flags[k]) {
+        tdq->queue[tdq->ip] = k;
+        tdq->flags[k] = 1;
+        if (++tdq->ip == tdq->n)
+            tdq->ip = 0;
+    }
+}
+
+int tdq_remove(tdq *tdq)
+{
+    int ret = tdq->queue[tdq->op];
+
+    if (!tdq->flags[ret])
+        return -1;
+
+    tdq->flags[ret] = 0;
+    if (++tdq->op == tdq->n)
+        tdq->op = 0;
+
+    return ret;
+}
+
+void tdq_fill(tdq *tdq)
+{
+    int i;
+    for (i = 0; i < tdq->n; i++)
+        tdq_add(tdq, i);
+}
diff --git a/tents.R b/tents.R
new file mode 100644 (file)
index 0000000..557f929
--- /dev/null
+++ b/tents.R
@@ -0,0 +1,24 @@
+# -*- makefile -*-
+
+TENTS_EXTRA = maxflow dsf
+
+tents    : [X] GTK COMMON tents TENTS_EXTRA tents-icon|no-icon
+
+tents    : [G] WINDOWS COMMON tents TENTS_EXTRA tents.res|noicon.res
+
+ALL += tents[COMBINED] TENTS_EXTRA
+
+tentssolver :   [U] tents[STANDALONE_SOLVER] TENTS_EXTRA STANDALONE
+tentssolver :   [C] tents[STANDALONE_SOLVER] TENTS_EXTRA STANDALONE
+
+!begin am gtk
+GAMES += tents
+!end
+
+!begin >list.c
+    A(tents) \
+!end
+
+!begin >gamedesc.txt
+tents:tents.exe:Tents:Tent-placing puzzle:Place a tent next to each tree.
+!end
diff --git a/tents.c b/tents.c
new file mode 100644 (file)
index 0000000..859b13e
--- /dev/null
+++ b/tents.c
@@ -0,0 +1,2740 @@
+/*
+ * tents.c: Puzzle involving placing tents next to trees subject to
+ * some confusing conditions.
+ * 
+ * TODO:
+ *
+ *  - it might be nice to make setter-provided tent/nontent clues
+ *    inviolable?
+ *     * on the other hand, this would introduce considerable extra
+ *       complexity and size into the game state; also inviolable
+ *       clues would have to be marked as such somehow, in an
+ *       intrusive and annoying manner. Since they're never
+ *       generated by _my_ generator, I'm currently more inclined
+ *       not to bother.
+ * 
+ *  - more difficult levels at the top end?
+ *     * for example, sometimes we can deduce that two BLANKs in
+ *       the same row are each adjacent to the same unattached tree
+ *       and to nothing else, implying that they can't both be
+ *       tents; this enables us to rule out some extra combinations
+ *       in the row-based deduction loop, and hence deduce more
+ *       from the number in that row than we could otherwise do.
+ *     * that by itself doesn't seem worth implementing a new
+ *       difficulty level for, but if I can find a few more things
+ *       like that then it might become worthwhile.
+ *     * I wonder if there's a sensible heuristic for where to
+ *       guess which would make a recursive solver viable?
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+#include "maxflow.h"
+
+/*
+ * Design discussion
+ * -----------------
+ * 
+ * The rules of this puzzle as available on the WWW are poorly
+ * specified. The bits about tents having to be orthogonally
+ * adjacent to trees, tents not being even diagonally adjacent to
+ * one another, and the number of tents in each row and column
+ * being given are simple enough; the difficult bit is the
+ * tent-to-tree matching.
+ * 
+ * Some sources use simplistic wordings such as `each tree is
+ * exactly connected to only one tent', which is extremely unclear:
+ * it's easy to read erroneously as `each tree is _orthogonally
+ * adjacent_ to exactly one tent', which is definitely incorrect.
+ * Even the most coherent sources I've found don't do a much better
+ * job of stating the rule.
+ * 
+ * A more precise statement of the rule is that it must be possible
+ * to find a bijection f between tents and trees such that each
+ * tree T is orthogonally adjacent to the tent f(T), but that a
+ * tent is permitted to be adjacent to other trees in addition to
+ * its own. This slightly non-obvious criterion is what gives this
+ * puzzle most of its subtlety.
+ * 
+ * However, there's a particularly subtle ambiguity left over. Is
+ * the bijection between tents and trees required to be _unique_?
+ * In other words, is that bijection conceptually something the
+ * player should be able to exhibit as part of the solution (even
+ * if they aren't actually required to do so)? Or is it sufficient
+ * to have a unique _placement_ of the tents which gives rise to at
+ * least one suitable bijection?
+ * 
+ * The puzzle shown to the right of this       .T. 2      *T* 2
+ * paragraph illustrates the problem. There    T.T 0  ->  T-T 0
+ * are two distinct bijections available.      .T. 2      *T* 2
+ * The answer to the above question will
+ * determine whether it's a valid puzzle.      202        202
+ * 
+ * This is an important question, because it affects both the
+ * player and the generator. Eventually I found all the instances
+ * of this puzzle I could Google up, solved them all by hand, and
+ * verified that in all cases the tree/tent matching was uniquely
+ * determined given the tree and tent positions. Therefore, the
+ * puzzle as implemented in this source file takes the following
+ * policy:
+ * 
+ *  - When checking a user-supplied solution for correctness, only
+ *    verify that there exists _at least_ one matching.
+ *  - When generating a puzzle, enforce that there must be
+ *    _exactly_ one.
+ * 
+ * Algorithmic implications
+ * ------------------------
+ * 
+ * Another way of phrasing the tree/tent matching criterion is to
+ * say that the bipartite adjacency graph between trees and tents
+ * has a perfect matching. That is, if you construct a graph which
+ * has a vertex per tree and a vertex per tent, and an edge between
+ * any tree and tent which are orthogonally adjacent, it is
+ * possible to find a set of N edges of that graph (where N is the
+ * number of trees and also the number of tents) which between them
+ * connect every tree to every tent.
+ * 
+ * The most efficient known algorithms for finding such a matching
+ * given a graph, as far as I'm aware, are the Munkres assignment
+ * algorithm (also known as the Hungarian algorithm) and the
+ * Ford-Fulkerson algorithm (for finding optimal flows in
+ * networks). Each of these takes O(N^3) running time; so we're
+ * talking O(N^3) time to verify any candidate solution to this
+ * puzzle. That's just about OK if you're doing it once per mouse
+ * click (and in fact not even that, since the sensible thing to do
+ * is check all the _other_ puzzle criteria and only wade into this
+ * quagmire if none are violated); but if the solver had to keep
+ * doing N^3 work internally, then it would probably end up with
+ * more like N^5 or N^6 running time, and grid generation would
+ * become very clunky.
+ * 
+ * Fortunately, I've been able to prove a very useful property of
+ * _unique_ perfect matchings, by adapting the proof of Hall's
+ * Marriage Theorem. For those unaware of Hall's Theorem, I'll
+ * recap it and its proof: it states that a bipartite graph
+ * contains a perfect matching iff every set of vertices on the
+ * left side of the graph have a neighbourhood _at least_ as big on
+ * the right.
+ * 
+ * This condition is obviously satisfied if a perfect matching does
+ * exist; each left-side node has a distinct right-side node which
+ * is the one assigned to it by the matching, and thus any set of n
+ * left vertices must have a combined neighbourhood containing at
+ * least the n corresponding right vertices, and possibly others
+ * too. Alternatively, imagine if you had (say) three left-side
+ * nodes all of which were connected to only two right-side nodes
+ * between them: any perfect matching would have to assign one of
+ * those two right nodes to each of the three left nodes, and still
+ * give the three left nodes a different right node each. This is
+ * of course impossible.
+ *
+ * To prove the converse (that if every subset of left vertices
+ * satisfies the Hall condition then a perfect matching exists),
+ * consider trying to find a proper subset of the left vertices
+ * which _exactly_ satisfies the Hall condition: that is, its right
+ * neighbourhood is precisely the same size as it. If we can find
+ * such a subset, then we can split the bipartite graph into two
+ * smaller ones: one consisting of the left subset and its right
+ * neighbourhood, the other consisting of everything else. Edges
+ * from the left side of the former graph to the right side of the
+ * latter do not exist, by construction; edges from the right side
+ * of the former to the left of the latter cannot be part of any
+ * perfect matching because otherwise the left subset would not be
+ * left with enough distinct right vertices to connect to (this is
+ * exactly the same deduction used in Solo's set analysis). You can
+ * then prove (left as an exercise) that both these smaller graphs
+ * still satisfy the Hall condition, and therefore the proof will
+ * follow by induction.
+ * 
+ * There's one other possibility, which is the case where _no_
+ * proper subset of the left vertices has a right neighbourhood of
+ * exactly the same size. That is, every left subset has a strictly
+ * _larger_ right neighbourhood. In this situation, we can simply
+ * remove an _arbitrary_ edge from the graph. This cannot reduce
+ * the size of any left subset's right neighbourhood by more than
+ * one, so if all neighbourhoods were strictly bigger than they
+ * needed to be initially, they must now still be _at least as big_
+ * as they need to be. So we can keep throwing out arbitrary edges
+ * until we find a set which exactly satisfies the Hall condition,
+ * and then proceed as above. []
+ * 
+ * That's Hall's theorem. I now build on this by examining the
+ * circumstances in which a bipartite graph can have a _unique_
+ * perfect matching. It is clear that in the second case, where no
+ * left subset exactly satisfies the Hall condition and so we can
+ * remove an arbitrary edge, there cannot be a unique perfect
+ * matching: given one perfect matching, we choose our arbitrary
+ * removed edge to be one of those contained in it, and then we can
+ * still find a perfect matching in the remaining graph, which will
+ * be a distinct perfect matching in the original.
+ * 
+ * So it is a necessary condition for a unique perfect matching
+ * that there must be at least one proper left subset which
+ * _exactly_ satisfies the Hall condition. But now consider the
+ * smaller graph constructed by taking that left subset and its
+ * neighbourhood: if the graph as a whole had a unique perfect
+ * matching, then so must this smaller one, which means we can find
+ * a proper left subset _again_, and so on. Repeating this process
+ * must eventually reduce us to a graph with only one left-side
+ * vertex (so there are no proper subsets at all); this vertex must
+ * be connected to only one right-side vertex, and hence must be so
+ * in the original graph as well (by construction). So we can
+ * discard this vertex pair from the graph, and any other edges
+ * that involved it (which will by construction be from other left
+ * vertices only), and the resulting smaller graph still has a
+ * unique perfect matching which means we can do the same thing
+ * again.
+ * 
+ * In other words, given any bipartite graph with a unique perfect
+ * matching, we can find that matching by the following extremely
+ * simple algorithm:
+ * 
+ *  - Find a left-side vertex which is only connected to one
+ *    right-side vertex.
+ *  - Assign those vertices to one another, and therefore discard
+ *    any other edges connecting to that right vertex.
+ *  - Repeat until all vertices have been matched.
+ * 
+ * This algorithm can be run in O(V+E) time (where V is the number
+ * of vertices and E is the number of edges in the graph), and the
+ * only way it can fail is if there is not a unique perfect
+ * matching (either because there is no matching at all, or because
+ * it isn't unique; but it can't distinguish those cases).
+ * 
+ * Thus, the internal solver in this source file can be confident
+ * that if the tree/tent matching is uniquely determined by the
+ * tree and tent positions, it can find it using only this kind of
+ * obvious and simple operation: assign a tree to a tent if it
+ * cannot possibly belong to any other tent, and vice versa. If the
+ * solver were _only_ trying to determine the matching, even that
+ * `vice versa' wouldn't be required; but it can come in handy when
+ * not all the tents have been placed yet. I can therefore be
+ * reasonably confident that as long as my solver doesn't need to
+ * cope with grids that have a non-unique matching, it will also
+ * not need to do anything complicated like set analysis between
+ * trees and tents.
+ */
+
+/*
+ * In standalone solver mode, `verbose' is a variable which can be
+ * set by command-line option; in debugging mode it's simply always
+ * true.
+ */
+#if defined STANDALONE_SOLVER
+#define SOLVER_DIAGNOSTICS
+int verbose = FALSE;
+#elif defined SOLVER_DIAGNOSTICS
+#define verbose TRUE
+#endif
+
+/*
+ * Difficulty levels. I do some macro ickery here to ensure that my
+ * enum and the various forms of my name list always match up.
+ */
+#define DIFFLIST(A) \
+    A(EASY,Easy,e) \
+    A(TRICKY,Tricky,t)
+#define ENUM(upper,title,lower) DIFF_ ## upper,
+#define TITLE(upper,title,lower) #title,
+#define ENCODE(upper,title,lower) #lower
+#define CONFIG(upper,title,lower) ":" #title
+enum { DIFFLIST(ENUM) DIFFCOUNT };
+static char const *const tents_diffnames[] = { DIFFLIST(TITLE) };
+static char const tents_diffchars[] = DIFFLIST(ENCODE);
+#define DIFFCONFIG DIFFLIST(CONFIG)
+
+enum {
+    COL_BACKGROUND,
+    COL_GRID,
+    COL_GRASS,
+    COL_TREETRUNK,
+    COL_TREELEAF,
+    COL_TENT,
+    COL_ERROR,
+    COL_ERRTEXT,
+    COL_ERRTRUNK,
+    NCOLOURS
+};
+
+enum { BLANK, TREE, TENT, NONTENT, MAGIC };
+
+struct game_params {
+    int w, h;
+    int diff;
+};
+
+struct numbers {
+    int refcount;
+    int *numbers;
+};
+
+struct game_state {
+    game_params p;
+    char *grid;
+    struct numbers *numbers;
+    int completed, used_solve;
+};
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = ret->h = 8;
+    ret->diff = DIFF_EASY;
+
+    return ret;
+}
+
+static const struct game_params tents_presets[] = {
+    {8, 8, DIFF_EASY},
+    {8, 8, DIFF_TRICKY},
+    {10, 10, DIFF_EASY},
+    {10, 10, DIFF_TRICKY},
+    {15, 15, DIFF_EASY},
+    {15, 15, DIFF_TRICKY},
+};
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params *ret;
+    char str[80];
+
+    if (i < 0 || i >= lenof(tents_presets))
+        return FALSE;
+
+    ret = snew(game_params);
+    *ret = tents_presets[i];
+
+    sprintf(str, "%dx%d %s", ret->w, ret->h, tents_diffnames[ret->diff]);
+
+    *name = dupstr(str);
+    *params = ret;
+    return TRUE;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+static void decode_params(game_params *params, char const *string)
+{
+    params->w = params->h = atoi(string);
+    while (*string && isdigit((unsigned char)*string)) string++;
+    if (*string == 'x') {
+        string++;
+        params->h = atoi(string);
+       while (*string && isdigit((unsigned char)*string)) string++;
+    }
+    if (*string == 'd') {
+       int i;
+       string++;
+       for (i = 0; i < DIFFCOUNT; i++)
+           if (*string == tents_diffchars[i])
+               params->diff = i;
+       if (*string) string++;
+    }
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char buf[120];
+
+    sprintf(buf, "%dx%d", params->w, params->h);
+    if (full)
+       sprintf(buf + strlen(buf), "d%c",
+               tents_diffchars[params->diff]);
+    return dupstr(buf);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+    char buf[80];
+
+    ret = snewn(4, config_item);
+
+    ret[0].name = "Width";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->w);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Height";
+    ret[1].type = C_STRING;
+    sprintf(buf, "%d", params->h);
+    ret[1].sval = dupstr(buf);
+    ret[1].ival = 0;
+
+    ret[2].name = "Difficulty";
+    ret[2].type = C_CHOICES;
+    ret[2].sval = DIFFCONFIG;
+    ret[2].ival = params->diff;
+
+    ret[3].name = NULL;
+    ret[3].type = C_END;
+    ret[3].sval = NULL;
+    ret[3].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = atoi(cfg[0].sval);
+    ret->h = atoi(cfg[1].sval);
+    ret->diff = cfg[2].ival;
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    /*
+     * Generating anything under 4x4 runs into trouble of one kind
+     * or another.
+     */
+    if (params->w < 4 || params->h < 4)
+       return "Width and height must both be at least four";
+    return NULL;
+}
+
+/*
+ * Scratch space for solver.
+ */
+enum { N, U, L, R, D, MAXDIR };               /* link directions */
+#define dx(d) ( ((d)==R) - ((d)==L) )
+#define dy(d) ( ((d)==D) - ((d)==U) )
+#define F(d) ( U + D - (d) )
+struct solver_scratch {
+    char *links;                      /* mapping between trees and tents */
+    int *locs;
+    char *place, *mrows, *trows;
+};
+
+static struct solver_scratch *new_scratch(int w, int h)
+{
+    struct solver_scratch *ret = snew(struct solver_scratch);
+
+    ret->links = snewn(w*h, char);
+    ret->locs = snewn(max(w, h), int);
+    ret->place = snewn(max(w, h), char);
+    ret->mrows = snewn(3 * max(w, h), char);
+    ret->trows = snewn(3 * max(w, h), char);
+
+    return ret;
+}
+
+static void free_scratch(struct solver_scratch *sc)
+{
+    sfree(sc->trows);
+    sfree(sc->mrows);
+    sfree(sc->place);
+    sfree(sc->locs);
+    sfree(sc->links);
+    sfree(sc);
+}
+
+/*
+ * Solver. Returns 0 for impossibility, 1 for success, 2 for
+ * ambiguity or failure to converge.
+ */
+static int tents_solve(int w, int h, const char *grid, int *numbers,
+                      char *soln, struct solver_scratch *sc, int diff)
+{
+    int x, y, d, i, j;
+    char *mrow, *trow, *trow1, *trow2;
+
+    /*
+     * Set up solver data.
+     */
+    memset(sc->links, N, w*h);
+
+    /*
+     * Set up solution array.
+     */
+    memcpy(soln, grid, w*h);
+
+    /*
+     * Main solver loop.
+     */
+    while (1) {
+       int done_something = FALSE;
+
+       /*
+        * Any tent which has only one unattached tree adjacent to
+        * it can be tied to that tree.
+        */
+       for (y = 0; y < h; y++)
+           for (x = 0; x < w; x++)
+               if (soln[y*w+x] == TENT && !sc->links[y*w+x]) {
+                   int linkd = 0;
+
+                   for (d = 1; d < MAXDIR; d++) {
+                       int x2 = x + dx(d), y2 = y + dy(d);
+                       if (x2 >= 0 && x2 < w && y2 >= 0 && y2 < h &&
+                           soln[y2*w+x2] == TREE &&
+                           !sc->links[y2*w+x2]) {
+                           if (linkd)
+                               break; /* found more than one */
+                           else
+                               linkd = d;
+                       }
+                   }
+
+                   if (d == MAXDIR && linkd == 0) {
+#ifdef SOLVER_DIAGNOSTICS
+                       if (verbose)
+                           printf("tent at %d,%d cannot link to anything\n",
+                                  x, y);
+#endif
+                       return 0;      /* no solution exists */
+                   } else if (d == MAXDIR) {
+                       int x2 = x + dx(linkd), y2 = y + dy(linkd);
+
+#ifdef SOLVER_DIAGNOSTICS
+                       if (verbose)
+                           printf("tent at %d,%d can only link to tree at"
+                                  " %d,%d\n", x, y, x2, y2);
+#endif
+
+                       sc->links[y*w+x] = linkd;
+                       sc->links[y2*w+x2] = F(linkd);
+                       done_something = TRUE;
+                   }
+               }
+
+       if (done_something)
+           continue;
+       if (diff < 0)
+           break;                     /* don't do anything else! */
+
+       /*
+        * Mark a blank square as NONTENT if it is not orthogonally
+        * adjacent to any unmatched tree.
+        */
+       for (y = 0; y < h; y++)
+           for (x = 0; x < w; x++)
+               if (soln[y*w+x] == BLANK) {
+                   int can_be_tent = FALSE;
+
+                   for (d = 1; d < MAXDIR; d++) {
+                       int x2 = x + dx(d), y2 = y + dy(d);
+                       if (x2 >= 0 && x2 < w && y2 >= 0 && y2 < h &&
+                           soln[y2*w+x2] == TREE &&
+                           !sc->links[y2*w+x2])
+                           can_be_tent = TRUE;
+                   }
+
+                   if (!can_be_tent) {
+#ifdef SOLVER_DIAGNOSTICS
+                       if (verbose)
+                           printf("%d,%d cannot be a tent (no adjacent"
+                                  " unmatched tree)\n", x, y);
+#endif
+                       soln[y*w+x] = NONTENT;
+                       done_something = TRUE;
+                   }
+               }
+
+       if (done_something)
+           continue;
+
+       /*
+        * Mark a blank square as NONTENT if it is (perhaps
+        * diagonally) adjacent to any other tent.
+        */
+       for (y = 0; y < h; y++)
+           for (x = 0; x < w; x++)
+               if (soln[y*w+x] == BLANK) {
+                   int dx, dy, imposs = FALSE;
+
+                   for (dy = -1; dy <= +1; dy++)
+                       for (dx = -1; dx <= +1; dx++)
+                           if (dy || dx) {
+                               int x2 = x + dx, y2 = y + dy;
+                               if (x2 >= 0 && x2 < w && y2 >= 0 && y2 < h &&
+                                   soln[y2*w+x2] == TENT)
+                                   imposs = TRUE;
+                           }
+
+                   if (imposs) {
+#ifdef SOLVER_DIAGNOSTICS
+                       if (verbose)
+                           printf("%d,%d cannot be a tent (adjacent tent)\n",
+                                  x, y);
+#endif
+                       soln[y*w+x] = NONTENT;
+                       done_something = TRUE;
+                   }
+               }
+
+       if (done_something)
+           continue;
+
+       /*
+        * Any tree which has exactly one {unattached tent, BLANK}
+        * adjacent to it must have its tent in that square.
+        */
+       for (y = 0; y < h; y++)
+           for (x = 0; x < w; x++)
+               if (soln[y*w+x] == TREE && !sc->links[y*w+x]) {
+                   int linkd = 0, linkd2 = 0, nd = 0;
+
+                   for (d = 1; d < MAXDIR; d++) {
+                       int x2 = x + dx(d), y2 = y + dy(d);
+                       if (!(x2 >= 0 && x2 < w && y2 >= 0 && y2 < h))
+                           continue;
+                       if (soln[y2*w+x2] == BLANK ||
+                           (soln[y2*w+x2] == TENT && !sc->links[y2*w+x2])) {
+                           if (linkd)
+                               linkd2 = d;
+                           else
+                               linkd = d;
+                           nd++;
+                       }
+                   }
+
+                   if (nd == 0) {
+#ifdef SOLVER_DIAGNOSTICS
+                       if (verbose)
+                           printf("tree at %d,%d cannot link to anything\n",
+                                  x, y);
+#endif
+                       return 0;      /* no solution exists */
+                   } else if (nd == 1) {
+                       int x2 = x + dx(linkd), y2 = y + dy(linkd);
+
+#ifdef SOLVER_DIAGNOSTICS
+                       if (verbose)
+                           printf("tree at %d,%d can only link to tent at"
+                                  " %d,%d\n", x, y, x2, y2);
+#endif
+                       soln[y2*w+x2] = TENT;
+                       sc->links[y*w+x] = linkd;
+                       sc->links[y2*w+x2] = F(linkd);
+                       done_something = TRUE;
+                   } else if (nd == 2 && (!dx(linkd) != !dx(linkd2)) &&
+                              diff >= DIFF_TRICKY) {
+                       /*
+                        * If there are two possible places where
+                        * this tree's tent can go, and they are
+                        * diagonally separated rather than being
+                        * on opposite sides of the tree, then the
+                        * square (other than the tree square)
+                        * which is adjacent to both of them must
+                        * be a non-tent.
+                        */
+                       int x2 = x + dx(linkd) + dx(linkd2);
+                       int y2 = y + dy(linkd) + dy(linkd2);
+                       assert(x2 >= 0 && x2 < w && y2 >= 0 && y2 < h);
+                       if (soln[y2*w+x2] == BLANK) {
+#ifdef SOLVER_DIAGNOSTICS
+                           if (verbose)
+                               printf("possible tent locations for tree at"
+                                      " %d,%d rule out tent at %d,%d\n",
+                                      x, y, x2, y2);
+#endif
+                           soln[y2*w+x2] = NONTENT;
+                           done_something = TRUE;
+                       }
+                   }
+               }
+
+       if (done_something)
+           continue;
+
+       /*
+        * If localised deductions about the trees and tents
+        * themselves haven't helped us, it's time to resort to the
+        * numbers round the grid edge. For each row and column, we
+        * go through all possible combinations of locations for
+        * the unplaced tents, rule out any which have adjacent
+        * tents, and spot any square which is given the same state
+        * by all remaining combinations.
+        */
+       for (i = 0; i < w+h; i++) {
+           int start, step, len, start1, start2, n, k;
+
+           if (i < w) {
+               /*
+                * This is the number for a column.
+                */
+               start = i;
+               step = w;
+               len = h;
+               if (i > 0)
+                   start1 = start - 1;
+               else
+                   start1 = -1;
+               if (i+1 < w)
+                   start2 = start + 1;
+               else
+                   start2 = -1;
+           } else {
+               /*
+                * This is the number for a row.
+                */
+               start = (i-w)*w;
+               step = 1;
+               len = w;
+               if (i > w)
+                   start1 = start - w;
+               else
+                   start1 = -1;
+               if (i+1 < w+h)
+                   start2 = start + w;
+               else
+                   start2 = -1;
+           }
+
+           if (diff < DIFF_TRICKY) {
+               /*
+                * In Easy mode, we don't look at the effect of one
+                * row on the next (i.e. ruling out a square if all
+                * possibilities for an adjacent row place a tent
+                * next to it).
+                */
+               start1 = start2 = -1;
+           }
+
+           k = numbers[i];
+
+           /*
+            * Count and store the locations of the free squares,
+            * and also count the number of tents already placed.
+            */
+           n = 0;
+           for (j = 0; j < len; j++) {
+               if (soln[start+j*step] == TENT)
+                   k--;               /* one fewer tent to place */
+               else if (soln[start+j*step] == BLANK)
+                   sc->locs[n++] = j;
+           }
+
+           if (n == 0)
+               continue;              /* nothing left to do here */
+
+           /*
+            * Now we know we're placing k tents in n squares. Set
+            * up the first possibility.
+            */
+           for (j = 0; j < n; j++)
+               sc->place[j] = (j < k ? TENT : NONTENT);
+
+           /*
+            * We're aiming to find squares in this row which are
+            * invariant over all valid possibilities. Thus, we
+            * maintain the current state of that invariance. We
+            * start everything off at MAGIC to indicate that it
+            * hasn't been set up yet.
+            */
+           mrow = sc->mrows;
+           trow = sc->trows;
+           trow1 = sc->trows + len;
+           trow2 = sc->trows + 2*len;
+           memset(mrow, MAGIC, 3*len);
+
+           /*
+            * And iterate over all possibilities.
+            */
+           while (1) {
+               int p, valid;
+
+               /*
+                * See if this possibility is valid. The only way
+                * it can fail to be valid is if it contains two
+                * adjacent tents. (Other forms of invalidity, such
+                * as containing a tent adjacent to one already
+                * placed, will have been dealt with already by
+                * other parts of the solver.)
+                */
+               valid = TRUE;
+               for (j = 0; j+1 < n; j++)
+                   if (sc->place[j] == TENT &&
+                       sc->place[j+1] == TENT &&
+                       sc->locs[j+1] == sc->locs[j]+1) {
+                       valid = FALSE;
+                       break;
+                   }
+
+               if (valid) {
+                   /*
+                    * Merge this valid combination into mrow.
+                    */
+                   memset(trow, MAGIC, len);
+                   memset(trow+len, BLANK, 2*len);
+                   for (j = 0; j < n; j++) {
+                       trow[sc->locs[j]] = sc->place[j];
+                       if (sc->place[j] == TENT) {
+                           int jj;
+                           for (jj = sc->locs[j]-1; jj <= sc->locs[j]+1; jj++)
+                               if (jj >= 0 && jj < len)
+                                   trow1[jj] = trow2[jj] = NONTENT;
+                       }
+                   }
+
+                   for (j = 0; j < 3*len; j++) {
+                       if (trow[j] == MAGIC)
+                           continue;
+                       if (mrow[j] == MAGIC || mrow[j] == trow[j]) {
+                           /*
+                            * Either this is the first valid
+                            * placement we've found at all, or
+                            * this square's contents are
+                            * consistent with every previous valid
+                            * combination.
+                            */
+                           mrow[j] = trow[j];
+                       } else {
+                           /*
+                            * This square's contents fail to match
+                            * what they were in a different
+                            * combination, so we cannot deduce
+                            * anything about this square.
+                            */
+                           mrow[j] = BLANK;
+                       }
+                   }
+               }
+
+               /*
+                * Find the next combination of k choices from n.
+                * We do this by finding the rightmost tent which
+                * can be moved one place right, doing so, and
+                * shunting all tents to the right of that as far
+                * left as they can go.
+                */
+               p = 0;
+               for (j = n-1; j > 0; j--) {
+                   if (sc->place[j] == TENT)
+                       p++;
+                   if (sc->place[j] == NONTENT && sc->place[j-1] == TENT) {
+                       sc->place[j-1] = NONTENT;
+                       sc->place[j] = TENT;
+                       while (p--)
+                           sc->place[++j] = TENT;
+                       while (++j < n)
+                           sc->place[j] = NONTENT;
+                       break;
+                   }
+               }
+               if (j <= 0)
+                   break;             /* we've finished */
+           }
+
+           /*
+            * It's just possible that _no_ placement was valid, in
+            * which case we have an internally inconsistent
+            * puzzle.
+            */
+           if (mrow[sc->locs[0]] == MAGIC)
+               return 0;              /* inconsistent */
+
+           /*
+            * Now go through mrow and see if there's anything
+            * we've deduced which wasn't already mentioned in soln.
+            */
+           for (j = 0; j < len; j++) {
+               int whichrow;
+
+               for (whichrow = 0; whichrow < 3; whichrow++) {
+                   char *mthis = mrow + whichrow * len;
+                   int tstart = (whichrow == 0 ? start :
+                                 whichrow == 1 ? start1 : start2);
+                   if (tstart >= 0 &&
+                       mthis[j] != MAGIC && mthis[j] != BLANK &&
+                       soln[tstart+j*step] == BLANK) {
+                       int pos = tstart+j*step;
+
+#ifdef SOLVER_DIAGNOSTICS
+                       if (verbose)
+                           printf("%s %d forces %s at %d,%d\n",
+                                  step==1 ? "row" : "column",
+                                  step==1 ? start/w : start,
+                                  mthis[j] == TENT ? "tent" : "non-tent",
+                                  pos % w, pos / w);
+#endif
+                       soln[pos] = mthis[j];
+                       done_something = TRUE;
+                   }
+               }
+           }
+       }
+
+       if (done_something)
+           continue;
+
+       if (!done_something)
+           break;
+    }
+
+    /*
+     * The solver has nothing further it can do. Return 1 if both
+     * soln and sc->links are completely filled in, or 2 otherwise.
+     */
+    for (y = 0; y < h; y++)
+       for (x = 0; x < w; x++) {
+           if (soln[y*w+x] == BLANK)
+               return 2;
+           if (soln[y*w+x] != NONTENT && sc->links[y*w+x] == 0)
+               return 2;
+       }
+
+    return 1;
+}
+
+static char *new_game_desc(const game_params *params_in, random_state *rs,
+                          char **aux, int interactive)
+{
+    game_params params_copy = *params_in; /* structure copy */
+    game_params *params = &params_copy;
+    int w = params->w, h = params->h;
+    int ntrees = w * h / 5;
+    char *grid = snewn(w*h, char);
+    char *puzzle = snewn(w*h, char);
+    int *numbers = snewn(w+h, int);
+    char *soln = snewn(w*h, char);
+    int *temp = snewn(2*w*h, int);
+    int maxedges = ntrees*4 + w*h;
+    int *edges = snewn(2*maxedges, int);
+    int *capacity = snewn(maxedges, int);
+    int *flow = snewn(maxedges, int);
+    struct solver_scratch *sc = new_scratch(w, h);
+    char *ret, *p;
+    int i, j, nedges;
+
+    /*
+     * Since this puzzle has many global deductions and doesn't
+     * permit limited clue sets, generating grids for this puzzle
+     * is hard enough that I see no better option than to simply
+     * generate a solution and see if it's unique and has the
+     * required difficulty. This turns out to be computationally
+     * plausible as well.
+     * 
+     * We chose our tree count (hence also tent count) by dividing
+     * the total grid area by five above. Why five? Well, w*h/4 is
+     * the maximum number of tents you can _possibly_ fit into the
+     * grid without violating the separation criterion, and to
+     * achieve that you are constrained to a very small set of
+     * possible layouts (the obvious one with a tent at every
+     * (even,even) coordinate, and trivial variations thereon). So
+     * if we reduce the tent count a bit more, we enable more
+     * random-looking placement; 5 turns out to be a plausible
+     * figure which yields sensible puzzles. Increasing the tent
+     * count would give puzzles whose solutions were too regimented
+     * and could be solved by the use of that knowledge (and would
+     * also take longer to find a viable placement); decreasing it
+     * would make the grids emptier and more boring.
+     * 
+     * Actually generating a grid is a matter of first placing the
+     * tents, and then placing the trees by the use of maxflow
+     * (finding a distinct square adjacent to every tent). We do it
+     * this way round because otherwise satisfying the tent
+     * separation condition would become onerous: most randomly
+     * chosen tent layouts do not satisfy this condition, so we'd
+     * have gone to a lot of work before finding that a candidate
+     * layout was unusable. Instead, we place the tents first and
+     * ensure they meet the separation criterion _before_ doing
+     * lots of computation; this works much better.
+     * 
+     * The maxflow algorithm is not randomised, so employed naively
+     * it would give rise to grids with clear structure and
+     * directional bias. Hence, I assign the network nodes as seen
+     * by maxflow to be a _random_ permutation of the squares of
+     * the grid, so that any bias shown by maxflow towards
+     * low-numbered nodes is turned into a random bias.
+     * 
+     * This generation strategy can fail at many points, including
+     * as early as tent placement (if you get a bad random order in
+     * which to greedily try the grid squares, you won't even
+     * manage to find enough mutually non-adjacent squares to put
+     * the tents in). Then it can fail if maxflow doesn't manage to
+     * find a good enough matching (i.e. the tent placements don't
+     * admit any adequate tree placements); and finally it can fail
+     * if the solver finds that the problem has the wrong
+     * difficulty (including being actually non-unique). All of
+     * these, however, are insufficiently frequent to cause
+     * trouble.
+     */
+
+    if (params->diff > DIFF_EASY && params->w <= 4 && params->h <= 4)
+       params->diff = DIFF_EASY;      /* downgrade to prevent tight loop */
+
+    while (1) {
+       /*
+        * Arrange the grid squares into a random order.
+        */
+       for (i = 0; i < w*h; i++)
+           temp[i] = i;
+       shuffle(temp, w*h, sizeof(*temp), rs);
+
+       /*
+        * The first `ntrees' entries in temp which we can get
+        * without making two tents adjacent will be the tent
+        * locations.
+        */
+       memset(grid, BLANK, w*h);
+       j = ntrees;
+       for (i = 0; i < w*h && j > 0; i++) {
+           int x = temp[i] % w, y = temp[i] / w;
+           int dy, dx, ok = TRUE;
+
+           for (dy = -1; dy <= +1; dy++)
+               for (dx = -1; dx <= +1; dx++)
+                   if (x+dx >= 0 && x+dx < w &&
+                       y+dy >= 0 && y+dy < h &&
+                       grid[(y+dy)*w+(x+dx)] == TENT)
+                       ok = FALSE;
+
+           if (ok) {
+               grid[temp[i]] = TENT;
+               j--;
+           }
+       }
+       if (j > 0)
+           continue;                  /* couldn't place all the tents */
+
+       /*
+        * Now we build up the list of graph edges.
+        */
+       nedges = 0;
+       for (i = 0; i < w*h; i++) {
+           if (grid[temp[i]] == TENT) {
+               for (j = 0; j < w*h; j++) {
+                   if (grid[temp[j]] != TENT) {
+                       int xi = temp[i] % w, yi = temp[i] / w;
+                       int xj = temp[j] % w, yj = temp[j] / w;
+                       if (abs(xi-xj) + abs(yi-yj) == 1) {
+                           edges[nedges*2] = i;
+                           edges[nedges*2+1] = j;
+                           capacity[nedges] = 1;
+                           nedges++;
+                       }
+                   }
+               }
+           } else {
+               /*
+                * Special node w*h is the sink node; any non-tent node
+                * has an edge going to it.
+                */
+               edges[nedges*2] = i;
+               edges[nedges*2+1] = w*h;
+               capacity[nedges] = 1;
+               nedges++;
+           }
+       }
+
+       /*
+        * Special node w*h+1 is the source node, with an edge going to
+        * every tent.
+        */
+       for (i = 0; i < w*h; i++) {
+           if (grid[temp[i]] == TENT) {
+               edges[nedges*2] = w*h+1;
+               edges[nedges*2+1] = i;
+               capacity[nedges] = 1;
+               nedges++;
+           }
+       }
+
+       assert(nedges <= maxedges);
+
+       /*
+        * Now we're ready to call the maxflow algorithm to place the
+        * trees.
+        */
+       j = maxflow(w*h+2, w*h+1, w*h, nedges, edges, capacity, flow, NULL);
+
+       if (j < ntrees)
+           continue;                  /* couldn't place all the trees */
+
+       /*
+        * We've placed the trees. Now we need to work out _where_
+        * we've placed them, which is a matter of reading back out
+        * from the `flow' array.
+        */
+       for (i = 0; i < nedges; i++) {
+           if (edges[2*i] < w*h && edges[2*i+1] < w*h && flow[i] > 0)
+               grid[temp[edges[2*i+1]]] = TREE;
+       }
+
+       /*
+        * I think it looks ugly if there isn't at least one of
+        * _something_ (tent or tree) in each row and each column
+        * of the grid. This doesn't give any information away
+        * since a completely empty row/column is instantly obvious
+        * from the clues (it has no trees and a zero).
+        */
+       for (i = 0; i < w; i++) {
+           for (j = 0; j < h; j++) {
+               if (grid[j*w+i] != BLANK)
+                   break;             /* found something in this column */
+           }
+           if (j == h)
+               break;                 /* found empty column */
+       }
+       if (i < w)
+           continue;                  /* a column was empty */
+
+       for (j = 0; j < h; j++) {
+           for (i = 0; i < w; i++) {
+               if (grid[j*w+i] != BLANK)
+                   break;             /* found something in this row */
+           }
+           if (i == w)
+               break;                 /* found empty row */
+       }
+       if (j < h)
+           continue;                  /* a row was empty */
+
+       /*
+        * Now set up the numbers round the edge.
+        */
+       for (i = 0; i < w; i++) {
+           int n = 0;
+           for (j = 0; j < h; j++)
+               if (grid[j*w+i] == TENT)
+                   n++;
+           numbers[i] = n;
+       }
+       for (i = 0; i < h; i++) {
+           int n = 0;
+           for (j = 0; j < w; j++)
+               if (grid[i*w+j] == TENT)
+                   n++;
+           numbers[w+i] = n;
+       }
+
+       /*
+        * And now actually solve the puzzle, to see whether it's
+        * unique and has the required difficulty.
+        */
+        for (i = 0; i < w*h; i++)
+            puzzle[i] = grid[i] == TREE ? TREE : BLANK;
+       i = tents_solve(w, h, puzzle, numbers, soln, sc, params->diff-1);
+       j = tents_solve(w, h, puzzle, numbers, soln, sc, params->diff);
+
+        /*
+         * We expect solving with difficulty params->diff to have
+         * succeeded (otherwise the problem is too hard), and
+         * solving with diff-1 to have failed (otherwise it's too
+         * easy).
+         */
+       if (i == 2 && j == 1)
+           break;
+    }
+
+    /*
+     * That's it. Encode as a game ID.
+     */
+    ret = snewn((w+h)*40 + ntrees + (w*h)/26 + 1, char);
+    p = ret;
+    j = 0;
+    for (i = 0; i <= w*h; i++) {
+       int c = (i < w*h ? grid[i] == TREE : 1);
+       if (c) {
+           *p++ = (j == 0 ? '_' : j-1 + 'a');
+           j = 0;
+       } else {
+           j++;
+           while (j > 25) {
+               *p++ = 'z';
+               j -= 25;
+           }
+       }
+    }
+    for (i = 0; i < w+h; i++)
+       p += sprintf(p, ",%d", numbers[i]);
+    *p++ = '\0';
+    ret = sresize(ret, p - ret, char);
+
+    /*
+     * And encode the solution as an aux_info.
+     */
+    *aux = snewn(ntrees * 40, char);
+    p = *aux;
+    *p++ = 'S';
+    for (i = 0; i < w*h; i++)
+        if (grid[i] == TENT)
+            p += sprintf(p, ";T%d,%d", i%w, i/w);
+    *p++ = '\0';
+    *aux = sresize(*aux, p - *aux, char);
+
+    free_scratch(sc);
+    sfree(flow);
+    sfree(capacity);
+    sfree(edges);
+    sfree(temp);
+    sfree(soln);
+    sfree(numbers);
+    sfree(puzzle);
+    sfree(grid);
+
+    return ret;
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    int w = params->w, h = params->h;
+    int area, i;
+
+    area = 0;
+    while (*desc && *desc != ',') {
+       if (*desc == '_')
+            area++;
+       else if (*desc >= 'a' && *desc < 'z')
+            area += *desc - 'a' + 2;
+       else if (*desc == 'z')
+            area += 25;
+        else if (*desc == '!' || *desc == '-')
+            /* do nothing */;
+        else
+            return "Invalid character in grid specification";
+
+       desc++;
+    }
+    if (area < w * h + 1)
+       return "Not enough data to fill grid";
+    else if (area > w * h + 1)
+       return "Too much data to fill grid";
+
+    for (i = 0; i < w+h; i++) {
+       if (!*desc)
+            return "Not enough numbers given after grid specification";
+        else if (*desc != ',')
+            return "Invalid character in number list";
+       desc++;
+       while (*desc && isdigit((unsigned char)*desc)) desc++;
+    }
+
+    if (*desc)
+        return "Unexpected additional data at end of game description";
+    return NULL;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    int w = params->w, h = params->h;
+    game_state *state = snew(game_state);
+    int i;
+
+    state->p = *params;                       /* structure copy */
+    state->grid = snewn(w*h, char);
+    state->numbers = snew(struct numbers);
+    state->numbers->refcount = 1;
+    state->numbers->numbers = snewn(w+h, int);
+    state->completed = state->used_solve = FALSE;
+
+    i = 0;
+    memset(state->grid, BLANK, w*h);
+
+    while (*desc) {
+       int run, type;
+
+       type = TREE;
+
+       if (*desc == '_')
+           run = 0;
+       else if (*desc >= 'a' && *desc < 'z')
+           run = *desc - ('a'-1);
+       else if (*desc == 'z') {
+           run = 25;
+           type = BLANK;
+       } else {
+           assert(*desc == '!' || *desc == '-');
+           run = -1;
+           type = (*desc == '!' ? TENT : NONTENT);
+       }
+
+       desc++;
+
+       i += run;
+       assert(i >= 0 && i <= w*h);
+       if (i == w*h) {
+           assert(type == TREE);
+           break;
+       } else {
+           if (type != BLANK)
+               state->grid[i++] = type;
+       }
+    }
+
+    for (i = 0; i < w+h; i++) {
+       assert(*desc == ',');
+       desc++;
+       state->numbers->numbers[i] = atoi(desc);
+       while (*desc && isdigit((unsigned char)*desc)) desc++;
+    }
+
+    assert(!*desc);
+
+    return state;
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    int w = state->p.w, h = state->p.h;
+    game_state *ret = snew(game_state);
+
+    ret->p = state->p;                /* structure copy */
+    ret->grid = snewn(w*h, char);
+    memcpy(ret->grid, state->grid, w*h);
+    ret->numbers = state->numbers;
+    state->numbers->refcount++;
+    ret->completed = state->completed;
+    ret->used_solve = state->used_solve;
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    if (--state->numbers->refcount <= 0) {
+       sfree(state->numbers->numbers);
+       sfree(state->numbers);
+    }
+    sfree(state->grid);
+    sfree(state);
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    int w = state->p.w, h = state->p.h;
+
+    if (aux) {
+       /*
+        * If we already have the solution, save ourselves some
+        * time.
+        */
+        return dupstr(aux);
+    } else {
+       struct solver_scratch *sc = new_scratch(w, h);
+        char *soln;
+        int ret;
+        char *move, *p;
+        int i;
+
+       soln = snewn(w*h, char);
+       ret = tents_solve(w, h, state->grid, state->numbers->numbers,
+                          soln, sc, DIFFCOUNT-1);
+       free_scratch(sc);
+       if (ret != 1) {
+           sfree(soln);
+           if (ret == 0)
+               *error = "This puzzle is not self-consistent";
+           else
+               *error = "Unable to find a unique solution for this puzzle";
+            return NULL;
+       }
+
+        /*
+         * Construct a move string which turns the current state
+         * into the solved state.
+         */
+        move = snewn(w*h * 40, char);
+        p = move;
+        *p++ = 'S';
+        for (i = 0; i < w*h; i++)
+            if (soln[i] == TENT)
+                p += sprintf(p, ";T%d,%d", i%w, i/w);
+        *p++ = '\0';
+        move = sresize(move, p - move, char);
+
+       sfree(soln);
+
+        return move;
+    }
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return params->w <= 1998 && params->h <= 1998; /* 999 tents */
+}
+
+static char *game_text_format(const game_state *state)
+{
+    int w = state->p.w, h = state->p.h, r, c;
+    int cw = 4, ch = 2, gw = (w+1)*cw + 2, gh = (h+1)*ch + 1, len = gw * gh;
+    char *board = snewn(len + 1, char);
+
+    sprintf(board, "%*s\n", len - 2, "");
+    for (r = 0; r <= h; ++r) {
+       for (c = 0; c <= w; ++c) {
+           int cell = r*ch*gw + cw*c, center = cell + gw*ch/2 + cw/2;
+           int i = r*w + c, n = 1000;
+
+           if (r == h && c == w) /* NOP */;
+           else if (c == w) n = state->numbers->numbers[w + r];
+           else if (r == h) n = state->numbers->numbers[c];
+           else switch (state->grid[i]) {
+               case BLANK: board[center] = '.'; break;
+               case TREE: board[center] = 'T'; break;
+               case TENT: memcpy(board + center - 1, "//\\", 3); break;
+               case NONTENT: break;
+               default: memcpy(board + center - 1, "wtf", 3);
+               }
+
+           if (n < 100) {
+                board[center] = '0' + n % 10;
+                if (n >= 10) board[center - 1] = '0' + n / 10;
+            } else if (n < 1000) {
+                board[center + 1] = '0' + n % 10;
+                board[center] = '0' + n / 10 % 10;
+                board[center - 1] = '0' + n / 100;
+           }
+
+           board[cell] = '+';
+           memset(board + cell + 1, '-', cw - 1);
+           for (i = 1; i < ch; ++i) board[cell + i*gw] = '|';
+       }
+
+       for (c = 0; c < ch; ++c) {
+           board[(r*ch+c)*gw + gw - 2] =
+               c == 0 ? '+' : r < h ? '|' : ' ';
+           board[(r*ch+c)*gw + gw - 1] = '\n';
+       }
+    }
+
+    memset(board + len - gw, '-', gw - 2 - cw);
+    for (c = 0; c <= w; ++c) board[len - gw + cw*c] = '+';
+
+    return board;
+}
+
+struct game_ui {
+    int dsx, dsy;                      /* coords of drag start */
+    int dex, dey;                      /* coords of drag end */
+    int drag_button;                   /* -1 for none, or a button code */
+    int drag_ok;                       /* dragged off the window, to cancel */
+
+    int cx, cy, cdisp;                 /* cursor position, and ?display. */
+};
+
+static game_ui *new_ui(const game_state *state)
+{
+    game_ui *ui = snew(game_ui);
+    ui->dsx = ui->dsy = -1;
+    ui->dex = ui->dey = -1;
+    ui->drag_button = -1;
+    ui->drag_ok = FALSE;
+    ui->cx = ui->cy = ui->cdisp = 0;
+    return ui;
+}
+
+static void free_ui(game_ui *ui)
+{
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+}
+
+struct game_drawstate {
+    int tilesize;
+    int started;
+    game_params p;
+    int *drawn, *numbersdrawn;
+    int cx, cy;         /* last-drawn cursor pos, or (-1,-1) if absent. */
+};
+
+#define PREFERRED_TILESIZE 32
+#define TILESIZE (ds->tilesize)
+#define TLBORDER (TILESIZE/2)
+#define BRBORDER (TILESIZE*3/2)
+#define COORD(x)  ( (x) * TILESIZE + TLBORDER )
+#define FROMCOORD(x)  ( ((x) - TLBORDER + TILESIZE) / TILESIZE - 1 )
+
+#define FLASH_TIME 0.30F
+
+static int drag_xform(const game_ui *ui, int x, int y, int v)
+{
+    int xmin, ymin, xmax, ymax;
+
+    xmin = min(ui->dsx, ui->dex);
+    xmax = max(ui->dsx, ui->dex);
+    ymin = min(ui->dsy, ui->dey);
+    ymax = max(ui->dsy, ui->dey);
+
+#ifndef STYLUS_BASED
+    /*
+     * Left-dragging has no effect, so we treat a left-drag as a
+     * single click on dsx,dsy.
+     */
+    if (ui->drag_button == LEFT_BUTTON) {
+        xmin = xmax = ui->dsx;
+        ymin = ymax = ui->dsy;
+    }
+#endif
+
+    if (x < xmin || x > xmax || y < ymin || y > ymax)
+        return v;                      /* no change outside drag area */
+
+    if (v == TREE)
+        return v;                      /* trees are inviolate always */
+
+    if (xmin == xmax && ymin == ymax) {
+        /*
+         * Results of a simple click. Left button sets blanks to
+         * tents; right button sets blanks to non-tents; either
+         * button clears a non-blank square.
+         * If stylus-based however, it loops instead.
+         */
+        if (ui->drag_button == LEFT_BUTTON)
+#ifdef STYLUS_BASED
+            v = (v == BLANK ? TENT : (v == TENT ? NONTENT : BLANK));
+        else
+            v = (v == BLANK ? NONTENT : (v == NONTENT ? TENT : BLANK));
+#else
+            v = (v == BLANK ? TENT : BLANK);
+        else
+            v = (v == BLANK ? NONTENT : BLANK);
+#endif
+    } else {
+        /*
+         * Results of a drag. Left-dragging has no effect.
+         * Right-dragging sets all blank squares to non-tents and
+         * has no effect on anything else.
+         */
+        if (ui->drag_button == RIGHT_BUTTON)
+            v = (v == BLANK ? NONTENT : v);
+        else
+#ifdef STYLUS_BASED
+            v = (v == BLANK ? NONTENT : v);
+#else
+            /* do nothing */;
+#endif
+    }
+
+    return v;
+}
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    int w = state->p.w, h = state->p.h;
+    char tmpbuf[80];
+    int shift = button & MOD_SHFT, control = button & MOD_CTRL;
+
+    button &= ~MOD_MASK;
+
+    if (button == LEFT_BUTTON || button == RIGHT_BUTTON) {
+        x = FROMCOORD(x);
+        y = FROMCOORD(y);
+        if (x < 0 || y < 0 || x >= w || y >= h)
+            return NULL;
+
+        ui->drag_button = button;
+        ui->dsx = ui->dex = x;
+        ui->dsy = ui->dey = y;
+        ui->drag_ok = TRUE;
+        ui->cdisp = 0;
+        return "";             /* ui updated */
+    }
+
+    if ((IS_MOUSE_DRAG(button) || IS_MOUSE_RELEASE(button)) &&
+        ui->drag_button > 0) {
+        int xmin, ymin, xmax, ymax;
+        char *buf, *sep;
+        int buflen, bufsize, tmplen;
+
+        x = FROMCOORD(x);
+        y = FROMCOORD(y);
+        if (x < 0 || y < 0 || x >= w || y >= h) {
+            ui->drag_ok = FALSE;
+        } else {
+            /*
+             * Drags are limited to one row or column. Hence, we
+             * work out which coordinate is closer to the drag
+             * start, and move it _to_ the drag start.
+             */
+            if (abs(x - ui->dsx) < abs(y - ui->dsy))
+                x = ui->dsx;
+            else
+                y = ui->dsy;
+
+            ui->dex = x;
+            ui->dey = y;
+
+            ui->drag_ok = TRUE;
+        }
+
+        if (IS_MOUSE_DRAG(button))
+            return "";                 /* ui updated */
+
+        /*
+         * The drag has been released. Enact it.
+         */
+        if (!ui->drag_ok) {
+            ui->drag_button = -1;
+            return "";                 /* drag was just cancelled */
+        }
+
+        xmin = min(ui->dsx, ui->dex);
+        xmax = max(ui->dsx, ui->dex);
+        ymin = min(ui->dsy, ui->dey);
+        ymax = max(ui->dsy, ui->dey);
+        assert(0 <= xmin && xmin <= xmax && xmax < w);
+        assert(0 <= ymin && ymin <= ymax && ymax < h);
+
+        buflen = 0;
+        bufsize = 256;
+        buf = snewn(bufsize, char);
+        sep = "";
+        for (y = ymin; y <= ymax; y++)
+            for (x = xmin; x <= xmax; x++) {
+                int v = drag_xform(ui, x, y, state->grid[y*w+x]);
+                if (state->grid[y*w+x] != v) {
+                    tmplen = sprintf(tmpbuf, "%s%c%d,%d", sep,
+                                     (int)(v == BLANK ? 'B' :
+                                           v == TENT ? 'T' : 'N'),
+                                     x, y);
+                    sep = ";";
+
+                    if (buflen + tmplen >= bufsize) {
+                        bufsize = buflen + tmplen + 256;
+                        buf = sresize(buf, bufsize, char);
+                    }
+
+                    strcpy(buf+buflen, tmpbuf);
+                    buflen += tmplen;
+                }
+            }
+
+        ui->drag_button = -1;          /* drag is terminated */
+
+        if (buflen == 0) {
+            sfree(buf);
+            return "";                 /* ui updated (drag was terminated) */
+        } else {
+            buf[buflen] = '\0';
+            return buf;
+        }
+    }
+
+    if (IS_CURSOR_MOVE(button)) {
+        ui->cdisp = 1;
+        if (shift || control) {
+            int len = 0, i, indices[2];
+            indices[0] = ui->cx + w * ui->cy;
+            move_cursor(button, &ui->cx, &ui->cy, w, h, 0);
+            indices[1] = ui->cx + w * ui->cy;
+
+            /* NONTENTify all unique traversed eligible squares */
+            for (i = 0; i <= (indices[0] != indices[1]); ++i)
+                if (state->grid[indices[i]] == BLANK ||
+                    (control && state->grid[indices[i]] == TENT)) {
+                    len += sprintf(tmpbuf + len, "%sN%d,%d", len ? ";" : "",
+                                   indices[i] % w, indices[i] / w);
+                    assert(len < lenof(tmpbuf));
+                }
+
+            tmpbuf[len] = '\0';
+            if (len) return dupstr(tmpbuf);
+        } else
+            move_cursor(button, &ui->cx, &ui->cy, w, h, 0);
+        return "";
+    }
+    if (ui->cdisp) {
+        char rep = 0;
+        int v = state->grid[ui->cy*w+ui->cx];
+
+        if (v != TREE) {
+#ifdef SINGLE_CURSOR_SELECT
+            if (button == CURSOR_SELECT)
+                /* SELECT cycles T, N, B */
+                rep = v == BLANK ? 'T' : v == TENT ? 'N' : 'B';
+#else
+            if (button == CURSOR_SELECT)
+                rep = v == BLANK ? 'T' : 'B';
+            else if (button == CURSOR_SELECT2)
+                rep = v == BLANK ? 'N' : 'B';
+            else if (button == 'T' || button == 'N' || button == 'B')
+                rep = (char)button;
+#endif
+        }
+
+        if (rep) {
+            sprintf(tmpbuf, "%c%d,%d", (int)rep, ui->cx, ui->cy);
+            return dupstr(tmpbuf);
+        }
+    } else if (IS_CURSOR_SELECT(button)) {
+        ui->cdisp = 1;
+        return "";
+    }
+
+    return NULL;
+}
+
+static game_state *execute_move(const game_state *state, const char *move)
+{
+    int w = state->p.w, h = state->p.h;
+    char c;
+    int x, y, m, n, i, j;
+    game_state *ret = dup_game(state);
+
+    while (*move) {
+        c = *move;
+       if (c == 'S') {
+            int i;
+           ret->used_solve = TRUE;
+            /*
+             * Set all non-tree squares to NONTENT. The rest of the
+             * solve move will fill the tents in over the top.
+             */
+            for (i = 0; i < w*h; i++)
+                if (ret->grid[i] != TREE)
+                    ret->grid[i] = NONTENT;
+           move++;
+       } else if (c == 'B' || c == 'T' || c == 'N') {
+            move++;
+            if (sscanf(move, "%d,%d%n", &x, &y, &n) != 2 ||
+                x < 0 || y < 0 || x >= w || y >= h) {
+                free_game(ret);
+                return NULL;
+            }
+            if (ret->grid[y*w+x] == TREE) {
+                free_game(ret);
+                return NULL;
+            }
+            ret->grid[y*w+x] = (c == 'B' ? BLANK : c == 'T' ? TENT : NONTENT);
+            move += n;
+        } else {
+            free_game(ret);
+            return NULL;
+        }
+        if (*move == ';')
+            move++;
+        else if (*move) {
+            free_game(ret);
+            return NULL;
+        }
+    }
+
+    /*
+     * Check for completion.
+     */
+    for (i = n = m = 0; i < w*h; i++) {
+        if (ret->grid[i] == TENT)
+            n++;
+        else if (ret->grid[i] == TREE)
+            m++;
+    }
+    if (n == m) {
+        int nedges, maxedges, *edges, *capacity, *flow;
+
+        /*
+         * We have the right number of tents, which is a
+         * precondition for the game being complete. Now check that
+         * the numbers add up.
+         */
+       for (i = 0; i < w; i++) {
+           n = 0;
+           for (j = 0; j < h; j++)
+               if (ret->grid[j*w+i] == TENT)
+                   n++;
+           if (ret->numbers->numbers[i] != n)
+                goto completion_check_done;
+       }
+       for (i = 0; i < h; i++) {
+            n = 0;
+           for (j = 0; j < w; j++)
+               if (ret->grid[i*w+j] == TENT)
+                   n++;
+           if (ret->numbers->numbers[w+i] != n)
+                goto completion_check_done;
+       }
+        /*
+         * Also, check that no two tents are adjacent.
+         */
+        for (y = 0; y < h; y++)
+            for (x = 0; x < w; x++) {
+                if (x+1 < w &&
+                    ret->grid[y*w+x] == TENT && ret->grid[y*w+x+1] == TENT)
+                    goto completion_check_done;
+                if (y+1 < h &&
+                    ret->grid[y*w+x] == TENT && ret->grid[(y+1)*w+x] == TENT)
+                    goto completion_check_done;
+                if (x+1 < w && y+1 < h) {
+                    if (ret->grid[y*w+x] == TENT &&
+                        ret->grid[(y+1)*w+(x+1)] == TENT)
+                        goto completion_check_done;
+                    if (ret->grid[(y+1)*w+x] == TENT &&
+                        ret->grid[y*w+(x+1)] == TENT)
+                        goto completion_check_done;
+                }
+            }
+
+        /*
+         * OK; we have the right number of tents, they match the
+         * numeric clues, and they satisfy the non-adjacency
+         * criterion. Finally, we need to verify that they can be
+         * placed in a one-to-one matching with the trees such that
+         * every tent is orthogonally adjacent to its tree.
+         * 
+         * This bit is where the hard work comes in: we have to do
+         * it by finding such a matching using maxflow.
+         * 
+         * So we construct a network with one special source node,
+         * one special sink node, one node per tent, and one node
+         * per tree.
+         */
+        maxedges = 6 * m;
+        edges = snewn(2 * maxedges, int);
+        capacity = snewn(maxedges, int);
+        flow = snewn(maxedges, int);
+        nedges = 0;
+        /*
+         * Node numbering:
+         * 
+         * 0..w*h   trees/tents
+         * w*h      source
+         * w*h+1    sink
+         */
+        for (y = 0; y < h; y++)
+            for (x = 0; x < w; x++)
+                if (ret->grid[y*w+x] == TREE) {
+                    int d;
+
+                    /*
+                     * Here we use the direction enum declared for
+                     * the solver. We make use of the fact that the
+                     * directions are declared in the order
+                     * U,L,R,D, meaning that we go through the four
+                     * neighbours of any square in numerically
+                     * increasing order.
+                     */
+                   for (d = 1; d < MAXDIR; d++) {
+                       int x2 = x + dx(d), y2 = y + dy(d);
+                       if (x2 >= 0 && x2 < w && y2 >= 0 && y2 < h &&
+                            ret->grid[y2*w+x2] == TENT) {
+                            assert(nedges < maxedges);
+                            edges[nedges*2] = y*w+x;
+                            edges[nedges*2+1] = y2*w+x2;
+                            capacity[nedges] = 1;
+                            nedges++;
+                        }
+                    }
+                } else if (ret->grid[y*w+x] == TENT) {
+                    assert(nedges < maxedges);
+                    edges[nedges*2] = y*w+x;
+                    edges[nedges*2+1] = w*h+1;   /* edge going to sink */
+                    capacity[nedges] = 1;
+                    nedges++;
+                }
+        for (y = 0; y < h; y++)
+            for (x = 0; x < w; x++)
+                if (ret->grid[y*w+x] == TREE) {
+                    assert(nedges < maxedges);
+                    edges[nedges*2] = w*h;   /* edge coming from source */
+                    edges[nedges*2+1] = y*w+x;
+                    capacity[nedges] = 1;
+                    nedges++;
+                }
+       n = maxflow(w*h+2, w*h, w*h+1, nedges, edges, capacity, flow, NULL);
+
+        sfree(flow);
+        sfree(capacity);
+        sfree(edges);
+
+        if (n != m)
+            goto completion_check_done;
+
+        /*
+         * We haven't managed to fault the grid on any count. Score!
+         */
+        ret->completed = TRUE;
+    }
+    completion_check_done:
+
+    return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    /* fool the macros */
+    struct dummy { int tilesize; } dummy, *ds = &dummy;
+    dummy.tilesize = tilesize;
+
+    *x = TLBORDER + BRBORDER + TILESIZE * params->w;
+    *y = TLBORDER + BRBORDER + TILESIZE * params->h;
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+
+    frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
+
+    ret[COL_GRID * 3 + 0] = 0.0F;
+    ret[COL_GRID * 3 + 1] = 0.0F;
+    ret[COL_GRID * 3 + 2] = 0.0F;
+
+    ret[COL_GRASS * 3 + 0] = 0.7F;
+    ret[COL_GRASS * 3 + 1] = 1.0F;
+    ret[COL_GRASS * 3 + 2] = 0.5F;
+
+    ret[COL_TREETRUNK * 3 + 0] = 0.6F;
+    ret[COL_TREETRUNK * 3 + 1] = 0.4F;
+    ret[COL_TREETRUNK * 3 + 2] = 0.0F;
+
+    ret[COL_TREELEAF * 3 + 0] = 0.0F;
+    ret[COL_TREELEAF * 3 + 1] = 0.7F;
+    ret[COL_TREELEAF * 3 + 2] = 0.0F;
+
+    ret[COL_TENT * 3 + 0] = 0.8F;
+    ret[COL_TENT * 3 + 1] = 0.7F;
+    ret[COL_TENT * 3 + 2] = 0.0F;
+
+    ret[COL_ERROR * 3 + 0] = 1.0F;
+    ret[COL_ERROR * 3 + 1] = 0.0F;
+    ret[COL_ERROR * 3 + 2] = 0.0F;
+
+    ret[COL_ERRTEXT * 3 + 0] = 1.0F;
+    ret[COL_ERRTEXT * 3 + 1] = 1.0F;
+    ret[COL_ERRTEXT * 3 + 2] = 1.0F;
+
+    ret[COL_ERRTRUNK * 3 + 0] = 0.6F;
+    ret[COL_ERRTRUNK * 3 + 1] = 0.0F;
+    ret[COL_ERRTRUNK * 3 + 2] = 0.0F;
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    int w = state->p.w, h = state->p.h;
+    struct game_drawstate *ds = snew(struct game_drawstate);
+    int i;
+
+    ds->tilesize = 0;
+    ds->started = FALSE;
+    ds->p = state->p;                  /* structure copy */
+    ds->drawn = snewn(w*h, int);
+    for (i = 0; i < w*h; i++)
+       ds->drawn[i] = MAGIC;
+    ds->numbersdrawn = snewn(w+h, int);
+    for (i = 0; i < w+h; i++)
+       ds->numbersdrawn[i] = 2;
+    ds->cx = ds->cy = -1;
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds->drawn);
+    sfree(ds->numbersdrawn);
+    sfree(ds);
+}
+
+enum {
+    ERR_ADJ_TOPLEFT = 4,
+    ERR_ADJ_TOP,
+    ERR_ADJ_TOPRIGHT,
+    ERR_ADJ_LEFT,
+    ERR_ADJ_RIGHT,
+    ERR_ADJ_BOTLEFT,
+    ERR_ADJ_BOT,
+    ERR_ADJ_BOTRIGHT,
+    ERR_OVERCOMMITTED
+};
+
+static int *find_errors(const game_state *state, char *grid)
+{
+    int w = state->p.w, h = state->p.h;
+    int *ret = snewn(w*h + w + h, int);
+    int *tmp = snewn(w*h*2, int), *dsf = tmp + w*h;
+    int x, y;
+
+    /*
+     * This function goes through a grid and works out where to
+     * highlight play errors in red. The aim is that it should
+     * produce at least one error highlight for any complete grid
+     * (or complete piece of grid) violating a puzzle constraint, so
+     * that a grid containing no BLANK squares is either a win or is
+     * marked up in some way that indicates why not.
+     *
+     * So it's easy enough to highlight errors in the numeric clues
+     * - just light up any row or column number which is not
+     * fulfilled - and it's just as easy to highlight adjacent
+     * tents. The difficult bit is highlighting failures in the
+     * tent/tree matching criterion.
+     *
+     * A natural approach would seem to be to apply the maxflow
+     * algorithm to find the tent/tree matching; if this fails, it
+     * must necessarily terminate with a min-cut which can be
+     * reinterpreted as some set of trees which have too few tents
+     * between them (or vice versa). However, it's bad for
+     * localising errors, because it's not easy to make the
+     * algorithm narrow down to the _smallest_ such set of trees: if
+     * trees A and B have only one tent between them, for instance,
+     * it might perfectly well highlight not only A and B but also
+     * trees C and D which are correctly matched on the far side of
+     * the grid, on the grounds that those four trees between them
+     * have only three tents.
+     *
+     * Also, that approach fares badly when you introduce the
+     * additional requirement that incomplete grids should have
+     * errors highlighted only when they can be proved to be errors
+     * - so that trees should not be marked as having too few tents
+     * if there are enough BLANK squares remaining around them that
+     * could be turned into the missing tents (to do so would be
+     * patronising, since the overwhelming likelihood is not that
+     * the player has forgotten to put a tree there but that they
+     * have merely not put one there _yet_). However, tents with too
+     * few trees can be marked immediately, since those are
+     * definitely player error.
+     *
+     * So I adopt an alternative approach, which is to consider the
+     * bipartite adjacency graph between trees and tents
+     * ('bipartite' in the sense that for these purposes I
+     * deliberately ignore two adjacent trees or two adjacent
+     * tents), divide that graph up into its connected components
+     * using a dsf, and look for components which contain different
+     * numbers of trees and tents. This allows me to highlight
+     * groups of tents with too few trees between them immediately,
+     * and then in order to find groups of trees with too few tents
+     * I redo the same process but counting BLANKs as potential
+     * tents (so that the only trees highlighted are those
+     * surrounded by enough NONTENTs to make it impossible to give
+     * them enough tents).
+     *
+     * However, this technique is incomplete: it is not a sufficient
+     * condition for the existence of a perfect matching that every
+     * connected component of the graph has the same number of tents
+     * and trees. An example of a graph which satisfies the latter
+     * condition but still has no perfect matching is
+     * 
+     *     A    B    C
+     *     |   /   ,/|
+     *     |  /  ,'/ |
+     *     | / ,' /  |
+     *     |/,'  /   |
+     *     1    2    3
+     *
+     * which can be realised in Tents as
+     * 
+     *       B
+     *     A 1 C 2
+     *         3
+     *
+     * The matching-error highlighter described above will not mark
+     * this construction as erroneous. However, something else will:
+     * the three tents in the above diagram (let us suppose A,B,C
+     * are the tents, though it doesn't matter which) contain two
+     * diagonally adjacent pairs. So there will be _an_ error
+     * highlighted for the above layout, even though not all types
+     * of error will be highlighted.
+     *
+     * And in fact we can prove that this will always be the case:
+     * that the shortcomings of the matching-error highlighter will
+     * always be made up for by the easy tent adjacency highlighter.
+     *
+     * Lemma: Let G be a bipartite graph between n trees and n
+     * tents, which is connected, and in which no tree has degree
+     * more than two (but a tent may). Then G has a perfect matching.
+     * 
+     * (Note: in the statement and proof of the Lemma I will
+     * consistently use 'tree' to indicate a type of graph vertex as
+     * opposed to a tent, and not to indicate a tree in the graph-
+     * theoretic sense.)
+     *
+     * Proof:
+     * 
+     * If we can find a tent of degree 1 joined to a tree of degree
+     * 2, then any perfect matching must pair that tent with that
+     * tree. Hence, we can remove both, leaving a smaller graph G'
+     * which still satisfies all the conditions of the Lemma, and
+     * which has a perfect matching iff G does.
+     *
+     * So, wlog, we may assume G contains no tent of degree 1 joined
+     * to a tree of degree 2; if it does, we can reduce it as above.
+     *
+     * If G has no tent of degree 1 at all, then every tent has
+     * degree at least two, so there are at least 2n edges in the
+     * graph. But every tree has degree at most two, so there are at
+     * most 2n edges. Hence there must be exactly 2n edges, so every
+     * tree and every tent must have degree exactly two, which means
+     * that the whole graph consists of a single loop (by
+     * connectedness), and therefore certainly has a perfect
+     * matching.
+     *
+     * Alternatively, if G does have a tent of degree 1 but it is
+     * not connected to a tree of degree 2, then the tree it is
+     * connected to must have degree 1 - and, by connectedness, that
+     * must mean that that tent and that tree between them form the
+     * entire graph. This trivial graph has a trivial perfect
+     * matching. []
+     *
+     * That proves the lemma. Hence, in any case where the matching-
+     * error highlighter fails to highlight an erroneous component
+     * (because it has the same number of tents as trees, but they
+     * cannot be matched up), the above lemma tells us that there
+     * must be a tree with degree more than 2, i.e. a tree
+     * orthogonally adjacent to at least three tents. But in that
+     * case, there must be some pair of those three tents which are
+     * diagonally adjacent to each other, so the tent-adjacency
+     * highlighter will necessarily show an error. So any filled
+     * layout in Tents which is not a correct solution to the puzzle
+     * must have _some_ error highlighted by the subroutine below.
+     *
+     * (Of course it would be nicer if we could highlight all
+     * errors: in the above example layout, we would like to
+     * highlight tents A,B as having too few trees between them, and
+     * trees 2,3 as having too few tents, in addition to marking the
+     * adjacency problems. But I can't immediately think of any way
+     * to find the smallest sets of such tents and trees without an
+     * O(2^N) loop over all subsets of a given component.)
+     */
+
+    /*
+     * ret[0] through to ret[w*h-1] give error markers for the grid
+     * squares. After that, ret[w*h] to ret[w*h+w-1] give error
+     * markers for the column numbers, and ret[w*h+w] to
+     * ret[w*h+w+h-1] for the row numbers.
+     */
+
+    /*
+     * Spot tent-adjacency violations.
+     */
+    for (x = 0; x < w*h; x++)
+       ret[x] = 0;
+    for (y = 0; y < h; y++) {
+       for (x = 0; x < w; x++) {
+           if (y+1 < h && x+1 < w &&
+               ((grid[y*w+x] == TENT &&
+                 grid[(y+1)*w+(x+1)] == TENT) ||
+                (grid[(y+1)*w+x] == TENT &&
+                 grid[y*w+(x+1)] == TENT))) {
+               ret[y*w+x] |= 1 << ERR_ADJ_BOTRIGHT;
+               ret[(y+1)*w+x] |= 1 << ERR_ADJ_TOPRIGHT;
+               ret[y*w+(x+1)] |= 1 << ERR_ADJ_BOTLEFT;
+               ret[(y+1)*w+(x+1)] |= 1 << ERR_ADJ_TOPLEFT;
+           }
+           if (y+1 < h &&
+               grid[y*w+x] == TENT &&
+               grid[(y+1)*w+x] == TENT) {
+               ret[y*w+x] |= 1 << ERR_ADJ_BOT;
+               ret[(y+1)*w+x] |= 1 << ERR_ADJ_TOP;
+           }
+           if (x+1 < w &&
+               grid[y*w+x] == TENT &&
+               grid[y*w+(x+1)] == TENT) {
+               ret[y*w+x] |= 1 << ERR_ADJ_RIGHT;
+               ret[y*w+(x+1)] |= 1 << ERR_ADJ_LEFT;
+           }
+       }
+    }
+
+    /*
+     * Spot numeric clue violations.
+     */
+    for (x = 0; x < w; x++) {
+       int tents = 0, maybetents = 0;
+       for (y = 0; y < h; y++) {
+           if (grid[y*w+x] == TENT)
+               tents++;
+           else if (grid[y*w+x] == BLANK)
+               maybetents++;
+       }
+       ret[w*h+x] = (tents > state->numbers->numbers[x] ||
+                     tents + maybetents < state->numbers->numbers[x]);
+    }
+    for (y = 0; y < h; y++) {
+       int tents = 0, maybetents = 0;
+       for (x = 0; x < w; x++) {
+           if (grid[y*w+x] == TENT)
+               tents++;
+           else if (grid[y*w+x] == BLANK)
+               maybetents++;
+       }
+       ret[w*h+w+y] = (tents > state->numbers->numbers[w+y] ||
+                       tents + maybetents < state->numbers->numbers[w+y]);
+    }
+
+    /*
+     * Identify groups of tents with too few trees between them,
+     * which we do by constructing the connected components of the
+     * bipartite adjacency graph between tents and trees
+     * ('bipartite' in the sense that we deliberately ignore
+     * adjacency between tents or between trees), and highlighting
+     * all the tents in any component which has a smaller tree
+     * count.
+     */
+    dsf_init(dsf, w*h);
+    /* Construct the equivalence classes. */
+    for (y = 0; y < h; y++) {
+       for (x = 0; x < w-1; x++) {
+           if ((grid[y*w+x] == TREE && grid[y*w+x+1] == TENT) ||
+               (grid[y*w+x] == TENT && grid[y*w+x+1] == TREE))
+               dsf_merge(dsf, y*w+x, y*w+x+1);
+       }
+    }
+    for (y = 0; y < h-1; y++) {
+       for (x = 0; x < w; x++) {
+           if ((grid[y*w+x] == TREE && grid[(y+1)*w+x] == TENT) ||
+               (grid[y*w+x] == TENT && grid[(y+1)*w+x] == TREE))
+               dsf_merge(dsf, y*w+x, (y+1)*w+x);
+       }
+    }
+    /* Count up the tent/tree difference in each one. */
+    for (x = 0; x < w*h; x++)
+       tmp[x] = 0;
+    for (x = 0; x < w*h; x++) {
+       y = dsf_canonify(dsf, x);
+       if (grid[x] == TREE)
+           tmp[y]++;
+       else if (grid[x] == TENT)
+           tmp[y]--;
+    }
+    /* And highlight any tent belonging to an equivalence class with
+     * a score less than zero. */
+    for (x = 0; x < w*h; x++) {
+       y = dsf_canonify(dsf, x);
+       if (grid[x] == TENT && tmp[y] < 0)
+           ret[x] |= 1 << ERR_OVERCOMMITTED;
+    }
+
+    /*
+     * Identify groups of trees with too few tents between them.
+     * This is done similarly, except that we now count BLANK as
+     * equivalent to TENT, i.e. we only highlight such trees when
+     * the user hasn't even left _room_ to provide tents for them
+     * all. (Otherwise, we'd highlight all trees red right at the
+     * start of the game, before the user had done anything wrong!)
+     */
+#define TENT(x) ((x)==TENT || (x)==BLANK)
+    dsf_init(dsf, w*h);
+    /* Construct the equivalence classes. */
+    for (y = 0; y < h; y++) {
+       for (x = 0; x < w-1; x++) {
+           if ((grid[y*w+x] == TREE && TENT(grid[y*w+x+1])) ||
+               (TENT(grid[y*w+x]) && grid[y*w+x+1] == TREE))
+               dsf_merge(dsf, y*w+x, y*w+x+1);
+       }
+    }
+    for (y = 0; y < h-1; y++) {
+       for (x = 0; x < w; x++) {
+           if ((grid[y*w+x] == TREE && TENT(grid[(y+1)*w+x])) ||
+               (TENT(grid[y*w+x]) && grid[(y+1)*w+x] == TREE))
+               dsf_merge(dsf, y*w+x, (y+1)*w+x);
+       }
+    }
+    /* Count up the tent/tree difference in each one. */
+    for (x = 0; x < w*h; x++)
+       tmp[x] = 0;
+    for (x = 0; x < w*h; x++) {
+       y = dsf_canonify(dsf, x);
+       if (grid[x] == TREE)
+           tmp[y]++;
+       else if (TENT(grid[x]))
+           tmp[y]--;
+    }
+    /* And highlight any tree belonging to an equivalence class with
+     * a score more than zero. */
+    for (x = 0; x < w*h; x++) {
+       y = dsf_canonify(dsf, x);
+       if (grid[x] == TREE && tmp[y] > 0)
+           ret[x] |= 1 << ERR_OVERCOMMITTED;
+    }
+#undef TENT
+
+    sfree(tmp);
+    return ret;
+}
+
+static void draw_err_adj(drawing *dr, game_drawstate *ds, int x, int y)
+{
+    int coords[8];
+    int yext, xext;
+
+    /*
+     * Draw a diamond.
+     */
+    coords[0] = x - TILESIZE*2/5;
+    coords[1] = y;
+    coords[2] = x;
+    coords[3] = y - TILESIZE*2/5;
+    coords[4] = x + TILESIZE*2/5;
+    coords[5] = y;
+    coords[6] = x;
+    coords[7] = y + TILESIZE*2/5;
+    draw_polygon(dr, coords, 4, COL_ERROR, COL_GRID);
+
+    /*
+     * Draw an exclamation mark in the diamond. This turns out to
+     * look unpleasantly off-centre if done via draw_text, so I do
+     * it by hand on the basis that exclamation marks aren't that
+     * difficult to draw...
+     */
+    xext = TILESIZE/16;
+    yext = TILESIZE*2/5 - (xext*2+2);
+    draw_rect(dr, x-xext, y-yext, xext*2+1, yext*2+1 - (xext*3),
+             COL_ERRTEXT);
+    draw_rect(dr, x-xext, y+yext-xext*2+1, xext*2+1, xext*2, COL_ERRTEXT);
+}
+
+static void draw_tile(drawing *dr, game_drawstate *ds,
+                      int x, int y, int v, int cur, int printing)
+{
+    int err;
+    int tx = COORD(x), ty = COORD(y);
+    int cx = tx + TILESIZE/2, cy = ty + TILESIZE/2;
+
+    err = v & ~15;
+    v &= 15;
+
+    clip(dr, tx, ty, TILESIZE, TILESIZE);
+
+    if (!printing) {
+       draw_rect(dr, tx, ty, TILESIZE, TILESIZE, COL_GRID);
+       draw_rect(dr, tx+1, ty+1, TILESIZE-1, TILESIZE-1,
+                 (v == BLANK ? COL_BACKGROUND : COL_GRASS));
+    }
+
+    if (v == TREE) {
+       int i;
+
+       (printing ? draw_rect_outline : draw_rect)
+       (dr, cx-TILESIZE/15, ty+TILESIZE*3/10,
+        2*(TILESIZE/15)+1, (TILESIZE*9/10 - TILESIZE*3/10),
+        (err & (1<<ERR_OVERCOMMITTED) ? COL_ERRTRUNK : COL_TREETRUNK));
+
+       for (i = 0; i < (printing ? 2 : 1); i++) {
+           int col = (i == 1 ? COL_BACKGROUND :
+                      (err & (1<<ERR_OVERCOMMITTED) ? COL_ERROR : 
+                       COL_TREELEAF));
+           int sub = i * (TILESIZE/32);
+           draw_circle(dr, cx, ty+TILESIZE*4/10, TILESIZE/4 - sub,
+                       col, col);
+           draw_circle(dr, cx+TILESIZE/5, ty+TILESIZE/4, TILESIZE/8 - sub,
+                       col, col);
+           draw_circle(dr, cx-TILESIZE/5, ty+TILESIZE/4, TILESIZE/8 - sub,
+                       col, col);
+           draw_circle(dr, cx+TILESIZE/4, ty+TILESIZE*6/13, TILESIZE/8 - sub,
+                       col, col);
+           draw_circle(dr, cx-TILESIZE/4, ty+TILESIZE*6/13, TILESIZE/8 - sub,
+                       col, col);
+       }
+    } else if (v == TENT) {
+        int coords[6];
+       int col;
+        coords[0] = cx - TILESIZE/3;
+        coords[1] = cy + TILESIZE/3;
+        coords[2] = cx + TILESIZE/3;
+        coords[3] = cy + TILESIZE/3;
+        coords[4] = cx;
+        coords[5] = cy - TILESIZE/3;
+       col = (err & (1<<ERR_OVERCOMMITTED) ? COL_ERROR : COL_TENT);
+        draw_polygon(dr, coords, 3, (printing ? -1 : col), col);
+    }
+
+    if (err & (1 << ERR_ADJ_TOPLEFT))
+       draw_err_adj(dr, ds, tx, ty);
+    if (err & (1 << ERR_ADJ_TOP))
+       draw_err_adj(dr, ds, tx+TILESIZE/2, ty);
+    if (err & (1 << ERR_ADJ_TOPRIGHT))
+       draw_err_adj(dr, ds, tx+TILESIZE, ty);
+    if (err & (1 << ERR_ADJ_LEFT))
+       draw_err_adj(dr, ds, tx, ty+TILESIZE/2);
+    if (err & (1 << ERR_ADJ_RIGHT))
+       draw_err_adj(dr, ds, tx+TILESIZE, ty+TILESIZE/2);
+    if (err & (1 << ERR_ADJ_BOTLEFT))
+       draw_err_adj(dr, ds, tx, ty+TILESIZE);
+    if (err & (1 << ERR_ADJ_BOT))
+       draw_err_adj(dr, ds, tx+TILESIZE/2, ty+TILESIZE);
+    if (err & (1 << ERR_ADJ_BOTRIGHT))
+       draw_err_adj(dr, ds, tx+TILESIZE, ty+TILESIZE);
+
+    if (cur) {
+      int coff = TILESIZE/8;
+      draw_rect_outline(dr, tx + coff, ty + coff,
+                        TILESIZE - coff*2 + 1, TILESIZE - coff*2 + 1,
+                       COL_GRID);
+    }
+
+    unclip(dr);
+    draw_update(dr, tx+1, ty+1, TILESIZE-1, TILESIZE-1);
+}
+
+/*
+ * Internal redraw function, used for printing as well as drawing.
+ */
+static void int_redraw(drawing *dr, game_drawstate *ds,
+                       const game_state *oldstate, const game_state *state,
+                       int dir, const game_ui *ui,
+                      float animtime, float flashtime, int printing)
+{
+    int w = state->p.w, h = state->p.h;
+    int x, y, flashing;
+    int cx = -1, cy = -1;
+    int cmoved = 0;
+    char *tmpgrid;
+    int *errors;
+
+    if (ui) {
+      if (ui->cdisp) { cx = ui->cx; cy = ui->cy; }
+      if (cx != ds->cx || cy != ds->cy) cmoved = 1;
+    }
+
+    if (printing || !ds->started) {
+       if (!printing) {
+           int ww, wh;
+           game_compute_size(&state->p, TILESIZE, &ww, &wh);
+           draw_rect(dr, 0, 0, ww, wh, COL_BACKGROUND);
+           draw_update(dr, 0, 0, ww, wh);
+           ds->started = TRUE;
+       }
+
+       if (printing)
+           print_line_width(dr, TILESIZE/64);
+
+        /*
+         * Draw the grid.
+         */
+        for (y = 0; y <= h; y++)
+            draw_line(dr, COORD(0), COORD(y), COORD(w), COORD(y), COL_GRID);
+        for (x = 0; x <= w; x++)
+            draw_line(dr, COORD(x), COORD(0), COORD(x), COORD(h), COL_GRID);
+    }
+
+    if (flashtime > 0)
+       flashing = (int)(flashtime * 3 / FLASH_TIME) != 1;
+    else
+       flashing = FALSE;
+
+    /*
+     * Find errors. For this we use _part_ of the information from a
+     * currently active drag: we transform dsx,dsy but not anything
+     * else. (This seems to strike a good compromise between having
+     * the error highlights respond instantly to single clicks, but
+     * not giving constant feedback during a right-drag.)
+     */
+    if (ui && ui->drag_button >= 0) {
+       tmpgrid = snewn(w*h, char);
+       memcpy(tmpgrid, state->grid, w*h);
+       tmpgrid[ui->dsy * w + ui->dsx] =
+           drag_xform(ui, ui->dsx, ui->dsy, tmpgrid[ui->dsy * w + ui->dsx]);
+       errors = find_errors(state, tmpgrid);
+       sfree(tmpgrid);
+    } else {
+       errors = find_errors(state, state->grid);
+    }
+
+    /*
+     * Draw the grid.
+     */
+    for (y = 0; y < h; y++) {
+        for (x = 0; x < w; x++) {
+            int v = state->grid[y*w+x];
+            int credraw = 0;
+
+            /*
+             * We deliberately do not take drag_ok into account
+             * here, because user feedback suggests that it's
+             * marginally nicer not to have the drag effects
+             * flickering on and off disconcertingly.
+             */
+            if (ui && ui->drag_button >= 0)
+                v = drag_xform(ui, x, y, v);
+
+            if (flashing && (v == TREE || v == TENT))
+                v = NONTENT;
+
+            if (cmoved) {
+              if ((x == cx && y == cy) ||
+                  (x == ds->cx && y == ds->cy)) credraw = 1;
+            }
+
+           v |= errors[y*w+x];
+
+            if (printing || ds->drawn[y*w+x] != v || credraw) {
+                draw_tile(dr, ds, x, y, v, (x == cx && y == cy), printing);
+                if (!printing)
+                   ds->drawn[y*w+x] = v;
+            }
+        }
+    }
+
+    /*
+     * Draw (or redraw, if their error-highlighted state has
+     * changed) the numbers.
+     */
+    for (x = 0; x < w; x++) {
+       if (printing || ds->numbersdrawn[x] != errors[w*h+x]) {
+           char buf[80];
+           draw_rect(dr, COORD(x), COORD(h)+1, TILESIZE, BRBORDER-1,
+                     COL_BACKGROUND);
+           sprintf(buf, "%d", state->numbers->numbers[x]);
+           draw_text(dr, COORD(x) + TILESIZE/2, COORD(h+1),
+                     FONT_VARIABLE, TILESIZE/2, ALIGN_HCENTRE|ALIGN_VNORMAL,
+                     (errors[w*h+x] ? COL_ERROR : COL_GRID), buf);
+           draw_update(dr, COORD(x), COORD(h)+1, TILESIZE, BRBORDER-1);
+           if (!printing)
+                ds->numbersdrawn[x] = errors[w*h+x];
+       }
+    }
+    for (y = 0; y < h; y++) {
+       if (printing || ds->numbersdrawn[w+y] != errors[w*h+w+y]) {
+           char buf[80];
+           draw_rect(dr, COORD(w)+1, COORD(y), BRBORDER-1, TILESIZE,
+                     COL_BACKGROUND);
+           sprintf(buf, "%d", state->numbers->numbers[w+y]);
+           draw_text(dr, COORD(w+1), COORD(y) + TILESIZE/2,
+                     FONT_VARIABLE, TILESIZE/2, ALIGN_HRIGHT|ALIGN_VCENTRE,
+                     (errors[w*h+w+y] ? COL_ERROR : COL_GRID), buf);
+           draw_update(dr, COORD(w)+1, COORD(y), BRBORDER-1, TILESIZE);
+           if (!printing)
+                ds->numbersdrawn[w+y] = errors[w*h+w+y];
+       }
+    }
+
+    if (cmoved) {
+       ds->cx = cx;
+       ds->cy = cy;
+    }
+
+    sfree(errors);
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int_redraw(dr, ds, oldstate, state, dir, ui, animtime, flashtime, FALSE);
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return 0.0F;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    if (!oldstate->completed && newstate->completed &&
+       !oldstate->used_solve && !newstate->used_solve)
+        return FLASH_TIME;
+
+    return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    return state->completed ? +1 : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+    int pw, ph;
+
+    /*
+     * I'll use 6mm squares by default.
+     */
+    game_compute_size(params, 600, &pw, &ph);
+    *x = pw / 100.0F;
+    *y = ph / 100.0F;
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+    int c;
+
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    game_drawstate ads, *ds = &ads;
+    game_set_size(dr, ds, NULL, tilesize);
+
+    c = print_mono_colour(dr, 1); assert(c == COL_BACKGROUND);
+    c = print_mono_colour(dr, 0); assert(c == COL_GRID);
+    c = print_mono_colour(dr, 1); assert(c == COL_GRASS);
+    c = print_mono_colour(dr, 0); assert(c == COL_TREETRUNK);
+    c = print_mono_colour(dr, 0); assert(c == COL_TREELEAF);
+    c = print_mono_colour(dr, 0); assert(c == COL_TENT);
+
+    int_redraw(dr, ds, NULL, state, +1, NULL, 0.0F, 0.0F, TRUE);
+}
+
+#ifdef COMBINED
+#define thegame tents
+#endif
+
+const struct game thegame = {
+    "Tents", "games.tents", "tents",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    TRUE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILESIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    TRUE, FALSE, game_print_size, game_print,
+    FALSE,                            /* wants_statusbar */
+    FALSE, game_timing_state,
+    REQUIRE_RBUTTON,                  /* flags */
+};
+
+#ifdef STANDALONE_SOLVER
+
+#include <stdarg.h>
+
+int main(int argc, char **argv)
+{
+    game_params *p;
+    game_state *s, *s2;
+    char *id = NULL, *desc, *err;
+    int grade = FALSE;
+    int ret, diff, really_verbose = FALSE;
+    struct solver_scratch *sc;
+
+    while (--argc > 0) {
+        char *p = *++argv;
+        if (!strcmp(p, "-v")) {
+            really_verbose = TRUE;
+        } else if (!strcmp(p, "-g")) {
+            grade = TRUE;
+        } else if (*p == '-') {
+            fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p);
+            return 1;
+        } else {
+            id = p;
+        }
+    }
+
+    if (!id) {
+        fprintf(stderr, "usage: %s [-g | -v] <game_id>\n", argv[0]);
+        return 1;
+    }
+
+    desc = strchr(id, ':');
+    if (!desc) {
+        fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]);
+        return 1;
+    }
+    *desc++ = '\0';
+
+    p = default_params();
+    decode_params(p, id);
+    err = validate_desc(p, desc);
+    if (err) {
+        fprintf(stderr, "%s: %s\n", argv[0], err);
+        return 1;
+    }
+    s = new_game(NULL, p, desc);
+    s2 = new_game(NULL, p, desc);
+
+    sc = new_scratch(p->w, p->h);
+
+    /*
+     * When solving an Easy puzzle, we don't want to bother the
+     * user with Hard-level deductions. For this reason, we grade
+     * the puzzle internally before doing anything else.
+     */
+    ret = -1;                         /* placate optimiser */
+    for (diff = 0; diff < DIFFCOUNT; diff++) {
+       ret = tents_solve(p->w, p->h, s->grid, s->numbers->numbers,
+                         s2->grid, sc, diff);
+       if (ret < 2)
+           break;
+    }
+
+    if (diff == DIFFCOUNT) {
+       if (grade)
+           printf("Difficulty rating: too hard to solve internally\n");
+       else
+           printf("Unable to find a unique solution\n");
+    } else {
+       if (grade) {
+           if (ret == 0)
+               printf("Difficulty rating: impossible (no solution exists)\n");
+           else if (ret == 1)
+               printf("Difficulty rating: %s\n", tents_diffnames[diff]);
+       } else {
+           verbose = really_verbose;
+           ret = tents_solve(p->w, p->h, s->grid, s->numbers->numbers,
+                             s2->grid, sc, diff);
+           if (ret == 0)
+               printf("Puzzle is inconsistent\n");
+           else
+               fputs(game_text_format(s2), stdout);
+       }
+    }
+
+    return 0;
+}
+
+#endif
+
+/* vim: set shiftwidth=4 tabstop=8: */
diff --git a/towers.R b/towers.R
new file mode 100644 (file)
index 0000000..c060c69
--- /dev/null
+++ b/towers.R
@@ -0,0 +1,25 @@
+# -*- makefile -*-
+
+TOWERS_LATIN_EXTRA = tree234 maxflow
+TOWERS_EXTRA = latin TOWERS_LATIN_EXTRA
+
+towers    : [X] GTK COMMON towers TOWERS_EXTRA towers-icon|no-icon
+
+towers    : [G] WINDOWS COMMON towers TOWERS_EXTRA towers.res|noicon.res
+
+towerssolver : [U] towers[STANDALONE_SOLVER] latin[STANDALONE_SOLVER] TOWERS_LATIN_EXTRA STANDALONE
+towerssolver : [C] towers[STANDALONE_SOLVER] latin[STANDALONE_SOLVER] TOWERS_LATIN_EXTRA STANDALONE
+
+ALL += towers[COMBINED] TOWERS_EXTRA
+
+!begin am gtk
+GAMES += towers
+!end
+
+!begin >list.c
+    A(towers) \
+!end
+
+!begin >gamedesc.txt
+towers:towers.exe:Towers:Tower-placing Latin square puzzle:Complete the latin square of towers in accordance with the clues.
+!end
diff --git a/towers.c b/towers.c
new file mode 100644 (file)
index 0000000..9525adb
--- /dev/null
+++ b/towers.c
@@ -0,0 +1,2104 @@
+/*
+ * towers.c: the puzzle also known as 'Skyscrapers'.
+ *
+ * Possible future work:
+ *
+ *  - Relax the upper bound on grid size at 9?
+ *     + I'd need TOCHAR and FROMCHAR macros a bit like group's, to
+ *      be used wherever this code has +'0' or -'0'
+ *     + the pencil marks in the drawstate would need a separate
+ *      word to live in
+ *     + the clues outside the grid would have to cope with being
+ *      multi-digit, meaning in particular that the text formatting
+ *      would become more unpleasant
+ *     + most importantly, though, the solver just isn't fast
+ *      enough. Even at size 9 it can't really do the solver_hard
+ *      factorial-time enumeration at a sensible rate. Easy puzzles
+ *      higher than that would be possible, but more latin-squarey
+ *      than skyscrapery, as it were.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+#include "latin.h"
+
+/*
+ * Difficulty levels. I do some macro ickery here to ensure that my
+ * enum and the various forms of my name list always match up.
+ */
+#define DIFFLIST(A) \
+    A(EASY,Easy,solver_easy,e) \
+    A(HARD,Hard,solver_hard,h) \
+    A(EXTREME,Extreme,NULL,x) \
+    A(UNREASONABLE,Unreasonable,NULL,u)
+#define ENUM(upper,title,func,lower) DIFF_ ## upper,
+#define TITLE(upper,title,func,lower) #title,
+#define ENCODE(upper,title,func,lower) #lower
+#define CONFIG(upper,title,func,lower) ":" #title
+enum { DIFFLIST(ENUM) DIFFCOUNT };
+static char const *const towers_diffnames[] = { DIFFLIST(TITLE) };
+static char const towers_diffchars[] = DIFFLIST(ENCODE);
+#define DIFFCONFIG DIFFLIST(CONFIG)
+
+enum {
+    COL_BACKGROUND,
+    COL_GRID,
+    COL_USER,
+    COL_HIGHLIGHT,
+    COL_ERROR,
+    COL_PENCIL,
+    COL_DONE,
+    NCOLOURS
+};
+
+struct game_params {
+    int w, diff;
+};
+
+struct clues {
+    int refcount;
+    int w;
+    /*
+     * An array of 4w integers, of which:
+     *  - the first w run across the top
+     *  - the next w across the bottom
+     *  - the third w down the left
+     *  - the last w down the right.
+     */
+    int *clues;
+
+    /*
+     * An array of w*w digits.
+     */
+    digit *immutable;
+};
+
+/*
+ * Macros to compute clue indices and coordinates.
+ */
+#define STARTSTEP(start, step, index, w) do { \
+    if (index < w) \
+       start = index, step = w; \
+    else if (index < 2*w) \
+       start = (w-1)*w+(index-w), step = -w; \
+    else if (index < 3*w) \
+       start = w*(index-2*w), step = 1; \
+    else \
+       start = w*(index-3*w)+(w-1), step = -1; \
+} while (0)
+#define CSTARTSTEP(start, step, index, w) \
+    STARTSTEP(start, step, (((index)+2*w)%(4*w)), w)
+#define CLUEPOS(x, y, index, w) do { \
+    if (index < w) \
+       x = index, y = -1; \
+    else if (index < 2*w) \
+       x = index-w, y = w; \
+    else if (index < 3*w) \
+       x = -1, y = index-2*w; \
+    else \
+       x = w, y = index-3*w; \
+} while (0)
+
+#ifdef STANDALONE_SOLVER
+static const char *const cluepos[] = {
+    "above column", "below column", "left of row", "right of row"
+};
+#endif
+
+struct game_state {
+    game_params par;
+    struct clues *clues;
+    unsigned char *clues_done;
+    digit *grid;
+    int *pencil;                      /* bitmaps using bits 1<<1..1<<n */
+    int completed, cheated;
+};
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = 5;
+    ret->diff = DIFF_EASY;
+
+    return ret;
+}
+
+const static struct game_params towers_presets[] = {
+    {  4, DIFF_EASY         },
+    {  5, DIFF_EASY         },
+    {  5, DIFF_HARD         },
+    {  6, DIFF_EASY         },
+    {  6, DIFF_HARD         },
+    {  6, DIFF_EXTREME      },
+    {  6, DIFF_UNREASONABLE },
+};
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params *ret;
+    char buf[80];
+
+    if (i < 0 || i >= lenof(towers_presets))
+        return FALSE;
+
+    ret = snew(game_params);
+    *ret = towers_presets[i]; /* structure copy */
+
+    sprintf(buf, "%dx%d %s", ret->w, ret->w, towers_diffnames[ret->diff]);
+
+    *name = dupstr(buf);
+    *params = ret;
+    return TRUE;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+static void decode_params(game_params *params, char const *string)
+{
+    char const *p = string;
+
+    params->w = atoi(p);
+    while (*p && isdigit((unsigned char)*p)) p++;
+
+    if (*p == 'd') {
+        int i;
+        p++;
+        params->diff = DIFFCOUNT+1; /* ...which is invalid */
+        if (*p) {
+            for (i = 0; i < DIFFCOUNT; i++) {
+                if (*p == towers_diffchars[i])
+                    params->diff = i;
+            }
+            p++;
+        }
+    }
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char ret[80];
+
+    sprintf(ret, "%d", params->w);
+    if (full)
+        sprintf(ret + strlen(ret), "d%c", towers_diffchars[params->diff]);
+
+    return dupstr(ret);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+    char buf[80];
+
+    ret = snewn(3, config_item);
+
+    ret[0].name = "Grid size";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->w);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Difficulty";
+    ret[1].type = C_CHOICES;
+    ret[1].sval = DIFFCONFIG;
+    ret[1].ival = params->diff;
+
+    ret[2].name = NULL;
+    ret[2].type = C_END;
+    ret[2].sval = NULL;
+    ret[2].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = atoi(cfg[0].sval);
+    ret->diff = cfg[1].ival;
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    if (params->w < 3 || params->w > 9)
+        return "Grid size must be between 3 and 9";
+    if (params->diff >= DIFFCOUNT)
+        return "Unknown difficulty rating";
+    return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Solver.
+ */
+
+struct solver_ctx {
+    int w, diff;
+    int started;
+    int *clues;
+    long *iscratch;
+    int *dscratch;
+};
+
+static int solver_easy(struct latin_solver *solver, void *vctx)
+{
+    struct solver_ctx *ctx = (struct solver_ctx *)vctx;
+    int w = ctx->w;
+    int c, i, j, n, m, furthest;
+    int start, step, cstart, cstep, clue, pos, cpos;
+    int ret = 0;
+#ifdef STANDALONE_SOLVER
+    char prefix[256];
+#endif
+
+    if (!ctx->started) {
+       ctx->started = TRUE;
+       /*
+        * One-off loop to help get started: when a pair of facing
+        * clues sum to w+1, it must mean that the row consists of
+        * two increasing sequences back to back, so we can
+        * immediately place the highest digit by knowing the
+        * lengths of those two sequences.
+        */
+       for (c = 0; c < 3*w; c = (c == w-1 ? 2*w : c+1)) {
+           int c2 = c + w;
+
+           if (ctx->clues[c] && ctx->clues[c2] &&
+               ctx->clues[c] + ctx->clues[c2] == w+1) {
+               STARTSTEP(start, step, c, w);
+               CSTARTSTEP(cstart, cstep, c, w);
+               pos = start + (ctx->clues[c]-1)*step;
+               cpos = cstart + (ctx->clues[c]-1)*cstep;
+               if (solver->cube[cpos*w+w-1]) {
+#ifdef STANDALONE_SOLVER
+                   if (solver_show_working) {
+                       printf("%*sfacing clues on %s %d are maximal:\n",
+                              solver_recurse_depth*4, "",
+                              c>=2*w ? "row" : "column", c % w + 1);
+                       printf("%*s  placing %d at (%d,%d)\n",
+                              solver_recurse_depth*4, "",
+                              w, pos%w+1, pos/w+1);
+                   }
+#endif
+                   latin_solver_place(solver, pos%w, pos/w, w);
+                   ret = 1;
+               } else {
+                   ret = -1;
+               }
+           }
+       }
+
+       if (ret)
+           return ret;
+    }
+
+    /*
+     * Go over every clue doing reasonably simple heuristic
+     * deductions.
+     */
+    for (c = 0; c < 4*w; c++) {
+       clue = ctx->clues[c];
+       if (!clue)
+           continue;
+       STARTSTEP(start, step, c, w);
+       CSTARTSTEP(cstart, cstep, c, w);
+
+       /* Find the location of each number in the row. */
+       for (i = 0; i < w; i++)
+           ctx->dscratch[i] = w;
+       for (i = 0; i < w; i++)
+           if (solver->grid[start+i*step])
+               ctx->dscratch[solver->grid[start+i*step]-1] = i;
+
+       n = m = 0;
+       furthest = w;
+       for (i = w; i >= 1; i--) {
+           if (ctx->dscratch[i-1] == w) {
+               break;
+           } else if (ctx->dscratch[i-1] < furthest) {
+               furthest = ctx->dscratch[i-1];
+               m = i;
+               n++;
+           }
+       }
+       if (clue == n+1 && furthest > 1) {
+#ifdef STANDALONE_SOLVER
+           if (solver_show_working)
+               sprintf(prefix, "%*sclue %s %d is nearly filled:\n",
+                       solver_recurse_depth*4, "",
+                       cluepos[c/w], c%w+1);
+           else
+               prefix[0] = '\0';              /* placate optimiser */
+#endif
+           /*
+            * We can already see an increasing sequence of the very
+            * highest numbers, of length one less than that
+            * specified in the clue. All of those numbers _must_ be
+            * part of the clue sequence, so the number right next
+            * to the clue must be the final one - i.e. it must be
+            * bigger than any of the numbers between it and m. This
+            * allows us to rule out small numbers in that square.
+            *
+            * (This is a generalisation of the obvious deduction
+            * that when you see a clue saying 1, it must be right
+            * next to the largest possible number; and similarly,
+            * when you see a clue saying 2 opposite that, it must
+            * be right next to the second-largest.)
+            */
+           j = furthest-1;  /* number of small numbers we can rule out */
+           for (i = 1; i <= w && j > 0; i++) {
+               if (ctx->dscratch[i-1] < w && ctx->dscratch[i-1] >= furthest)
+                   continue;          /* skip this number, it's elsewhere */
+               j--;
+               if (solver->cube[cstart*w+i-1]) {
+#ifdef STANDALONE_SOLVER
+                   if (solver_show_working) {
+                       printf("%s%*s  ruling out %d at (%d,%d)\n",
+                              prefix, solver_recurse_depth*4, "",
+                              i, start%w+1, start/w+1);
+                       prefix[0] = '\0';
+                   }
+#endif
+                   solver->cube[cstart*w+i-1] = 0;
+                   ret = 1;
+               }
+           }
+       }
+
+       if (ret)
+           return ret;
+
+#ifdef STANDALONE_SOLVER
+           if (solver_show_working)
+               sprintf(prefix, "%*slower bounds for clue %s %d:\n",
+                       solver_recurse_depth*4, "",
+                       cluepos[c/w], c%w+1);
+           else
+               prefix[0] = '\0';              /* placate optimiser */
+#endif
+
+       i = 0;
+       for (n = w; n > 0; n--) {
+           /*
+            * The largest number cannot occur in the first (clue-1)
+            * squares of the row, or else there wouldn't be space
+            * for a sufficiently long increasing sequence which it
+            * terminated. The second-largest number (not counting
+            * any that are known to be on the far side of a larger
+            * number and hence excluded from this sequence) cannot
+            * occur in the first (clue-2) squares, similarly, and
+            * so on.
+            */
+
+           if (ctx->dscratch[n-1] < w) {
+               for (m = n+1; m < w; m++)
+                   if (ctx->dscratch[m] < ctx->dscratch[n-1])
+                       break;
+               if (m < w)
+                   continue;          /* this number doesn't count */
+           }
+
+           for (j = 0; j < clue - i - 1; j++)
+               if (solver->cube[(cstart + j*cstep)*w+n-1]) {
+#ifdef STANDALONE_SOLVER
+                   if (solver_show_working) {
+                       int pos = start+j*step;
+                       printf("%s%*s  ruling out %d at (%d,%d)\n",
+                              prefix, solver_recurse_depth*4, "",
+                              n, pos%w+1, pos/w+1);
+                       prefix[0] = '\0';
+                   }
+#endif
+                   solver->cube[(cstart + j*cstep)*w+n-1] = 0;
+                   ret = 1;
+               }
+           i++;
+       }
+    }
+
+    if (ret)
+       return ret;
+
+    return 0;
+}
+
+static int solver_hard(struct latin_solver *solver, void *vctx)
+{
+    struct solver_ctx *ctx = (struct solver_ctx *)vctx;
+    int w = ctx->w;
+    int c, i, j, n, best, clue, start, step, ret;
+    long bitmap;
+#ifdef STANDALONE_SOLVER
+    char prefix[256];
+#endif
+
+    /*
+     * Go over every clue analysing all possibilities.
+     */
+    for (c = 0; c < 4*w; c++) {
+       clue = ctx->clues[c];
+       if (!clue)
+           continue;
+       CSTARTSTEP(start, step, c, w);
+
+       for (i = 0; i < w; i++)
+           ctx->iscratch[i] = 0;
+
+       /*
+        * Instead of a tedious physical recursion, I iterate in the
+        * scratch array through all possibilities. At any given
+        * moment, i indexes the element of the box that will next
+        * be incremented.
+        */
+       i = 0;
+       ctx->dscratch[i] = 0;
+       best = n = 0;
+       bitmap = 0;
+
+       while (1) {
+           if (i < w) {
+               /*
+                * Find the next valid value for cell i.
+                */
+               int limit = (n == clue ? best : w);
+               int pos = start + step * i;
+               for (j = ctx->dscratch[i] + 1; j <= limit; j++) {
+                   if (bitmap & (1L << j))
+                       continue;      /* used this one already */
+                   if (!solver->cube[pos*w+j-1])
+                       continue;      /* ruled out already */
+
+                   /* Found one. */
+                   break;
+               }
+
+               if (j > limit) {
+                   /* No valid values left; drop back. */
+                   i--;
+                   if (i < 0)
+                       break;         /* overall iteration is finished */
+                   bitmap &= ~(1L << ctx->dscratch[i]);
+                   if (ctx->dscratch[i] == best) {
+                       n--;
+                       best = 0;
+                       for (j = 0; j < i; j++)
+                           if (best < ctx->dscratch[j])
+                               best = ctx->dscratch[j];
+                   }
+               } else {
+                   /* Got a valid value; store it and move on. */
+                   bitmap |= 1L << j;
+                   ctx->dscratch[i++] = j;
+                   if (j > best) {
+                       best = j;
+                       n++;
+                   }
+                   ctx->dscratch[i] = 0;
+               }
+           } else {
+               if (n == clue) {
+                   for (j = 0; j < w; j++)
+                       ctx->iscratch[j] |= 1L << ctx->dscratch[j];
+               }
+               i--;
+               bitmap &= ~(1L << ctx->dscratch[i]);
+               if (ctx->dscratch[i] == best) {
+                   n--;
+                   best = 0;
+                   for (j = 0; j < i; j++)
+                       if (best < ctx->dscratch[j])
+                           best = ctx->dscratch[j];
+               }
+           }
+       }
+
+#ifdef STANDALONE_SOLVER
+       if (solver_show_working)
+           sprintf(prefix, "%*sexhaustive analysis of clue %s %d:\n",
+                   solver_recurse_depth*4, "",
+                   cluepos[c/w], c%w+1);
+       else
+           prefix[0] = '\0';          /* placate optimiser */
+#endif
+
+       ret = 0;
+
+       for (i = 0; i < w; i++) {
+           int pos = start + step * i;
+           for (j = 1; j <= w; j++) {
+               if (solver->cube[pos*w+j-1] &&
+                   !(ctx->iscratch[i] & (1L << j))) {
+#ifdef STANDALONE_SOLVER
+                   if (solver_show_working) {
+                       printf("%s%*s  ruling out %d at (%d,%d)\n",
+                              prefix, solver_recurse_depth*4, "",
+                              j, pos/w+1, pos%w+1);
+                       prefix[0] = '\0';
+                   }
+#endif
+                   solver->cube[pos*w+j-1] = 0;
+                   ret = 1;
+               }
+           }
+
+           /*
+            * Once we find one clue we can do something with in
+            * this way, revert to trying easier deductions, so as
+            * not to generate solver diagnostics that make the
+            * problem look harder than it is.
+            */
+           if (ret)
+               return ret;
+       }
+    }
+
+    return 0;
+}
+
+#define SOLVER(upper,title,func,lower) func,
+static usersolver_t const towers_solvers[] = { DIFFLIST(SOLVER) };
+
+static int solver(int w, int *clues, digit *soln, int maxdiff)
+{
+    int ret;
+    struct solver_ctx ctx;
+
+    ctx.w = w;
+    ctx.diff = maxdiff;
+    ctx.clues = clues;
+    ctx.started = FALSE;
+    ctx.iscratch = snewn(w, long);
+    ctx.dscratch = snewn(w+1, int);
+
+    ret = latin_solver(soln, w, maxdiff,
+                      DIFF_EASY, DIFF_HARD, DIFF_EXTREME,
+                      DIFF_EXTREME, DIFF_UNREASONABLE,
+                      towers_solvers, &ctx, NULL, NULL);
+
+    sfree(ctx.iscratch);
+    sfree(ctx.dscratch);
+
+    return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * Grid generation.
+ */
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                          char **aux, int interactive)
+{
+    int w = params->w, a = w*w;
+    digit *grid, *soln, *soln2;
+    int *clues, *order;
+    int i, ret;
+    int diff = params->diff;
+    char *desc, *p;
+
+    /*
+     * Difficulty exceptions: some combinations of size and
+     * difficulty cannot be satisfied, because all puzzles of at
+     * most that difficulty are actually even easier.
+     *
+     * Remember to re-test this whenever a change is made to the
+     * solver logic!
+     *
+     * I tested it using the following shell command:
+
+for d in e h x u; do
+  for i in {3..9}; do
+    echo -n "./towers --generate 1 ${i}d${d}: "
+    perl -e 'alarm 30; exec @ARGV' ./towers --generate 1 ${i}d${d} >/dev/null \
+      && echo ok
+  done
+done
+
+     * Of course, it's better to do that after taking the exceptions
+     * _out_, so as to detect exceptions that should be removed as
+     * well as those which should be added.
+     */
+    if (diff > DIFF_HARD && w <= 3)
+       diff = DIFF_HARD;
+
+    grid = NULL;
+    clues = snewn(4*w, int);
+    soln = snewn(a, digit);
+    soln2 = snewn(a, digit);
+    order = snewn(max(4*w,a), int);
+
+    while (1) {
+       /*
+        * Construct a latin square to be the solution.
+        */
+       sfree(grid);
+       grid = latin_generate(w, rs);
+
+       /*
+        * Fill in the clues.
+        */
+       for (i = 0; i < 4*w; i++) {
+           int start, step, j, k, best;
+           STARTSTEP(start, step, i, w);
+           k = best = 0;
+           for (j = 0; j < w; j++) {
+               if (grid[start+j*step] > best) {
+                   best = grid[start+j*step];
+                   k++;
+               }
+           }
+           clues[i] = k;
+       }
+
+       /*
+        * Remove the grid numbers and then the clues, one by one,
+        * for as long as the game remains soluble at the given
+        * difficulty.
+        */
+       memcpy(soln, grid, a);
+
+       if (diff == DIFF_EASY && w <= 5) {
+           /*
+            * Special case: for Easy-mode grids that are small
+            * enough, it's nice to be able to find completely empty
+            * grids.
+            */
+           memset(soln2, 0, a);
+           ret = solver(w, clues, soln2, diff);
+           if (ret > diff)
+               continue;
+       }
+
+       for (i = 0; i < a; i++)
+           order[i] = i;
+       shuffle(order, a, sizeof(*order), rs);
+       for (i = 0; i < a; i++) {
+           int j = order[i];
+
+           memcpy(soln2, grid, a);
+           soln2[j] = 0;
+           ret = solver(w, clues, soln2, diff);
+           if (ret <= diff)
+               grid[j] = 0;
+       }
+
+       if (diff > DIFF_EASY) {        /* leave all clues on Easy mode */
+           for (i = 0; i < 4*w; i++)
+               order[i] = i;
+           shuffle(order, 4*w, sizeof(*order), rs);
+           for (i = 0; i < 4*w; i++) {
+               int j = order[i];
+               int clue = clues[j];
+
+               memcpy(soln2, grid, a);
+               clues[j] = 0;
+               ret = solver(w, clues, soln2, diff);
+               if (ret > diff)
+                   clues[j] = clue;
+           }
+       }
+
+       /*
+        * See if the game can be solved at the specified difficulty
+        * level, but not at the one below.
+        */
+       memcpy(soln2, grid, a);
+       ret = solver(w, clues, soln2, diff);
+       if (ret != diff)
+           continue;                  /* go round again */
+
+       /*
+        * We've got a usable puzzle!
+        */
+       break;
+    }
+
+    /*
+     * Encode the puzzle description.
+     */
+    desc = snewn(40*a, char);
+    p = desc;
+    for (i = 0; i < 4*w; i++) {
+        if (i)
+            *p++ = '/';
+        if (clues[i])
+            p += sprintf(p, "%d", clues[i]);
+    }
+    for (i = 0; i < a; i++)
+       if (grid[i])
+           break;
+    if (i < a) {
+       int run = 0;
+
+       *p++ = ',';
+
+       for (i = 0; i <= a; i++) {
+           int n = (i < a ? grid[i] : -1);
+
+           if (!n)
+               run++;
+           else {
+               if (run) {
+                   while (run > 0) {
+                       int thisrun = min(run, 26);
+                       *p++ = thisrun - 1 + 'a';
+                       run -= thisrun;
+                   }
+               } else {
+                   /*
+                    * If there's a number in the very top left or
+                    * bottom right, there's no point putting an
+                    * unnecessary _ before or after it.
+                    */
+                   if (i > 0 && n > 0)
+                       *p++ = '_';
+               }
+               if (n > 0)
+                   p += sprintf(p, "%d", n);
+               run = 0;
+           }
+       }
+    }
+    *p++ = '\0';
+    desc = sresize(desc, p - desc, char);
+
+    /*
+     * Encode the solution.
+     */
+    *aux = snewn(a+2, char);
+    (*aux)[0] = 'S';
+    for (i = 0; i < a; i++)
+       (*aux)[i+1] = '0' + soln[i];
+    (*aux)[a+1] = '\0';
+
+    sfree(grid);
+    sfree(clues);
+    sfree(soln);
+    sfree(soln2);
+    sfree(order);
+
+    return desc;
+}
+
+/* ----------------------------------------------------------------------
+ * Gameplay.
+ */
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    int w = params->w, a = w*w;
+    const char *p = desc;
+    int i, clue;
+
+    /*
+     * Verify that the right number of clues are given, and that
+     * they're in range.
+     */
+    for (i = 0; i < 4*w; i++) {
+       if (!*p)
+           return "Too few clues for grid size";
+
+       if (i > 0) {
+           if (*p != '/')
+               return "Expected commas between clues";
+           p++;
+       }
+
+       if (isdigit((unsigned char)*p)) {
+           clue = atoi(p);
+           while (*p && isdigit((unsigned char)*p)) p++;
+
+           if (clue <= 0 || clue > w)
+               return "Clue number out of range";
+       }
+    }
+    if (*p == '/')
+       return "Too many clues for grid size";
+
+    if (*p == ',') {
+       /*
+        * Verify that the right amount of grid data is given, and
+        * that any grid elements provided are in range.
+        */
+       int squares = 0;
+
+       p++;
+       while (*p) {
+           int c = *p++;
+           if (c >= 'a' && c <= 'z') {
+               squares += c - 'a' + 1;
+           } else if (c == '_') {
+               /* do nothing */;
+           } else if (c > '0' && c <= '9') {
+               int val = atoi(p-1);
+               if (val < 1 || val > w)
+                   return "Out-of-range number in grid description";
+               squares++;
+               while (*p && isdigit((unsigned char)*p)) p++;
+           } else
+               return "Invalid character in game description";
+       }
+
+       if (squares < a)
+           return "Not enough data to fill grid";
+
+       if (squares > a)
+           return "Too much data to fit in grid";
+    }
+
+    return NULL;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    int w = params->w, a = w*w;
+    game_state *state = snew(game_state);
+    const char *p = desc;
+    int i;
+
+    state->par = *params;             /* structure copy */
+    state->clues = snew(struct clues);
+    state->clues->refcount = 1;
+    state->clues->w = w;
+    state->clues->clues = snewn(4*w, int);
+    state->clues->immutable = snewn(a, digit);
+    state->grid = snewn(a, digit);
+    state->clues_done = snewn(4*w, unsigned char);
+    state->pencil = snewn(a, int);
+
+    for (i = 0; i < a; i++) {
+       state->grid[i] = 0;
+       state->pencil[i] = 0;
+    }
+
+    memset(state->clues->immutable, 0, a);
+    memset(state->clues_done, 0, 4*w*sizeof(unsigned char));
+
+    for (i = 0; i < 4*w; i++) {
+       if (i > 0) {
+           assert(*p == '/');
+           p++;
+       }
+       if (*p && isdigit((unsigned char)*p)) {
+           state->clues->clues[i] = atoi(p);
+           while (*p && isdigit((unsigned char)*p)) p++;
+       } else
+           state->clues->clues[i] = 0;
+    }
+
+    if (*p == ',') {
+       int pos = 0;
+       p++;
+       while (*p) {
+           int c = *p++;
+           if (c >= 'a' && c <= 'z') {
+               pos += c - 'a' + 1;
+           } else if (c == '_') {
+               /* do nothing */;
+           } else if (c > '0' && c <= '9') {
+               int val = atoi(p-1);
+               assert(val >= 1 && val <= w);
+               assert(pos < a);
+               state->grid[pos] = state->clues->immutable[pos] = val;
+               pos++;
+               while (*p && isdigit((unsigned char)*p)) p++;
+           } else
+               assert(!"Corrupt game description");
+       }
+       assert(pos == a);
+    }
+    assert(!*p);
+
+    state->completed = state->cheated = FALSE;
+
+    return state;
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    int w = state->par.w, a = w*w;
+    game_state *ret = snew(game_state);
+
+    ret->par = state->par;            /* structure copy */
+
+    ret->clues = state->clues;
+    ret->clues->refcount++;
+
+    ret->grid = snewn(a, digit);
+    ret->pencil = snewn(a, int);
+    ret->clues_done = snewn(4*w, unsigned char);
+    memcpy(ret->grid, state->grid, a*sizeof(digit));
+    memcpy(ret->pencil, state->pencil, a*sizeof(int));
+    memcpy(ret->clues_done, state->clues_done, 4*w*sizeof(unsigned char));
+
+    ret->completed = state->completed;
+    ret->cheated = state->cheated;
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    sfree(state->grid);
+    sfree(state->pencil);
+    sfree(state->clues_done);
+    if (--state->clues->refcount <= 0) {
+       sfree(state->clues->immutable);
+       sfree(state->clues->clues);
+       sfree(state->clues);
+    }
+    sfree(state);
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    int w = state->par.w, a = w*w;
+    int i, ret;
+    digit *soln;
+    char *out;
+
+    if (aux)
+       return dupstr(aux);
+
+    soln = snewn(a, digit);
+    memcpy(soln, state->clues->immutable, a);
+
+    ret = solver(w, state->clues->clues, soln, DIFFCOUNT-1);
+
+    if (ret == diff_impossible) {
+       *error = "No solution exists for this puzzle";
+       out = NULL;
+    } else if (ret == diff_ambiguous) {
+       *error = "Multiple solutions exist for this puzzle";
+       out = NULL;
+    } else {
+       out = snewn(a+2, char);
+       out[0] = 'S';
+       for (i = 0; i < a; i++)
+           out[i+1] = '0' + soln[i];
+       out[a+1] = '\0';
+    }
+
+    sfree(soln);
+    return out;
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    int w = state->par.w /* , a = w*w */;
+    char *ret;
+    char *p;
+    int x, y;
+    int total;
+
+    /*
+     * We have:
+     *         - a top clue row, consisting of three spaces, then w clue
+     *           digits with spaces between (total 2*w+3 chars including
+     *           newline)
+     *  - a blank line (one newline)
+     *         - w main rows, consisting of a left clue digit, two spaces,
+     *           w grid digits with spaces between, two spaces and a right
+     *           clue digit (total 2*w+6 chars each including newline)
+     *  - a blank line (one newline)
+     *  - a bottom clue row (same as top clue row)
+     *  - terminating NUL.
+     *
+     * Total size is therefore 2*(2*w+3) + 2 + w*(2*w+6) + 1
+     * = 2w^2+10w+9.
+     */
+    total = 2*w*w + 10*w + 9;
+    ret = snewn(total, char);
+    p = ret;
+
+    /* Top clue row. */
+    *p++ = ' '; *p++ = ' ';
+    for (x = 0; x < w; x++) {
+       *p++ = ' ';
+       *p++ = (state->clues->clues[x] ? '0' + state->clues->clues[x] : ' ');
+    }
+    *p++ = '\n';
+
+    /* Blank line. */
+    *p++ = '\n';
+
+    /* Main grid. */
+    for (y = 0; y < w; y++) {
+       *p++ = (state->clues->clues[y+2*w] ? '0' + state->clues->clues[y+2*w] :
+               ' ');
+       *p++ = ' ';
+       for (x = 0; x < w; x++) {
+           *p++ = ' ';
+           *p++ = (state->grid[y*w+x] ? '0' + state->grid[y*w+x] : ' ');
+       }
+       *p++ = ' '; *p++ = ' ';
+       *p++ = (state->clues->clues[y+3*w] ? '0' + state->clues->clues[y+3*w] :
+               ' ');
+       *p++ = '\n';
+    }
+
+    /* Blank line. */
+    *p++ = '\n';
+
+    /* Bottom clue row. */
+    *p++ = ' '; *p++ = ' ';
+    for (x = 0; x < w; x++) {
+       *p++ = ' ';
+       *p++ = (state->clues->clues[x+w] ? '0' + state->clues->clues[x+w] :
+               ' ');
+    }
+    *p++ = '\n';
+
+    *p++ = '\0';
+    assert(p == ret + total);
+
+    return ret;
+}
+
+struct game_ui {
+    /*
+     * These are the coordinates of the currently highlighted
+     * square on the grid, if hshow = 1.
+     */
+    int hx, hy;
+    /*
+     * This indicates whether the current highlight is a
+     * pencil-mark one or a real one.
+     */
+    int hpencil;
+    /*
+     * This indicates whether or not we're showing the highlight
+     * (used to be hx = hy = -1); important so that when we're
+     * using the cursor keys it doesn't keep coming back at a
+     * fixed position. When hshow = 1, pressing a valid number
+     * or letter key or Space will enter that number or letter in the grid.
+     */
+    int hshow;
+    /*
+     * This indicates whether we're using the highlight as a cursor;
+     * it means that it doesn't vanish on a keypress, and that it is
+     * allowed on immutable squares.
+     */
+    int hcursor;
+};
+
+static game_ui *new_ui(const game_state *state)
+{
+    game_ui *ui = snew(game_ui);
+
+    ui->hx = ui->hy = 0;
+    ui->hpencil = ui->hshow = ui->hcursor = 0;
+
+    return ui;
+}
+
+static void free_ui(game_ui *ui)
+{
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+    int w = newstate->par.w;
+    /*
+     * We prevent pencil-mode highlighting of a filled square, unless
+     * we're using the cursor keys. So if the user has just filled in
+     * a square which we had a pencil-mode highlight in (by Undo, or
+     * by Redo, or by Solve), then we cancel the highlight.
+     */
+    if (ui->hshow && ui->hpencil && !ui->hcursor &&
+        newstate->grid[ui->hy * w + ui->hx] != 0) {
+        ui->hshow = 0;
+    }
+}
+
+#define PREFERRED_TILESIZE 48
+#define TILESIZE (ds->tilesize)
+#define BORDER (TILESIZE * 9 / 8)
+#define COORD(x) ((x)*TILESIZE + BORDER)
+#define FROMCOORD(x) (((x)+(TILESIZE-BORDER)) / TILESIZE - 1)
+
+/* These always return positive values, though y offsets are actually -ve */
+#define X_3D_DISP(height, w) ((height) * TILESIZE / (8 * (w)))
+#define Y_3D_DISP(height, w) ((height) * TILESIZE / (4 * (w)))
+
+#define FLASH_TIME 0.4F
+
+#define DF_PENCIL_SHIFT 16
+#define DF_CLUE_DONE 0x10000
+#define DF_ERROR 0x8000
+#define DF_HIGHLIGHT 0x4000
+#define DF_HIGHLIGHT_PENCIL 0x2000
+#define DF_IMMUTABLE 0x1000
+#define DF_PLAYAREA 0x0800
+#define DF_DIGIT_MASK 0x00FF
+
+struct game_drawstate {
+    int tilesize;
+    int three_d;               /* default 3D graphics are user-disableable */
+    int started;
+    long *tiles;                      /* (w+2)*(w+2) temp space */
+    long *drawn;                      /* (w+2)*(w+2)*4: current drawn data */
+    int *errtmp;
+};
+
+static int check_errors(const game_state *state, int *errors)
+{
+    int w = state->par.w /*, a = w*w */;
+    int W = w+2, A = W*W;             /* the errors array is (w+2) square */
+    int *clues = state->clues->clues;
+    digit *grid = state->grid;
+    int i, x, y, errs = FALSE;
+    int tmp[32];
+
+    assert(w < lenof(tmp));
+
+    if (errors)
+       for (i = 0; i < A; i++)
+           errors[i] = 0;
+
+    for (y = 0; y < w; y++) {
+       unsigned long mask = 0, errmask = 0;
+       for (x = 0; x < w; x++) {
+           unsigned long bit = 1UL << grid[y*w+x];
+           errmask |= (mask & bit);
+           mask |= bit;
+       }
+
+       if (mask != (1L << (w+1)) - (1L << 1)) {
+           errs = TRUE;
+           errmask &= ~1UL;
+           if (errors) {
+               for (x = 0; x < w; x++)
+                   if (errmask & (1UL << grid[y*w+x]))
+                       errors[(y+1)*W+(x+1)] = TRUE;
+           }
+       }
+    }
+
+    for (x = 0; x < w; x++) {
+       unsigned long mask = 0, errmask = 0;
+       for (y = 0; y < w; y++) {
+           unsigned long bit = 1UL << grid[y*w+x];
+           errmask |= (mask & bit);
+           mask |= bit;
+       }
+
+       if (mask != (1 << (w+1)) - (1 << 1)) {
+           errs = TRUE;
+           errmask &= ~1UL;
+           if (errors) {
+               for (y = 0; y < w; y++)
+                   if (errmask & (1UL << grid[y*w+x]))
+                       errors[(y+1)*W+(x+1)] = TRUE;
+           }
+       }
+    }
+
+    for (i = 0; i < 4*w; i++) {
+       int start, step, j, n, best;
+       STARTSTEP(start, step, i, w);
+
+       if (!clues[i])
+           continue;
+
+       best = n = 0;
+       for (j = 0; j < w; j++) {
+           int number = grid[start+j*step];
+           if (!number)
+               break;                 /* can't tell what happens next */
+           if (number > best) {
+               best = number;
+               n++;
+           }
+       }
+
+       if (n > clues[i] || (best == w && n < clues[i]) ||
+           (best < w && n == clues[i])) {
+           if (errors) {
+               int x, y;
+               CLUEPOS(x, y, i, w);
+               errors[(y+1)*W+(x+1)] = TRUE;
+           }
+           errs = TRUE;
+       }
+    }
+
+    return errs;
+}
+
+static int clue_index(const game_state *state, int x, int y)
+{
+    int w = state->par.w;
+
+    if (x == -1 || x == w)
+        return w * (x == -1 ? 2 : 3) + y;
+    else if (y == -1 || y == w)
+        return (y == -1 ? 0 : w) + x;
+
+    return -1;
+}
+
+static int is_clue(const game_state *state, int x, int y)
+{
+    int w = state->par.w;
+
+    if (((x == -1 || x == w) && y >= 0 && y < w) ||
+        ((y == -1 || y == w) && x >= 0 && x < w))
+    {
+        if (state->clues->clues[clue_index(state, x, y)] & DF_DIGIT_MASK)
+            return TRUE;
+    }
+
+    return FALSE;
+}
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    int w = state->par.w;
+    int shift_or_control = button & (MOD_SHFT | MOD_CTRL);
+    int tx, ty;
+    char buf[80];
+
+    button &= ~MOD_MASK;
+
+    tx = FROMCOORD(x);
+    ty = FROMCOORD(y);
+
+    if (ds->three_d) {
+       /*
+        * In 3D mode, just locating the mouse click in the natural
+        * square grid may not be sufficient to tell which tower the
+        * user clicked on. Investigate the _tops_ of the nearby
+        * towers to see if a click on one grid square was actually
+        * a click on a tower protruding into that region from
+        * another.
+        */
+       int dx, dy;
+       for (dy = 0; dy <= 1; dy++)
+           for (dx = 0; dx >= -1; dx--) {
+               int cx = tx + dx, cy = ty + dy;
+               if (cx >= 0 && cx < w && cy >= 0 && cy < w) {
+                   int height = state->grid[cy*w+cx];
+                   int bx = COORD(cx), by = COORD(cy);
+                   int ox = bx + X_3D_DISP(height, w);
+                   int oy = by - Y_3D_DISP(height, w);
+                   if (/* on top face? */
+                       (x - ox >= 0 && x - ox < TILESIZE &&
+                        y - oy >= 0 && y - oy < TILESIZE) ||
+                       /* in triangle between top-left corners? */
+                       (ox > bx && x >= bx && x <= ox && y <= by &&
+                        (by-y) * (ox-bx) <= (by-oy) * (x-bx)) ||
+                       /* in triangle between bottom-right corners? */
+                       (ox > bx && x >= bx+TILESIZE && x <= ox+TILESIZE &&
+                        y >= oy+TILESIZE &&
+                        (by-y+TILESIZE)*(ox-bx) >= (by-oy)*(x-bx-TILESIZE))) {
+                       tx = cx;
+                       ty = cy;
+                   }
+               }
+           }
+    }
+
+    if (tx >= 0 && tx < w && ty >= 0 && ty < w) {
+        if (button == LEFT_BUTTON) {
+           if (tx == ui->hx && ty == ui->hy &&
+               ui->hshow && ui->hpencil == 0) {
+                ui->hshow = 0;
+            } else {
+                ui->hx = tx;
+                ui->hy = ty;
+               ui->hshow = !state->clues->immutable[ty*w+tx];
+                ui->hpencil = 0;
+            }
+            ui->hcursor = 0;
+            return "";                /* UI activity occurred */
+        }
+        if (button == RIGHT_BUTTON) {
+            /*
+             * Pencil-mode highlighting for non filled squares.
+             */
+            if (state->grid[ty*w+tx] == 0) {
+                if (tx == ui->hx && ty == ui->hy &&
+                    ui->hshow && ui->hpencil) {
+                    ui->hshow = 0;
+                } else {
+                    ui->hpencil = 1;
+                    ui->hx = tx;
+                    ui->hy = ty;
+                    ui->hshow = 1;
+                }
+            } else {
+                ui->hshow = 0;
+            }
+            ui->hcursor = 0;
+            return "";                /* UI activity occurred */
+        }
+    } else if (button == LEFT_BUTTON) {
+        if (is_clue(state, tx, ty)) {
+            sprintf(buf, "%c%d,%d", 'D', tx, ty);
+            return dupstr(buf);
+        }
+    }
+    if (IS_CURSOR_MOVE(button)) {
+        if (shift_or_control) {
+            int x = ui->hx, y = ui->hy;
+            switch (button) {
+            case CURSOR_LEFT:   x = -1; break;
+            case CURSOR_RIGHT:  x =  w; break;
+            case CURSOR_UP:     y = -1; break;
+            case CURSOR_DOWN:   y =  w; break;
+            }
+            if (is_clue(state, x, y)) {
+                sprintf(buf, "%c%d,%d", 'D', x, y);
+                return dupstr(buf);
+            }
+            return NULL;
+        }
+        move_cursor(button, &ui->hx, &ui->hy, w, w, 0);
+        ui->hshow = ui->hcursor = 1;
+        return "";
+    }
+    if (ui->hshow &&
+        (button == CURSOR_SELECT)) {
+        ui->hpencil = 1 - ui->hpencil;
+        ui->hcursor = 1;
+        return "";
+    }
+
+    if (ui->hshow &&
+       ((button >= '0' && button <= '9' && button - '0' <= w) ||
+        button == CURSOR_SELECT2 || button == '\b')) {
+       int n = button - '0';
+       if (button == CURSOR_SELECT2 || button == '\b')
+           n = 0;
+
+        /*
+         * Can't make pencil marks in a filled square. This can only
+         * become highlighted if we're using cursor keys.
+         */
+        if (ui->hpencil && state->grid[ui->hy*w+ui->hx])
+            return NULL;
+
+       /*
+        * Can't do anything to an immutable square.
+        */
+        if (state->clues->immutable[ui->hy*w+ui->hx])
+            return NULL;
+
+       sprintf(buf, "%c%d,%d,%d",
+               (char)(ui->hpencil && n > 0 ? 'P' : 'R'), ui->hx, ui->hy, n);
+
+        if (!ui->hcursor) ui->hshow = 0;
+
+       return dupstr(buf);
+    }
+
+    if (button == 'M' || button == 'm')
+        return dupstr("M");
+
+    return NULL;
+}
+
+static game_state *execute_move(const game_state *from, const char *move)
+{
+    int w = from->par.w, a = w*w;
+    game_state *ret = dup_game(from);
+    int x, y, i, n;
+
+    if (move[0] == 'S') {
+       ret->completed = ret->cheated = TRUE;
+
+       for (i = 0; i < a; i++) {
+            if (move[i+1] < '1' || move[i+1] > '0'+w)
+                goto badmove;
+           ret->grid[i] = move[i+1] - '0';
+           ret->pencil[i] = 0;
+       }
+
+        if (move[a+1] != '\0')
+            goto badmove;
+
+       return ret;
+    } else if ((move[0] == 'P' || move[0] == 'R') &&
+       sscanf(move+1, "%d,%d,%d", &x, &y, &n) == 3 &&
+       x >= 0 && x < w && y >= 0 && y < w && n >= 0 && n <= w) {
+       if (from->clues->immutable[y*w+x])
+            goto badmove;
+
+        if (move[0] == 'P' && n > 0) {
+            ret->pencil[y*w+x] ^= 1L << n;
+        } else {
+            ret->grid[y*w+x] = n;
+            ret->pencil[y*w+x] = 0;
+
+            if (!ret->completed && !check_errors(ret, NULL))
+                ret->completed = TRUE;
+        }
+       return ret;
+    } else if (move[0] == 'M') {
+       /*
+        * Fill in absolutely all pencil marks everywhere. (I
+        * wouldn't use this for actual play, but it's a handy
+        * starting point when following through a set of
+        * diagnostics output by the standalone solver.)
+        */
+       for (i = 0; i < a; i++) {
+           if (!ret->grid[i])
+               ret->pencil[i] = (1L << (w+1)) - (1L << 1);
+       }
+       return ret;
+    } else if (move[0] == 'D' && sscanf(move+1, "%d,%d", &x, &y) == 2 &&
+               is_clue(from, x, y)) {
+        int index = clue_index(from, x, y);
+        ret->clues_done[index] = !ret->clues_done[index];
+        return ret;
+    }
+
+  badmove:
+    /* couldn't parse move string */
+    free_game(ret);
+    return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+#define SIZE(w) ((w) * TILESIZE + 2*BORDER)
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    struct { int tilesize; } ads, *ds = &ads;
+    ads.tilesize = tilesize;
+
+    *x = *y = SIZE(params->w);
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+
+    frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
+
+    ret[COL_GRID * 3 + 0] = 0.0F;
+    ret[COL_GRID * 3 + 1] = 0.0F;
+    ret[COL_GRID * 3 + 2] = 0.0F;
+
+    ret[COL_USER * 3 + 0] = 0.0F;
+    ret[COL_USER * 3 + 1] = 0.6F * ret[COL_BACKGROUND * 3 + 1];
+    ret[COL_USER * 3 + 2] = 0.0F;
+
+    ret[COL_HIGHLIGHT * 3 + 0] = 0.78F * ret[COL_BACKGROUND * 3 + 0];
+    ret[COL_HIGHLIGHT * 3 + 1] = 0.78F * ret[COL_BACKGROUND * 3 + 1];
+    ret[COL_HIGHLIGHT * 3 + 2] = 0.78F * ret[COL_BACKGROUND * 3 + 2];
+
+    ret[COL_ERROR * 3 + 0] = 1.0F;
+    ret[COL_ERROR * 3 + 1] = 0.0F;
+    ret[COL_ERROR * 3 + 2] = 0.0F;
+
+    ret[COL_PENCIL * 3 + 0] = 0.5F * ret[COL_BACKGROUND * 3 + 0];
+    ret[COL_PENCIL * 3 + 1] = 0.5F * ret[COL_BACKGROUND * 3 + 1];
+    ret[COL_PENCIL * 3 + 2] = ret[COL_BACKGROUND * 3 + 2];
+
+    ret[COL_DONE * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] / 1.5F;
+    ret[COL_DONE * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] / 1.5F;
+    ret[COL_DONE * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] / 1.5F;
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    int w = state->par.w /*, a = w*w */;
+    struct game_drawstate *ds = snew(struct game_drawstate);
+    int i;
+
+    ds->tilesize = 0;
+    ds->three_d = !getenv("TOWERS_2D");
+    ds->started = FALSE;
+    ds->tiles = snewn((w+2)*(w+2), long);
+    ds->drawn = snewn((w+2)*(w+2)*4, long);
+    for (i = 0; i < (w+2)*(w+2)*4; i++)
+       ds->drawn[i] = -1;
+    ds->errtmp = snewn((w+2)*(w+2), int);
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds->errtmp);
+    sfree(ds->tiles);
+    sfree(ds->drawn);
+    sfree(ds);
+}
+
+static void draw_tile(drawing *dr, game_drawstate *ds, struct clues *clues,
+                     int x, int y, long tile)
+{
+    int w = clues->w /* , a = w*w */;
+    int tx, ty, bg;
+    char str[64];
+
+    tx = COORD(x);
+    ty = COORD(y);
+
+    bg = (tile & DF_HIGHLIGHT) ? COL_HIGHLIGHT : COL_BACKGROUND;
+
+    /* draw tower */
+    if (ds->three_d && (tile & DF_PLAYAREA) && (tile & DF_DIGIT_MASK)) {
+       int coords[8];
+       int xoff = X_3D_DISP(tile & DF_DIGIT_MASK, w);
+       int yoff = Y_3D_DISP(tile & DF_DIGIT_MASK, w);
+
+       /* left face of tower */
+       coords[0] = tx;
+       coords[1] = ty - 1;
+       coords[2] = tx;
+       coords[3] = ty + TILESIZE - 1;
+       coords[4] = coords[2] + xoff;
+       coords[5] = coords[3] - yoff;
+       coords[6] = coords[0] + xoff;
+       coords[7] = coords[1] - yoff;
+       draw_polygon(dr, coords, 4, bg, COL_GRID);
+
+       /* bottom face of tower */
+       coords[0] = tx + TILESIZE;
+       coords[1] = ty + TILESIZE - 1;
+       coords[2] = tx;
+       coords[3] = ty + TILESIZE - 1;
+       coords[4] = coords[2] + xoff;
+       coords[5] = coords[3] - yoff;
+       coords[6] = coords[0] + xoff;
+       coords[7] = coords[1] - yoff;
+       draw_polygon(dr, coords, 4, bg, COL_GRID);
+
+       /* now offset all subsequent drawing to the top of the tower */
+       tx += xoff;
+       ty -= yoff;
+    }
+
+    /* erase background */
+    draw_rect(dr, tx, ty, TILESIZE, TILESIZE, bg);
+
+    /* pencil-mode highlight */
+    if (tile & DF_HIGHLIGHT_PENCIL) {
+        int coords[6];
+        coords[0] = tx;
+        coords[1] = ty;
+        coords[2] = tx+TILESIZE/2;
+        coords[3] = ty;
+        coords[4] = tx;
+        coords[5] = ty+TILESIZE/2;
+        draw_polygon(dr, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT);
+    }
+
+    /* draw box outline */
+    if (tile & DF_PLAYAREA) {
+        int coords[8];
+        coords[0] = tx;
+        coords[1] = ty - 1;
+        coords[2] = tx + TILESIZE;
+        coords[3] = ty - 1;
+        coords[4] = tx + TILESIZE;
+        coords[5] = ty + TILESIZE - 1;
+        coords[6] = tx;
+        coords[7] = ty + TILESIZE - 1;
+        draw_polygon(dr, coords, 4, -1, COL_GRID);
+    }
+
+    /* new number needs drawing? */
+    if (tile & DF_DIGIT_MASK) {
+        int color;
+
+       str[1] = '\0';
+       str[0] = (tile & DF_DIGIT_MASK) + '0';
+
+        if (tile & DF_ERROR)
+            color = COL_ERROR;
+        else if (tile & DF_CLUE_DONE)
+            color = COL_DONE;
+        else if (x < 0 || y < 0 || x >= w || y >= w)
+            color = COL_GRID;
+        else if (tile & DF_IMMUTABLE)
+            color = COL_GRID;
+        else
+            color = COL_USER;
+
+       draw_text(dr, tx + TILESIZE/2, ty + TILESIZE/2, FONT_VARIABLE,
+                 (tile & DF_PLAYAREA ? TILESIZE/2 : TILESIZE*2/5),
+                  ALIGN_VCENTRE | ALIGN_HCENTRE, color, str);
+    } else {
+        int i, j, npencil;
+       int pl, pr, pt, pb;
+       float bestsize;
+       int pw, ph, minph, pbest, fontsize;
+
+        /* Count the pencil marks required. */
+        for (i = 1, npencil = 0; i <= w; i++)
+            if (tile & (1L << (i + DF_PENCIL_SHIFT)))
+               npencil++;
+       if (npencil) {
+
+           minph = 2;
+
+           /*
+            * Determine the bounding rectangle within which we're going
+            * to put the pencil marks.
+            */
+           /* Start with the whole square, minus space for impinging towers */
+           pl = tx + (ds->three_d ? X_3D_DISP(w,w) : 0);
+           pr = tx + TILESIZE;
+           pt = ty;
+           pb = ty + TILESIZE - (ds->three_d ? Y_3D_DISP(w,w) : 0);
+
+           /*
+            * We arrange our pencil marks in a grid layout, with
+            * the number of rows and columns adjusted to allow the
+            * maximum font size.
+            *
+            * So now we work out what the grid size ought to be.
+            */
+           bestsize = 0.0;
+           pbest = 0;
+           /* Minimum */
+           for (pw = 3; pw < max(npencil,4); pw++) {
+               float fw, fh, fs;
+
+               ph = (npencil + pw - 1) / pw;
+               ph = max(ph, minph);
+               fw = (pr - pl) / (float)pw;
+               fh = (pb - pt) / (float)ph;
+               fs = min(fw, fh);
+               if (fs > bestsize) {
+                   bestsize = fs;
+                   pbest = pw;
+               }
+           }
+           assert(pbest > 0);
+           pw = pbest;
+           ph = (npencil + pw - 1) / pw;
+           ph = max(ph, minph);
+
+           /*
+            * Now we've got our grid dimensions, work out the pixel
+            * size of a grid element, and round it to the nearest
+            * pixel. (We don't want rounding errors to make the
+            * grid look uneven at low pixel sizes.)
+            */
+           fontsize = min((pr - pl) / pw, (pb - pt) / ph);
+
+           /*
+            * Centre the resulting figure in the square.
+            */
+           pl = pl + (pr - pl - fontsize * pw) / 2;
+           pt = pt + (pb - pt - fontsize * ph) / 2;
+
+           /*
+            * Now actually draw the pencil marks.
+            */
+           for (i = 1, j = 0; i <= w; i++)
+               if (tile & (1L << (i + DF_PENCIL_SHIFT))) {
+                   int dx = j % pw, dy = j / pw;
+
+                   str[1] = '\0';
+                   str[0] = i + '0';
+                   draw_text(dr, pl + fontsize * (2*dx+1) / 2,
+                             pt + fontsize * (2*dy+1) / 2,
+                             FONT_VARIABLE, fontsize,
+                             ALIGN_VCENTRE | ALIGN_HCENTRE, COL_PENCIL, str);
+                   j++;
+               }
+       }
+    }
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int w = state->par.w /*, a = w*w */;
+    int i, x, y;
+
+    if (!ds->started) {
+       /*
+        * The initial contents of the window are not guaranteed and
+        * can vary with front ends. To be on the safe side, all
+        * games should start by drawing a big background-colour
+        * rectangle covering the whole window.
+        */
+       draw_rect(dr, 0, 0, SIZE(w), SIZE(w), COL_BACKGROUND);
+
+       draw_update(dr, 0, 0, SIZE(w), SIZE(w));
+
+       ds->started = TRUE;
+    }
+
+    check_errors(state, ds->errtmp);
+
+    /*
+     * Work out what data each tile should contain.
+     */
+    for (i = 0; i < (w+2)*(w+2); i++)
+       ds->tiles[i] = 0;              /* completely blank square */
+    /* The clue squares... */
+    for (i = 0; i < 4*w; i++) {
+       long tile = state->clues->clues[i];
+
+       CLUEPOS(x, y, i, w);
+
+       if (ds->errtmp[(y+1)*(w+2)+(x+1)])
+           tile |= DF_ERROR;
+        else if (state->clues_done[i])
+            tile |= DF_CLUE_DONE;
+
+       ds->tiles[(y+1)*(w+2)+(x+1)] = tile;
+    }
+    /* ... and the main grid. */
+    for (y = 0; y < w; y++) {
+       for (x = 0; x < w; x++) {
+           long tile = DF_PLAYAREA;
+
+           if (state->grid[y*w+x])
+               tile |= state->grid[y*w+x];
+           else
+               tile |= (long)state->pencil[y*w+x] << DF_PENCIL_SHIFT;
+
+           if (ui->hshow && ui->hx == x && ui->hy == y)
+               tile |= (ui->hpencil ? DF_HIGHLIGHT_PENCIL : DF_HIGHLIGHT);
+
+           if (state->clues->immutable[y*w+x])
+               tile |= DF_IMMUTABLE;
+
+            if (flashtime > 0 &&
+                (flashtime <= FLASH_TIME/3 ||
+                 flashtime >= FLASH_TIME*2/3))
+                tile |= DF_HIGHLIGHT;  /* completion flash */
+
+           if (ds->errtmp[(y+1)*(w+2)+(x+1)])
+               tile |= DF_ERROR;
+
+           ds->tiles[(y+1)*(w+2)+(x+1)] = tile;
+       }
+    }
+
+    /*
+     * Now actually draw anything that needs to be changed.
+     */
+    for (y = 0; y < w+2; y++) {
+       for (x = 0; x < w+2; x++) {
+           long tl, tr, bl, br;
+           int i = y*(w+2)+x;
+
+           tr = ds->tiles[y*(w+2)+x];
+           tl = (x == 0 ? 0 : ds->tiles[y*(w+2)+(x-1)]);
+           br = (y == w+1 ? 0 : ds->tiles[(y+1)*(w+2)+x]);
+           bl = (x == 0 || y == w+1 ? 0 : ds->tiles[(y+1)*(w+2)+(x-1)]);
+
+           if (ds->drawn[i*4] != tl || ds->drawn[i*4+1] != tr ||
+               ds->drawn[i*4+2] != bl || ds->drawn[i*4+3] != br) {
+               clip(dr, COORD(x-1), COORD(y-1), TILESIZE, TILESIZE);
+
+               draw_tile(dr, ds, state->clues, x-1, y-1, tr);
+               if (x > 0)
+                   draw_tile(dr, ds, state->clues, x-2, y-1, tl);
+               if (y <= w)
+                   draw_tile(dr, ds, state->clues, x-1, y, br);
+               if (x > 0 && y <= w)
+                   draw_tile(dr, ds, state->clues, x-2, y, bl);
+
+               unclip(dr);
+               draw_update(dr, COORD(x-1), COORD(y-1), TILESIZE, TILESIZE);
+
+               ds->drawn[i*4] = tl;
+               ds->drawn[i*4+1] = tr;
+               ds->drawn[i*4+2] = bl;
+               ds->drawn[i*4+3] = br;
+           }
+       }
+    }
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return 0.0F;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    if (!oldstate->completed && newstate->completed &&
+       !oldstate->cheated && !newstate->cheated)
+        return FLASH_TIME;
+    return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    return state->completed ? +1 : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    if (state->completed)
+       return FALSE;
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+    int pw, ph;
+
+    /*
+     * We use 9mm squares by default, like Solo.
+     */
+    game_compute_size(params, 900, &pw, &ph);
+    *x = pw / 100.0F;
+    *y = ph / 100.0F;
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+    int w = state->par.w;
+    int ink = print_mono_colour(dr, 0);
+    int i, x, y;
+
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    game_drawstate ads, *ds = &ads;
+    game_set_size(dr, ds, NULL, tilesize);
+
+    /*
+     * Border.
+     */
+    print_line_width(dr, 3 * TILESIZE / 40);
+    draw_rect_outline(dr, BORDER, BORDER, w*TILESIZE, w*TILESIZE, ink);
+
+    /*
+     * Main grid.
+     */
+    for (x = 1; x < w; x++) {
+       print_line_width(dr, TILESIZE / 40);
+       draw_line(dr, BORDER+x*TILESIZE, BORDER,
+                 BORDER+x*TILESIZE, BORDER+w*TILESIZE, ink);
+    }
+    for (y = 1; y < w; y++) {
+       print_line_width(dr, TILESIZE / 40);
+       draw_line(dr, BORDER, BORDER+y*TILESIZE,
+                 BORDER+w*TILESIZE, BORDER+y*TILESIZE, ink);
+    }
+
+    /*
+     * Clues.
+     */
+    for (i = 0; i < 4*w; i++) {
+       char str[128];
+
+       if (!state->clues->clues[i])
+           continue;
+
+       CLUEPOS(x, y, i, w);
+
+       sprintf (str, "%d", state->clues->clues[i]);
+
+       draw_text(dr, BORDER + x*TILESIZE + TILESIZE/2,
+                 BORDER + y*TILESIZE + TILESIZE/2,
+                 FONT_VARIABLE, TILESIZE/2,
+                 ALIGN_VCENTRE | ALIGN_HCENTRE, ink, str);
+    }
+
+    /*
+     * Numbers for the solution, if any.
+     */
+    for (y = 0; y < w; y++)
+       for (x = 0; x < w; x++)
+           if (state->grid[y*w+x]) {
+               char str[2];
+               str[1] = '\0';
+               str[0] = state->grid[y*w+x] + '0';
+               draw_text(dr, BORDER + x*TILESIZE + TILESIZE/2,
+                         BORDER + y*TILESIZE + TILESIZE/2,
+                         FONT_VARIABLE, TILESIZE/2,
+                         ALIGN_VCENTRE | ALIGN_HCENTRE, ink, str);
+           }
+}
+
+#ifdef COMBINED
+#define thegame towers
+#endif
+
+const struct game thegame = {
+    "Towers", "games.towers", "towers",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    TRUE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILESIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    TRUE, FALSE, game_print_size, game_print,
+    FALSE,                            /* wants_statusbar */
+    FALSE, game_timing_state,
+    REQUIRE_RBUTTON | REQUIRE_NUMPAD,  /* flags */
+};
+
+#ifdef STANDALONE_SOLVER
+
+#include <stdarg.h>
+
+int main(int argc, char **argv)
+{
+    game_params *p;
+    game_state *s;
+    char *id = NULL, *desc, *err;
+    int grade = FALSE;
+    int ret, diff, really_show_working = FALSE;
+
+    while (--argc > 0) {
+        char *p = *++argv;
+        if (!strcmp(p, "-v")) {
+            really_show_working = TRUE;
+        } else if (!strcmp(p, "-g")) {
+            grade = TRUE;
+        } else if (*p == '-') {
+            fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p);
+            return 1;
+        } else {
+            id = p;
+        }
+    }
+
+    if (!id) {
+        fprintf(stderr, "usage: %s [-g | -v] <game_id>\n", argv[0]);
+        return 1;
+    }
+
+    desc = strchr(id, ':');
+    if (!desc) {
+        fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]);
+        return 1;
+    }
+    *desc++ = '\0';
+
+    p = default_params();
+    decode_params(p, id);
+    err = validate_desc(p, desc);
+    if (err) {
+        fprintf(stderr, "%s: %s\n", argv[0], err);
+        return 1;
+    }
+    s = new_game(NULL, p, desc);
+
+    /*
+     * When solving an Easy puzzle, we don't want to bother the
+     * user with Hard-level deductions. For this reason, we grade
+     * the puzzle internally before doing anything else.
+     */
+    ret = -1;                         /* placate optimiser */
+    solver_show_working = FALSE;
+    for (diff = 0; diff < DIFFCOUNT; diff++) {
+       memcpy(s->grid, s->clues->immutable, p->w * p->w);
+       ret = solver(p->w, s->clues->clues, s->grid, diff);
+       if (ret <= diff)
+           break;
+    }
+
+    if (diff == DIFFCOUNT) {
+       if (grade)
+           printf("Difficulty rating: ambiguous\n");
+       else
+           printf("Unable to find a unique solution\n");
+    } else {
+       if (grade) {
+           if (ret == diff_impossible)
+               printf("Difficulty rating: impossible (no solution exists)\n");
+           else
+               printf("Difficulty rating: %s\n", towers_diffnames[ret]);
+       } else {
+           solver_show_working = really_show_working;
+           memcpy(s->grid, s->clues->immutable, p->w * p->w);
+           ret = solver(p->w, s->clues->clues, s->grid, diff);
+           if (ret != diff)
+               printf("Puzzle is inconsistent\n");
+           else
+               fputs(game_text_format(s), stdout);
+       }
+    }
+
+    return 0;
+}
+
+#endif
+
+/* vim: set shiftwidth=4 tabstop=8: */
diff --git a/tracks.R b/tracks.R
new file mode 100644 (file)
index 0000000..f88dfb0
--- /dev/null
+++ b/tracks.R
@@ -0,0 +1,21 @@
+# -*- makefile -*-
+
+TRACKS_EXTRA = dsf findloop
+
+tracks  : [X] GTK COMMON tracks TRACKS_EXTRA tracks-icon|no-icon
+
+tracks  : [G] WINDOWS COMMON tracks TRACKS_EXTRA tracks.res|noicon.res
+
+ALL += tracks[COMBINED] TRACKS_EXTRA
+
+!begin am gtk
+GAMES += tracks
+!end
+
+!begin >list.c
+    A(tracks) \
+!end
+
+!begin >gamedesc.txt
+tracks:tracks.exe:Tracks:Path-finding railway track puzzle:Fill in the railway track according to the clues.
+!end
diff --git a/tracks.c b/tracks.c
new file mode 100644 (file)
index 0000000..ca44ce1
--- /dev/null
+++ b/tracks.c
@@ -0,0 +1,2660 @@
+/*
+ * Implementation of 'Train Tracks', a puzzle from the Times on Saturday.
+ *
+ * "Lay tracks to enable the train to travel from village A to village B.
+ * The numbers indicate how many sections of rail go in each row and
+ * column. There are only straight rails and curved rails. The track
+ * cannot cross itself."
+ *
+ * Puzzles:
+ * #9     8x8:d9s5c6zgAa,1,4,1,4,4,3,S3,5,2,2,4,S5,3,3,5,1
+ * #112   8x8:w6x5mAa,1,3,1,4,6,4,S4,3,3,4,5,2,4,2,S5,1
+ * #113   8x8:gCx5xAf,1,S4,2,5,4,6,2,3,4,2,5,2,S4,4,5,1
+ * #114   8x8:p5fAzkAb,1,6,3,3,3,S6,2,3,5,4,S3,3,5,1,5,1
+ * #115   8x8:zi9d5tAb,1,3,4,5,3,S4,2,4,2,6,2,3,6,S3,3,1
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+
+/* --- Game parameters --- */
+
+/*
+ * Difficulty levels. I do some macro ickery here to ensure that my
+ * enum and the various forms of my name list always match up.
+ */
+#define DIFFLIST(A) \
+    A(EASY,Easy,e) \
+    A(TRICKY,Tricky,t)
+
+#define ENUM(upper,title,lower) DIFF_ ## upper,
+#define TITLE(upper,title,lower) #title,
+#define ENCODE(upper,title,lower) #lower
+#define CONFIG(upper,title,lower) ":" #title
+enum { DIFFLIST(ENUM) DIFFCOUNT };
+static char const *const tracks_diffnames[] = { DIFFLIST(TITLE) };
+static char const tracks_diffchars[] = DIFFLIST(ENCODE);
+#define DIFFCONFIG DIFFLIST(CONFIG)
+
+struct game_params {
+    int w, h, diff, single_ones;
+};
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = ret->h = 8;
+    ret->diff = DIFF_TRICKY;
+    ret->single_ones = TRUE;
+
+    return ret;
+}
+
+static const struct game_params tracks_presets[] = {
+    {8, 8, DIFF_EASY, 1},
+    {8, 8, DIFF_TRICKY, 1},
+    {10, 8, DIFF_EASY, 1},
+    {10, 8, DIFF_TRICKY, 1 },
+    {10, 10, DIFF_EASY, 1},
+    {10, 10, DIFF_TRICKY, 1},
+    {15, 10, DIFF_EASY, 1},
+    {15, 10, DIFF_TRICKY, 1},
+    {15, 15, DIFF_EASY, 1},
+    {15, 15, DIFF_TRICKY, 1},
+};
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params *ret;
+    char str[80];
+
+    if (i < 0 || i >= lenof(tracks_presets))
+        return FALSE;
+
+    ret = snew(game_params);
+    *ret = tracks_presets[i];
+
+    sprintf(str, "%dx%d %s", ret->w, ret->h, tracks_diffnames[ret->diff]);
+
+    *name = dupstr(str);
+    *params = ret;
+    return TRUE;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+static void decode_params(game_params *params, char const *string)
+{
+    params->w = params->h = atoi(string);
+    while (*string && isdigit((unsigned char)*string)) string++;
+    if (*string == 'x') {
+        string++;
+        params->h = atoi(string);
+        while (*string && isdigit((unsigned char)*string)) string++;
+    }
+    if (*string == 'd') {
+        int i;
+        string++;
+        params->diff = DIFF_TRICKY;
+        for (i = 0; i < DIFFCOUNT; i++)
+            if (*string == tracks_diffchars[i])
+                params->diff = i;
+        if (*string) string++;
+    }
+    params->single_ones = TRUE;
+    if (*string == 'o') {
+        params->single_ones = FALSE;
+        string++;
+    }
+
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char buf[120];
+
+    sprintf(buf, "%dx%d", params->w, params->h);
+    if (full)
+        sprintf(buf + strlen(buf), "d%c%s",
+                tracks_diffchars[params->diff],
+                params->single_ones ? "" : "o");
+    return dupstr(buf);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+    char buf[80];
+
+    ret = snewn(5, config_item);
+
+    ret[0].name = "Width";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->w);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Height";
+    ret[1].type = C_STRING;
+    sprintf(buf, "%d", params->h);
+    ret[1].sval = dupstr(buf);
+    ret[1].ival = 0;
+
+    ret[2].name = "Difficulty";
+    ret[2].type = C_CHOICES;
+    ret[2].sval = DIFFCONFIG;
+    ret[2].ival = params->diff;
+
+    ret[3].name = "Disallow consecutive 1 clues";
+    ret[3].type = C_BOOLEAN;
+    ret[3].ival = params->single_ones;
+
+    ret[4].name = NULL;
+    ret[4].type = C_END;
+    ret[4].sval = NULL;
+    ret[4].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = atoi(cfg[0].sval);
+    ret->h = atoi(cfg[1].sval);
+    ret->diff = cfg[2].ival;
+    ret->single_ones = cfg[3].ival;
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    /*
+     * Generating anything under 4x4 runs into trouble of one kind
+     * or another.
+     */
+    if (params->w < 4 || params->h < 4)
+        return "Width and height must both be at least four";
+    return NULL;
+}
+
+/* --- Game state --- */
+
+/* flag usage copied from pearl */
+
+#define R 1
+#define U 2
+#define L 4
+#define D 8
+
+#define MOVECHAR(m) ((m==R)?'R':(m==U)?'U':(m==L)?'L':(m==D)?'D':'?')
+
+#define DX(d) ( ((d)==R) - ((d)==L) )
+#define DY(d) ( ((d)==D) - ((d)==U) )
+
+#define F(d) (((d << 2) | (d >> 2)) & 0xF)
+#define C(d) (((d << 3) | (d >> 1)) & 0xF)
+#define A(d) (((d << 1) | (d >> 3)) & 0xF)
+
+#define LR (L | R)
+#define RL (R | L)
+#define UD (U | D)
+#define DU (D | U)
+#define LU (L | U)
+#define UL (U | L)
+#define LD (L | D)
+#define DL (D | L)
+#define RU (R | U)
+#define UR (U | R)
+#define RD (R | D)
+#define DR (D | R)
+#define ALLDIR 15
+#define BLANK 0
+#define UNKNOWN 15
+
+int nbits[] = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4 };
+
+/* square grid flags */
+#define S_TRACK 1     /* a track passes through this square (--> 2 edges) */
+#define S_NOTRACK 2   /* no track passes through this square */
+#define S_ERROR 4
+#define S_CLUE 8
+#define S_MARK 16
+
+#define S_TRACK_SHIFT   16 /* U/D/L/R flags for edge track indicators */
+#define S_NOTRACK_SHIFT 20 /* U/D/L/R flags for edge no-track indicators */
+
+/* edge grid flags */
+#define E_TRACK 1     /* a track passes through this edge */
+#define E_NOTRACK 2   /* no track passes through this edge */
+
+struct numbers {
+    int refcount;
+    int *numbers;     /* sz w+h */
+    int row_s, col_s; /* stations: TODO think about multiple lines
+                         (for bigger grids)? */
+};
+
+#define INGRID(state, gx, gy) ((gx) >= 0 && (gx) < (state)->p.w && \
+                               (gy) >= 0 && (gy) < (state)->p.h)
+
+struct game_state {
+    game_params p;
+    unsigned int *sflags;       /* size w*h */
+    struct numbers *numbers;
+    int *num_errors;            /* size w+h */
+    int completed, used_solve, impossible;
+};
+
+/* Return the four directions in which a particular edge flag is set, around a square. */
+int S_E_DIRS(const game_state *state, int sx, int sy, unsigned int eflag) {
+    return (state->sflags[sy*state->p.w+sx] >>
+            ((eflag == E_TRACK) ? S_TRACK_SHIFT : S_NOTRACK_SHIFT)) & ALLDIR;
+}
+
+/* Count the number of a particular edge flag around a grid square. */
+int S_E_COUNT(const game_state *state, int sx, int sy, unsigned int eflag) {
+    return nbits[S_E_DIRS(state, sx, sy, eflag)];
+}
+
+/* Return the two flags (E_TRACK and/or E_NOTRACK) set on a specific
+ * edge of a square. */
+unsigned S_E_FLAGS(const game_state *state, int sx, int sy, int d) {
+    unsigned f = state->sflags[sy*state->p.w+sx];
+    int t = (f & (d << S_TRACK_SHIFT)), nt = (f & (d << S_NOTRACK_SHIFT));
+    return (t ? E_TRACK : 0) | (nt ? E_NOTRACK : 0);
+}
+
+int S_E_ADJ(const game_state *state, int sx, int sy, int d, int *ax, int *ay, unsigned int *ad) {
+    if (d == L && sx > 0)            { *ax = sx-1; *ay = sy;   *ad = R; return 1; }
+    if (d == R && sx < state->p.w-1) { *ax = sx+1; *ay = sy;   *ad = L; return 1; }
+    if (d == U && sy > 0)            { *ax = sx;   *ay = sy-1; *ad = D; return 1; }
+    if (d == D && sy < state->p.h-1) { *ax = sx;   *ay = sy+1; *ad = U; return 1; }
+
+    return 0;
+}
+
+/* Sets flag (E_TRACK or E_NOTRACK) on a given edge of a square. */
+void S_E_SET(game_state *state, int sx, int sy, int d, unsigned int eflag) {
+    unsigned shift = (eflag == E_TRACK) ? S_TRACK_SHIFT : S_NOTRACK_SHIFT, ad;
+    int ax, ay;
+
+    state->sflags[sy*state->p.w+sx] |= (d << shift);
+
+    if (S_E_ADJ(state, sx, sy, d, &ax, &ay, &ad)) {
+        state->sflags[ay*state->p.w+ax] |= (ad << shift);
+    }
+}
+
+/* Clears flag (E_TRACK or E_NOTRACK) on a given edge of a square. */
+void S_E_CLEAR(game_state *state, int sx, int sy, int d, unsigned int eflag) {
+    unsigned shift = (eflag == E_TRACK) ? S_TRACK_SHIFT : S_NOTRACK_SHIFT, ad;
+    int ax, ay;
+
+    state->sflags[sy*state->p.w+sx] &= ~(d << shift);
+
+    if (S_E_ADJ(state, sx, sy, d, &ax, &ay, &ad)) {
+        state->sflags[ay*state->p.w+ax] &= ~(ad << shift);
+    }
+}
+
+static void clear_game(game_state *state)
+{
+    int w = state->p.w, h = state->p.h;
+
+    memset(state->sflags, 0, w*h * sizeof(unsigned int));
+
+    memset(state->numbers->numbers, 0, (w+h) * sizeof(int));
+    state->numbers->col_s = state->numbers->row_s = -1;
+
+    memset(state->num_errors, 0, (w+h) * sizeof(int));
+
+    state->completed = state->used_solve = state->impossible = FALSE;
+}
+
+static game_state *blank_game(const game_params *params)
+{
+    game_state *state = snew(game_state);
+    int w = params->w, h = params->h;
+
+    state->p = *params;
+
+    state->sflags = snewn(w*h, unsigned int);
+
+    state->numbers = snew(struct numbers);
+    state->numbers->refcount = 1;
+    state->numbers->numbers = snewn(w+h, int);
+
+    state->num_errors = snewn(w+h, int);
+
+    clear_game(state);
+
+    return state;
+}
+
+static void copy_game_flags(const game_state *src, game_state *dest)
+{
+    int w = src->p.w, h = src->p.h;
+
+    memcpy(dest->sflags, src->sflags, w*h*sizeof(unsigned int));
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    int w = state->p.w, h = state->p.h;
+    game_state *ret = snew(game_state);
+
+    ret->p = state->p;                /* structure copy */
+
+    ret->sflags = snewn(w*h, unsigned int);
+    copy_game_flags(state, ret);
+
+    ret->numbers = state->numbers;
+    state->numbers->refcount++;
+    ret->num_errors = snewn(w+h, int);
+    memcpy(ret->num_errors, state->num_errors, (w+h)*sizeof(int));
+
+    ret->completed = state->completed;
+    ret->used_solve = state->used_solve;
+    ret->impossible = state->impossible;
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    if (--state->numbers->refcount <= 0) {
+        sfree(state->numbers->numbers);
+        sfree(state->numbers);
+    }
+    sfree(state->num_errors);
+    sfree(state->sflags);
+    sfree(state);
+}
+
+#define NDIRS 4
+const unsigned int dirs_const[] = { U, D, L, R };
+
+static unsigned int find_direction(game_state *state, random_state *rs,
+                                   int x, int y)
+{
+    int i, nx, ny, w=state->p.w, h=state->p.h;
+    unsigned int dirs[NDIRS];
+
+    memcpy(dirs, dirs_const, sizeof(dirs));
+    shuffle(dirs, NDIRS, sizeof(*dirs), rs);
+    for (i = 0; i < NDIRS; i++) {
+        nx = x + DX(dirs[i]);
+        ny = y + DY(dirs[i]);
+        if (nx >= 0 && nx < w && ny == h) {
+            /* off the bottom of the board: we've finished the path. */
+            return dirs[i];
+        } else if (!INGRID(state, nx, ny)) {
+            /* off the board: can't move here */
+            continue;
+        } else if (S_E_COUNT(state, nx, ny, E_TRACK) > 0) {
+            /* already tracks here: can't move */
+            continue;
+        }
+        return dirs[i];
+    }
+    return 0; /* no possible directions left. */
+}
+
+static int check_completion(game_state *state, int mark);
+
+static void lay_path(game_state *state, random_state *rs)
+{
+    int px, py, w=state->p.w, h=state->p.h;
+    unsigned int d;
+
+start:
+    clear_game(state);
+
+    /* pick a random entry point, lay its left edge */
+    state->numbers->row_s = py = random_upto(rs, h);
+    px = 0;
+    S_E_SET(state, px, py, L, E_TRACK);
+
+    while (INGRID(state, px, py)) {
+        d = find_direction(state, rs, px, py);
+        if (d == 0)
+            goto start; /* nowhere else to go, restart */
+
+        S_E_SET(state, px, py, d, E_TRACK);
+        px += DX(d);
+        py += DY(d);
+    }
+    /* double-check we got to the right place */
+    assert(px >= 0 && px < w && py == h);
+
+    state->numbers->col_s = px;
+}
+
+static int tracks_solve(game_state *state, int diff);
+static void debug_state(game_state *state, const char *what);
+
+/* Clue-setting algorithm:
+
+ - first lay clues randomly until it's soluble
+ - then remove clues randomly if removing them doesn't affect solubility
+
+ - We start with two clues, one at each path entrance.
+
+ More details:
+ - start with an array of all square i positions
+ - if the grid is already soluble by a level easier than we've requested,
+    go back and make a new grid
+ - if the grid is already soluble by our requested difficulty level, skip
+    the clue-laying step
+ - count the number of flags the solver managed to place, remember this.
+
+ - to lay clues:
+   - shuffle the i positions
+   - for each possible clue position:
+     - copy the solved board, strip it
+     - take the next position, add a clue there on the copy
+     - try and solve the copy
+     - if it's soluble by a level easier than we've requested, continue (on
+        to next clue position: putting a clue here makes it too easy)
+     - if it's soluble by our difficulty level, we're done:
+       - put the clue flag into the solved board
+       - go to strip-clues.
+     - if the solver didn't manage to place any more flags, continue (on to next
+        clue position: putting a clue here didn't help he solver)
+     - otherwise put the clue flag in the original board, and go on to the next
+        clue position
+   - if we get here and we've not solved it yet, we never will (did we really
+      fill _all_ the clues in?!). Go back and make a new grid.
+
+ - to strip clues:
+   - shuffle the i positions
+   - for each possible clue position:
+     - if the solved grid doesn't have a clue here, skip
+     - copy the solved board, remove this clue, strip it
+     - try and solve the copy
+     - assert that it is not soluble by a level easier than we've requested
+       - (because this should never happen)
+     - if this is (still) soluble by our difficulty level:
+       - remove this clue from the solved board, it's redundant (with the other
+          clues)
+
+  - that should be it.
+*/
+
+static game_state *copy_and_strip(const game_state *state, game_state *ret, int flipcluei)
+{
+    int i, j, w = state->p.w, h = state->p.h;
+
+    copy_game_flags(state, ret);
+
+    /* Add/remove a clue before stripping, if required */
+
+    if (flipcluei != -1)
+        ret->sflags[flipcluei] ^= S_CLUE;
+
+    /* All squares that are not clue squares have square track info erased, and some edge flags.. */
+
+    for (i = 0; i < w*h; i++) {
+        if (!(ret->sflags[i] & S_CLUE)) {
+            ret->sflags[i] &= ~(S_TRACK|S_NOTRACK|S_ERROR|S_MARK);
+            for (j = 0; j < 4; j++) {
+                unsigned f = 1<<j;
+                int xx = i%w + DX(f), yy = i/w + DY(f);
+                if (!INGRID(state, xx, yy) || !(ret->sflags[yy*w+xx] & S_CLUE)) {
+                    /* only erase an edge flag if neither side of the edge is S_CLUE. */
+                    S_E_CLEAR(ret, i%w, i/w, f, E_TRACK);
+                    S_E_CLEAR(ret, i%w, i/w, f, E_NOTRACK);
+                }
+            }
+        }
+    }
+    return ret;
+}
+
+static int solve_progress(const game_state *state) {
+    int i, w = state->p.w, h = state->p.h, progress = 0;
+
+    /* Work out how many flags the solver managed to set (either TRACK
+       or NOTRACK) and return this as a progress measure, to check whether
+       a partially-solved board gets any further than a previous partially-
+       solved board. */
+
+    for (i = 0; i < w*h; i++) {
+        if (state->sflags[i] & S_TRACK) progress++;
+        if (state->sflags[i] & S_NOTRACK) progress++;
+        progress += S_E_COUNT(state, i%w, i/w, E_TRACK);
+        progress += S_E_COUNT(state, i%w, i/w, E_NOTRACK);
+    }
+    return progress;
+}
+
+static int check_phantom_moves(const game_state *state) {
+    int x, y, i;
+
+    /* Check that this state won't show 'phantom moves' at the start of the
+     * game: squares which have multiple edge flags set but no clue flag
+     * cause a piece of track to appear that isn't on a clue square. */
+
+    for (x = 0; x < state->p.w; x++) {
+        for (y = 0; y < state->p.h; y++) {
+            i = y*state->p.w+x;
+            if (state->sflags[i] & S_CLUE)
+                continue;
+            if (S_E_COUNT(state, x, y, E_TRACK) > 1)
+                return 1; /* found one! */
+        }
+    }
+    return 0;
+}
+
+static int add_clues(game_state *state, random_state *rs, int diff)
+{
+    int i, j, pi, w = state->p.w, h = state->p.h, progress, ret = 0, sr;
+    int *positions = snewn(w*h, int), npositions = 0;
+    int *nedges_previous_solve = snewn(w*h, int);
+    game_state *scratch = dup_game(state);
+
+    debug_state(state, "gen: Initial board");
+
+    debug(("gen: Adding clues..."));
+
+    /* set up the shuffly-position grid for later, used for adding clues:
+     * we only bother adding clues where any edges are set. */
+    for (i = 0; i < w*h; i++) {
+        if (S_E_DIRS(state, i%w, i/w, E_TRACK) != 0) {
+            positions[npositions++] = i;
+        }
+        nedges_previous_solve[i] = 0;
+    }
+
+    /* First, check whether the puzzle is already either too easy, or just right */
+    scratch = copy_and_strip(state, scratch, -1);
+    if (diff > 0) {
+        sr = tracks_solve(scratch, diff-1);
+        if (sr < 0)
+            assert(!"Generator should not have created impossible puzzle");
+        if (sr > 0) {
+            ret = -1; /* already too easy, even without adding clues. */
+            debug(("gen:  ...already too easy, need new board."));
+            goto done;
+        }
+    }
+    sr = tracks_solve(scratch, diff);
+    if (sr < 0)
+        assert(!"Generator should not have created impossible puzzle");
+    if (sr > 0) {
+        ret = 1; /* already soluble without any extra clues. */
+        debug(("gen:  ...soluble without clues, nothing to do."));
+        goto done;
+    }
+    debug_state(scratch, "gen: Initial part-solved state: ");
+    progress = solve_progress(scratch);
+    debug(("gen: Initial solve progress is %d", progress));
+
+    /* First, lay clues until we're soluble. */
+    shuffle(positions, npositions, sizeof(int), rs);
+    for (pi = 0; pi < npositions; pi++) {
+        i = positions[pi]; /* pick a random position */
+        if (state->sflags[i] & S_CLUE)
+            continue; /* already a clue here (entrance location?) */
+        if (nedges_previous_solve[i] == 2)
+            continue; /* no point putting a clue here, we could solve both edges
+                         with the previous set of clues */
+
+        /* set a clue in that position (on a copy of the board) and test solubility */
+        scratch = copy_and_strip(state, scratch, i);
+
+        if (check_phantom_moves(scratch))
+            continue; /* adding a clue here would add phantom track */
+
+        if (diff > 0) {
+            if (tracks_solve(scratch, diff-1) > 0) {
+                continue; /* adding a clue here makes it too easy */
+            }
+        }
+        if (tracks_solve(scratch, diff) > 0) {
+            /* we're now soluble (and we weren't before): add this clue, and then
+               start stripping clues */
+            debug(("gen:  ...adding clue at (%d,%d), now soluble", i%w, i/w));
+            state->sflags[i] |= S_CLUE;
+            goto strip_clues;
+        }
+        if (solve_progress(scratch) > progress) {
+            /* We've made more progress solving: add this clue, then. */
+            progress = solve_progress(scratch);
+            debug(("gen:  ... adding clue at (%d,%d), new progress %d", i%w, i/w, progress));
+            state->sflags[i] |= S_CLUE;
+
+            for (j = 0; j < w*h; j++)
+                nedges_previous_solve[j] = S_E_COUNT(scratch, j%w, j/w, E_TRACK);
+        }
+    }
+    /* If we got here we didn't ever manage to make the puzzle soluble
+       (without making it too easily soluble, that is): give up. */
+
+    debug(("gen: Unable to make soluble with clues, need new board."));
+    ret = -1;
+    goto done;
+
+strip_clues:
+    debug(("gen: Stripping clues."));
+
+    /* Now, strip redundant clues (i.e. those without which the puzzle is still
+       soluble) */
+    shuffle(positions, npositions, sizeof(int), rs);
+    for (pi = 0; pi < npositions; pi++) {
+        i = positions[pi]; /* pick a random position */
+        if (!(state->sflags[i] & S_CLUE))
+            continue; /* no clue here to strip */
+        if ((i%w == 0 && i/w == state->numbers->row_s) ||
+                (i/w == (h-1) && i%w == state->numbers->col_s))
+            continue; /* don't strip clues at entrance/exit */
+
+        scratch = copy_and_strip(state, scratch, i);
+        if (check_phantom_moves(scratch))
+            continue; /* removing a clue here would add phantom track */
+
+        if (tracks_solve(scratch, diff) > 0) {
+            debug(("gen:  ... removing clue at (%d,%d), still soluble without it", i%w, i/w));
+            state->sflags[i] &= ~S_CLUE; /* still soluble without this clue. */
+        }
+    }
+    debug(("gen: Finished stripping clues."));
+    ret = 1;
+
+done:
+    sfree(positions);
+    free_game(scratch);
+    return ret;
+}
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                           char **aux, int interactive)
+{
+    int i, j, w = params->w, h = params->h, x, y, ret;
+    game_state *state;
+    char *desc, *p;
+    game_params adjusted_params;
+
+    /*
+     * 4x4 Tricky cannot be generated, so fall back to Easy.
+     */
+    if (w == 4 && h == 4 && params->diff > DIFF_EASY) {
+        adjusted_params = *params;     /* structure copy */
+        adjusted_params.diff = DIFF_EASY;
+        params = &adjusted_params;
+    }
+
+    state = blank_game(params);
+
+    /* --- lay the random path */
+
+newpath:
+    lay_path(state, rs);
+    for (x = 0; x < w; x++) {
+        for (y = 0; y < h; y++) {
+            if (S_E_COUNT(state, x, y, E_TRACK) > 0) {
+                state->sflags[y*w + x] |= S_TRACK;
+            }
+            if ((x == 0 && y == state->numbers->row_s) ||
+                    (y == (h-1) && x == state->numbers->col_s)) {
+                state->sflags[y*w + x] |= S_CLUE;
+            }
+        }
+    }
+
+    /* --- Update the clue numbers based on the tracks we have generated. */
+    for (x = 0; x < w; x++) {
+        for (y = 0; y < h; y++) {
+            if (state->sflags[y*w + x] & S_TRACK) {
+                state->numbers->numbers[x]++;
+                state->numbers->numbers[y+w]++;
+            }
+        }
+    }
+    for (i = 0; i < w+h; i++) {
+        if (state->numbers->numbers[i] == 0)
+            goto newpath; /* too boring */
+    }
+
+    if (params->single_ones) {
+        int last_was_one = 1, is_one; /* (disallow 1 clue at entry point) */
+        for (i = 0; i < w+h; i++) {
+            is_one = (state->numbers->numbers[i] == 1);
+            if (is_one && last_was_one)
+                goto newpath; /* disallow consecutive 1 clues. */
+            last_was_one = is_one;
+        }
+        if (state->numbers->numbers[w+h-1] == 1)
+            goto newpath; /* (disallow 1 clue at exit point) */
+    }
+
+    /* --- Add clues to make a soluble puzzle */
+    ret = add_clues(state, rs, params->diff);
+    if (ret != 1) goto newpath; /* couldn't make it soluble, or too easy */
+
+    /* --- Generate the game desc based on the generated grid. */
+    desc = snewn(w*h*3 + (w+h)*5, char);
+    for (i = j = 0; i < w*h; i++) {
+        if (!(state->sflags[i] & S_CLUE) && j > 0 &&
+                desc[j-1] >= 'a' && desc[j-1] < 'z')
+            desc[j-1]++;
+        else if (!(state->sflags[i] & S_CLUE))
+            desc[j++] = 'a';
+        else {
+            unsigned int f = S_E_DIRS(state, i%w, i/w, E_TRACK);
+            desc[j++] = (f < 10) ? ('0' + f) : ('A' + (f-10));
+        }
+    }
+
+    p = desc + j;
+    for (x = 0; x < w; x++) {
+        p += sprintf(p, ",%s%d", x == state->numbers->col_s ? "S" : "",
+                     state->numbers->numbers[x]);
+    }
+    for (y = 0; y < h; y++) {
+        p += sprintf(p, ",%s%d", y == state->numbers->row_s ? "S" : "",
+                     state->numbers->numbers[y+w]);
+    }
+    *p++ = '\0';
+
+    ret = tracks_solve(state, DIFFCOUNT);
+    assert(ret >= 0);
+    free_game(state);
+
+    debug(("new_game_desc: %s", desc));
+    return desc;
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    int i = 0, w = params->w, h = params->h, in = 0, out = 0;
+
+    while (*desc) {
+        unsigned int f = 0;
+        if (*desc >= '0' && *desc <= '9')
+            f = (*desc - '0');
+        else if (*desc >= 'A' && *desc <= 'F')
+            f = (*desc - 'A' + 10);
+        else if (*desc >= 'a' && *desc <= 'z')
+            i += *desc - 'a';
+        else
+            return "Game description contained unexpected characters";
+
+        if (f != 0) {
+            if (nbits[f] != 2)
+                return "Clue did not provide 2 direction flags";
+        }
+        i++;
+        desc++;
+        if (i == w*h) break;
+    }
+    for (i = 0; i < w+h; i++) {
+        if (!*desc)
+            return "Not enough numbers given after grid specification";
+        else if (*desc != ',')
+            return "Invalid character in number list";
+        desc++;
+        if (*desc == 'S') {
+            if (i < w)
+                out++;
+            else
+                in++;
+            desc++;
+        }
+        while (*desc && isdigit((unsigned char)*desc)) desc++;
+    }
+    if (in != 1 || out != 1)
+        return "Puzzle must have one entrance and one exit";
+    if (*desc)
+        return "Unexpected additional character at end of game description";
+    return NULL;
+}
+
+static game_state *new_game(midend *me, const game_params *params, const char *desc)
+{
+    game_state *state = blank_game(params);
+    int w = params->w, h = params->h, i = 0;
+
+    while (*desc) {
+        unsigned int f = 0;
+        if (*desc >= '0' && *desc <= '9')
+            f = (*desc - '0');
+        else if (*desc >= 'A' && *desc <= 'F')
+            f = (*desc - 'A' + 10);
+        else if (*desc >= 'a' && *desc <= 'z')
+            i += *desc - 'a';
+
+        if (f != 0) {
+            int x = i % w, y = i / w;
+            assert(f < 16);
+            assert(nbits[f] == 2);
+
+            state->sflags[i] |= (S_TRACK | S_CLUE);
+            if (f & U) S_E_SET(state, x, y, U, E_TRACK);
+            if (f & D) S_E_SET(state, x, y, D, E_TRACK);
+            if (f & L) S_E_SET(state, x, y, L, E_TRACK);
+            if (f & R) S_E_SET(state, x, y, R, E_TRACK);
+        }
+        i++;
+        desc++;
+        if (i == w*h) break;
+    }
+    for (i = 0; i < w+h; i++) {
+        assert(*desc == ',');
+        desc++;
+
+        if (*desc == 'S') {
+            if (i < w)
+                state->numbers->col_s = i;
+            else
+                state->numbers->row_s = i-w;
+            desc++;
+        }
+        state->numbers->numbers[i] = atoi(desc);
+        while (*desc && isdigit((unsigned char)*desc)) desc++;
+    }
+
+    assert(!*desc);
+
+    return state;
+}
+
+static int solve_set_sflag(game_state *state, int x, int y,
+                           unsigned int f, const char *why)
+{
+    int w = state->p.w, i = y*w + x;
+
+    if (state->sflags[i] & f)
+        return 0;
+    debug(("solve: square (%d,%d) -> %s: %s",
+           x, y, (f == S_TRACK ? "TRACK" : "NOTRACK"), why));
+    if (state->sflags[i] & (f == S_TRACK ? S_NOTRACK : S_TRACK)) {
+        debug(("solve: opposite flag already set there, marking IMPOSSIBLE"));
+        state->impossible = TRUE;
+    }
+    state->sflags[i] |= f;
+    return 1;
+}
+
+static int solve_set_eflag(game_state *state, int x, int y, int d,
+                           unsigned int f, const char *why)
+{
+    int sf = S_E_FLAGS(state, x, y, d);
+
+    if (sf & f)
+        return 0;
+    debug(("solve: edge (%d,%d)/%c -> %s: %s", x, y,
+           (d == U) ? 'U' : (d == D) ? 'D' : (d == L) ? 'L' : 'R',
+           (f == S_TRACK ? "TRACK" : "NOTRACK"), why));
+    if (sf & (f == E_TRACK ? E_NOTRACK : E_TRACK)) {
+        debug(("solve: opposite flag already set there, marking IMPOSSIBLE"));
+        state->impossible = TRUE;
+    }
+    S_E_SET(state, x, y, d, f);
+    return 1;
+}
+
+static int solve_update_flags(game_state *state)
+{
+    int x, y, i, w = state->p.w, h = state->p.h, did = 0;
+
+    for (x = 0; x < w; x++) {
+        for (y = 0; y < h; y++) {
+            /* If a square is NOTRACK, all four edges must be. */
+            if (state->sflags[y*w + x] & S_NOTRACK) {
+                for (i = 0; i < 4; i++) {
+                    unsigned int d = 1<<i;
+                    did += solve_set_eflag(state, x, y, d, E_NOTRACK, "edges around NOTRACK");
+                }
+            }
+
+            /* If 3 or more edges around a square are NOTRACK, the square is. */
+            if (S_E_COUNT(state, x, y, E_NOTRACK) >= 3) {
+                did += solve_set_sflag(state, x, y, S_NOTRACK, "square has >2 NOTRACK edges");
+            }
+
+            /* If any edge around a square is TRACK, the square is. */
+            if (S_E_COUNT(state, x, y, E_TRACK) > 0) {
+                did += solve_set_sflag(state, x, y, S_TRACK, "square has TRACK edge");
+            }
+
+            /* If a square is TRACK and 2 edges are NOTRACK,
+               the other two edges must be TRACK. */
+            if ((state->sflags[y*w + x] & S_TRACK) &&
+                    (S_E_COUNT(state, x, y, E_NOTRACK) == 2) &&
+                    (S_E_COUNT(state, x, y, E_TRACK) < 2)) {
+                for (i = 0; i < 4; i++) {
+                    unsigned int d = 1<<i;
+                    if (!(S_E_FLAGS(state, x, y, d) & (E_TRACK|E_NOTRACK))) {
+                        did += solve_set_eflag(state, x, y, d, E_TRACK,
+                                               "TRACK square/2 NOTRACK edges");
+                    }
+                }
+            }
+
+            /* If a square is TRACK and 2 edges are TRACK, the other two
+               must be NOTRACK. */
+            if ((state->sflags[y*w + x] & S_TRACK) &&
+                    (S_E_COUNT(state, x, y, E_TRACK) == 2) &&
+                    (S_E_COUNT(state, x, y, E_NOTRACK) < 2)) {
+                for (i = 0; i < 4; i++) {
+                    unsigned int d = 1<<i;
+                    if (!(S_E_FLAGS(state, x, y, d) & (E_TRACK|E_NOTRACK))) {
+                        did += solve_set_eflag(state, x, y, d, E_NOTRACK,
+                                               "TRACK square/2 TRACK edges");
+                    }
+                }
+            }
+        }
+    }
+    return did;
+}
+
+static int solve_count_col(game_state *state, int col, unsigned int f)
+{
+    int i, n, c = 0, h = state->p.h, w = state->p.w;
+    for (n = 0, i = col; n < h; n++, i += w) {
+        if (state->sflags[i] & f) c++;
+    }
+    return c;
+}
+
+static int solve_count_row(game_state *state, int row, unsigned int f)
+{
+    int i, n, c = 0, w = state->p.w;
+    for (n = 0, i = w*row; n < state->p.w; n++, i++) {
+        if (state->sflags[i] & f) c++;
+    }
+    return c;
+}
+
+static int solve_count_clues_sub(game_state *state, int si, int id, int n,
+                                 int target, const char *what)
+{
+    int ctrack = 0, cnotrack = 0, did = 0, j, i, w = state->p.w;
+
+    for (j = 0, i = si; j < n; j++, i += id) {
+        if (state->sflags[i] & S_TRACK)
+            ctrack++;
+        if (state->sflags[i] & S_NOTRACK)
+            cnotrack++;
+    }
+    if (ctrack == target) {
+        /* everything that's not S_TRACK must be S_NOTRACK. */
+        for (j = 0, i = si; j < n; j++, i += id) {
+            if (!(state->sflags[i] & S_TRACK))
+                did += solve_set_sflag(state, i%w, i/w, S_NOTRACK, what);
+        }
+    }
+    if (cnotrack == (n-target)) {
+        /* everything that's not S_NOTRACK must be S_TRACK. */
+        for (j = 0, i = si; j < n; j++, i += id) {
+            if (!(state->sflags[i] & S_NOTRACK))
+                did += solve_set_sflag(state, i%w, i/w, S_TRACK, what);
+        }
+    }
+    return did;
+}
+
+static int solve_count_clues(game_state *state)
+{
+    int w = state->p.w, h = state->p.h, x, y, target, did = 0;
+
+    for (x = 0; x < w; x++) {
+        target = state->numbers->numbers[x];
+        did += solve_count_clues_sub(state, x, w, h, target, "col count");
+    }
+    for (y = 0; y < h; y++) {
+        target = state->numbers->numbers[w+y];
+        did += solve_count_clues_sub(state, y*w, 1, w, target, "row count");
+    }
+    return did;
+}
+
+static int solve_check_single_sub(game_state *state, int si, int id, int n,
+                                  int target, unsigned int perpf,
+                                  const char *what)
+{
+    int ctrack = 0, nperp = 0, did = 0, j, i, w = state->p.w;
+    int n1edge = 0, i1edge = 0, ox, oy, x, y;
+    unsigned int impossible = 0;
+
+    /* For rows or columns which only have one more square to put a track in, we
+       know the only way a new track section could be there would be to run
+       perpendicular to the track (otherwise we'd need at least two free squares).
+       So, if there is nowhere we can run perpendicular to the track (e.g. because
+       we're on an edge) we know the extra track section much be on one end of an
+       existing section. */
+
+    for (j = 0, i = si; j < n; j++, i += id) {
+        if (state->sflags[i] & S_TRACK)
+            ctrack++;
+        impossible = S_E_DIRS(state, i%w, i/w, E_NOTRACK);
+        if ((perpf & impossible) == 0)
+            nperp++;
+        if (S_E_COUNT(state, i%w, i/w, E_TRACK) <= 1) {
+            n1edge++;
+            i1edge = i;
+        }
+    }
+    if (ctrack != (target-1)) return 0;
+    if (nperp > 0 || n1edge != 1) return 0;
+
+    debug(("check_single from (%d,%d): 1 match from (%d,%d)",
+           si%w, si/w, i1edge%w, i1edge/w));
+
+    /* We have a match: anything that's more than 1 away from this square
+       cannot now contain a track. */
+    ox = i1edge%w;
+    oy = i1edge/w;
+    for (j = 0, i = si; j < n; j++, i += id) {
+        x = i%w;
+        y = i/w;
+        if (abs(ox-x) > 1 || abs(oy-y) > 1) {
+            if (!(state->sflags[i] & S_TRACK))
+                did += solve_set_sflag(state, x, y, S_NOTRACK, what);
+        }
+    }
+
+    return did;
+}
+
+static int solve_check_single(game_state *state)
+{
+    int w = state->p.w, h = state->p.h, x, y, target, did = 0;
+
+    for (x = 0; x < w; x++) {
+        target = state->numbers->numbers[x];
+        did += solve_check_single_sub(state, x, w, h, target, R|L, "single on col");
+    }
+    for (y = 0; y < h; y++) {
+        target = state->numbers->numbers[w+y];
+        did += solve_check_single_sub(state, y*w, 1, w, target, U|D, "single on row");
+    }
+    return did;
+}
+
+static int solve_check_loose_sub(game_state *state, int si, int id, int n,
+                                 int target, unsigned int perpf,
+                                 const char *what)
+{
+    int nperp = 0, nloose = 0, e2count = 0, did = 0, i, j, k;
+    int w = state->p.w;
+    unsigned int parf = ALLDIR & (~perpf);
+
+    for (j = 0, i = si; j < n; j++, i += id) {
+        int fcount = S_E_COUNT(state, i%w, i/w, E_TRACK);
+        if (fcount == 2)
+            e2count++; /* this cell has 2 definite edges */
+        state->sflags[i] &= ~S_MARK;
+        if (fcount == 1 && (parf & S_E_DIRS(state, i%w, i/w, E_TRACK))) {
+            nloose++; /* this cell has a loose end (single flag set parallel
+                    to the direction of this row/column) */
+            state->sflags[i] |= S_MARK; /* mark loose ends */
+        }
+        if (fcount != 2 && !(perpf & S_E_DIRS(state, i%w, i/w, E_NOTRACK)))
+            nperp++; /* we could lay perpendicular across this cell */
+    }
+
+    if (nloose > (target - e2count)) {
+        debug(("check %s from (%d,%d): more loose (%d) than empty (%d), IMPOSSIBLE",
+               what, si%w, si/w, nloose, target-e2count));
+        state->impossible = TRUE;
+    }
+    if (nloose > 0 && nloose == (target - e2count)) {
+        debug(("check %s from (%d,%d): nloose = empty (%d), forcing loners out.",
+               what, si%w, si/w, nloose));
+        for (j = 0, i = si; j < n; j++, i += id) {
+            if (!(state->sflags[i] & S_MARK))
+                continue; /* skip non-loose ends */
+            if (j > 0 && state->sflags[i-id] & S_MARK)
+                continue; /* next to other loose end, could join up */
+            if (j < (n-1) && state->sflags[i+id] & S_MARK)
+                continue; /* ditto */
+
+            for (k = 0; k < 4; k++) {
+                if ((parf & (1<<k)) &&
+                        !(S_E_DIRS(state, i%w, i/w, E_TRACK) & (1<<k))) {
+                    /* set as NOTRACK the edge parallel to the row/column that's
+                       not already set. */
+                    did += solve_set_eflag(state, i%w, i/w, 1<<k, E_NOTRACK, what);
+                }
+            }
+        }
+    }
+    if (nloose == 1 && (target - e2count) == 2 && nperp == 0) {
+        debug(("check %s from (%d,%d): 1 loose end, 2 empty squares, forcing parallel",
+               what, si%w, si/w));
+        for (j = 0, i = si; j < n; j++, i += id) {
+            if (!(state->sflags[i] & S_MARK))
+                continue; /* skip non-loose ends */
+            for (k = 0; k < 4; k++) {
+                if (parf & (1<<k))
+                    did += solve_set_eflag(state, i%w, i/w, 1<<k, E_TRACK, what);
+            }
+        }
+    }
+
+    return did;
+}
+
+static int solve_check_loose_ends(game_state *state)
+{
+    int w = state->p.w, h = state->p.h, x, y, target, did = 0;
+
+    for (x = 0; x < w; x++) {
+        target = state->numbers->numbers[x];
+        did += solve_check_loose_sub(state, x, w, h, target, R|L, "loose on col");
+    }
+    for (y = 0; y < h; y++) {
+        target = state->numbers->numbers[w+y];
+        did += solve_check_loose_sub(state, y*w, 1, w, target, U|D, "loose on row");
+    }
+    return did;
+}
+
+static int solve_check_loop_sub(game_state *state, int x, int y, int dir,
+                                int *dsf, int startc, int endc)
+{
+    int w = state->p.w, h = state->p.h, i = y*w+x, j, k, satisfied = 1;
+
+    j = (y+DY(dir))*w + (x+DX(dir));
+
+    assert(i < w*h && j < w*h);
+
+    if ((state->sflags[i] & S_TRACK) &&
+        (state->sflags[j] & S_TRACK) &&
+        !(S_E_DIRS(state, x, y, E_TRACK) & dir) &&
+        !(S_E_DIRS(state, x, y, E_NOTRACK) & dir)) {
+        int ic = dsf_canonify(dsf, i), jc = dsf_canonify(dsf, j);
+        if (ic == jc) {
+            return solve_set_eflag(state, x, y, dir, E_NOTRACK, "would close loop");
+        }
+        if ((ic == startc && jc == endc) || (ic == endc && jc == startc)) {
+            debug(("Adding link at (%d,%d) would join start to end", x, y));
+            /* We mustn't join the start to the end if:
+               - there are other bits of track that aren't attached to either end
+               - the clues are not fully satisfied yet
+             */
+            for (k = 0; k < w*h; k++) {
+                if (state->sflags[k] & S_TRACK &&
+                        dsf_canonify(dsf, k) != startc && dsf_canonify(dsf, k) != endc) {
+                    return solve_set_eflag(state, x, y, dir, E_NOTRACK,
+                                           "joins start to end but misses tracks");
+                }
+            }
+            for (k = 0; k < w; k++) {
+                int target = state->numbers->numbers[k];
+                int ntracks = solve_count_col(state, k, S_TRACK);
+                if (ntracks < target) satisfied = 0;
+            }
+            for (k = 0; k < h; k++) {
+                int target = state->numbers->numbers[w+k];
+                int ntracks = solve_count_row(state, k, S_TRACK);
+                if (ntracks < target) satisfied = 0;
+            }
+            if (!satisfied) {
+                return solve_set_eflag(state, x, y, dir, E_NOTRACK,
+                                       "joins start to end with incomplete clues");
+            }
+        }
+    }
+    return 0;
+}
+
+static int solve_check_loop(game_state *state)
+{
+    int w = state->p.w, h = state->p.h, x, y, i, j, did = 0;
+    int *dsf, startc, endc;
+
+    /* TODO eventually we should pull this out into a solver struct and keep it
+       updated as we connect squares. For now we recreate it every time we try
+       this particular solver step. */
+    dsf = snewn(w*h, int);
+    dsf_init(dsf, w*h);
+
+    /* Work out the connectedness of the current loop set. */
+    for (x = 0; x < w; x++) {
+        for (y = 0; y < h; y++) {
+            i = y*w + x;
+            if (x < (w-1) && S_E_DIRS(state, x, y, E_TRACK) & R) {
+                /* connection to the right... */
+                j = y*w + (x+1);
+                assert(i < w*h && j < w*h);
+                dsf_merge(dsf, i, j);
+            }
+            if (y < (h-1) && S_E_DIRS(state, x, y, E_TRACK) & D) {
+                /* connection down... */
+                j = (y+1)*w + x;
+                assert(i < w*h && j < w*h);
+                dsf_merge(dsf, i, j);
+            }
+            /* NB no need to check up and left because they'll have been checked
+               by the other side. */
+        }
+    }
+
+    startc = dsf_canonify(dsf, state->numbers->row_s*w);
+    endc = dsf_canonify(dsf, (h-1)*w+state->numbers->col_s);
+
+    /* Now look at all adjacent squares that are both S_TRACK: if connecting
+       any of them would complete a loop (i.e. they're both the same dsf class
+       already) then that edge must be NOTRACK. */
+    for (x = 0; x < w; x++) {
+        for (y = 0; y < h; y++) {
+            if (x < (w-1))
+              did += solve_check_loop_sub(state, x, y, R, dsf, startc, endc);
+            if (y < (h-1))
+              did += solve_check_loop_sub(state, x, y, D, dsf, startc, endc);
+        }
+    }
+
+    sfree(dsf);
+
+    return did;
+}
+
+static void solve_discount_edge(game_state *state, int x, int y, int d)
+{
+    if (S_E_DIRS(state, x, y, E_TRACK) & d) {
+        assert(state->sflags[y*state->p.w + x] & S_CLUE);
+        return; /* (only) clue squares can have outer edges set. */
+    }
+    solve_set_eflag(state, x, y, d, E_NOTRACK, "outer edge");
+}
+
+static int tracks_solve(game_state *state, int diff)
+{
+    int didsth, x, y, w = state->p.w, h = state->p.h;
+
+    debug(("solve..."));
+    state->impossible = FALSE;
+
+    /* Set all the outer border edges as no-track. */
+    for (x = 0; x < w; x++) {
+        solve_discount_edge(state, x, 0, U);
+        solve_discount_edge(state, x, h-1, D);
+    }
+    for (y = 0; y < h; y++) {
+        solve_discount_edge(state, 0, y, L);
+        solve_discount_edge(state, w-1, y, R);
+    }
+
+    while (1) {
+        didsth = 0;
+
+        didsth += solve_update_flags(state);
+        didsth += solve_count_clues(state);
+        didsth += solve_check_loop(state);
+
+        if (diff >= DIFF_TRICKY) {
+            didsth += solve_check_single(state);
+            didsth += solve_check_loose_ends(state);
+        }
+
+        if (!didsth || state->impossible) break;
+    }
+
+    return state->impossible ? -1 : check_completion(state, FALSE) ? 1 : 0;
+}
+
+static char *move_string_diff(const game_state *before, const game_state *after, int issolve)
+{
+    int w = after->p.w, h = after->p.h, i, j;
+    char *move = snewn(w*h*40, char), *p = move;
+    const char *sep = "";
+    unsigned int otf, ntf, onf, nnf;
+
+    if (issolve) {
+        *p++ = 'S';
+        sep = ";";
+    }
+    for (i = 0; i < w*h; i++) {
+        otf = S_E_DIRS(before, i%w, i/w, E_TRACK);
+        ntf = S_E_DIRS(after, i%w, i/w, E_TRACK);
+        onf = S_E_DIRS(before, i%w, i/w, E_NOTRACK);
+        nnf = S_E_DIRS(after, i%w, i/w, E_NOTRACK);
+
+        for (j = 0; j < 4; j++) {
+            unsigned df = 1<<j;
+            if ((otf & df) != (ntf & df)) {
+                p += sprintf(p, "%s%c%c%d,%d", sep,
+                             (ntf & df) ? 'T' : 't', MOVECHAR(df), i%w, i/w);
+                sep = ";";
+            }
+            if ((onf & df) != (nnf & df)) {
+                p += sprintf(p, "%s%c%c%d,%d", sep,
+                             (nnf & df) ? 'N' : 'n', MOVECHAR(df), i%w, i/w);
+                sep = ";";
+            }
+        }
+
+        if ((before->sflags[i] & S_NOTRACK) != (after->sflags[i] & S_NOTRACK)) {
+            p += sprintf(p, "%s%cS%d,%d", sep,
+                         (after->sflags[i] & S_NOTRACK) ? 'N' : 'n', i%w, i/w);
+            sep = ";";
+        }
+        if ((before->sflags[i] & S_TRACK) != (after->sflags[i] & S_TRACK)) {
+            p += sprintf(p, "%s%cS%d,%d", sep,
+                         (after->sflags[i] & S_TRACK) ? 'T' : 't', i%w, i/w);
+            sep = ";";
+        }
+    }
+    *p++ = '\0';
+    move = sresize(move, p - move, char);
+
+    return move;
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    game_state *solved;
+    int ret;
+    char *move;
+
+    solved = dup_game(currstate);
+    ret = tracks_solve(solved, DIFFCOUNT);
+    if (ret < 1) {
+        free_game(solved);
+        solved = dup_game(state);
+        ret = tracks_solve(solved, DIFFCOUNT);
+    }
+
+    if (ret < 1) {
+        *error = "Unable to find solution";
+        move = NULL;
+    } else {
+        move = move_string_diff(currstate, solved, TRUE);
+    }
+
+    free_game(solved);
+    return move;
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    char *ret, *p;
+    int x, y, len, w = state->p.w, h = state->p.h;
+
+    len = ((w*2) + 4) * ((h*2)+4) + 2;
+    ret = snewn(len+1, char);
+    p = ret;
+
+    /* top line: column clues */
+    *p++ = ' ';
+    *p++ = ' ';
+    for (x = 0; x < w; x++) {
+        *p++ = (state->numbers->numbers[x] < 10 ?
+                '0' + state->numbers->numbers[x] :
+                'A' + state->numbers->numbers[x] - 10);
+        *p++ = ' ';
+    }
+    *p++ = '\n';
+
+    /* second line: top edge */
+    *p++ = ' ';
+    *p++ = '+';
+    for (x = 0; x < w*2-1; x++)
+        *p++ = '-';
+    *p++ = '+';
+    *p++ = '\n';
+
+    /* grid rows: one line of squares, one line of edges. */
+    for (y = 0; y < h; y++) {
+        /* grid square line */
+        *p++ = (y == state->numbers->row_s) ? 'A' : ' ';
+        *p++ = (y == state->numbers->row_s) ? '-' : '|';
+
+        for (x = 0; x < w; x++) {
+            unsigned int f = S_E_DIRS(state, x, y, E_TRACK);
+            if (state->sflags[y*w+x] & S_CLUE) *p++ = 'C';
+            else if (f == LU || f == RD) *p++ = '/';
+            else if (f == LD || f == RU) *p++ = '\\';
+            else if (f == UD) *p++ = '|';
+            else if (f == RL) *p++ = '-';
+            else if (state->sflags[y*w+x] & S_NOTRACK) *p++ = 'x';
+            else *p++ = ' ';
+
+            if (x < w-1) {
+                *p++ = (f & R) ? '-' : ' ';
+            } else
+                *p++ = '|';
+        }
+        *p++ = (state->numbers->numbers[w+y] < 10 ?
+                '0' + state->numbers->numbers[w+y] :
+                'A' + state->numbers->numbers[w+y] - 10);
+        *p++ = '\n';
+
+        if (y == h-1) continue;
+
+        /* edges line */
+        *p++ = ' ';
+        *p++ = '|';
+        for (x = 0; x < w; x++) {
+            unsigned int f = S_E_DIRS(state, x, y, E_TRACK);
+            *p++ = (f & D) ? '|' : ' ';
+            *p++ = (x < w-1) ? ' ' : '|';
+        }
+        *p++ = '\n';
+    }
+
+    /* next line: bottom edge */
+    *p++ = ' ';
+    *p++ = '+';
+    for (x = 0; x < w*2-1; x++)
+        *p++ = (x == state->numbers->col_s*2) ? '|' : '-';
+    *p++ = '+';
+    *p++ = '\n';
+
+    /* final line: bottom clue */
+    *p++ = ' ';
+    *p++ = ' ';
+    for (x = 0; x < w*2-1; x++)
+        *p++ = (x == state->numbers->col_s*2) ? 'B' : ' ';
+    *p++ = '\n';
+
+    *p = '\0';
+    return ret;
+}
+
+static void debug_state(game_state *state, const char *what) {
+    char *sstring = game_text_format(state);
+    debug(("%s: %s", what, sstring));
+    sfree(sstring);
+}
+
+static void dsf_update_completion(game_state *state, int ax, int ay,
+                                  char dir, int *dsf)
+{
+    int w = state->p.w, ai = ay*w+ax, bx, by, bi;
+
+    if (!(S_E_DIRS(state, ax, ay, E_TRACK) & dir)) return;
+    bx = ax + DX(dir);
+    by = ay + DY(dir);
+
+    if (!INGRID(state, bx, by)) return;
+    bi = by*w+bx;
+
+    dsf_merge(dsf, ai, bi);
+}
+
+struct tracks_neighbour_ctx {
+    game_state *state;
+    int i, n, neighbours[4];
+};
+static int tracks_neighbour(int vertex, void *vctx)
+{
+    struct tracks_neighbour_ctx *ctx = (struct tracks_neighbour_ctx *)vctx;
+    if (vertex >= 0) {
+        game_state *state = ctx->state;
+        int w = state->p.w, x = vertex % w, y = vertex / w;
+        int dirs = S_E_DIRS(state, x, y, E_TRACK);
+        int j;
+
+        ctx->i = ctx->n = 0;
+
+        for (j = 0; j < 4; j++) {
+            int dir = 1<<j;
+            if (dirs & dir) {
+                int nx = x + DX(dir), ny = y + DY(dir);
+                if (INGRID(state, nx, ny))
+                    ctx->neighbours[ctx->n++] = ny * w + nx;
+            }
+        }
+    }
+
+    if (ctx->i < ctx->n)
+        return ctx->neighbours[ctx->i++];
+    else
+        return -1;
+}
+
+static int check_completion(game_state *state, int mark)
+{
+    int w = state->p.w, h = state->p.h, x, y, i, target, ret = TRUE;
+    int ntrack, nnotrack, ntrackcomplete;
+    int *dsf, pathclass;
+    struct findloopstate *fls;
+    struct tracks_neighbour_ctx ctx;
+
+    if (mark) {
+        for (i = 0; i < w+h; i++) {
+            state->num_errors[i] = 0;
+        }
+        for (i = 0; i < w*h; i++) {
+            state->sflags[i] &= ~S_ERROR;
+            if (S_E_COUNT(state, i%w, i/w, E_TRACK) > 0) {
+                if (S_E_COUNT(state, i%w, i/w, E_TRACK) > 2) {
+                    ret = FALSE;
+                    state->sflags[i] |= S_ERROR;
+                }
+            }
+        }
+    }
+
+    /* A cell is 'complete', for the purposes of marking the game as
+     * finished, if it has two edges marked as TRACK. But it only has
+     * to have one edge marked as TRACK, or be filled in as trackful
+     * without any specific edges known, to count towards checking
+     * row/column clue errors. */
+    for (x = 0; x < w; x++) {
+        target = state->numbers->numbers[x];
+        ntrack = nnotrack = ntrackcomplete = 0;
+        for (y = 0; y < h; y++) {
+            if (S_E_COUNT(state, x, y, E_TRACK) > 0 ||
+                state->sflags[y*w+x] & S_TRACK)
+                ntrack++;
+            if (S_E_COUNT(state, x, y, E_TRACK) == 2)
+                ntrackcomplete++;
+            if (state->sflags[y*w+x] & S_NOTRACK)
+                nnotrack++;
+        }
+        if (mark) {
+            if (ntrack > target || nnotrack > (h-target)) {
+                debug(("col %d error: target %d, track %d, notrack %d",
+                       x, target, ntrack, nnotrack));
+                state->num_errors[x] = 1;
+                ret = FALSE;
+            }
+        }
+        if (ntrackcomplete != target)
+            ret = FALSE;
+    }
+    for (y = 0; y < h; y++) {
+        target = state->numbers->numbers[w+y];
+        ntrack = nnotrack = ntrackcomplete = 0;
+        for (x = 0; x < w; x++) {
+            if (S_E_COUNT(state, x, y, E_TRACK) > 0 ||
+                state->sflags[y*w+x] & S_TRACK)
+                ntrack++;
+            if (S_E_COUNT(state, x, y, E_TRACK) == 2)
+                ntrackcomplete++;
+            if (state->sflags[y*w+x] & S_NOTRACK)
+                nnotrack++;
+        }
+        if (mark) {
+            if (ntrack > target || nnotrack > (w-target)) {
+                debug(("row %d error: target %d, track %d, notrack %d",
+                       y, target, ntrack, nnotrack));
+                state->num_errors[w+y] = 1;
+                ret = FALSE;
+            }
+        }
+        if (ntrackcomplete != target)
+            ret = FALSE;
+    }
+
+    dsf = snewn(w*h, int);
+    dsf_init(dsf, w*h);
+
+    for (x = 0; x < w; x++) {
+        for (y = 0; y < h; y++) {
+            dsf_update_completion(state, x, y, R, dsf);
+            dsf_update_completion(state, x, y, D, dsf);
+        }
+    }
+
+    fls = findloop_new_state(w*h);
+    ctx.state = state;
+    if (findloop_run(fls, w*h, tracks_neighbour, &ctx)) {
+        debug(("loop detected, not complete"));
+        ret = FALSE; /* no loop allowed */
+        if (mark) {
+            for (x = 0; x < w; x++) {
+                for (y = 0; y < h; y++) {
+                    int u, v;
+
+                    u = y*w + x;
+                    for (v = tracks_neighbour(u, &ctx); v >= 0;
+                         v = tracks_neighbour(-1, &ctx))
+                        if (findloop_is_loop_edge(fls, u, v))
+                            state->sflags[y*w+x] |= S_ERROR;
+                }
+            }
+        }
+    }
+    findloop_free_state(fls);
+
+    if (mark) {
+        pathclass = dsf_canonify(dsf, state->numbers->row_s*w);
+        if (pathclass == dsf_canonify(dsf, (h-1)*w + state->numbers->col_s)) {
+            /* We have a continuous path between the entrance and the exit: any
+               other path must be in error. */
+            for (i = 0; i < w*h; i++) {
+                if ((dsf_canonify(dsf, i) != pathclass) &&
+                    ((state->sflags[i] & S_TRACK) ||
+                     (S_E_COUNT(state, i%w, i/w, E_TRACK) > 0))) {
+                    ret = FALSE;
+                    state->sflags[i] |= S_ERROR;
+                }
+            }
+        } else {
+            /* If we _don't_ have such a path, then certainly the game
+             * can't be in a winning state. So even if we're not
+             * highlighting any _errors_, we certainly shouldn't
+             * return true. */
+            ret = FALSE;
+        }
+    }
+
+    if (mark)
+        state->completed = ret;
+    sfree(dsf);
+    return ret;
+}
+
+/* Code borrowed from Pearl. */
+
+struct game_ui {
+    int dragging, clearing, notrack;
+    int drag_sx, drag_sy, drag_ex, drag_ey; /* drag start and end grid coords */
+    int clickx, clicky;    /* pixel position of initial click */
+
+    int curx, cury;        /* grid position of keyboard cursor; uses half-size grid */
+    int cursor_active;     /* TRUE iff cursor is shown */
+};
+
+static game_ui *new_ui(const game_state *state)
+{
+    game_ui *ui = snew(game_ui);
+
+    ui->clearing = ui->notrack = ui->dragging = 0;
+    ui->drag_sx = ui->drag_sy = ui->drag_ex = ui->drag_ey = -1;
+    ui->cursor_active = FALSE;
+    ui->curx = ui->cury = 1;
+
+    return ui;
+}
+
+static void free_ui(game_ui *ui)
+{
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+}
+
+#define PREFERRED_TILE_SIZE 30
+#define HALFSZ (ds->sz6*3)
+#define THIRDSZ (ds->sz6*2)
+#define TILE_SIZE (ds->sz6*6)
+
+#define BORDER (TILE_SIZE/8)
+#define BORDER_WIDTH (max(TILE_SIZE / 32, 1))
+
+#define COORD(x) ( (x+1) * TILE_SIZE + BORDER )
+#define CENTERED_COORD(x) ( COORD(x) + TILE_SIZE/2 )
+#define FROMCOORD(x) ( ((x) < BORDER) ? -1 : ( ((x) - BORDER) / TILE_SIZE) - 1 )
+
+#define DS_DSHIFT 4     /* R/U/L/D shift, for drag-in-progress flags */
+
+#define DS_ERROR (1 << 8)
+#define DS_CLUE (1 << 9)
+#define DS_NOTRACK (1 << 10)
+#define DS_FLASH (1 << 11)
+#define DS_CURSOR (1 << 12) /* cursor in square (centre, or on edge) */
+#define DS_TRACK (1 << 13)
+#define DS_CLEARING (1 << 14)
+
+#define DS_NSHIFT 16    /* R/U/L/D shift, for no-track edge flags */
+#define DS_CSHIFT 20    /* R/U/L/D shift, for cursor-on-edge */
+
+struct game_drawstate {
+    int sz6;
+    int started;
+
+    int w, h, sz;
+    unsigned int *flags, *flags_drag;
+    int *num_errors;
+};
+
+static void update_ui_drag(const game_state *state, game_ui *ui, int gx, int gy)
+{
+    int w = state->p.w, h = state->p.h;
+    int dx = abs(ui->drag_sx - gx), dy = abs(ui->drag_sy - gy);
+
+    if (dy == 0) {
+        ui->drag_ex = gx < 0 ? 0 : gx >= w ? w-1 : gx;
+        ui->drag_ey = ui->drag_sy;
+        ui->dragging = TRUE;
+    } else if (dx == 0) {
+        ui->drag_ex = ui->drag_sx;
+        ui->drag_ey = gy < 0 ? 0 : gy >= h ? h-1 : gy;
+        ui->dragging = TRUE;
+    } else {
+        ui->drag_ex = ui->drag_sx;
+        ui->drag_ey = ui->drag_sy;
+        ui->dragging = FALSE;
+    }
+}
+
+static int ui_can_flip_edge(const game_state *state, int x, int y, int dir,
+                            int notrack)
+{
+    int w = state->p.w /*, h = state->shared->h, sz = state->shared->sz */;
+    int x2 = x + DX(dir);
+    int y2 = y + DY(dir);
+    unsigned int sf1, sf2, ef;
+
+    if (!INGRID(state, x, y) || !INGRID(state, x2, y2))
+        return FALSE;
+
+    sf1 = state->sflags[y*w + x];
+    sf2 = state->sflags[y2*w + x2];
+    if ( !notrack && ((sf1 & S_CLUE) || (sf2 & S_CLUE)) )
+        return FALSE;
+
+    ef = S_E_FLAGS(state, x, y, dir);
+    if (notrack) {
+      /* if we're going to _set_ NOTRACK (i.e. the flag is currently unset),
+         make sure the edge is not already set to TRACK. The adjacent squares
+         could be set to TRACK, because we don't know which edges the general
+         square setting refers to. */
+      if (!(ef & E_NOTRACK) && (ef & E_TRACK))
+          return FALSE;
+    } else {
+      if (!(ef & E_TRACK)) {
+          /* if we're going to _set_ TRACK, make sure neither adjacent square nor
+             the edge itself is already set to NOTRACK. */
+          if ((sf1 & S_NOTRACK) || (sf2 & S_NOTRACK) || (ef & E_NOTRACK))
+              return FALSE;
+          /* if we're going to _set_ TRACK, make sure neither adjacent square has
+             2 track flags already.  */
+          if ((S_E_COUNT(state, x, y, E_TRACK) >= 2) ||
+              (S_E_COUNT(state, x2, y2, E_TRACK) >= 2))
+              return FALSE;
+          }
+    }
+    return TRUE;
+}
+
+static int ui_can_flip_square(const game_state *state, int x, int y, int notrack)
+{
+    int w = state->p.w, trackc;
+    unsigned sf;
+
+    if (!INGRID(state, x, y)) return FALSE;
+    sf = state->sflags[y*w+x];
+    trackc = S_E_COUNT(state, x, y, E_TRACK);
+
+    if (sf & S_CLUE) return FALSE;
+
+    if (notrack) {
+        /* If we're setting S_NOTRACK, we cannot have either S_TRACK or any E_TRACK. */
+        if (!(sf & S_NOTRACK) && ((sf & S_TRACK) || (trackc > 0)))
+            return FALSE;
+    } else {
+        /* If we're setting S_TRACK, we cannot have any S_NOTRACK (we could have
+          E_NOTRACK, though, because one or two wouldn't rule out a track) */
+        if (!(sf & S_TRACK) && (sf & S_NOTRACK))
+            return FALSE;
+    }
+    return TRUE;
+}
+
+static char *edge_flip_str(const game_state *state, int x, int y, int dir, int notrack, char *buf) {
+    unsigned ef = S_E_FLAGS(state, x, y, dir);
+    char c;
+
+    if (notrack)
+        c = (ef & E_NOTRACK) ? 'n' : 'N';
+    else
+        c = (ef & E_TRACK) ? 't' : 'T';
+
+    sprintf(buf, "%c%c%d,%d", c, MOVECHAR(dir), x, y);
+    return dupstr(buf);
+}
+
+static char *square_flip_str(const game_state *state, int x, int y, int notrack, char *buf) {
+    unsigned f = state->sflags[y*state->p.w+x];
+    char c;
+
+    if (notrack)
+        c = (f & E_NOTRACK) ? 'n' : 'N';
+    else
+        c = (f & E_TRACK) ? 't' : 'T';
+
+    sprintf(buf, "%cS%d,%d", c, x, y);
+    return dupstr(buf);
+}
+
+#define SIGN(x) ((x<0) ? -1 : (x>0))
+
+static game_state *copy_and_apply_drag(const game_state *state, const game_ui *ui)
+{
+    game_state *after = dup_game(state);
+    int x1, y1, x2, y2, x, y, w = state->p.w;
+    unsigned f = ui->notrack ? S_NOTRACK : S_TRACK, ff;
+
+    x1 = min(ui->drag_sx, ui->drag_ex); x2 = max(ui->drag_sx, ui->drag_ex);
+    y1 = min(ui->drag_sy, ui->drag_ey); y2 = max(ui->drag_sy, ui->drag_ey);
+
+    /* actually either x1 == x2, or y1 == y2, but it's easier just to code
+       the nested loop. */
+    for (x = x1; x <= x2; x++) {
+        for (y = y1; y <= y2; y++) {
+            ff = state->sflags[y*w+x];
+            if (ui->clearing && !(ff & f))
+                continue; /* nothing to do, clearing and already clear */
+            else if (!ui->clearing && (ff & f))
+                continue; /* nothing to do, setting and already set */
+            else if (ui_can_flip_square(state, x, y, ui->notrack))
+                after->sflags[y*w+x] ^= f;
+        }
+    }
+    return after;
+}
+
+#define KEY_DIRECTION(btn) (\
+    (btn) == CURSOR_DOWN ? D : (btn) == CURSOR_UP ? U :\
+    (btn) == CURSOR_LEFT ? L : R)
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    int w = state->p.w, h = state->p.h, direction;
+    int gx = FROMCOORD(x), gy = FROMCOORD(y);
+    char tmpbuf[80];
+
+    /* --- mouse operations --- */
+
+    if (IS_MOUSE_DOWN(button)) {
+        ui->cursor_active = FALSE;
+        ui->dragging = FALSE;
+
+        if (!INGRID(state, gx, gy)) {
+            /* can't drag from off grid */
+            return NULL;
+        }
+
+        if (button == RIGHT_BUTTON) {
+            ui->notrack = TRUE;
+            ui->clearing = state->sflags[gy*w+gx] & S_NOTRACK;
+        } else {
+            ui->notrack = FALSE;
+            ui->clearing = state->sflags[gy*w+gx] & S_TRACK;
+        }
+
+        ui->clickx = x;
+        ui->clicky = y;
+        ui->drag_sx = ui->drag_ex = gx;
+        ui->drag_sy = ui->drag_ey = gy;
+
+        return "";
+    }
+
+    if (IS_MOUSE_DRAG(button)) {
+        ui->cursor_active = FALSE;
+        update_ui_drag(state, ui, gx, gy);
+        return "";
+    }
+
+    if (IS_MOUSE_RELEASE(button)) {
+        ui->cursor_active = FALSE;
+        if (ui->dragging &&
+            (ui->drag_sx != ui->drag_ex || ui->drag_sy != ui->drag_ey)) {
+            game_state *dragged = copy_and_apply_drag(state, ui);
+            char *ret = move_string_diff(state, dragged, FALSE);
+
+            ui->dragging = 0;
+            free_game(dragged);
+
+            return ret;
+        } else {
+            int cx, cy;
+
+            /* We might still have been dragging (and just done a one-
+             * square drag): cancel drag, so undo doesn't make it like
+             * a drag-in-progress. */
+            ui->dragging = 0;
+
+            /* Click (or tiny drag). Work out which edge we were
+             * closest to. */
+
+            /*
+             * We process clicks based on the mouse-down location,
+             * because that's more natural for a user to carefully
+             * control than the mouse-up.
+             */
+            x = ui->clickx;
+            y = ui->clicky;
+
+            cx = CENTERED_COORD(gx);
+            cy = CENTERED_COORD(gy);
+
+            if (!INGRID(state, gx, gy) || FROMCOORD(x) != gx || FROMCOORD(y) != gy)
+                return "";
+
+            if (max(abs(x-cx),abs(y-cy)) < TILE_SIZE/4) {
+                if (ui_can_flip_square(state, gx, gy, button == RIGHT_RELEASE))
+                    return square_flip_str(state, gx, gy, button == RIGHT_RELEASE, tmpbuf);
+                return "";
+            } else {
+                if (abs(x-cx) < abs(y-cy)) {
+                    /* Closest to top/bottom edge. */
+                    direction = (y < cy) ? U : D;
+                } else {
+                    /* Closest to left/right edge. */
+                    direction = (x < cx) ? L : R;
+                }
+                if (ui_can_flip_edge(state, gx, gy, direction,
+                        button == RIGHT_RELEASE))
+                    return edge_flip_str(state, gx, gy, direction,
+                            button == RIGHT_RELEASE, tmpbuf);
+                else
+                    return "";
+            }
+        }
+    }
+
+    /* --- cursor/keyboard operations --- */
+
+    if (IS_CURSOR_MOVE(button)) {
+        int dx = (button == CURSOR_LEFT) ? -1 : ((button == CURSOR_RIGHT) ? +1 : 0);
+        int dy = (button == CURSOR_DOWN) ? +1 : ((button == CURSOR_UP)    ? -1 : 0);
+
+        if (!ui->cursor_active) {
+            ui->cursor_active = TRUE;
+            return "";
+        }
+
+        ui->curx = ui->curx + dx;
+        ui->cury = ui->cury + dy;
+        if ((ui->curx % 2 == 0) && (ui->cury % 2 == 0)) {
+            /* disallow cursor on square corners: centres and edges only */
+            ui->curx += dx; ui->cury += dy;
+        }
+        ui->curx = min(max(ui->curx, 1), 2*w-1);
+        ui->cury = min(max(ui->cury, 1), 2*h-1);
+        return "";
+    }
+
+    if (IS_CURSOR_SELECT(button)) {
+        if (!ui->cursor_active) {
+            ui->cursor_active = TRUE;
+            return "";
+        }
+        /* click on square corner does nothing (shouldn't get here) */
+        if ((ui->curx % 2) == 0 && (ui->cury % 2 == 0))
+            return "";
+
+        gx = ui->curx / 2;
+        gy = ui->cury / 2;
+        direction = ((ui->curx % 2) == 0) ? L : ((ui->cury % 2) == 0) ? U : 0;
+
+        if (direction &&
+            ui_can_flip_edge(state, gx, gy, direction, button == CURSOR_SELECT2))
+            return edge_flip_str(state, gx, gy, direction, button == CURSOR_SELECT2, tmpbuf);
+        else if (!direction &&
+                 ui_can_flip_square(state, gx, gy, button == CURSOR_SELECT2))
+            return square_flip_str(state, gx, gy, button == CURSOR_SELECT2, tmpbuf);
+        return "";
+    }
+
+#if 0
+    /* helps to debug the solver */
+    if (button == 'H' || button == 'h')
+        return dupstr("H");
+#endif
+
+    return NULL;
+}
+
+static game_state *execute_move(const game_state *state, const char *move)
+{
+    int w = state->p.w, x, y, n, i;
+    char c, d;
+    unsigned f;
+    game_state *ret = dup_game(state);
+
+    /* this is breaking the bank on GTK, which vsprintf's into a fixed-size buffer
+     * which is 4096 bytes long. vsnprintf needs a feature-test macro to use, faff. */
+    /*debug(("move: %s\n", move));*/
+
+    while (*move) {
+        c = *move;
+        if (c == 'S') {
+            ret->used_solve = TRUE;
+            move++;
+        } else if (c == 'T' || c == 't' || c == 'N' || c == 'n') {
+            /* set track, clear track; set notrack, clear notrack */
+            move++;
+            if (sscanf(move, "%c%d,%d%n", &d, &x, &y, &n) != 3)
+                goto badmove;
+            if (!INGRID(state, x, y)) goto badmove;
+
+            f = (c == 'T' || c == 't') ? S_TRACK : S_NOTRACK;
+
+            if (d == 'S') {
+                if (c == 'T' || c == 'N')
+                    ret->sflags[y*w+x] |= f;
+                else
+                    ret->sflags[y*w+x] &= ~f;
+            } else if (d == 'U' || d == 'D' || d == 'L' || d == 'R') {
+                for (i = 0; i < 4; i++) {
+                    unsigned df = 1<<i;
+
+                    if (MOVECHAR(df) == d) {
+                        if (c == 'T' || c == 'N')
+                            S_E_SET(ret, x, y, df, f);
+                        else
+                            S_E_CLEAR(ret, x, y, df, f);
+                    }
+                }
+            } else
+                goto badmove;
+            move += n;
+        } else if (c == 'H') {
+            tracks_solve(ret, DIFFCOUNT);
+            move++;
+        } else {
+            goto badmove;
+        }
+        if (*move == ';')
+            move++;
+        else if (*move)
+            goto badmove;
+    }
+
+    check_completion(ret, TRUE);
+
+    return ret;
+
+    badmove:
+    free_game(ret);
+    return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+#define FLASH_TIME 0.5F
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    struct {
+        int sz6;
+    } ads, *ds = &ads;
+    ads.sz6 = tilesize/6;
+
+    *x = (params->w+2) * TILE_SIZE + 2 * BORDER;
+    *y = (params->h+2) * TILE_SIZE + 2 * BORDER;
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->sz6 = tilesize/6;
+}
+
+enum {
+    COL_BACKGROUND, COL_LOWLIGHT, COL_HIGHLIGHT,
+    COL_TRACK_BACKGROUND = COL_LOWLIGHT,
+    COL_GRID, COL_CLUE, COL_CURSOR,
+    COL_TRACK, COL_TRACK_CLUE, COL_SLEEPER,
+    COL_DRAGON, COL_DRAGOFF,
+    COL_ERROR, COL_FLASH,
+    NCOLOURS
+};
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+    int i;
+
+    game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT);
+
+    for (i = 0; i < 3; i++) {
+        ret[COL_TRACK_CLUE * 3 + i] = 0.0F;
+        ret[COL_TRACK * 3 + i] = 0.5F;
+        ret[COL_CLUE * 3 + i] = 0.0F;
+        ret[COL_GRID * 3 + i] = 0.75F;
+        ret[COL_CURSOR * 3 + i] = 0.6F;
+    }
+
+    ret[COL_SLEEPER * 3 + 0] = 0.5F;
+    ret[COL_SLEEPER * 3 + 1] = 0.4F;
+    ret[COL_SLEEPER * 3 + 2] = 0.1F;
+
+    ret[COL_ERROR * 3 + 0] = 1.0F;
+    ret[COL_ERROR * 3 + 1] = 0.0F;
+    ret[COL_ERROR * 3 + 2] = 0.0F;
+
+    ret[COL_DRAGON * 3 + 0] = 0.0F;
+    ret[COL_DRAGON * 3 + 1] = 0.0F;
+    ret[COL_DRAGON * 3 + 2] = 1.0F;
+
+    ret[COL_DRAGOFF * 3 + 0] = 0.8F;
+    ret[COL_DRAGOFF * 3 + 1] = 0.8F;
+    ret[COL_DRAGOFF * 3 + 2] = 1.0F;
+
+    ret[COL_FLASH * 3 + 0] = 1.0F;
+    ret[COL_FLASH * 3 + 1] = 1.0F;
+    ret[COL_FLASH * 3 + 2] = 1.0F;
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    struct game_drawstate *ds = snew(struct game_drawstate);
+    int i;
+
+    ds->sz6 = 0;
+    ds->started = FALSE;
+
+    ds->w = state->p.w;
+    ds->h = state->p.h;
+    ds->sz = ds->w*ds->h;
+    ds->flags = snewn(ds->sz, unsigned int);
+    ds->flags_drag = snewn(ds->sz, unsigned int);
+    for (i = 0; i < ds->sz; i++)
+        ds->flags[i] = ds->flags_drag[i] = 0;
+
+    ds->num_errors = snewn(ds->w+ds->h, int);
+    for (i = 0; i < ds->w+ds->h; i++)
+        ds->num_errors[i] = 0;
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds->flags);
+    sfree(ds->flags_drag);
+    sfree(ds->num_errors);
+    sfree(ds);
+}
+
+static void draw_circle_sleepers(drawing *dr, game_drawstate *ds,
+                                 float cx, float cy, float r2, float thickness, int c)
+{
+    float qr6 = (float)PI/12, qr3 = (float)PI/6, th, x1, y1, x2, y2;
+    float t6 = THIRDSZ/2.0F, r1 = t6;
+    int i;
+
+    for (i = 0; i < 12; i++) {
+        th = qr6 + (i*qr3);
+        x1 = r1*(float)cos(th);
+        x2 = r2*(float)cos(th);
+        y1 = r1*(float)sin(th);
+        y2 = r2*(float)sin(th);
+        draw_thick_line(dr, thickness, cx+x1, cy+y1, cx+x2, cy+y2, c);
+    }
+}
+
+static void draw_thick_circle_outline(drawing *dr, float thickness,
+                                      float cx, float cy, float r,
+                                      int colour)
+{
+    float circ4 = 0.5F * (float)PI * r, ang, x1, y1, x2, y2;
+    int i, nseg;
+
+    nseg = (int)(circ4 / 4.0F)*4; /* ensure a quarter-circle has a whole #segs */
+    ang = 2.0F*(float)PI / nseg;
+
+    for (i = 0; i < nseg; i++) {
+        float th = ang * i, th2 = ang * (i+1);
+        x1 = cx + r*(float)cos(th);
+        x2 = cx + r*(float)cos(th2);
+        y1 = cy + r*(float)sin(th);
+        y2 = cy + r*(float)sin(th2);
+        debug(("circ outline: x=%.2f -> %.2f, thick=%.2f", x1, x2, thickness));
+        draw_thick_line(dr, thickness, x1, y1, x2, y2, colour);
+    }
+}
+
+static void draw_tracks_specific(drawing *dr, game_drawstate *ds,
+                                 int x, int y, unsigned int flags,
+                                 int ctrack, int csleeper)
+{
+    float ox = (float)COORD(x), oy = (float)COORD(y), cx, cy;
+    float t1 = (float)TILE_SIZE, t3 = TILE_SIZE/3.0F, t6 = TILE_SIZE/6.0F;
+    int d, i;
+    float thick_track = TILE_SIZE/8.0F, thick_sleeper = TILE_SIZE/12.0F;
+
+    if (flags == LR) {
+        for (i = 1; i <= 7; i+=2) {
+            cx = ox + TILE_SIZE/8.0F*i;
+            draw_thick_line(dr, thick_sleeper,
+                            cx, oy+t6, cx, oy+t6+2*t3, csleeper);
+        }
+        draw_thick_line(dr, thick_track, ox, oy + t3, ox + TILE_SIZE, oy + t3, ctrack);
+        draw_thick_line(dr, thick_track, ox, oy + 2*t3, ox + TILE_SIZE, oy + 2*t3, ctrack);
+        return;
+    }
+    if (flags == UD) {
+        for (i = 1; i <= 7; i+=2) {
+            cy = oy + TILE_SIZE/8.0F*i;
+            draw_thick_line(dr, thick_sleeper,
+                            ox+t6, cy, ox+t6+2*t3, cy, csleeper);
+        }
+        debug(("vert line: x=%.2f, thick=%.2f", ox + t3, thick_track));
+        draw_thick_line(dr, thick_track, ox + t3, oy, ox + t3, oy + TILE_SIZE, ctrack);
+        draw_thick_line(dr, thick_track, ox + 2*t3, oy, ox + 2*t3, oy + TILE_SIZE, ctrack);
+        return;
+    }
+    if (flags == UL || flags == DL || flags == UR || flags == DR) {
+        cx = (flags & L) ? ox : ox + TILE_SIZE;
+        cy = (flags & U) ? oy : oy + TILE_SIZE;
+
+        draw_circle_sleepers(dr, ds, cx, cy, (float)(5*t6), thick_sleeper, csleeper);
+
+        draw_thick_circle_outline(dr, thick_track, (float)cx, (float)cy,
+                                  2*t3, ctrack);
+        draw_thick_circle_outline(dr, thick_track, (float)cx, (float)cy,
+                                  t3, ctrack);
+
+        return;
+    }
+
+    for (d = 1; d < 16; d *= 2) {
+        float ox1 = 0, ox2 = 0, oy1 = 0, oy2 = 0;
+
+        if (!(flags & d)) continue;
+
+        for (i = 1; i <= 2; i++) {
+            if (d == L) {
+                ox1 = 0;
+                ox2 = thick_track;
+                oy1 = oy2 = i*t3;
+            } else if (d == R) {
+                ox1 = t1;
+                ox2 = t1 - thick_track;
+                oy1 = oy2 = i*t3;
+            } else if (d == U) {
+                ox1 = ox2 = i*t3;
+                oy1 = 0;
+                oy2 = thick_track;
+            } else if (d == D) {
+                ox1 = ox2 = i*t3;
+                oy1 = t1;
+                oy2 = t1 - thick_track;
+            }
+            draw_thick_line(dr, thick_track, ox+ox1, oy+oy1, ox+ox2, oy+oy2, ctrack);
+        }
+    }
+}
+
+static unsigned int best_bits(unsigned int flags, unsigned int flags_drag, int *col)
+{
+    int nb_orig = nbits[flags & ALLDIR], nb_drag = nbits[flags_drag & ALLDIR];
+
+    if (nb_orig > nb_drag) {
+        *col = COL_DRAGOFF;
+        return flags & ALLDIR;
+    } else if (nb_orig < nb_drag) {
+        *col = COL_DRAGON;
+        return flags_drag & ALLDIR;
+    }
+    return flags & ALLDIR; /* same number of bits: no special colour. */
+}
+
+static void draw_square(drawing *dr, game_drawstate *ds,
+                        int x, int y, unsigned int flags, unsigned int flags_drag)
+{
+    int t2 = HALFSZ, t16 = HALFSZ/4, off;
+    int ox = COORD(x), oy = COORD(y), cx = ox + t2, cy = oy + t2, d, c;
+    int bg = (flags & DS_TRACK) ? COL_TRACK_BACKGROUND : COL_BACKGROUND;
+    unsigned int flags_best;
+
+    assert(dr);
+
+    /* Clip to the grid square. */
+    clip(dr, ox, oy, TILE_SIZE, TILE_SIZE);
+
+    /* Clear the square. */
+    best_bits((flags & DS_TRACK) == DS_TRACK,
+              (flags_drag & DS_TRACK) == DS_TRACK, &bg);
+    draw_rect(dr, ox, oy, TILE_SIZE, TILE_SIZE, bg);
+
+    /* Draw outline of grid square */
+    draw_line(dr, ox, oy, COORD(x+1), oy, COL_GRID);
+    draw_line(dr, ox, oy, ox, COORD(y+1), COL_GRID);
+
+    /* More outlines for clue squares. */
+    if (flags & DS_CURSOR) {
+        int curx, cury, curw, curh;
+
+        off = t16;
+        curx = ox + off; cury = oy + off;
+        curw = curh = TILE_SIZE - (2*off) + 1;
+
+        if (flags & (U << DS_CSHIFT)) {
+            cury = oy - off; curh = 2*off + 1;
+        } else if (flags & (D << DS_CSHIFT)) {
+            cury = oy + TILE_SIZE - off; curh = 2*off + 1;
+        } else if (flags & (L << DS_CSHIFT)) {
+            curx = ox - off; curw = 2*off + 1;
+        } else if (flags & (R << DS_CSHIFT)) {
+            curx = ox + TILE_SIZE - off; curw = 2*off + 1;
+        }
+
+        draw_rect_outline(dr, curx, cury, curw, curh, COL_GRID);
+    }
+
+    /* Draw tracks themselves */
+    c = (flags & DS_ERROR) ? COL_ERROR :
+      (flags & DS_FLASH) ? COL_FLASH :
+      (flags & DS_CLUE) ? COL_TRACK_CLUE : COL_TRACK;
+    flags_best = best_bits(flags, flags_drag, &c);
+    draw_tracks_specific(dr, ds, x, y, flags_best, c, COL_SLEEPER);
+
+    /* Draw no-track marks, if present, in square and on edges. */
+    c = COL_TRACK;
+    flags_best = best_bits((flags & DS_NOTRACK) == DS_NOTRACK,
+                           (flags_drag & DS_NOTRACK) == DS_NOTRACK, &c);
+    if (flags_best) {
+        off = HALFSZ/2;
+        draw_line(dr, cx - off, cy - off, cx + off, cy + off, c);
+        draw_line(dr, cx - off, cy + off, cx + off, cy - off, c);
+    }
+
+    c = COL_TRACK;
+    flags_best = best_bits(flags >> DS_NSHIFT, flags_drag >> DS_NSHIFT, &c);
+    for (d = 1; d < 16; d *= 2) {
+        off = t16;
+        cx = ox + t2;
+        cy = oy + t2;
+
+        if (flags_best & d) {
+            cx += (d == R) ? t2 : (d == L) ? -t2 : 0;
+            cy += (d == D) ? t2 : (d == U) ? -t2 : 0;
+
+            draw_line(dr, cx - off, cy - off, cx + off, cy + off, c);
+            draw_line(dr, cx - off, cy + off, cx + off, cy - off, c);
+        }
+    }
+
+    unclip(dr);
+    draw_update(dr, ox, oy, TILE_SIZE, TILE_SIZE);
+}
+
+static void draw_clue(drawing *dr, game_drawstate *ds, int w, int clue, int i, int col)
+{
+    int cx, cy, tsz = TILE_SIZE/2;
+    char buf[20];
+
+    if (i < w) {
+        cx = CENTERED_COORD(i);
+        cy = CENTERED_COORD(-1);
+    } else {
+        cx = CENTERED_COORD(w);
+        cy = CENTERED_COORD(i-w);
+    }
+
+    draw_rect(dr, cx - tsz + BORDER, cy - tsz + BORDER,
+              TILE_SIZE - BORDER, TILE_SIZE - BORDER, COL_BACKGROUND);
+    sprintf(buf, "%d", clue);
+    draw_text(dr, cx, cy, FONT_VARIABLE, tsz, ALIGN_VCENTRE|ALIGN_HCENTRE,
+              col, buf);
+    draw_update(dr, cx - tsz, cy - tsz, TILE_SIZE, TILE_SIZE);
+}
+
+static void draw_loop_ends(drawing *dr, game_drawstate *ds,
+                           const game_state *state, int c)
+{
+    int tsz = TILE_SIZE/2;
+
+    draw_text(dr, CENTERED_COORD(-1), CENTERED_COORD(state->numbers->row_s),
+              FONT_VARIABLE, tsz, ALIGN_VCENTRE|ALIGN_HCENTRE,
+              c, "A");
+
+    draw_text(dr, CENTERED_COORD(state->numbers->col_s), CENTERED_COORD(state->p.h),
+              FONT_VARIABLE, tsz, ALIGN_VCENTRE|ALIGN_HCENTRE,
+              c, "B");
+}
+
+static unsigned int s2d_flags(const game_state *state, int x, int y, const game_ui *ui)
+{
+    unsigned int f;
+    int w = state->p.w;
+
+    f = S_E_DIRS(state, x, y, E_TRACK);
+    f |= (S_E_DIRS(state, x, y, E_NOTRACK) << DS_NSHIFT);
+
+    if (state->sflags[y*w+x] & S_ERROR)
+        f |= DS_ERROR;
+    if (state->sflags[y*w+x] & S_CLUE)
+        f |= DS_CLUE;
+    if (state->sflags[y*w+x] & S_NOTRACK)
+        f |= DS_NOTRACK;
+    if ((state->sflags[y*w+x] & S_TRACK) || (S_E_COUNT(state, x, y, E_TRACK) > 0))
+        f |= DS_TRACK;
+
+    if (ui->cursor_active) {
+        if (ui->curx >= x*2 && ui->curx <= (x+1)*2 &&
+            ui->cury >= y*2 && ui->cury <= (y+1)*2) {
+            f |= DS_CURSOR;
+            if (ui->curx == x*2)        f |= (L << DS_CSHIFT);
+            if (ui->curx == (x+1)*2)    f |= (R << DS_CSHIFT);
+            if (ui->cury == y*2)        f |= (U << DS_CSHIFT);
+            if (ui->cury == (y+1)*2)    f |= (D << DS_CSHIFT);
+        }
+    }
+
+    return f;
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds, const game_state *oldstate,
+                        const game_state *state, int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int i, x, y, force = 0, flashing = 0, w = ds->w, h = ds->h;
+    game_state *drag_state = NULL;
+
+    if (!ds->started) {
+        /*
+         * The initial contents of the window are not guaranteed and
+         * can vary with front ends. To be on the safe side, all games
+         * should start by drawing a big background-colour rectangle
+         * covering the whole window.
+         */
+        draw_rect(dr, 0, 0, (w+2)*TILE_SIZE + 2*BORDER, (h+2)*TILE_SIZE + 2*BORDER,
+                  COL_BACKGROUND);
+
+        draw_loop_ends(dr, ds, state, COL_CLUE);
+
+        draw_line(dr, COORD(ds->w), COORD(0), COORD(ds->w), COORD(ds->h), COL_GRID);
+        draw_line(dr, COORD(0), COORD(ds->h), COORD(ds->w), COORD(ds->h), COL_GRID);
+
+        draw_update(dr, 0, 0, (w+2)*TILE_SIZE + 2*BORDER, (h+2)*TILE_SIZE + 2*BORDER);
+
+        ds->started = TRUE;
+        force = 1;
+    }
+
+    for (i = 0; i < w+h; i++) {
+        if (force || (state->num_errors[i] != ds->num_errors[i])) {
+            ds->num_errors[i] = state->num_errors[i];
+            draw_clue(dr, ds, w, state->numbers->numbers[i], i,
+                      ds->num_errors[i] ? COL_ERROR : COL_CLUE);
+        }
+    }
+
+    if (flashtime > 0 &&
+            (flashtime <= FLASH_TIME/3 ||
+             flashtime >= FLASH_TIME*2/3))
+        flashing = DS_FLASH;
+
+    if (ui->dragging)
+        drag_state = copy_and_apply_drag(state, ui);
+
+    for (x = 0; x < w; x++) {
+        for (y = 0; y < h; y++) {
+            unsigned int f, f_d;
+
+            f = s2d_flags(state, x, y, ui) | flashing;
+            f_d = drag_state ? s2d_flags(drag_state, x, y, ui) : f;
+
+            if (f != ds->flags[y*w+x] || f_d != ds->flags_drag[y*w+x] || force) {
+                ds->flags[y*w+x] = f;
+                ds->flags_drag[y*w+x] = f_d;
+                draw_square(dr, ds, x, y, f, f_d);
+            }
+        }
+    }
+
+    if (drag_state) free_game(drag_state);
+}
+
+static float game_anim_length(const game_state *oldstate, const game_state *newstate,
+                              int dir, game_ui *ui)
+{
+    return 0.0F;
+}
+
+static float game_flash_length(const game_state *oldstate, const game_state *newstate,
+                               int dir, game_ui *ui)
+{
+    if (!oldstate->completed &&
+            newstate->completed && !newstate->used_solve)
+        return FLASH_TIME;
+    else
+        return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    return state->completed ? +1 : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+    int pw, ph;
+
+    /* The Times uses 7mm squares */
+    game_compute_size(params, 700, &pw, &ph);
+    *x = pw / 100.0F;
+    *y = ph / 100.0F;
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+    int w = state->p.w, h = state->p.h;
+    int black = print_mono_colour(dr, 0), grey = print_grey_colour(dr, 0.5F);
+    int x, y, i;
+
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    game_drawstate ads, *ds = &ads;
+    game_set_size(dr, ds, NULL, tilesize);
+
+    /* Grid, then border (second so it is on top) */
+    print_line_width(dr, TILE_SIZE / 24);
+    for (x = 1; x < w; x++)
+        draw_line(dr, COORD(x), COORD(0), COORD(x), COORD(h), grey);
+    for (y = 1; y < h; y++)
+        draw_line(dr, COORD(0), COORD(y), COORD(w), COORD(y), grey);
+
+    print_line_width(dr, TILE_SIZE / 16);
+    draw_rect_outline(dr, COORD(0), COORD(0), w*TILE_SIZE, h*TILE_SIZE, black);
+
+    print_line_width(dr, TILE_SIZE / 24);
+
+    /* clue numbers, and loop ends */
+    for (i = 0; i < w+h; i++)
+        draw_clue(dr, ds, w, state->numbers->numbers[i], i, black);
+    draw_loop_ends(dr, ds, state, black);
+
+    /* clue tracks / solution */
+    for (x = 0; x < w; x++) {
+        for (y = 0; y < h; y++) {
+            clip(dr, COORD(x), COORD(y), TILE_SIZE, TILE_SIZE);
+            draw_tracks_specific(dr, ds, x, y, S_E_DIRS(state, x, y, E_TRACK),
+                                 black, grey);
+            unclip(dr);
+        }
+    }
+}
+
+#ifdef COMBINED
+#define thegame tracks
+#endif
+
+const struct game thegame = {
+    "Train Tracks", "games.tracks", "tracks",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    TRUE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    TRUE, FALSE, game_print_size, game_print,
+    FALSE,                            /* wants_statusbar */
+    FALSE, game_timing_state,
+    0,                                /* flags */
+};
+
+/* vim: set shiftwidth=4 tabstop=8: */
diff --git a/tree234.c b/tree234.c
new file mode 100644 (file)
index 0000000..4b3151e
--- /dev/null
+++ b/tree234.c
@@ -0,0 +1,2200 @@
+/*
+ * tree234.c: reasonably generic counted 2-3-4 tree routines.
+ * 
+ * This file is copyright 1999-2001 Simon Tatham.
+ * 
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT.  IN NO EVENT SHALL SIMON TATHAM BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "tree234.h"
+
+#include "puzzles.h"                  /* for smalloc/sfree */
+
+#ifdef TEST
+#define LOG(x) (printf x)
+#define smalloc malloc
+#define srealloc realloc
+#define sfree free
+#else
+#define LOG(x)
+#endif
+
+typedef struct node234_Tag node234;
+
+struct tree234_Tag {
+    node234 *root;
+    cmpfn234 cmp;
+};
+
+struct node234_Tag {
+    node234 *parent;
+    node234 *kids[4];
+    int counts[4];
+    void *elems[3];
+};
+
+/*
+ * Create a 2-3-4 tree.
+ */
+tree234 *newtree234(cmpfn234 cmp) {
+    tree234 *ret = snew(tree234);
+    LOG(("created tree %p\n", ret));
+    ret->root = NULL;
+    ret->cmp = cmp;
+    return ret;
+}
+
+/*
+ * Free a 2-3-4 tree (not including freeing the elements).
+ */
+static void freenode234(node234 *n) {
+    if (!n)
+       return;
+    freenode234(n->kids[0]);
+    freenode234(n->kids[1]);
+    freenode234(n->kids[2]);
+    freenode234(n->kids[3]);
+    sfree(n);
+}
+void freetree234(tree234 *t) {
+    freenode234(t->root);
+    sfree(t);
+}
+
+/*
+ * Internal function to count a node.
+ */
+static int countnode234(node234 *n) {
+    int count = 0;
+    int i;
+    if (!n)
+       return 0;
+    for (i = 0; i < 4; i++)
+       count += n->counts[i];
+    for (i = 0; i < 3; i++)
+       if (n->elems[i])
+           count++;
+    return count;
+}
+
+/*
+ * Count the elements in a tree.
+ */
+int count234(tree234 *t) {
+    if (t->root)
+       return countnode234(t->root);
+    else
+       return 0;
+}
+
+/*
+ * Propagate a node overflow up a tree until it stops. Returns 0 or
+ * 1, depending on whether the root had to be split or not.
+ */
+static int add234_insert(node234 *left, void *e, node234 *right,
+                        node234 **root, node234 *n, int ki) {
+    int lcount, rcount;
+    /*
+     * We need to insert the new left/element/right set in n at
+     * child position ki.
+     */
+    lcount = countnode234(left);
+    rcount = countnode234(right);
+    while (n) {
+       LOG(("  at %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n",
+            n,
+            n->kids[0], n->counts[0], n->elems[0],
+            n->kids[1], n->counts[1], n->elems[1],
+            n->kids[2], n->counts[2], n->elems[2],
+            n->kids[3], n->counts[3]));
+       LOG(("  need to insert %p/%d \"%s\" %p/%d at position %d\n",
+            left, lcount, e, right, rcount, ki));
+       if (n->elems[1] == NULL) {
+           /*
+            * Insert in a 2-node; simple.
+            */
+           if (ki == 0) {
+               LOG(("  inserting on left of 2-node\n"));
+               n->kids[2] = n->kids[1];     n->counts[2] = n->counts[1];
+               n->elems[1] = n->elems[0];
+               n->kids[1] = right;          n->counts[1] = rcount;
+               n->elems[0] = e;
+               n->kids[0] = left;           n->counts[0] = lcount;
+           } else { /* ki == 1 */
+               LOG(("  inserting on right of 2-node\n"));
+               n->kids[2] = right;          n->counts[2] = rcount;
+               n->elems[1] = e;
+               n->kids[1] = left;           n->counts[1] = lcount;
+           }
+           if (n->kids[0]) n->kids[0]->parent = n;
+           if (n->kids[1]) n->kids[1]->parent = n;
+           if (n->kids[2]) n->kids[2]->parent = n;
+           LOG(("  done\n"));
+           break;
+       } else if (n->elems[2] == NULL) {
+           /*
+            * Insert in a 3-node; simple.
+            */
+           if (ki == 0) {
+               LOG(("  inserting on left of 3-node\n"));
+               n->kids[3] = n->kids[2];    n->counts[3] = n->counts[2];
+               n->elems[2] = n->elems[1];
+               n->kids[2] = n->kids[1];    n->counts[2] = n->counts[1];
+               n->elems[1] = n->elems[0];
+               n->kids[1] = right;         n->counts[1] = rcount;
+               n->elems[0] = e;
+               n->kids[0] = left;          n->counts[0] = lcount;
+           } else if (ki == 1) {
+               LOG(("  inserting in middle of 3-node\n"));
+               n->kids[3] = n->kids[2];    n->counts[3] = n->counts[2];
+               n->elems[2] = n->elems[1];
+               n->kids[2] = right;         n->counts[2] = rcount;
+               n->elems[1] = e;
+               n->kids[1] = left;          n->counts[1] = lcount;
+           } else { /* ki == 2 */
+               LOG(("  inserting on right of 3-node\n"));
+               n->kids[3] = right;         n->counts[3] = rcount;
+               n->elems[2] = e;
+               n->kids[2] = left;          n->counts[2] = lcount;
+           }
+           if (n->kids[0]) n->kids[0]->parent = n;
+           if (n->kids[1]) n->kids[1]->parent = n;
+           if (n->kids[2]) n->kids[2]->parent = n;
+           if (n->kids[3]) n->kids[3]->parent = n;
+           LOG(("  done\n"));
+           break;
+       } else {
+           node234 *m = snew(node234);
+           m->parent = n->parent;
+           LOG(("  splitting a 4-node; created new node %p\n", m));
+           /*
+            * Insert in a 4-node; split into a 2-node and a
+            * 3-node, and move focus up a level.
+            * 
+            * I don't think it matters which way round we put the
+            * 2 and the 3. For simplicity, we'll put the 3 first
+            * always.
+            */
+           if (ki == 0) {
+               m->kids[0] = left;          m->counts[0] = lcount;
+               m->elems[0] = e;
+               m->kids[1] = right;         m->counts[1] = rcount;
+               m->elems[1] = n->elems[0];
+               m->kids[2] = n->kids[1];    m->counts[2] = n->counts[1];
+               e = n->elems[1];
+               n->kids[0] = n->kids[2];    n->counts[0] = n->counts[2];
+               n->elems[0] = n->elems[2];
+               n->kids[1] = n->kids[3];    n->counts[1] = n->counts[3];
+           } else if (ki == 1) {
+               m->kids[0] = n->kids[0];    m->counts[0] = n->counts[0];
+               m->elems[0] = n->elems[0];
+               m->kids[1] = left;          m->counts[1] = lcount;
+               m->elems[1] = e;
+               m->kids[2] = right;         m->counts[2] = rcount;
+               e = n->elems[1];
+               n->kids[0] = n->kids[2];    n->counts[0] = n->counts[2];
+               n->elems[0] = n->elems[2];
+               n->kids[1] = n->kids[3];    n->counts[1] = n->counts[3];
+           } else if (ki == 2) {
+               m->kids[0] = n->kids[0];    m->counts[0] = n->counts[0];
+               m->elems[0] = n->elems[0];
+               m->kids[1] = n->kids[1];    m->counts[1] = n->counts[1];
+               m->elems[1] = n->elems[1];
+               m->kids[2] = left;          m->counts[2] = lcount;
+               /* e = e; */
+               n->kids[0] = right;         n->counts[0] = rcount;
+               n->elems[0] = n->elems[2];
+               n->kids[1] = n->kids[3];    n->counts[1] = n->counts[3];
+           } else { /* ki == 3 */
+               m->kids[0] = n->kids[0];    m->counts[0] = n->counts[0];
+               m->elems[0] = n->elems[0];
+               m->kids[1] = n->kids[1];    m->counts[1] = n->counts[1];
+               m->elems[1] = n->elems[1];
+               m->kids[2] = n->kids[2];    m->counts[2] = n->counts[2];
+               n->kids[0] = left;          n->counts[0] = lcount;
+               n->elems[0] = e;
+               n->kids[1] = right;         n->counts[1] = rcount;
+               e = n->elems[2];
+           }
+           m->kids[3] = n->kids[3] = n->kids[2] = NULL;
+           m->counts[3] = n->counts[3] = n->counts[2] = 0;
+           m->elems[2] = n->elems[2] = n->elems[1] = NULL;
+           if (m->kids[0]) m->kids[0]->parent = m;
+           if (m->kids[1]) m->kids[1]->parent = m;
+           if (m->kids[2]) m->kids[2]->parent = m;
+           if (n->kids[0]) n->kids[0]->parent = n;
+           if (n->kids[1]) n->kids[1]->parent = n;
+           LOG(("  left (%p): %p/%d \"%s\" %p/%d \"%s\" %p/%d\n", m,
+                m->kids[0], m->counts[0], m->elems[0],
+                m->kids[1], m->counts[1], m->elems[1],
+                m->kids[2], m->counts[2]));
+           LOG(("  right (%p): %p/%d \"%s\" %p/%d\n", n,
+                n->kids[0], n->counts[0], n->elems[0],
+                n->kids[1], n->counts[1]));
+           left = m;  lcount = countnode234(left);
+           right = n; rcount = countnode234(right);
+       }
+       if (n->parent)
+           ki = (n->parent->kids[0] == n ? 0 :
+                 n->parent->kids[1] == n ? 1 :
+                 n->parent->kids[2] == n ? 2 : 3);
+       n = n->parent;
+    }
+
+    /*
+     * If we've come out of here by `break', n will still be
+     * non-NULL and all we need to do is go back up the tree
+     * updating counts. If we've come here because n is NULL, we
+     * need to create a new root for the tree because the old one
+     * has just split into two. */
+    if (n) {
+       while (n->parent) {
+           int count = countnode234(n);
+           int childnum;
+           childnum = (n->parent->kids[0] == n ? 0 :
+                       n->parent->kids[1] == n ? 1 :
+                       n->parent->kids[2] == n ? 2 : 3);
+           n->parent->counts[childnum] = count;
+           n = n->parent;
+       }
+       return 0;                      /* root unchanged */
+    } else {
+       LOG(("  root is overloaded, split into two\n"));
+       (*root) = snew(node234);
+       (*root)->kids[0] = left;     (*root)->counts[0] = lcount;
+       (*root)->elems[0] = e;
+       (*root)->kids[1] = right;    (*root)->counts[1] = rcount;
+       (*root)->elems[1] = NULL;
+       (*root)->kids[2] = NULL;     (*root)->counts[2] = 0;
+       (*root)->elems[2] = NULL;
+       (*root)->kids[3] = NULL;     (*root)->counts[3] = 0;
+       (*root)->parent = NULL;
+       if ((*root)->kids[0]) (*root)->kids[0]->parent = (*root);
+       if ((*root)->kids[1]) (*root)->kids[1]->parent = (*root);
+       LOG(("  new root is %p/%d \"%s\" %p/%d\n",
+            (*root)->kids[0], (*root)->counts[0],
+            (*root)->elems[0],
+            (*root)->kids[1], (*root)->counts[1]));
+       return 1;                      /* root moved */
+    }
+}
+
+/*
+ * Add an element e to a 2-3-4 tree t. Returns e on success, or if
+ * an existing element compares equal, returns that.
+ */
+static void *add234_internal(tree234 *t, void *e, int index) {
+    node234 *n;
+    int ki;
+    void *orig_e = e;
+    int c;
+
+    LOG(("adding element \"%s\" to tree %p\n", e, t));
+    if (t->root == NULL) {
+       t->root = snew(node234);
+       t->root->elems[1] = t->root->elems[2] = NULL;
+       t->root->kids[0] = t->root->kids[1] = NULL;
+       t->root->kids[2] = t->root->kids[3] = NULL;
+       t->root->counts[0] = t->root->counts[1] = 0;
+       t->root->counts[2] = t->root->counts[3] = 0;
+       t->root->parent = NULL;
+       t->root->elems[0] = e;
+       LOG(("  created root %p\n", t->root));
+       return orig_e;
+    }
+
+    n = t->root;
+    while (n) {
+       LOG(("  node %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n",
+            n,
+            n->kids[0], n->counts[0], n->elems[0],
+            n->kids[1], n->counts[1], n->elems[1],
+            n->kids[2], n->counts[2], n->elems[2],
+            n->kids[3], n->counts[3]));
+       if (index >= 0) {
+           if (!n->kids[0]) {
+               /*
+                * Leaf node. We want to insert at kid position
+                * equal to the index:
+                * 
+                *   0 A 1 B 2 C 3
+                */
+               ki = index;
+           } else {
+               /*
+                * Internal node. We always descend through it (add
+                * always starts at the bottom, never in the
+                * middle).
+                */
+               if (index <= n->counts[0]) {
+                   ki = 0;
+               } else if (index -= n->counts[0] + 1, index <= n->counts[1]) {
+                   ki = 1;
+               } else if (index -= n->counts[1] + 1, index <= n->counts[2]) {
+                   ki = 2;
+               } else if (index -= n->counts[2] + 1, index <= n->counts[3]) {
+                   ki = 3;
+               } else
+                   return NULL;       /* error: index out of range */
+           }
+       } else {
+           if ((c = t->cmp(e, n->elems[0])) < 0)
+               ki = 0;
+           else if (c == 0)
+               return n->elems[0];            /* already exists */
+           else if (n->elems[1] == NULL || (c = t->cmp(e, n->elems[1])) < 0)
+               ki = 1;
+           else if (c == 0)
+               return n->elems[1];            /* already exists */
+           else if (n->elems[2] == NULL || (c = t->cmp(e, n->elems[2])) < 0)
+               ki = 2;
+           else if (c == 0)
+               return n->elems[2];            /* already exists */
+           else
+               ki = 3;
+       }
+       LOG(("  moving to child %d (%p)\n", ki, n->kids[ki]));
+       if (!n->kids[ki])
+           break;
+       n = n->kids[ki];
+    }
+
+    add234_insert(NULL, e, NULL, &t->root, n, ki);
+
+    return orig_e;
+}
+
+void *add234(tree234 *t, void *e) {
+    if (!t->cmp)                      /* tree is unsorted */
+       return NULL;
+
+    return add234_internal(t, e, -1);
+}
+void *addpos234(tree234 *t, void *e, int index) {
+    if (index < 0 ||                  /* index out of range */
+       t->cmp)                        /* tree is sorted */
+       return NULL;                   /* return failure */
+
+    return add234_internal(t, e, index);  /* this checks the upper bound */
+}
+
+/*
+ * Look up the element at a given numeric index in a 2-3-4 tree.
+ * Returns NULL if the index is out of range.
+ */
+void *index234(tree234 *t, int index) {
+    node234 *n;
+
+    if (!t->root)
+       return NULL;                   /* tree is empty */
+
+    if (index < 0 || index >= countnode234(t->root))
+       return NULL;                   /* out of range */
+
+    n = t->root;
+    
+    while (n) {
+       if (index < n->counts[0])
+           n = n->kids[0];
+       else if (index -= n->counts[0] + 1, index < 0)
+           return n->elems[0];
+       else if (index < n->counts[1])
+           n = n->kids[1];
+       else if (index -= n->counts[1] + 1, index < 0)
+           return n->elems[1];
+       else if (index < n->counts[2])
+           n = n->kids[2];
+       else if (index -= n->counts[2] + 1, index < 0)
+           return n->elems[2];
+       else
+           n = n->kids[3];
+    }
+
+    /* We shouldn't ever get here. I wonder how we did. */
+    return NULL;
+}
+
+/*
+ * Find an element e in a sorted 2-3-4 tree t. Returns NULL if not
+ * found. e is always passed as the first argument to cmp, so cmp
+ * can be an asymmetric function if desired. cmp can also be passed
+ * as NULL, in which case the compare function from the tree proper
+ * will be used.
+ */
+void *findrelpos234(tree234 *t, void *e, cmpfn234 cmp,
+                   int relation, int *index) {
+    node234 *n;
+    void *ret;
+    int c;
+    int idx, ecount, kcount, cmpret;
+
+    if (t->root == NULL)
+       return NULL;
+
+    if (cmp == NULL)
+       cmp = t->cmp;
+
+    n = t->root;
+    /*
+     * Attempt to find the element itself.
+     */
+    idx = 0;
+    ecount = -1;
+    /*
+     * Prepare a fake `cmp' result if e is NULL.
+     */
+    cmpret = 0;
+    if (e == NULL) {
+       assert(relation == REL234_LT || relation == REL234_GT);
+       if (relation == REL234_LT)
+           cmpret = +1;               /* e is a max: always greater */
+       else if (relation == REL234_GT)
+           cmpret = -1;               /* e is a min: always smaller */
+    }
+    while (1) {
+       for (kcount = 0; kcount < 4; kcount++) {
+           if (kcount >= 3 || n->elems[kcount] == NULL ||
+               (c = cmpret ? cmpret : cmp(e, n->elems[kcount])) < 0) {
+               break;
+           }
+           if (n->kids[kcount]) idx += n->counts[kcount];
+           if (c == 0) {
+               ecount = kcount;
+               break;
+           }
+           idx++;
+       }
+       if (ecount >= 0)
+           break;
+       if (n->kids[kcount])
+           n = n->kids[kcount];
+       else
+           break;
+    }
+
+    if (ecount >= 0) {
+       /*
+        * We have found the element we're looking for. It's
+        * n->elems[ecount], at tree index idx. If our search
+        * relation is EQ, LE or GE we can now go home.
+        */
+       if (relation != REL234_LT && relation != REL234_GT) {
+           if (index) *index = idx;
+           return n->elems[ecount];
+       }
+
+       /*
+        * Otherwise, we'll do an indexed lookup for the previous
+        * or next element. (It would be perfectly possible to
+        * implement these search types in a non-counted tree by
+        * going back up from where we are, but far more fiddly.)
+        */
+       if (relation == REL234_LT)
+           idx--;
+       else
+           idx++;
+    } else {
+       /*
+        * We've found our way to the bottom of the tree and we
+        * know where we would insert this node if we wanted to:
+        * we'd put it in in place of the (empty) subtree
+        * n->kids[kcount], and it would have index idx
+        * 
+        * But the actual element isn't there. So if our search
+        * relation is EQ, we're doomed.
+        */
+       if (relation == REL234_EQ)
+           return NULL;
+
+       /*
+        * Otherwise, we must do an index lookup for index idx-1
+        * (if we're going left - LE or LT) or index idx (if we're
+        * going right - GE or GT).
+        */
+       if (relation == REL234_LT || relation == REL234_LE) {
+           idx--;
+       }
+    }
+
+    /*
+     * We know the index of the element we want; just call index234
+     * to do the rest. This will return NULL if the index is out of
+     * bounds, which is exactly what we want.
+     */
+    ret = index234(t, idx);
+    if (ret && index) *index = idx;
+    return ret;
+}
+void *find234(tree234 *t, void *e, cmpfn234 cmp) {
+    return findrelpos234(t, e, cmp, REL234_EQ, NULL);
+}
+void *findrel234(tree234 *t, void *e, cmpfn234 cmp, int relation) {
+    return findrelpos234(t, e, cmp, relation, NULL);
+}
+void *findpos234(tree234 *t, void *e, cmpfn234 cmp, int *index) {
+    return findrelpos234(t, e, cmp, REL234_EQ, index);
+}
+
+/*
+ * Tree transformation used in delete and split: move a subtree
+ * right, from child ki of a node to the next child. Update k and
+ * index so that they still point to the same place in the
+ * transformed tree. Assumes the destination child is not full, and
+ * that the source child does have a subtree to spare. Can cope if
+ * the destination child is undersized.
+ * 
+ *                . C .                     . B .
+ *               /     \     ->            /     \
+ * [more] a A b B c   d D e      [more] a A b   c C d D e
+ * 
+ *                 . C .                     . B .
+ *                /     \    ->             /     \
+ *  [more] a A b B c     d        [more] a A b   c C d
+ */
+static void trans234_subtree_right(node234 *n, int ki, int *k, int *index) {
+    node234 *src, *dest;
+    int i, srclen, adjust;
+
+    src = n->kids[ki];
+    dest = n->kids[ki+1];
+
+    LOG(("  trans234_subtree_right(%p, %d):\n", n, ki));
+    LOG(("    parent %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n",
+        n,
+        n->kids[0], n->counts[0], n->elems[0],
+        n->kids[1], n->counts[1], n->elems[1],
+        n->kids[2], n->counts[2], n->elems[2],
+        n->kids[3], n->counts[3]));
+    LOG(("    src %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n",
+        src,
+        src->kids[0], src->counts[0], src->elems[0],
+        src->kids[1], src->counts[1], src->elems[1],
+        src->kids[2], src->counts[2], src->elems[2],
+        src->kids[3], src->counts[3]));
+    LOG(("    dest %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n",
+        dest,
+        dest->kids[0], dest->counts[0], dest->elems[0],
+        dest->kids[1], dest->counts[1], dest->elems[1],
+        dest->kids[2], dest->counts[2], dest->elems[2],
+        dest->kids[3], dest->counts[3]));
+    /*
+     * Move over the rest of the destination node to make space.
+     */
+    dest->kids[3] = dest->kids[2];    dest->counts[3] = dest->counts[2];
+    dest->elems[2] = dest->elems[1];
+    dest->kids[2] = dest->kids[1];    dest->counts[2] = dest->counts[1];
+    dest->elems[1] = dest->elems[0];
+    dest->kids[1] = dest->kids[0];    dest->counts[1] = dest->counts[0];
+
+    /* which element to move over */
+    i = (src->elems[2] ? 2 : src->elems[1] ? 1 : 0);
+
+    dest->elems[0] = n->elems[ki];
+    n->elems[ki] = src->elems[i];
+    src->elems[i] = NULL;
+
+    dest->kids[0] = src->kids[i+1];   dest->counts[0] = src->counts[i+1];
+    src->kids[i+1] = NULL;            src->counts[i+1] = 0;
+
+    if (dest->kids[0]) dest->kids[0]->parent = dest;
+
+    adjust = dest->counts[0] + 1;
+
+    n->counts[ki] -= adjust;
+    n->counts[ki+1] += adjust;
+
+    srclen = n->counts[ki];
+
+    if (k) {
+       LOG(("    before: k,index = %d,%d\n", (*k), (*index)));
+       if ((*k) == ki && (*index) > srclen) {
+           (*index) -= srclen + 1;
+           (*k)++;
+       } else if ((*k) == ki+1) {
+           (*index) += adjust;
+       }
+       LOG(("    after: k,index = %d,%d\n", (*k), (*index)));
+    }
+
+    LOG(("    parent %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n",
+        n,
+        n->kids[0], n->counts[0], n->elems[0],
+        n->kids[1], n->counts[1], n->elems[1],
+        n->kids[2], n->counts[2], n->elems[2],
+        n->kids[3], n->counts[3]));
+    LOG(("    src %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n",
+        src,
+        src->kids[0], src->counts[0], src->elems[0],
+        src->kids[1], src->counts[1], src->elems[1],
+        src->kids[2], src->counts[2], src->elems[2],
+        src->kids[3], src->counts[3]));
+    LOG(("    dest %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n",
+        dest,
+        dest->kids[0], dest->counts[0], dest->elems[0],
+        dest->kids[1], dest->counts[1], dest->elems[1],
+        dest->kids[2], dest->counts[2], dest->elems[2],
+        dest->kids[3], dest->counts[3]));
+}
+
+/*
+ * Tree transformation used in delete and split: move a subtree
+ * left, from child ki of a node to the previous child. Update k
+ * and index so that they still point to the same place in the
+ * transformed tree. Assumes the destination child is not full, and
+ * that the source child does have a subtree to spare. Can cope if
+ * the destination child is undersized. 
+ *
+ *      . B .                             . C .
+ *     /     \                ->         /     \
+ *  a A b   c C d D e [more]      a A b B c   d D e [more]
+ *
+ *     . A .                             . B .
+ *    /     \                 ->        /     \
+ *   a   b B c C d [more]            a A b   c C d [more]
+ */
+static void trans234_subtree_left(node234 *n, int ki, int *k, int *index) {
+    node234 *src, *dest;
+    int i, adjust;
+
+    src = n->kids[ki];
+    dest = n->kids[ki-1];
+
+    LOG(("  trans234_subtree_left(%p, %d):\n", n, ki));
+    LOG(("    parent %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n",
+        n,
+        n->kids[0], n->counts[0], n->elems[0],
+        n->kids[1], n->counts[1], n->elems[1],
+        n->kids[2], n->counts[2], n->elems[2],
+        n->kids[3], n->counts[3]));
+    LOG(("    dest %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n",
+        dest,
+        dest->kids[0], dest->counts[0], dest->elems[0],
+        dest->kids[1], dest->counts[1], dest->elems[1],
+        dest->kids[2], dest->counts[2], dest->elems[2],
+        dest->kids[3], dest->counts[3]));
+    LOG(("    src %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n",
+        src,
+        src->kids[0], src->counts[0], src->elems[0],
+        src->kids[1], src->counts[1], src->elems[1],
+        src->kids[2], src->counts[2], src->elems[2],
+        src->kids[3], src->counts[3]));
+
+    /* where in dest to put it */
+    i = (dest->elems[1] ? 2 : dest->elems[0] ? 1 : 0);
+    dest->elems[i] = n->elems[ki-1];
+    n->elems[ki-1] = src->elems[0];
+
+    dest->kids[i+1] = src->kids[0];   dest->counts[i+1] = src->counts[0];
+
+    if (dest->kids[i+1]) dest->kids[i+1]->parent = dest;
+
+    /*
+     * Move over the rest of the source node.
+     */
+    src->kids[0] = src->kids[1];      src->counts[0] = src->counts[1];
+    src->elems[0] = src->elems[1];
+    src->kids[1] = src->kids[2];      src->counts[1] = src->counts[2];
+    src->elems[1] = src->elems[2];
+    src->kids[2] = src->kids[3];      src->counts[2] = src->counts[3];
+    src->elems[2] = NULL;
+    src->kids[3] = NULL;              src->counts[3] = 0;
+
+    adjust = dest->counts[i+1] + 1;
+
+    n->counts[ki] -= adjust;
+    n->counts[ki-1] += adjust;
+
+    if (k) {
+       LOG(("    before: k,index = %d,%d\n", (*k), (*index)));
+       if ((*k) == ki) {
+           (*index) -= adjust;
+           if ((*index) < 0) {
+               (*index) += n->counts[ki-1] + 1;
+               (*k)--;
+           }
+       }
+       LOG(("    after: k,index = %d,%d\n", (*k), (*index)));
+    }
+
+    LOG(("    parent %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n",
+        n,
+        n->kids[0], n->counts[0], n->elems[0],
+        n->kids[1], n->counts[1], n->elems[1],
+        n->kids[2], n->counts[2], n->elems[2],
+        n->kids[3], n->counts[3]));
+    LOG(("    dest %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n",
+        dest,
+        dest->kids[0], dest->counts[0], dest->elems[0],
+        dest->kids[1], dest->counts[1], dest->elems[1],
+        dest->kids[2], dest->counts[2], dest->elems[2],
+        dest->kids[3], dest->counts[3]));
+    LOG(("    src %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n",
+        src,
+        src->kids[0], src->counts[0], src->elems[0],
+        src->kids[1], src->counts[1], src->elems[1],
+        src->kids[2], src->counts[2], src->elems[2],
+        src->kids[3], src->counts[3]));
+}
+
+/*
+ * Tree transformation used in delete and split: merge child nodes
+ * ki and ki+1 of a node. Update k and index so that they still
+ * point to the same place in the transformed tree. Assumes both
+ * children _are_ sufficiently small.
+ *
+ *      . B .                .
+ *     /     \     ->        |
+ *  a A b   c C d      a A b B c C d
+ * 
+ * This routine can also cope with either child being undersized:
+ * 
+ *     . A .                 .
+ *    /     \      ->        |
+ *   a     b B c         a A b B c
+ *
+ *    . A .                  .
+ *   /     \       ->        |
+ *  a   b B c C d      a A b B c C d
+ */
+static void trans234_subtree_merge(node234 *n, int ki, int *k, int *index) {
+    node234 *left, *right;
+    int i, leftlen, rightlen, lsize, rsize;
+
+    left = n->kids[ki];               leftlen = n->counts[ki];
+    right = n->kids[ki+1];            rightlen = n->counts[ki+1];
+
+    LOG(("  trans234_subtree_merge(%p, %d):\n", n, ki));
+    LOG(("    parent %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n",
+        n,
+        n->kids[0], n->counts[0], n->elems[0],
+        n->kids[1], n->counts[1], n->elems[1],
+        n->kids[2], n->counts[2], n->elems[2],
+        n->kids[3], n->counts[3]));
+    LOG(("    left %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n",
+        left,
+        left->kids[0], left->counts[0], left->elems[0],
+        left->kids[1], left->counts[1], left->elems[1],
+        left->kids[2], left->counts[2], left->elems[2],
+        left->kids[3], left->counts[3]));
+    LOG(("    right %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n",
+        right,
+        right->kids[0], right->counts[0], right->elems[0],
+        right->kids[1], right->counts[1], right->elems[1],
+        right->kids[2], right->counts[2], right->elems[2],
+        right->kids[3], right->counts[3]));
+
+    assert(!left->elems[2] && !right->elems[2]);   /* neither is large! */
+    lsize = (left->elems[1] ? 2 : left->elems[0] ? 1 : 0);
+    rsize = (right->elems[1] ? 2 : right->elems[0] ? 1 : 0);
+
+    left->elems[lsize] = n->elems[ki];
+
+    for (i = 0; i < rsize+1; i++) {
+       left->kids[lsize+1+i] = right->kids[i];
+       left->counts[lsize+1+i] = right->counts[i];
+       if (left->kids[lsize+1+i])
+           left->kids[lsize+1+i]->parent = left;
+       if (i < rsize)
+           left->elems[lsize+1+i] = right->elems[i];
+    }
+
+    n->counts[ki] += rightlen + 1;
+
+    sfree(right);
+
+    /*
+     * Move the rest of n up by one.
+     */
+    for (i = ki+1; i < 3; i++) {
+       n->kids[i] = n->kids[i+1];
+       n->counts[i] = n->counts[i+1];
+    }
+    for (i = ki; i < 2; i++) {
+       n->elems[i] = n->elems[i+1];
+    }
+    n->kids[3] = NULL;
+    n->counts[3] = 0;
+    n->elems[2] = NULL;
+
+    if (k) {
+       LOG(("    before: k,index = %d,%d\n", (*k), (*index)));
+       if ((*k) == ki+1) {
+           (*k)--;
+           (*index) += leftlen + 1;
+       } else if ((*k) > ki+1) {
+           (*k)--;
+       }
+       LOG(("    after: k,index = %d,%d\n", (*k), (*index)));
+    }
+
+    LOG(("    parent %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n",
+        n,
+        n->kids[0], n->counts[0], n->elems[0],
+        n->kids[1], n->counts[1], n->elems[1],
+        n->kids[2], n->counts[2], n->elems[2],
+        n->kids[3], n->counts[3]));
+    LOG(("    merged %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n",
+        left,
+        left->kids[0], left->counts[0], left->elems[0],
+        left->kids[1], left->counts[1], left->elems[1],
+        left->kids[2], left->counts[2], left->elems[2],
+        left->kids[3], left->counts[3]));
+
+}
+    
+/*
+ * Delete an element e in a 2-3-4 tree. Does not free the element,
+ * merely removes all links to it from the tree nodes.
+ */
+static void *delpos234_internal(tree234 *t, int index) {
+    node234 *n;
+    void *retval;
+    int ki, i;
+
+    retval = NULL;
+
+    n = t->root;                      /* by assumption this is non-NULL */
+    LOG(("deleting item %d from tree %p\n", index, t));
+    while (1) {
+       node234 *sub;
+
+       LOG(("  node %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d index=%d\n",
+            n,
+            n->kids[0], n->counts[0], n->elems[0],
+            n->kids[1], n->counts[1], n->elems[1],
+            n->kids[2], n->counts[2], n->elems[2],
+            n->kids[3], n->counts[3],
+            index));
+       if (index <= n->counts[0]) {
+           ki = 0;
+       } else if (index -= n->counts[0]+1, index <= n->counts[1]) {
+           ki = 1;
+       } else if (index -= n->counts[1]+1, index <= n->counts[2]) {
+           ki = 2;
+       } else if (index -= n->counts[2]+1, index <= n->counts[3]) {
+           ki = 3;
+       } else {
+           assert(0);                 /* can't happen */
+       }
+
+       if (!n->kids[0])
+           break;                     /* n is a leaf node; we're here! */
+
+       /*
+        * Check to see if we've found our target element. If so,
+        * we must choose a new target (we'll use the old target's
+        * successor, which will be in a leaf), move it into the
+        * place of the old one, continue down to the leaf and
+        * delete the old copy of the new target.
+        */
+       if (index == n->counts[ki]) {
+           node234 *m;
+           LOG(("  found element in internal node, index %d\n", ki));
+           assert(n->elems[ki]);      /* must be a kid _before_ an element */
+           ki++; index = 0;
+           for (m = n->kids[ki]; m->kids[0]; m = m->kids[0])
+               continue;
+           LOG(("  replacing with element \"%s\" from leaf node %p\n",
+                m->elems[0], m));
+           retval = n->elems[ki-1];
+           n->elems[ki-1] = m->elems[0];
+       }
+
+       /*
+        * Recurse down to subtree ki. If it has only one element,
+        * we have to do some transformation to start with.
+        */
+       LOG(("  moving to subtree %d\n", ki));
+       sub = n->kids[ki];
+       if (!sub->elems[1]) {
+           LOG(("  subtree has only one element!\n"));
+           if (ki > 0 && n->kids[ki-1]->elems[1]) {
+               /*
+                * Child ki has only one element, but child
+                * ki-1 has two or more. So we need to move a
+                * subtree from ki-1 to ki.
+                */
+               trans234_subtree_right(n, ki-1, &ki, &index);
+           } else if (ki < 3 && n->kids[ki+1] &&
+                      n->kids[ki+1]->elems[1]) {
+               /*
+                * Child ki has only one element, but ki+1 has
+                * two or more. Move a subtree from ki+1 to ki.
+                */
+               trans234_subtree_left(n, ki+1, &ki, &index);
+           } else {
+               /*
+                * ki is small with only small neighbours. Pick a
+                * neighbour and merge with it.
+                */
+               trans234_subtree_merge(n, ki>0 ? ki-1 : ki, &ki, &index);
+               sub = n->kids[ki];
+
+               if (!n->elems[0]) {
+                   /*
+                    * The root is empty and needs to be
+                    * removed.
+                    */
+                   LOG(("  shifting root!\n"));
+                   t->root = sub;
+                   sub->parent = NULL;
+                   sfree(n);
+                   n = NULL;
+               }
+           }
+       }
+
+       if (n)
+           n->counts[ki]--;
+       n = sub;
+    }
+
+    /*
+     * Now n is a leaf node, and ki marks the element number we
+     * want to delete. We've already arranged for the leaf to be
+     * bigger than minimum size, so let's just go to it.
+     */
+    assert(!n->kids[0]);
+    if (!retval)
+       retval = n->elems[ki];
+
+    for (i = ki; i < 2 && n->elems[i+1]; i++)
+       n->elems[i] = n->elems[i+1];
+    n->elems[i] = NULL;
+
+    /*
+     * It's just possible that we have reduced the leaf to zero
+     * size. This can only happen if it was the root - so destroy
+     * it and make the tree empty.
+     */
+    if (!n->elems[0]) {
+       LOG(("  removed last element in tree, destroying empty root\n"));
+       assert(n == t->root);
+       sfree(n);
+       t->root = NULL;
+    }
+
+    return retval;                    /* finished! */
+}
+void *delpos234(tree234 *t, int index) {
+    if (index < 0 || index >= countnode234(t->root))
+       return NULL;
+    return delpos234_internal(t, index);
+}
+void *del234(tree234 *t, void *e) {
+    int index;
+    if (!findrelpos234(t, e, NULL, REL234_EQ, &index))
+       return NULL;                   /* it wasn't in there anyway */
+    return delpos234_internal(t, index); /* it's there; delete it. */
+}
+
+/*
+ * Join two subtrees together with a separator element between
+ * them, given their relative height.
+ * 
+ * (Height<0 means the left tree is shorter, >0 means the right
+ * tree is shorter, =0 means (duh) they're equal.)
+ * 
+ * It is assumed that any checks needed on the ordering criterion
+ * have _already_ been done.
+ * 
+ * The value returned in `height' is 0 or 1 depending on whether the
+ * resulting tree is the same height as the original larger one, or
+ * one higher.
+ */
+static node234 *join234_internal(node234 *left, void *sep,
+                                node234 *right, int *height) {
+    node234 *root, *node;
+    int relht = *height;
+    int ki;
+
+    LOG(("  join: joining %p \"%s\" %p, relative height is %d\n",
+        left, sep, right, relht));
+    if (relht == 0) {
+       /*
+        * The trees are the same height. Create a new one-element
+        * root containing the separator and pointers to the two
+        * nodes.
+        */
+       node234 *newroot;
+       newroot = snew(node234);
+       newroot->kids[0] = left;     newroot->counts[0] = countnode234(left);
+       newroot->elems[0] = sep;
+       newroot->kids[1] = right;    newroot->counts[1] = countnode234(right);
+       newroot->elems[1] = NULL;
+       newroot->kids[2] = NULL;     newroot->counts[2] = 0;
+       newroot->elems[2] = NULL;
+       newroot->kids[3] = NULL;     newroot->counts[3] = 0;
+       newroot->parent = NULL;
+       if (left) left->parent = newroot;
+       if (right) right->parent = newroot;
+       *height = 1;
+       LOG(("  join: same height, brand new root\n"));
+       return newroot;
+    }
+
+    /*
+     * This now works like the addition algorithm on the larger
+     * tree. We're replacing a single kid pointer with two kid
+     * pointers separated by an element; if that causes the node to
+     * overload, we split it in two, move a separator element up to
+     * the next node, and repeat.
+     */
+    if (relht < 0) {
+       /*
+        * Left tree is shorter. Search down the right tree to find
+        * the pointer we're inserting at.
+        */
+       node = root = right;
+       while (++relht < 0) {
+           node = node->kids[0];
+       }
+       ki = 0;
+       right = node->kids[ki];
+    } else {
+       /*
+        * Right tree is shorter; search down the left to find the
+        * pointer we're inserting at.
+        */
+       node = root = left;
+       while (--relht > 0) {
+           if (node->elems[2])
+               node = node->kids[3];
+           else if (node->elems[1])
+               node = node->kids[2];
+           else
+               node = node->kids[1];
+       }
+       if (node->elems[2])
+           ki = 3;
+       else if (node->elems[1])
+           ki = 2;
+       else
+           ki = 1;
+       left = node->kids[ki];
+    }
+
+    /*
+     * Now proceed as for addition.
+     */
+    *height = add234_insert(left, sep, right, &root, node, ki);
+
+    return root;
+}
+static int height234(tree234 *t) {
+    int level = 0;
+    node234 *n = t->root;
+    while (n) {
+       level++;
+       n = n->kids[0];
+    }
+    return level;
+}
+tree234 *join234(tree234 *t1, tree234 *t2) {
+    int size2 = countnode234(t2->root);
+    if (size2 > 0) {
+       void *element;
+       int relht;
+
+       if (t1->cmp) {
+           element = index234(t2, 0);
+           element = findrelpos234(t1, element, NULL, REL234_GE, NULL);
+           if (element)
+               return NULL;
+       }
+
+       element = delpos234(t2, 0);
+       relht = height234(t1) - height234(t2);
+       t1->root = join234_internal(t1->root, element, t2->root, &relht);
+       t2->root = NULL;
+    }
+    return t1;
+}
+tree234 *join234r(tree234 *t1, tree234 *t2) {
+    int size1 = countnode234(t1->root);
+    if (size1 > 0) {
+       void *element;
+       int relht;
+
+       if (t2->cmp) {
+           element = index234(t1, size1-1);
+           element = findrelpos234(t2, element, NULL, REL234_LE, NULL);
+           if (element)
+               return NULL;
+       }
+
+       element = delpos234(t1, size1-1);
+       relht = height234(t1) - height234(t2);
+       t2->root = join234_internal(t1->root, element, t2->root, &relht);
+       t1->root = NULL;
+    }
+    return t2;
+}
+
+/*
+ * Split out the first <index> elements in a tree and return a
+ * pointer to the root node. Leave the root node of the remainder
+ * in t.
+ */
+static node234 *split234_internal(tree234 *t, int index) {
+    node234 *halves[2] = { NULL, NULL }, *n, *sib, *sub;
+    node234 *lparent, *rparent;
+    int ki, pki, i, half, lcount, rcount;
+
+    n = t->root;
+    LOG(("splitting tree %p at point %d\n", t, index));
+
+    /*
+     * Easy special cases. After this we have also dealt completely
+     * with the empty-tree case and we can assume the root exists.
+     */
+    if (index == 0)                   /* return nothing */
+       return NULL;
+    if (index == countnode234(t->root)) {   /* return the whole tree */
+       node234 *ret = t->root;
+       t->root = NULL;
+       return ret;
+    }
+
+    /*
+     * Search down the tree to find the split point.
+     */
+    halves[0] = halves[1] = NULL;
+    lparent = rparent = NULL;
+    pki = -1;
+    while (n) {
+       LOG(("  node %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d index=%d\n",
+            n,
+            n->kids[0], n->counts[0], n->elems[0],
+            n->kids[1], n->counts[1], n->elems[1],
+            n->kids[2], n->counts[2], n->elems[2],
+            n->kids[3], n->counts[3],
+            index));
+       lcount = index;
+       rcount = countnode234(n) - lcount;
+       if (index <= n->counts[0]) {
+           ki = 0;
+       } else if (index -= n->counts[0]+1, index <= n->counts[1]) {
+           ki = 1;
+       } else if (index -= n->counts[1]+1, index <= n->counts[2]) {
+           ki = 2;
+       } else {
+           index -= n->counts[2]+1;
+           ki = 3;
+       }
+
+       LOG(("  splitting at subtree %d\n", ki));
+       sub = n->kids[ki];
+
+       LOG(("  splitting at child index %d\n", ki));
+
+       /*
+        * Split the node, put halves[0] on the right of the left
+        * one and halves[1] on the left of the right one, put the
+        * new node pointers in halves[0] and halves[1], and go up
+        * a level.
+        */
+       sib = snew(node234);
+       for (i = 0; i < 3; i++) {
+           if (i+ki < 3 && n->elems[i+ki]) {
+               sib->elems[i] = n->elems[i+ki];
+               sib->kids[i+1] = n->kids[i+ki+1];
+               if (sib->kids[i+1]) sib->kids[i+1]->parent = sib;
+               sib->counts[i+1] = n->counts[i+ki+1];
+               n->elems[i+ki] = NULL;
+               n->kids[i+ki+1] = NULL;
+               n->counts[i+ki+1] = 0;
+           } else {
+               sib->elems[i] = NULL;
+               sib->kids[i+1] = NULL;
+               sib->counts[i+1] = 0;
+           }
+       }
+       if (lparent) {
+           lparent->kids[pki] = n;
+           lparent->counts[pki] = lcount;
+           n->parent = lparent;
+           rparent->kids[0] = sib;
+           rparent->counts[0] = rcount;
+           sib->parent = rparent;
+       } else {
+           halves[0] = n;
+           n->parent = NULL;
+           halves[1] = sib;
+           sib->parent = NULL;
+       }
+       lparent = n;
+       rparent = sib;
+       pki = ki;
+       LOG(("  left node %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n",
+            n,
+            n->kids[0], n->counts[0], n->elems[0],
+            n->kids[1], n->counts[1], n->elems[1],
+            n->kids[2], n->counts[2], n->elems[2],
+            n->kids[3], n->counts[3]));
+       LOG(("  right node %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n",
+            sib,
+            sib->kids[0], sib->counts[0], sib->elems[0],
+            sib->kids[1], sib->counts[1], sib->elems[1],
+            sib->kids[2], sib->counts[2], sib->elems[2],
+            sib->kids[3], sib->counts[3]));
+
+       n = sub;
+    }
+
+    /*
+     * We've come off the bottom here, so we've successfully split
+     * the tree into two equally high subtrees. The only problem is
+     * that some of the nodes down the fault line will be smaller
+     * than the minimum permitted size. (Since this is a 2-3-4
+     * tree, that means they'll be zero-element one-child nodes.)
+     */
+    LOG(("  fell off bottom, lroot is %p, rroot is %p\n",
+        halves[0], halves[1]));
+    assert(halves[0] != NULL);
+    assert(halves[1] != NULL);
+    lparent->counts[pki] = rparent->counts[0] = 0;
+    lparent->kids[pki] = rparent->kids[0] = NULL;
+
+    /*
+     * So now we go back down the tree from each of the two roots,
+     * fixing up undersize nodes.
+     */
+    for (half = 0; half < 2; half++) {
+       /*
+        * Remove the root if it's undersize (it will contain only
+        * one child pointer, so just throw it away and replace it
+        * with its child). This might happen several times.
+        */
+       while (halves[half] && !halves[half]->elems[0]) {
+           LOG(("  root %p is undersize, throwing away\n", halves[half]));
+           halves[half] = halves[half]->kids[0];
+           sfree(halves[half]->parent);
+           halves[half]->parent = NULL;
+           LOG(("  new root is %p\n", halves[half]));
+       }
+
+       n = halves[half];
+       while (n) {
+           void (*toward)(node234 *n, int ki, int *k, int *index);
+           int ni, merge;
+
+           /*
+            * Now we have a potentially undersize node on the
+            * right (if half==0) or left (if half==1). Sort it
+            * out, by merging with a neighbour or by transferring
+            * subtrees over. At this time we must also ensure that
+            * nodes are bigger than minimum, in case we need an
+            * element to merge two nodes below.
+            */
+           LOG(("  node %p: %p/%d \"%s\" %p/%d \"%s\" %p/%d \"%s\" %p/%d\n",
+                n,
+                n->kids[0], n->counts[0], n->elems[0],
+                n->kids[1], n->counts[1], n->elems[1],
+                n->kids[2], n->counts[2], n->elems[2],
+                n->kids[3], n->counts[3]));
+           if (half == 1) {
+               ki = 0;                /* the kid we're interested in */
+               ni = 1;                /* the neighbour */
+               merge = 0;             /* for merge: leftmost of the two */
+               toward = trans234_subtree_left;
+           } else {
+               ki = (n->kids[3] ? 3 : n->kids[2] ? 2 : 1);
+               ni = ki-1;
+               merge = ni;
+               toward = trans234_subtree_right;
+           }
+
+           sub = n->kids[ki];
+           if (sub && !sub->elems[1]) {
+               /*
+                * This node is undersized or minimum-size. If we
+                * can merge it with its neighbour, we do so;
+                * otherwise we must be able to transfer subtrees
+                * over to it until it is greater than minimum
+                * size.
+                */
+               int undersized = (!sub->elems[0]);
+               LOG(("  child %d is %ssize\n", ki,
+                    undersized ? "under" : "minimum-"));
+               LOG(("  neighbour is %s\n",
+                    n->kids[ni]->elems[2] ? "large" :
+                    n->kids[ni]->elems[1] ? "medium" : "small"));
+               if (!n->kids[ni]->elems[1] ||
+                   (undersized && !n->kids[ni]->elems[2])) {
+                   /*
+                    * Neighbour is small, or possibly neighbour is
+                    * medium and we are undersize.
+                    */
+                   trans234_subtree_merge(n, merge, NULL, NULL);
+                   sub = n->kids[merge];
+                   if (!n->elems[0]) {
+                       /*
+                        * n is empty, and hence must have been the
+                        * root and needs to be removed.
+                        */
+                       assert(!n->parent);
+                       LOG(("  shifting root!\n"));
+                       halves[half] = sub;
+                       halves[half]->parent = NULL;
+                       sfree(n);
+                   }
+               } else {
+                   /* Neighbour is big enough to move trees over. */
+                   toward(n, ni, NULL, NULL);
+                   if (undersized)
+                       toward(n, ni, NULL, NULL);
+               }
+           }
+           n = sub;
+       }
+    }
+
+    t->root = halves[1];
+    return halves[0];
+}
+tree234 *splitpos234(tree234 *t, int index, int before) {
+    tree234 *ret;
+    node234 *n;
+    int count;
+
+    count = countnode234(t->root);
+    if (index < 0 || index > count)
+       return NULL;                   /* error */
+    ret = newtree234(t->cmp);
+    n = split234_internal(t, index);
+    if (before) {
+       /* We want to return the ones before the index. */
+       ret->root = n;
+    } else {
+       /*
+        * We want to keep the ones before the index and return the
+        * ones after.
+        */
+       ret->root = t->root;
+       t->root = n;
+    }
+    return ret;
+}
+tree234 *split234(tree234 *t, void *e, cmpfn234 cmp, int rel) {
+    int before;
+    int index;
+
+    assert(rel != REL234_EQ);
+
+    if (rel == REL234_GT || rel == REL234_GE) {
+       before = 1;
+       rel = (rel == REL234_GT ? REL234_LE : REL234_LT);
+    } else {
+       before = 0;
+    }
+    if (!findrelpos234(t, e, cmp, rel, &index))
+       index = 0;
+
+    return splitpos234(t, index+1, before);
+}
+
+static node234 *copynode234(node234 *n, copyfn234 copyfn, void *copyfnstate) {
+    int i;
+    node234 *n2 = snew(node234);
+
+    for (i = 0; i < 3; i++) {
+       if (n->elems[i] && copyfn)
+           n2->elems[i] = copyfn(copyfnstate, n->elems[i]);
+       else
+           n2->elems[i] = n->elems[i];
+    }
+
+    for (i = 0; i < 4; i++) {
+       if (n->kids[i]) {
+           n2->kids[i] = copynode234(n->kids[i], copyfn, copyfnstate);
+           n2->kids[i]->parent = n2;
+       } else {
+           n2->kids[i] = NULL;
+       }
+       n2->counts[i] = n->counts[i];
+    }
+
+    return n2;
+}
+tree234 *copytree234(tree234 *t, copyfn234 copyfn, void *copyfnstate) {
+    tree234 *t2;
+
+    t2 = newtree234(t->cmp);
+    if (t->root) {
+       t2->root = copynode234(t->root, copyfn, copyfnstate);
+       t2->root->parent = NULL;
+    } else
+       t2->root = NULL;
+
+    return t2;
+}
+
+#ifdef TEST
+
+/*
+ * Test code for the 2-3-4 tree. This code maintains an alternative
+ * representation of the data in the tree, in an array (using the
+ * obvious and slow insert and delete functions). After each tree
+ * operation, the verify() function is called, which ensures all
+ * the tree properties are preserved:
+ *  - node->child->parent always equals node
+ *  - tree->root->parent always equals NULL
+ *  - number of kids == 0 or number of elements + 1;
+ *  - tree has the same depth everywhere
+ *  - every node has at least one element
+ *  - subtree element counts are accurate
+ *  - any NULL kid pointer is accompanied by a zero count
+ *  - in a sorted tree: ordering property between elements of a
+ *    node and elements of its children is preserved
+ * and also ensures the list represented by the tree is the same
+ * list it should be. (This last check also doubly verifies the
+ * ordering properties, because the `same list it should be' is by
+ * definition correctly ordered. It also ensures all nodes are
+ * distinct, because the enum functions would get caught in a loop
+ * if not.)
+ */
+
+#include <string.h>
+#include <stdarg.h>
+
+#define srealloc realloc
+
+/*
+ * Error reporting function.
+ */
+void error(char *fmt, ...) {
+    va_list ap;
+    printf("ERROR: ");
+    va_start(ap, fmt);
+    vfprintf(stdout, fmt, ap);
+    va_end(ap);
+    printf("\n");
+}
+
+/* The array representation of the data. */
+void **array;
+int arraylen, arraysize;
+cmpfn234 cmp;
+
+/* The tree representation of the same data. */
+tree234 *tree;
+
+/*
+ * Routines to provide a diagnostic printout of a tree. Currently
+ * relies on every element in the tree being a one-character string
+ * :-)
+ */
+typedef struct {
+    char **levels;
+} dispctx;
+
+int dispnode(node234 *n, int level, dispctx *ctx) {
+    if (level == 0) {
+       int xpos = strlen(ctx->levels[0]);
+       int len;
+
+       if (n->elems[2])
+           len = sprintf(ctx->levels[0]+xpos, " %s%s%s",
+                         n->elems[0], n->elems[1], n->elems[2]);
+       else if (n->elems[1])
+           len = sprintf(ctx->levels[0]+xpos, " %s%s",
+                         n->elems[0], n->elems[1]);
+       else
+           len = sprintf(ctx->levels[0]+xpos, " %s",
+                         n->elems[0]);
+       return xpos + 1 + (len-1) / 2;
+    } else {
+       int xpos[4], nkids;
+       int nodelen, mypos, myleft, x, i;
+
+       xpos[0] = dispnode(n->kids[0], level-3, ctx);
+       xpos[1] = dispnode(n->kids[1], level-3, ctx);
+       nkids = 2;
+       if (n->kids[2]) {
+           xpos[2] = dispnode(n->kids[2], level-3, ctx);
+           nkids = 3;
+       }
+       if (n->kids[3]) {
+           xpos[3] = dispnode(n->kids[3], level-3, ctx);
+           nkids = 4;
+       }
+
+       if (nkids == 4)
+           mypos = (xpos[1] + xpos[2]) / 2;
+       else if (nkids == 3)
+           mypos = xpos[1];
+       else
+           mypos = (xpos[0] + xpos[1]) / 2;
+       nodelen = nkids * 2 - 1;
+       myleft = mypos - ((nodelen-1)/2);
+       assert(myleft >= xpos[0]);
+       assert(myleft + nodelen-1 <= xpos[nkids-1]);
+
+       x = strlen(ctx->levels[level]);
+       while (x <= xpos[0] && x < myleft)
+           ctx->levels[level][x++] = ' ';
+       while (x < myleft)
+           ctx->levels[level][x++] = '_';
+       if (nkids==4)
+           x += sprintf(ctx->levels[level]+x, ".%s.%s.%s.",
+                        n->elems[0], n->elems[1], n->elems[2]);
+       else if (nkids==3)
+           x += sprintf(ctx->levels[level]+x, ".%s.%s.",
+                        n->elems[0], n->elems[1]);
+       else
+           x += sprintf(ctx->levels[level]+x, ".%s.",
+                        n->elems[0]);
+       while (x < xpos[nkids-1])
+           ctx->levels[level][x++] = '_';
+       ctx->levels[level][x] = '\0';
+
+       x = strlen(ctx->levels[level-1]);
+       for (i = 0; i < nkids; i++) {
+           int rpos, pos;
+           rpos = xpos[i];
+           if (i > 0 && i < nkids-1)
+               pos = myleft + 2*i;
+           else
+               pos = rpos;
+           if (rpos < pos)
+               rpos++;
+           while (x < pos && x < rpos)
+               ctx->levels[level-1][x++] = ' ';
+           if (x == pos)
+               ctx->levels[level-1][x++] = '|';
+           while (x < pos || x < rpos)
+               ctx->levels[level-1][x++] = '_';
+           if (x == pos)
+               ctx->levels[level-1][x++] = '|';
+       }
+       ctx->levels[level-1][x] = '\0';
+
+       x = strlen(ctx->levels[level-2]);
+       for (i = 0; i < nkids; i++) {
+           int rpos = xpos[i];
+
+           while (x < rpos)
+               ctx->levels[level-2][x++] = ' ';
+           ctx->levels[level-2][x++] = '|';
+       }
+       ctx->levels[level-2][x] = '\0';
+
+       return mypos;
+    }
+}
+
+void disptree(tree234 *t) {
+    dispctx ctx;
+    char *leveldata;
+    int width = count234(t);
+    int ht = height234(t) * 3 - 2;
+    int i;
+
+    if (!t->root) {
+       printf("[empty tree]\n");
+    }
+
+    leveldata = smalloc(ht * (width+2));
+    ctx.levels = smalloc(ht * sizeof(char *));
+    for (i = 0; i < ht; i++) {
+       ctx.levels[i] = leveldata + i * (width+2);
+       ctx.levels[i][0] = '\0';
+    }
+
+    (void) dispnode(t->root, ht-1, &ctx);
+
+    for (i = ht; i-- ;)
+       printf("%s\n", ctx.levels[i]);
+
+    sfree(ctx.levels);
+    sfree(leveldata);
+}
+
+typedef struct {
+    int treedepth;
+    int elemcount;
+} chkctx;
+
+int chknode(chkctx *ctx, int level, node234 *node,
+           void *lowbound, void *highbound) {
+    int nkids, nelems;
+    int i;
+    int count;
+
+    /* Count the non-NULL kids. */
+    for (nkids = 0; nkids < 4 && node->kids[nkids]; nkids++);
+    /* Ensure no kids beyond the first NULL are non-NULL. */
+    for (i = nkids; i < 4; i++)
+        if (node->kids[i]) {
+            error("node %p: nkids=%d but kids[%d] non-NULL",
+                   node, nkids, i);
+        } else if (node->counts[i]) {
+            error("node %p: kids[%d] NULL but count[%d]=%d nonzero",
+                   node, i, i, node->counts[i]);
+       }
+
+    /* Count the non-NULL elements. */
+    for (nelems = 0; nelems < 3 && node->elems[nelems]; nelems++);
+    /* Ensure no elements beyond the first NULL are non-NULL. */
+    for (i = nelems; i < 3; i++)
+        if (node->elems[i]) {
+            error("node %p: nelems=%d but elems[%d] non-NULL",
+                   node, nelems, i);
+        }
+
+    if (nkids == 0) {
+        /*
+         * If nkids==0, this is a leaf node; verify that the tree
+         * depth is the same everywhere.
+         */
+        if (ctx->treedepth < 0)
+            ctx->treedepth = level;    /* we didn't know the depth yet */
+        else if (ctx->treedepth != level)
+            error("node %p: leaf at depth %d, previously seen depth %d",
+                   node, level, ctx->treedepth);
+    } else {
+        /*
+         * If nkids != 0, then it should be nelems+1, unless nelems
+         * is 0 in which case nkids should also be 0 (and so we
+         * shouldn't be in this condition at all).
+         */
+        int shouldkids = (nelems ? nelems+1 : 0);
+        if (nkids != shouldkids) {
+            error("node %p: %d elems should mean %d kids but has %d",
+                   node, nelems, shouldkids, nkids);
+        }
+    }
+
+    /*
+     * nelems should be at least 1.
+     */
+    if (nelems == 0) {
+        error("node %p: no elems", node, nkids);
+    }
+
+    /*
+     * Add nelems to the running element count of the whole tree.
+     */
+    ctx->elemcount += nelems;
+
+    /*
+     * Check ordering property: all elements should be strictly >
+     * lowbound, strictly < highbound, and strictly < each other in
+     * sequence. (lowbound and highbound are NULL at edges of tree
+     * - both NULL at root node - and NULL is considered to be <
+     * everything and > everything. IYSWIM.)
+     */
+    if (cmp) {
+       for (i = -1; i < nelems; i++) {
+           void *lower = (i == -1 ? lowbound : node->elems[i]);
+           void *higher = (i+1 == nelems ? highbound : node->elems[i+1]);
+           if (lower && higher && cmp(lower, higher) >= 0) {
+               error("node %p: kid comparison [%d=%s,%d=%s] failed",
+                     node, i, lower, i+1, higher);
+           }
+       }
+    }
+
+    /*
+     * Check parent pointers: all non-NULL kids should have a
+     * parent pointer coming back to this node.
+     */
+    for (i = 0; i < nkids; i++)
+        if (node->kids[i]->parent != node) {
+            error("node %p kid %d: parent ptr is %p not %p",
+                   node, i, node->kids[i]->parent, node);
+        }
+
+
+    /*
+     * Now (finally!) recurse into subtrees.
+     */
+    count = nelems;
+
+    for (i = 0; i < nkids; i++) {
+        void *lower = (i == 0 ? lowbound : node->elems[i-1]);
+        void *higher = (i >= nelems ? highbound : node->elems[i]);
+       int subcount = chknode(ctx, level+1, node->kids[i], lower, higher);
+       if (node->counts[i] != subcount) {
+           error("node %p kid %d: count says %d, subtree really has %d",
+                 node, i, node->counts[i], subcount);
+       }
+        count += subcount;
+    }
+
+    return count;
+}
+
+void verifytree(tree234 *tree, void **array, int arraylen) {
+    chkctx ctx;
+    int i;
+    void *p;
+
+    ctx.treedepth = -1;                /* depth unknown yet */
+    ctx.elemcount = 0;                 /* no elements seen yet */
+    /*
+     * Verify validity of tree properties.
+     */
+    if (tree->root) {
+       if (tree->root->parent != NULL)
+           error("root->parent is %p should be null", tree->root->parent);
+        chknode(&ctx, 0, tree->root, NULL, NULL);
+    }
+    printf("tree depth: %d\n", ctx.treedepth);
+    /*
+     * Enumerate the tree and ensure it matches up to the array.
+     */
+    for (i = 0; NULL != (p = index234(tree, i)); i++) {
+        if (i >= arraylen)
+            error("tree contains more than %d elements", arraylen);
+        if (array[i] != p)
+            error("enum at position %d: array says %s, tree says %s",
+                   i, array[i], p);
+    }
+    if (ctx.elemcount != i) {
+        error("tree really contains %d elements, enum gave %d",
+               ctx.elemcount, i);
+    }
+    if (i < arraylen) {
+        error("enum gave only %d elements, array has %d", i, arraylen);
+    }
+    i = count234(tree);
+    if (ctx.elemcount != i) {
+        error("tree really contains %d elements, count234 gave %d",
+             ctx.elemcount, i);
+    }
+}
+void verify(void) { verifytree(tree, array, arraylen); }
+
+void internal_addtest(void *elem, int index, void *realret) {
+    int i, j;
+    void *retval;
+
+    if (arraysize < arraylen+1) {
+        arraysize = arraylen+1+256;
+        array = (array == NULL ? smalloc(arraysize*sizeof(*array)) :
+                 srealloc(array, arraysize*sizeof(*array)));
+    }
+
+    i = index;
+    /* now i points to the first element >= elem */
+    retval = elem;                  /* expect elem returned (success) */
+    for (j = arraylen; j > i; j--)
+       array[j] = array[j-1];
+    array[i] = elem;                /* add elem to array */
+    arraylen++;
+
+    if (realret != retval) {
+        error("add: retval was %p expected %p", realret, retval);
+    }
+
+    verify();
+}
+
+void addtest(void *elem) {
+    int i;
+    void *realret;
+
+    realret = add234(tree, elem);
+
+    i = 0;
+    while (i < arraylen && cmp(elem, array[i]) > 0)
+        i++;
+    if (i < arraylen && !cmp(elem, array[i])) {
+        void *retval = array[i];       /* expect that returned not elem */
+       if (realret != retval) {
+           error("add: retval was %p expected %p", realret, retval);
+       }
+    } else
+       internal_addtest(elem, i, realret);
+}
+
+void addpostest(void *elem, int i) {
+    void *realret;
+
+    realret = addpos234(tree, elem, i);
+
+    internal_addtest(elem, i, realret);
+}
+
+void delpostest(int i) {
+    int index = i;
+    void *elem = array[i], *ret;
+
+    /* i points to the right element */
+    while (i < arraylen-1) {
+       array[i] = array[i+1];
+       i++;
+    }
+    arraylen--;                               /* delete elem from array */
+
+    if (tree->cmp)
+       ret = del234(tree, elem);
+    else
+       ret = delpos234(tree, index);
+
+    if (ret != elem) {
+       error("del returned %p, expected %p", ret, elem);
+    }
+
+    verify();
+}
+
+void deltest(void *elem) {
+    int i;
+
+    i = 0;
+    while (i < arraylen && cmp(elem, array[i]) > 0)
+        i++;
+    if (i >= arraylen || cmp(elem, array[i]) != 0)
+        return;                        /* don't do it! */
+    delpostest(i);
+}
+
+/* A sample data set and test utility. Designed for pseudo-randomness,
+ * and yet repeatability. */
+
+/*
+ * This random number generator uses the `portable implementation'
+ * given in ANSI C99 draft N869. It assumes `unsigned' is 32 bits;
+ * change it if not.
+ */
+int randomnumber(unsigned *seed) {
+    *seed *= 1103515245;
+    *seed += 12345;
+    return ((*seed) / 65536) % 32768;
+}
+
+int mycmp(void *av, void *bv) {
+    char const *a = (char const *)av;
+    char const *b = (char const *)bv;
+    return strcmp(a, b);
+}
+
+char *strings[] = {
+    "0", "2", "3", "I", "K", "d", "H", "J", "Q", "N", "n", "q", "j", "i",
+    "7", "G", "F", "D", "b", "x", "g", "B", "e", "v", "V", "T", "f", "E",
+    "S", "8", "A", "k", "X", "p", "C", "R", "a", "o", "r", "O", "Z", "u",
+    "6", "1", "w", "L", "P", "M", "c", "U", "h", "9", "t", "5", "W", "Y",
+    "m", "s", "l", "4",
+#if 0
+    "a", "ab", "absque", "coram", "de",
+    "palam", "clam", "cum", "ex", "e",
+    "sine", "tenus", "pro", "prae",
+    "banana", "carrot", "cabbage", "broccoli", "onion", "zebra",
+    "penguin", "blancmange", "pangolin", "whale", "hedgehog",
+    "giraffe", "peanut", "bungee", "foo", "bar", "baz", "quux",
+    "murfl", "spoo", "breen", "flarn", "octothorpe",
+    "snail", "tiger", "elephant", "octopus", "warthog", "armadillo",
+    "aardvark", "wyvern", "dragon", "elf", "dwarf", "orc", "goblin",
+    "pixie", "basilisk", "warg", "ape", "lizard", "newt", "shopkeeper",
+    "wand", "ring", "amulet"
+#endif
+};
+
+#define NSTR lenof(strings)
+
+void findtest(void) {
+    static const int rels[] = {
+       REL234_EQ, REL234_GE, REL234_LE, REL234_LT, REL234_GT
+    };
+    static const char *const relnames[] = {
+       "EQ", "GE", "LE", "LT", "GT"
+    };
+    int i, j, rel, index;
+    char *p, *ret, *realret, *realret2;
+    int lo, hi, mid, c;
+
+    for (i = 0; i < (int)NSTR; i++) {
+       p = strings[i];
+       for (j = 0; j < (int)(sizeof(rels)/sizeof(*rels)); j++) {
+           rel = rels[j];
+
+           lo = 0; hi = arraylen-1;
+           while (lo <= hi) {
+               mid = (lo + hi) / 2;
+               c = strcmp(p, array[mid]);
+               if (c < 0)
+                   hi = mid-1;
+               else if (c > 0)
+                   lo = mid+1;
+               else
+                   break;
+           }
+
+           if (c == 0) {
+               if (rel == REL234_LT)
+                   ret = (mid > 0 ? array[--mid] : NULL);
+               else if (rel == REL234_GT)
+                   ret = (mid < arraylen-1 ? array[++mid] : NULL);
+               else
+                   ret = array[mid];
+           } else {
+               assert(lo == hi+1);
+               if (rel == REL234_LT || rel == REL234_LE) {
+                   mid = hi;
+                   ret = (hi >= 0 ? array[hi] : NULL);
+               } else if (rel == REL234_GT || rel == REL234_GE) {
+                   mid = lo;
+                   ret = (lo < arraylen ? array[lo] : NULL);
+               } else
+                   ret = NULL;
+           }
+
+           realret = findrelpos234(tree, p, NULL, rel, &index);
+           if (realret != ret) {
+               error("find(\"%s\",%s) gave %s should be %s",
+                     p, relnames[j], realret, ret);
+           }
+           if (realret && index != mid) {
+               error("find(\"%s\",%s) gave %d should be %d",
+                     p, relnames[j], index, mid);
+           }
+           if (realret && rel == REL234_EQ) {
+               realret2 = index234(tree, index);
+               if (realret2 != realret) {
+                   error("find(\"%s\",%s) gave %s(%d) but %d -> %s",
+                         p, relnames[j], realret, index, index, realret2);
+               }
+           }
+#if 0
+           printf("find(\"%s\",%s) gave %s(%d)\n", p, relnames[j],
+                  realret, index);
+#endif
+       }
+    }
+
+    realret = findrelpos234(tree, NULL, NULL, REL234_GT, &index);
+    if (arraylen && (realret != array[0] || index != 0)) {
+       error("find(NULL,GT) gave %s(%d) should be %s(0)",
+             realret, index, array[0]);
+    } else if (!arraylen && (realret != NULL)) {
+       error("find(NULL,GT) gave %s(%d) should be NULL",
+             realret, index);
+    }
+
+    realret = findrelpos234(tree, NULL, NULL, REL234_LT, &index);
+    if (arraylen && (realret != array[arraylen-1] || index != arraylen-1)) {
+       error("find(NULL,LT) gave %s(%d) should be %s(0)",
+             realret, index, array[arraylen-1]);
+    } else if (!arraylen && (realret != NULL)) {
+       error("find(NULL,LT) gave %s(%d) should be NULL",
+             realret, index);
+    }
+}
+
+void splittest(tree234 *tree, void **array, int arraylen) {
+    int i;
+    tree234 *tree3, *tree4;
+    for (i = 0; i <= arraylen; i++) {
+       tree3 = copytree234(tree, NULL, NULL);
+       tree4 = splitpos234(tree3, i, 0);
+       verifytree(tree3, array, i);
+       verifytree(tree4, array+i, arraylen-i);
+       join234(tree3, tree4);
+       freetree234(tree4);            /* left empty by join */
+       verifytree(tree3, array, arraylen);
+       freetree234(tree3);
+    }
+}
+
+int main(void) {
+    int in[NSTR];
+    int i, j, k;
+    int tworoot, tmplen;
+    unsigned seed = 0;
+    tree234 *tree2, *tree3, *tree4;
+    int c;
+
+    setvbuf(stdout, NULL, _IOLBF, 0);
+
+    for (i = 0; i < (int)NSTR; i++) in[i] = 0;
+    array = NULL;
+    arraylen = arraysize = 0;
+    tree = newtree234(mycmp);
+    cmp = mycmp;
+
+    verify();
+    for (i = 0; i < 10000; i++) {
+        j = randomnumber(&seed);
+        j %= NSTR;
+        printf("trial: %d\n", i);
+        if (in[j]) {
+            printf("deleting %s (%d)\n", strings[j], j);
+            deltest(strings[j]);
+            in[j] = 0;
+        } else {
+            printf("adding %s (%d)\n", strings[j], j);
+            addtest(strings[j]);
+            in[j] = 1;
+        }
+       disptree(tree);
+       findtest();
+    }
+
+    while (arraylen > 0) {
+        j = randomnumber(&seed);
+        j %= arraylen;
+        deltest(array[j]);
+    }
+
+    freetree234(tree);
+
+    /*
+     * Now try an unsorted tree. We don't really need to test
+     * delpos234 because we know del234 is based on it, so it's
+     * already been tested in the above sorted-tree code; but for
+     * completeness we'll use it to tear down our unsorted tree
+     * once we've built it.
+     */
+    tree = newtree234(NULL);
+    cmp = NULL;
+    verify();
+    for (i = 0; i < 1000; i++) {
+       printf("trial: %d\n", i);
+       j = randomnumber(&seed);
+       j %= NSTR;
+       k = randomnumber(&seed);
+       k %= count234(tree)+1;
+       printf("adding string %s at index %d\n", strings[j], k);
+       addpostest(strings[j], k);
+    }
+
+    /*
+     * While we have this tree in its full form, we'll take a copy
+     * of it to use in split and join testing.
+     */
+    tree2 = copytree234(tree, NULL, NULL);
+    verifytree(tree2, array, arraylen);/* check the copy is accurate */
+    /*
+     * Split tests. Split the tree at every possible point and
+     * check the resulting subtrees.
+     */
+    tworoot = (!tree2->root->elems[1]);/* see if it has a 2-root */
+    splittest(tree2, array, arraylen);
+    /*
+     * Now do the split test again, but on a tree that has a 2-root
+     * (if the previous one didn't) or doesn't (if the previous one
+     * did).
+     */
+    tmplen = arraylen;
+    while ((!tree2->root->elems[1]) == tworoot) {
+       delpos234(tree2, --tmplen);
+    }
+    printf("now trying splits on second tree\n");
+    splittest(tree2, array, tmplen);
+    freetree234(tree2);
+
+    /*
+     * Back to the main testing of uncounted trees.
+     */
+    while (count234(tree) > 0) {
+       printf("cleanup: tree size %d\n", count234(tree));
+       j = randomnumber(&seed);
+       j %= count234(tree);
+       printf("deleting string %s from index %d\n", (char *)array[j], j);
+       delpostest(j);
+    }
+    freetree234(tree);
+
+    /*
+     * Finally, do some testing on split/join on _sorted_ trees. At
+     * the same time, we'll be testing split on very small trees.
+     */
+    tree = newtree234(mycmp);
+    cmp = mycmp;
+    arraylen = 0;
+    for (i = 0; i < 17; i++) {
+       tree2 = copytree234(tree, NULL, NULL);
+       splittest(tree2, array, arraylen);
+       freetree234(tree2);
+       if (i < 16)
+           addtest(strings[i]);
+    }
+    freetree234(tree);
+
+    /*
+     * Test silly cases of join: join(emptytree, emptytree), and
+     * also ensure join correctly spots when sorted trees fail the
+     * ordering constraint.
+     */
+    tree = newtree234(mycmp);
+    tree2 = newtree234(mycmp);
+    tree3 = newtree234(mycmp);
+    tree4 = newtree234(mycmp);
+    assert(mycmp(strings[0], strings[1]) < 0);   /* just in case :-) */
+    add234(tree2, strings[1]);
+    add234(tree4, strings[0]);
+    array[0] = strings[0];
+    array[1] = strings[1];
+    verifytree(tree, array, 0);
+    verifytree(tree2, array+1, 1);
+    verifytree(tree3, array, 0);
+    verifytree(tree4, array, 1);
+
+    /*
+     * So:
+     *  - join(tree,tree3) should leave both tree and tree3 unchanged.
+     *  - joinr(tree,tree2) should leave both tree and tree2 unchanged.
+     *  - join(tree4,tree3) should leave both tree3 and tree4 unchanged.
+     *  - join(tree, tree2) should move the element from tree2 to tree.
+     *  - joinr(tree4, tree3) should move the element from tree4 to tree3.
+     *  - join(tree,tree3) should return NULL and leave both unchanged.
+     *  - join(tree3,tree) should work and create a bigger tree in tree3.
+     */
+    assert(tree == join234(tree, tree3));
+    verifytree(tree, array, 0);
+    verifytree(tree3, array, 0);
+    assert(tree2 == join234r(tree, tree2));
+    verifytree(tree, array, 0);
+    verifytree(tree2, array+1, 1);
+    assert(tree4 == join234(tree4, tree3));
+    verifytree(tree3, array, 0);
+    verifytree(tree4, array, 1);
+    assert(tree == join234(tree, tree2));
+    verifytree(tree, array+1, 1);
+    verifytree(tree2, array, 0);
+    assert(tree3 == join234r(tree4, tree3));
+    verifytree(tree3, array, 1);
+    verifytree(tree4, array, 0);
+    assert(NULL == join234(tree, tree3));
+    verifytree(tree, array+1, 1);
+    verifytree(tree3, array, 1);
+    assert(tree3 == join234(tree3, tree));
+    verifytree(tree3, array, 2);
+    verifytree(tree, array, 0);
+
+    return 0;
+}
+
+#endif
+
+#if 0 /* sorted list of strings might be useful */
+{
+    "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x",
+}
+#endif
diff --git a/tree234.h b/tree234.h
new file mode 100644 (file)
index 0000000..f75c8f7
--- /dev/null
+++ b/tree234.h
@@ -0,0 +1,202 @@
+/*
+ * tree234.h: header defining functions in tree234.c.
+ * 
+ * This file is copyright 1999-2001 Simon Tatham.
+ * 
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ * 
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT.  IN NO EVENT SHALL SIMON TATHAM BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef TREE234_H
+#define TREE234_H
+
+/*
+ * This typedef is opaque outside tree234.c itself.
+ */
+typedef struct tree234_Tag tree234;
+
+typedef int (*cmpfn234)(void *, void *);
+
+typedef void *(*copyfn234)(void *state, void *element);
+
+/*
+ * Create a 2-3-4 tree. If `cmp' is NULL, the tree is unsorted, and
+ * lookups by key will fail: you can only look things up by numeric
+ * index, and you have to use addpos234() and delpos234().
+ */
+tree234 *newtree234(cmpfn234 cmp);
+
+/*
+ * Free a 2-3-4 tree (not including freeing the elements).
+ */
+void freetree234(tree234 *t);
+
+/*
+ * Add an element e to a sorted 2-3-4 tree t. Returns e on success,
+ * or if an existing element compares equal, returns that.
+ */
+void *add234(tree234 *t, void *e);
+
+/*
+ * Add an element e to an unsorted 2-3-4 tree t. Returns e on
+ * success, NULL on failure. (Failure should only occur if the
+ * index is out of range or the tree is sorted.)
+ * 
+ * Index range can be from 0 to the tree's current element count,
+ * inclusive.
+ */
+void *addpos234(tree234 *t, void *e, int index);
+
+/*
+ * Look up the element at a given numeric index in a 2-3-4 tree.
+ * Returns NULL if the index is out of range.
+ * 
+ * One obvious use for this function is in iterating over the whole
+ * of a tree (sorted or unsorted):
+ * 
+ *   for (i = 0; (p = index234(tree, i)) != NULL; i++) consume(p);
+ * 
+ * or
+ * 
+ *   int maxcount = count234(tree);
+ *   for (i = 0; i < maxcount; i++) {
+ *       p = index234(tree, i);
+ *       assert(p != NULL);
+ *       consume(p);
+ *   }
+ */
+void *index234(tree234 *t, int index);
+
+/*
+ * Find an element e in a sorted 2-3-4 tree t. Returns NULL if not
+ * found. e is always passed as the first argument to cmp, so cmp
+ * can be an asymmetric function if desired. cmp can also be passed
+ * as NULL, in which case the compare function from the tree proper
+ * will be used.
+ * 
+ * Three of these functions are special cases of findrelpos234. The
+ * non-`pos' variants lack the `index' parameter: if the parameter
+ * is present and non-NULL, it must point to an integer variable
+ * which will be filled with the numeric index of the returned
+ * element.
+ * 
+ * The non-`rel' variants lack the `relation' parameter. This
+ * parameter allows you to specify what relation the element you
+ * provide has to the element you're looking for. This parameter
+ * can be:
+ * 
+ *   REL234_EQ     - find only an element that compares equal to e
+ *   REL234_LT     - find the greatest element that compares < e
+ *   REL234_LE     - find the greatest element that compares <= e
+ *   REL234_GT     - find the smallest element that compares > e
+ *   REL234_GE     - find the smallest element that compares >= e
+ * 
+ * Non-`rel' variants assume REL234_EQ.
+ * 
+ * If `rel' is REL234_GT or REL234_LT, the `e' parameter may be
+ * NULL. In this case, REL234_GT will return the smallest element
+ * in the tree, and REL234_LT will return the greatest. This gives
+ * an alternative means of iterating over a sorted tree, instead of
+ * using index234:
+ * 
+ *   // to loop forwards
+ *   for (p = NULL; (p = findrel234(tree, p, NULL, REL234_GT)) != NULL ;)
+ *       consume(p);
+ * 
+ *   // to loop backwards
+ *   for (p = NULL; (p = findrel234(tree, p, NULL, REL234_LT)) != NULL ;)
+ *       consume(p);
+ */
+enum {
+    REL234_EQ, REL234_LT, REL234_LE, REL234_GT, REL234_GE
+};
+void *find234(tree234 *t, void *e, cmpfn234 cmp);
+void *findrel234(tree234 *t, void *e, cmpfn234 cmp, int relation);
+void *findpos234(tree234 *t, void *e, cmpfn234 cmp, int *index);
+void *findrelpos234(tree234 *t, void *e, cmpfn234 cmp, int relation,
+                   int *index);
+
+/*
+ * Delete an element e in a 2-3-4 tree. Does not free the element,
+ * merely removes all links to it from the tree nodes.
+ * 
+ * delpos234 deletes the element at a particular tree index: it
+ * works on both sorted and unsorted trees.
+ * 
+ * del234 deletes the element passed to it, so it only works on
+ * sorted trees. (It's equivalent to using findpos234 to determine
+ * the index of an element, and then passing that index to
+ * delpos234.)
+ * 
+ * Both functions return a pointer to the element they delete, for
+ * the user to free or pass on elsewhere or whatever. If the index
+ * is out of range (delpos234) or the element is already not in the
+ * tree (del234) then they return NULL.
+ */
+void *del234(tree234 *t, void *e);
+void *delpos234(tree234 *t, int index);
+
+/*
+ * Return the total element count of a tree234.
+ */
+int count234(tree234 *t);
+
+/*
+ * Split a tree234 into two valid tree234s.
+ * 
+ * splitpos234 splits at a given index. If `before' is TRUE, the
+ * items at and after that index are left in t and the ones before
+ * are returned; if `before' is FALSE, the items before that index
+ * are left in t and the rest are returned.
+ * 
+ * split234 splits at a given key. You can pass any of the
+ * relations used with findrel234, except for REL234_EQ. The items
+ * in the tree that satisfy the relation are returned; the
+ * remainder are left.
+ */
+tree234 *splitpos234(tree234 *t, int index, int before);
+tree234 *split234(tree234 *t, void *e, cmpfn234 cmp, int rel);
+
+/*
+ * Join two tree234s together into a single one.
+ * 
+ * All the elements in t1 are placed to the left of all the
+ * elements in t2. If the trees are sorted, there will be a test to
+ * ensure that this satisfies the ordering criterion, and NULL will
+ * be returned otherwise. If the trees are unsorted, there is no
+ * restriction on the use of join234.
+ * 
+ * The tree returned is t1 (join234) or t2 (join234r), if the
+ * operation is successful.
+ */
+tree234 *join234(tree234 *t1, tree234 *t2);
+tree234 *join234r(tree234 *t1, tree234 *t2);
+
+/*
+ * Make a complete copy of a tree234. Element pointers will be
+ * reused unless copyfn is non-NULL, in which case it will be used
+ * to copy each element. (copyfn takes two `void *' parameters; the
+ * first is private state and the second is the element. A simple
+ * copy routine probably won't need private state.)
+ */
+tree234 *copytree234(tree234 *t, copyfn234 copyfn, void *copyfnstate);
+
+#endif /* TREE234_H */
diff --git a/twiddle.R b/twiddle.R
new file mode 100644 (file)
index 0000000..1495c33
--- /dev/null
+++ b/twiddle.R
@@ -0,0 +1,19 @@
+# -*- makefile -*-
+
+twiddle  : [X] GTK COMMON twiddle twiddle-icon|no-icon
+
+twiddle  : [G] WINDOWS COMMON twiddle twiddle.res|noicon.res
+
+ALL += twiddle[COMBINED]
+
+!begin am gtk
+GAMES += twiddle
+!end
+
+!begin >list.c
+    A(twiddle) \
+!end
+
+!begin >gamedesc.txt
+twiddle:twiddle.exe:Twiddle:Rotational sliding block puzzle:Rotate the tiles around themselves to arrange them into order.
+!end
diff --git a/twiddle.c b/twiddle.c
new file mode 100644 (file)
index 0000000..a1d32cd
--- /dev/null
+++ b/twiddle.c
@@ -0,0 +1,1319 @@
+/*
+ * twiddle.c: Puzzle involving rearranging a grid of squares by
+ * rotating subsquares. Adapted and generalised from a
+ * door-unlocking puzzle in Metroid Prime 2 (the one in the Main
+ * Gyro Chamber).
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+
+#define PREFERRED_TILE_SIZE 48
+#define TILE_SIZE (ds->tilesize)
+#define BORDER    (TILE_SIZE / 2)
+#define HIGHLIGHT_WIDTH (TILE_SIZE / 20)
+#define COORD(x)  ( (x) * TILE_SIZE + BORDER )
+#define FROMCOORD(x)  ( ((x) - BORDER + TILE_SIZE) / TILE_SIZE - 1 )
+
+#define ANIM_PER_BLKSIZE_UNIT 0.13F
+#define FLASH_FRAME 0.13F
+
+enum {
+    COL_BACKGROUND,
+    COL_TEXT,
+    COL_HIGHLIGHT,
+    COL_HIGHLIGHT_GENTLE,
+    COL_LOWLIGHT,
+    COL_LOWLIGHT_GENTLE,
+    COL_HIGHCURSOR, COL_LOWCURSOR,
+    NCOLOURS
+};
+
+struct game_params {
+    int w, h, n;
+    int rowsonly;
+    int orientable;
+    int movetarget;
+};
+
+struct game_state {
+    int w, h, n;
+    int orientable;
+    int *grid;
+    int completed;
+    int used_solve;                   /* used to suppress completion flash */
+    int movecount, movetarget;
+    int lastx, lasty, lastr;          /* coordinates of last rotation */
+};
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = ret->h = 3;
+    ret->n = 2;
+    ret->rowsonly = ret->orientable = FALSE;
+    ret->movetarget = 0;
+
+    return ret;
+}
+
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    static struct {
+        char *title;
+        game_params params;
+    } presets[] = {
+        { "3x3 rows only", { 3, 3, 2, TRUE, FALSE } },
+        { "3x3 normal", { 3, 3, 2, FALSE, FALSE } },
+        { "3x3 orientable", { 3, 3, 2, FALSE, TRUE } },
+        { "4x4 normal", { 4, 4, 2, FALSE } },
+        { "4x4 orientable", { 4, 4, 2, FALSE, TRUE } },
+        { "4x4, rotating 3x3 blocks", { 4, 4, 3, FALSE } },
+        { "5x5, rotating 3x3 blocks", { 5, 5, 3, FALSE } },
+        { "6x6, rotating 4x4 blocks", { 6, 6, 4, FALSE } },
+    };
+
+    if (i < 0 || i >= lenof(presets))
+        return FALSE;
+
+    *name = dupstr(presets[i].title);
+    *params = dup_params(&presets[i].params);
+
+    return TRUE;
+}
+
+static void decode_params(game_params *ret, char const *string)
+{
+    ret->w = ret->h = atoi(string);
+    ret->n = 2;
+    ret->rowsonly = ret->orientable = FALSE;
+    ret->movetarget = 0;
+    while (*string && isdigit((unsigned char)*string)) string++;
+    if (*string == 'x') {
+        string++;
+        ret->h = atoi(string);
+       while (*string && isdigit((unsigned char)*string)) string++;
+    }
+    if (*string == 'n') {
+        string++;
+        ret->n = atoi(string);
+       while (*string && isdigit((unsigned char)*string)) string++;
+    }
+    while (*string) {
+       if (*string == 'r') {
+           ret->rowsonly = TRUE;
+       } else if (*string == 'o') {
+           ret->orientable = TRUE;
+       } else if (*string == 'm') {
+            string++;
+           ret->movetarget = atoi(string);
+            while (string[1] && isdigit((unsigned char)string[1])) string++;
+       }
+       string++;
+    }
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char buf[256];
+    sprintf(buf, "%dx%dn%d%s%s", params->w, params->h, params->n,
+           params->rowsonly ? "r" : "",
+           params->orientable ? "o" : "");
+    /* Shuffle limit is part of the limited parameters, because we have to
+     * supply the target move count. */
+    if (params->movetarget)
+        sprintf(buf + strlen(buf), "m%d", params->movetarget);
+    return dupstr(buf);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+    char buf[80];
+
+    ret = snewn(7, config_item);
+
+    ret[0].name = "Width";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->w);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Height";
+    ret[1].type = C_STRING;
+    sprintf(buf, "%d", params->h);
+    ret[1].sval = dupstr(buf);
+    ret[1].ival = 0;
+
+    ret[2].name = "Rotating block size";
+    ret[2].type = C_STRING;
+    sprintf(buf, "%d", params->n);
+    ret[2].sval = dupstr(buf);
+    ret[2].ival = 0;
+
+    ret[3].name = "One number per row";
+    ret[3].type = C_BOOLEAN;
+    ret[3].sval = NULL;
+    ret[3].ival = params->rowsonly;
+
+    ret[4].name = "Orientation matters";
+    ret[4].type = C_BOOLEAN;
+    ret[4].sval = NULL;
+    ret[4].ival = params->orientable;
+
+    ret[5].name = "Number of shuffling moves";
+    ret[5].type = C_STRING;
+    sprintf(buf, "%d", params->movetarget);
+    ret[5].sval = dupstr(buf);
+    ret[5].ival = 0;
+
+    ret[6].name = NULL;
+    ret[6].type = C_END;
+    ret[6].sval = NULL;
+    ret[6].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = atoi(cfg[0].sval);
+    ret->h = atoi(cfg[1].sval);
+    ret->n = atoi(cfg[2].sval);
+    ret->rowsonly = cfg[3].ival;
+    ret->orientable = cfg[4].ival;
+    ret->movetarget = atoi(cfg[5].sval);
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    if (params->n < 2)
+       return "Rotating block size must be at least two";
+    if (params->w < params->n)
+       return "Width must be at least the rotating block size";
+    if (params->h < params->n)
+       return "Height must be at least the rotating block size";
+    return NULL;
+}
+
+/*
+ * This function actually performs a rotation on a grid. The `x'
+ * and `y' coordinates passed in are the coordinates of the _top
+ * left corner_ of the rotated region. (Using the centre would have
+ * involved half-integers and been annoyingly fiddly. Clicking in
+ * the centre is good for a user interface, but too inconvenient to
+ * use internally.)
+ */
+static void do_rotate(int *grid, int w, int h, int n, int orientable,
+                     int x, int y, int dir)
+{
+    int i, j;
+
+    assert(x >= 0 && x+n <= w);
+    assert(y >= 0 && y+n <= h);
+    dir &= 3;
+    if (dir == 0)
+       return;                        /* nothing to do */
+
+    grid += y*w+x;                    /* translate region to top corner */
+
+    /*
+     * If we were leaving the result of the rotation in a separate
+     * grid, the simple thing to do would be to loop over each
+     * square within the rotated region and assign it from its
+     * source square. However, to do it in place without taking
+     * O(n^2) memory, we need to be marginally more clever. What
+     * I'm going to do is loop over about one _quarter_ of the
+     * rotated region and permute each element within that quarter
+     * with its rotational coset.
+     * 
+     * The size of the region I need to loop over is (n+1)/2 by
+     * n/2, which is an obvious exact quarter for even n and is a
+     * rectangle for odd n. (For odd n, this technique leaves out
+     * one element of the square, which is of course the central
+     * one that never moves anyway.)
+     */
+    for (i = 0; i < (n+1)/2; i++) {
+       for (j = 0; j < n/2; j++) {
+           int k;
+           int g[4];
+           int p[4];
+            
+            p[0] = j*w+i;
+            p[1] = i*w+(n-j-1);
+            p[2] = (n-j-1)*w+(n-i-1);
+            p[3] = (n-i-1)*w+j;
+
+           for (k = 0; k < 4; k++)
+               g[k] = grid[p[k]];
+
+           for (k = 0; k < 4; k++) {
+               int v = g[(k+dir) & 3];
+               if (orientable)
+                   v ^= ((v+dir) ^ v) & 3;  /* alter orientation */
+               grid[p[k]] = v;
+           }
+       }
+    }
+
+    /*
+     * Don't forget the orientation on the centre square, if n is
+     * odd.
+     */
+    if (orientable && (n & 1)) {
+       int v = grid[n/2*(w+1)];
+       v ^= ((v+dir) ^ v) & 3;  /* alter orientation */
+       grid[n/2*(w+1)] = v;
+    }
+}
+
+static int grid_complete(int *grid, int wh, int orientable)
+{
+    int ok = TRUE;
+    int i;
+    for (i = 1; i < wh; i++)
+       if (grid[i] < grid[i-1])
+           ok = FALSE;
+    if (orientable) {
+       for (i = 0; i < wh; i++)
+           if (grid[i] & 3)
+               ok = FALSE;
+    }
+    return ok;
+}
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                          char **aux, int interactive)
+{
+    int *grid;
+    int w = params->w, h = params->h, n = params->n, wh = w*h;
+    int i;
+    char *ret;
+    int retlen;
+    int total_moves;
+
+    /*
+     * Set up a solved grid.
+     */
+    grid = snewn(wh, int);
+    for (i = 0; i < wh; i++)
+       grid[i] = ((params->rowsonly ? i/w : i) + 1) * 4;
+
+    /*
+     * Shuffle it. This game is complex enough that I don't feel up
+     * to analysing its full symmetry properties (particularly at
+     * n=4 and above!), so I'm going to do it the pedestrian way
+     * and simply shuffle the grid by making a long sequence of
+     * randomly chosen moves.
+     */
+    total_moves = params->movetarget;
+    if (!total_moves)
+        /* Add a random move to avoid parity issues. */
+        total_moves = w*h*n*n*2 + random_upto(rs, 2);
+
+    do {
+        int *prevmoves;
+        int rw, rh;                    /* w/h of rotation centre space */
+
+        rw = w - n + 1;
+        rh = h - n + 1;
+        prevmoves = snewn(rw * rh, int);
+        for (i = 0; i < rw * rh; i++)
+            prevmoves[i] = 0;
+
+        for (i = 0; i < total_moves; i++) {
+            int x, y, r, oldtotal, newtotal, dx, dy;
+
+            do {
+                x = random_upto(rs, w - n + 1);
+                y = random_upto(rs, h - n + 1);
+                r = 2 * random_upto(rs, 2) - 1;
+
+                /*
+                 * See if any previous rotations has happened at
+                 * this point which nothing has overlapped since.
+                 * If so, ensure we haven't either undone a
+                 * previous move or repeated one so many times that
+                 * it turns into fewer moves in the inverse
+                 * direction (i.e. three identical rotations).
+                 */
+                oldtotal = prevmoves[y*rw+x];
+                newtotal = oldtotal + r;
+                
+                /*
+                 * Special case here for w==h==n, in which case
+                 * there is actually no way to _avoid_ all moves
+                 * repeating or undoing previous ones.
+                 */
+            } while ((w != n || h != n) &&
+                     (abs(newtotal) < abs(oldtotal) || abs(newtotal) > 2));
+
+            do_rotate(grid, w, h, n, params->orientable, x, y, r);
+
+            /*
+             * Log the rotation we've just performed at this point,
+             * for inversion detection in the next move.
+             * 
+             * Also zero a section of the prevmoves array, because
+             * any rotation area which _overlaps_ this one is now
+             * entirely safe to perform further moves in.
+             * 
+             * Two rotation areas overlap if their top left
+             * coordinates differ by strictly less than n in both
+             * directions
+             */
+            prevmoves[y*rw+x] += r;
+            for (dy = -n+1; dy <= n-1; dy++) {
+                if (y + dy < 0 || y + dy >= rh)
+                    continue;
+                for (dx = -n+1; dx <= n-1; dx++) {
+                    if (x + dx < 0 || x + dx >= rw)
+                        continue;
+                    if (dx == 0 && dy == 0)
+                        continue;
+                    prevmoves[(y+dy)*rw+(x+dx)] = 0;
+                }
+            }
+        }
+
+        sfree(prevmoves);
+
+    } while (grid_complete(grid, wh, params->orientable));
+
+    /*
+     * Now construct the game description, by describing the grid
+     * as a simple sequence of integers. They're comma-separated,
+     * unless the puzzle is orientable in which case they're
+     * separated by orientation letters `u', `d', `l' and `r'.
+     */
+    ret = NULL;
+    retlen = 0;
+    for (i = 0; i < wh; i++) {
+        char buf[80];
+        int k;
+
+        k = sprintf(buf, "%d%c", grid[i] / 4,
+                   (char)(params->orientable ? "uldr"[grid[i] & 3] : ','));
+
+        ret = sresize(ret, retlen + k + 1, char);
+        strcpy(ret + retlen, buf);
+        retlen += k;
+    }
+    if (!params->orientable)
+       ret[retlen-1] = '\0';          /* delete last comma */
+
+    sfree(grid);
+    return ret;
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    const char *p;
+    int w = params->w, h = params->h, wh = w*h;
+    int i;
+
+    p = desc;
+
+    for (i = 0; i < wh; i++) {
+       if (*p < '0' || *p > '9')
+           return "Not enough numbers in string";
+       while (*p >= '0' && *p <= '9')
+           p++;
+       if (!params->orientable && i < wh-1) {
+           if (*p != ',')
+               return "Expected comma after number";
+       } else if (params->orientable && i < wh) {
+           if (*p != 'l' && *p != 'r' && *p != 'u' && *p != 'd')
+               return "Expected orientation letter after number";
+       } else if (i == wh-1 && *p) {
+           return "Excess junk at end of string";
+       }
+
+       if (*p) p++;                   /* eat comma */
+    }
+
+    return NULL;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    game_state *state = snew(game_state);
+    int w = params->w, h = params->h, n = params->n, wh = w*h;
+    int i;
+    const char *p;
+
+    state->w = w;
+    state->h = h;
+    state->n = n;
+    state->orientable = params->orientable;
+    state->completed = 0;
+    state->used_solve = FALSE;
+    state->movecount = 0;
+    state->movetarget = params->movetarget;
+    state->lastx = state->lasty = state->lastr = -1;
+
+    state->grid = snewn(wh, int);
+
+    p = desc;
+
+    for (i = 0; i < wh; i++) {
+       state->grid[i] = 4 * atoi(p);
+       while (*p >= '0' && *p <= '9')
+           p++;
+       if (*p) {
+           if (params->orientable) {
+               switch (*p) {
+                 case 'l': state->grid[i] |= 1; break;
+                 case 'd': state->grid[i] |= 2; break;
+                 case 'r': state->grid[i] |= 3; break;
+               }
+           }
+           p++;
+       }
+    }
+
+    return state;
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    game_state *ret = snew(game_state);
+
+    ret->w = state->w;
+    ret->h = state->h;
+    ret->n = state->n;
+    ret->orientable = state->orientable;
+    ret->completed = state->completed;
+    ret->movecount = state->movecount;
+    ret->movetarget = state->movetarget;
+    ret->lastx = state->lastx;
+    ret->lasty = state->lasty;
+    ret->lastr = state->lastr;
+    ret->used_solve = state->used_solve;
+
+    ret->grid = snewn(ret->w * ret->h, int);
+    memcpy(ret->grid, state->grid, ret->w * ret->h * sizeof(int));
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    sfree(state->grid);
+    sfree(state);
+}
+
+static int compare_int(const void *av, const void *bv)
+{
+    const int *a = (const int *)av;
+    const int *b = (const int *)bv;
+    if (*a < *b)
+       return -1;
+    else if (*a > *b)
+       return +1;
+    else
+       return 0;
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    return dupstr("S");
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    char *ret, *p, buf[80];
+    int i, x, y, col, o, maxlen;
+
+    /*
+     * First work out how many characters we need to display each
+     * number. We're pretty flexible on grid contents here, so we
+     * have to scan the entire grid.
+     */
+    col = 0;
+    for (i = 0; i < state->w * state->h; i++) {
+       x = sprintf(buf, "%d", state->grid[i] / 4);
+       if (col < x) col = x;
+    }
+    o = (state->orientable ? 1 : 0);
+
+    /*
+     * Now we know the exact total size of the grid we're going to
+     * produce: it's got h rows, each containing w lots of col+o,
+     * w-1 spaces and a trailing newline.
+     */
+    maxlen = state->h * state->w * (col+o+1);
+
+    ret = snewn(maxlen+1, char);
+    p = ret;
+
+    for (y = 0; y < state->h; y++) {
+       for (x = 0; x < state->w; x++) {
+           int v = state->grid[state->w*y+x];
+           sprintf(buf, "%*d", col, v/4);
+           memcpy(p, buf, col);
+           p += col;
+           if (o)
+               *p++ = "^<v>"[v & 3];
+           if (x+1 == state->w)
+               *p++ = '\n';
+           else
+               *p++ = ' ';
+       }
+    }
+
+    assert(p - ret == maxlen);
+    *p = '\0';
+    return ret;
+}
+
+struct game_ui {
+    int cur_x, cur_y;
+    int cur_visible;
+};
+
+static game_ui *new_ui(const game_state *state)
+{
+    game_ui *ui = snew(game_ui);
+
+    ui->cur_x = 0;
+    ui->cur_y = 0;
+    ui->cur_visible = FALSE;
+
+    return ui;
+}
+
+static void free_ui(game_ui *ui)
+{
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+}
+
+struct game_drawstate {
+    int started;
+    int w, h, bgcolour;
+    int *grid;
+    int tilesize;
+    int cur_x, cur_y;
+};
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    int w = state->w, h = state->h, n = state->n /* , wh = w*h */;
+    char buf[80];
+    int dir;
+
+    button = button & (~MOD_MASK | MOD_NUM_KEYPAD);
+
+    if (IS_CURSOR_MOVE(button)) {
+        if (button == CURSOR_LEFT && ui->cur_x > 0)
+            ui->cur_x--;
+        if (button == CURSOR_RIGHT && (ui->cur_x+n) < (w))
+            ui->cur_x++;
+        if (button == CURSOR_UP && ui->cur_y > 0)
+            ui->cur_y--;
+        if (button == CURSOR_DOWN && (ui->cur_y+n) < (h))
+            ui->cur_y++;
+        ui->cur_visible = 1;
+        return "";
+    }
+
+    if (button == LEFT_BUTTON || button == RIGHT_BUTTON) {
+       /*
+        * Determine the coordinates of the click. We offset by n-1
+        * half-blocks so that the user must click at the centre of
+        * a rotation region rather than at the corner.
+        */
+       x -= (n-1) * TILE_SIZE / 2;
+       y -= (n-1) * TILE_SIZE / 2;
+       x = FROMCOORD(x);
+       y = FROMCOORD(y);
+       dir = (button == LEFT_BUTTON ? 1 : -1);
+       if (x < 0 || x > w-n || y < 0 || y > h-n)
+           return NULL;
+        ui->cur_visible = 0;
+    } else if (IS_CURSOR_SELECT(button)) {
+        if (ui->cur_visible) {
+            x = ui->cur_x;
+            y = ui->cur_y;
+            dir = (button == CURSOR_SELECT2) ? -1 : +1;
+        } else {
+            ui->cur_visible = 1;
+            return "";
+        }
+    } else if (button == 'a' || button == 'A' || button==MOD_NUM_KEYPAD+'7') {
+        x = y = 0;
+        dir = (button == 'A' ? -1 : +1);
+    } else if (button == 'b' || button == 'B' || button==MOD_NUM_KEYPAD+'9') {
+        x = w-n;
+        y = 0;
+        dir = (button == 'B' ? -1 : +1);
+    } else if (button == 'c' || button == 'C' || button==MOD_NUM_KEYPAD+'1') {
+        x = 0;
+        y = h-n;
+        dir = (button == 'C' ? -1 : +1);
+    } else if (button == 'd' || button == 'D' || button==MOD_NUM_KEYPAD+'3') {
+        x = w-n;
+        y = h-n;
+        dir = (button == 'D' ? -1 : +1);
+    } else if (button==MOD_NUM_KEYPAD+'8' && (w-n) % 2 == 0) {
+        x = (w-n) / 2;
+        y = 0;
+        dir = +1;
+    } else if (button==MOD_NUM_KEYPAD+'2' && (w-n) % 2 == 0) {
+        x = (w-n) / 2;
+        y = h-n;
+        dir = +1;
+    } else if (button==MOD_NUM_KEYPAD+'4' && (h-n) % 2 == 0) {
+        x = 0;
+        y = (h-n) / 2;
+        dir = +1;
+    } else if (button==MOD_NUM_KEYPAD+'6' && (h-n) % 2 == 0) {
+        x = w-n;
+        y = (h-n) / 2;
+        dir = +1;
+    } else if (button==MOD_NUM_KEYPAD+'5' && (w-n) % 2 == 0 && (h-n) % 2 == 0){
+        x = (w-n) / 2;
+        y = (h-n) / 2;
+        dir = +1;
+    } else {
+        return NULL;                   /* no move to be made */
+    }
+
+    /*
+     * If we reach here, we have a valid move.
+     */
+    sprintf(buf, "M%d,%d,%d", x, y, dir);
+    return dupstr(buf);
+}
+
+static game_state *execute_move(const game_state *from, const char *move)
+{
+    game_state *ret;
+    int w = from->w, h = from->h, n = from->n, wh = w*h;
+    int x, y, dir;
+
+    if (!strcmp(move, "S")) {
+       int i;
+       ret = dup_game(from);
+
+       /*
+        * Simply replace the grid with a solved one. For this game,
+        * this isn't a useful operation for actually telling the user
+        * what they should have done, but it is useful for
+        * conveniently being able to get hold of a clean state from
+        * which to practise manoeuvres.
+        */
+       qsort(ret->grid, ret->w*ret->h, sizeof(int), compare_int);
+       for (i = 0; i < ret->w*ret->h; i++)
+           ret->grid[i] &= ~3;
+       ret->used_solve = TRUE;
+       ret->completed = ret->movecount = 1;
+
+       return ret;
+    }
+
+    if (move[0] != 'M' ||
+       sscanf(move+1, "%d,%d,%d", &x, &y, &dir) != 3 ||
+       x < 0 || y < 0 || x > from->w - n || y > from->h - n)
+       return NULL;                   /* can't parse this move string */
+
+    ret = dup_game(from);
+    ret->movecount++;
+    do_rotate(ret->grid, w, h, n, ret->orientable, x, y, dir);
+    ret->lastx = x;
+    ret->lasty = y;
+    ret->lastr = dir;
+
+    /*
+     * See if the game has been completed. To do this we simply
+     * test that the grid contents are in increasing order.
+     */
+    if (!ret->completed && grid_complete(ret->grid, wh, ret->orientable))
+        ret->completed = ret->movecount;
+    return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    struct { int tilesize; } ads, *ds = &ads;
+    ads.tilesize = tilesize;
+
+    *x = TILE_SIZE * params->w + 2 * BORDER;
+    *y = TILE_SIZE * params->h + 2 * BORDER;
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+    int i;
+
+    game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT);
+
+    /* cursor is light-background with a red tinge. */
+    ret[COL_HIGHCURSOR * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 1.0F;
+    ret[COL_HIGHCURSOR * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 0.5F;
+    ret[COL_HIGHCURSOR * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] * 0.5F;
+
+    for (i = 0; i < 3; i++) {
+        ret[COL_HIGHLIGHT_GENTLE * 3 + i] = ret[COL_BACKGROUND * 3 + i] * 1.1F;
+        ret[COL_LOWLIGHT_GENTLE * 3 + i] = ret[COL_BACKGROUND * 3 + i] * 0.9F;
+        ret[COL_TEXT * 3 + i] = 0.0;
+        ret[COL_LOWCURSOR * 3 + i] = ret[COL_HIGHCURSOR * 3 + i] * 0.6F;
+    }
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    struct game_drawstate *ds = snew(struct game_drawstate);
+    int i;
+
+    ds->started = FALSE;
+    ds->w = state->w;
+    ds->h = state->h;
+    ds->bgcolour = COL_BACKGROUND;
+    ds->grid = snewn(ds->w*ds->h, int);
+    ds->tilesize = 0;                  /* haven't decided yet */
+    for (i = 0; i < ds->w*ds->h; i++)
+        ds->grid[i] = -1;
+    ds->cur_x = ds->cur_y = -state->n;
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds->grid);
+    sfree(ds);
+}
+
+struct rotation {
+    int cx, cy, cw, ch;                       /* clip region */
+    int ox, oy;                               /* rotation origin */
+    float c, s;                               /* cos and sin of rotation angle */
+    int lc, rc, tc, bc;                       /* colours of tile edges */
+};
+
+static void rotate(int *xy, struct rotation *rot)
+{
+    if (rot) {
+       float xf = (float)xy[0] - rot->ox, yf = (float)xy[1] - rot->oy;
+       float xf2, yf2;
+
+       xf2 = rot->c * xf + rot->s * yf;
+       yf2 = - rot->s * xf + rot->c * yf;
+
+       xy[0] = (int)(xf2 + rot->ox + 0.5);   /* round to nearest */
+       xy[1] = (int)(yf2 + rot->oy + 0.5);   /* round to nearest */
+    }
+}
+
+#define CUR_TOP         1
+#define CUR_RIGHT       2
+#define CUR_BOTTOM      4
+#define CUR_LEFT        8
+
+static void draw_tile(drawing *dr, game_drawstate *ds, const game_state *state,
+                      int x, int y, int tile, int flash_colour,
+                      struct rotation *rot, unsigned cedges)
+{
+    int coords[8];
+    char str[40];
+
+    /*
+     * If we've been passed a rotation region but we're drawing a
+     * tile which is outside it, we must draw it normally. This can
+     * occur if we're cleaning up after a completion flash while a
+     * new move is also being made.
+     */
+    if (rot && (x < rot->cx || y < rot->cy ||
+                x >= rot->cx+rot->cw || y >= rot->cy+rot->ch))
+        rot = NULL;
+
+    if (rot)
+       clip(dr, rot->cx, rot->cy, rot->cw, rot->ch);
+
+    /*
+     * We must draw each side of the tile's highlight separately,
+     * because in some cases (during rotation) they will all need
+     * to be different colours.
+     */
+
+    /* The centre point is common to all sides. */
+    coords[4] = x + TILE_SIZE / 2;
+    coords[5] = y + TILE_SIZE / 2;
+    rotate(coords+4, rot);
+
+    /* Right side. */
+    coords[0] = x + TILE_SIZE - 1;
+    coords[1] = y + TILE_SIZE - 1;
+    rotate(coords+0, rot);
+    coords[2] = x + TILE_SIZE - 1;
+    coords[3] = y;
+    rotate(coords+2, rot);
+    draw_polygon(dr, coords, 3, rot ? rot->rc : COL_LOWLIGHT,
+                rot ? rot->rc : (cedges & CUR_RIGHT) ? COL_LOWCURSOR : COL_LOWLIGHT);
+
+    /* Bottom side. */
+    coords[2] = x;
+    coords[3] = y + TILE_SIZE - 1;
+    rotate(coords+2, rot);
+    draw_polygon(dr, coords, 3, rot ? rot->bc : COL_LOWLIGHT,
+                rot ? rot->bc : (cedges & CUR_BOTTOM) ? COL_LOWCURSOR : COL_LOWLIGHT);
+
+    /* Left side. */
+    coords[0] = x;
+    coords[1] = y;
+    rotate(coords+0, rot);
+    draw_polygon(dr, coords, 3, rot ? rot->lc : COL_HIGHLIGHT,
+                rot ? rot->lc : (cedges & CUR_LEFT) ? COL_HIGHCURSOR : COL_HIGHLIGHT);
+
+    /* Top side. */
+    coords[2] = x + TILE_SIZE - 1;
+    coords[3] = y;
+    rotate(coords+2, rot);
+    draw_polygon(dr, coords, 3, rot ? rot->tc : COL_HIGHLIGHT,
+                rot ? rot->tc : (cedges & CUR_TOP) ? COL_HIGHCURSOR : COL_HIGHLIGHT);
+
+    /*
+     * Now the main blank area in the centre of the tile.
+     */
+    if (rot) {
+       coords[0] = x + HIGHLIGHT_WIDTH;
+       coords[1] = y + HIGHLIGHT_WIDTH;
+       rotate(coords+0, rot);
+       coords[2] = x + HIGHLIGHT_WIDTH;
+       coords[3] = y + TILE_SIZE - 1 - HIGHLIGHT_WIDTH;
+       rotate(coords+2, rot);
+       coords[4] = x + TILE_SIZE - 1 - HIGHLIGHT_WIDTH;
+       coords[5] = y + TILE_SIZE - 1 - HIGHLIGHT_WIDTH;
+       rotate(coords+4, rot);
+       coords[6] = x + TILE_SIZE - 1 - HIGHLIGHT_WIDTH;
+       coords[7] = y + HIGHLIGHT_WIDTH;
+       rotate(coords+6, rot);
+       draw_polygon(dr, coords, 4, flash_colour, flash_colour);
+    } else {
+       draw_rect(dr, x + HIGHLIGHT_WIDTH, y + HIGHLIGHT_WIDTH,
+                 TILE_SIZE - 2*HIGHLIGHT_WIDTH, TILE_SIZE - 2*HIGHLIGHT_WIDTH,
+                 flash_colour);
+    }
+
+    /*
+     * Next, the triangles for orientation.
+     */
+    if (state->orientable) {
+       int xdx, xdy, ydx, ydy;
+       int cx, cy, displ, displ2;
+       switch (tile & 3) {
+         case 0:
+           xdx = 1, xdy = 0;
+           ydx = 0, ydy = 1;
+           break;
+         case 1:
+           xdx = 0, xdy = -1;
+           ydx = 1, ydy = 0;
+           break;
+         case 2:
+           xdx = -1, xdy = 0;
+           ydx = 0, ydy = -1;
+           break;
+         default /* case 3 */:
+           xdx = 0, xdy = 1;
+           ydx = -1, ydy = 0;
+           break;
+       }
+
+       cx = x + TILE_SIZE / 2;
+       cy = y + TILE_SIZE / 2;
+       displ = TILE_SIZE / 2 - HIGHLIGHT_WIDTH - 2;
+       displ2 = TILE_SIZE / 3 - HIGHLIGHT_WIDTH;
+
+       coords[0] = cx - displ * xdx + displ2 * ydx;
+       coords[1] = cy - displ * xdy + displ2 * ydy;
+       rotate(coords+0, rot);
+       coords[2] = cx + displ * xdx + displ2 * ydx;
+       coords[3] = cy + displ * xdy + displ2 * ydy;
+       rotate(coords+2, rot);
+       coords[4] = cx - displ * ydx;
+       coords[5] = cy - displ * ydy;
+       rotate(coords+4, rot);
+       draw_polygon(dr, coords, 3, COL_LOWLIGHT_GENTLE, COL_LOWLIGHT_GENTLE);
+    }
+
+    coords[0] = x + TILE_SIZE/2;
+    coords[1] = y + TILE_SIZE/2;
+    rotate(coords+0, rot);
+    sprintf(str, "%d", tile / 4);
+    draw_text(dr, coords[0], coords[1],
+             FONT_VARIABLE, TILE_SIZE/3, ALIGN_VCENTRE | ALIGN_HCENTRE,
+             COL_TEXT, str);
+
+    if (rot)
+       unclip(dr);
+
+    draw_update(dr, x, y, TILE_SIZE, TILE_SIZE);
+}
+
+static int highlight_colour(float angle)
+{
+    int colours[32] = {
+       COL_LOWLIGHT,
+       COL_LOWLIGHT_GENTLE,
+       COL_LOWLIGHT_GENTLE,
+       COL_LOWLIGHT_GENTLE,
+       COL_HIGHLIGHT_GENTLE,
+       COL_HIGHLIGHT_GENTLE,
+       COL_HIGHLIGHT_GENTLE,
+       COL_HIGHLIGHT,
+       COL_HIGHLIGHT,
+       COL_HIGHLIGHT,
+       COL_HIGHLIGHT,
+       COL_HIGHLIGHT,
+       COL_HIGHLIGHT,
+       COL_HIGHLIGHT,
+       COL_HIGHLIGHT,
+       COL_HIGHLIGHT,
+       COL_HIGHLIGHT,
+       COL_HIGHLIGHT_GENTLE,
+       COL_HIGHLIGHT_GENTLE,
+       COL_HIGHLIGHT_GENTLE,
+       COL_LOWLIGHT_GENTLE,
+       COL_LOWLIGHT_GENTLE,
+       COL_LOWLIGHT_GENTLE,
+       COL_LOWLIGHT,
+       COL_LOWLIGHT,
+       COL_LOWLIGHT,
+       COL_LOWLIGHT,
+       COL_LOWLIGHT,
+       COL_LOWLIGHT,
+       COL_LOWLIGHT,
+       COL_LOWLIGHT,
+       COL_LOWLIGHT,
+    };
+
+    return colours[(int)((angle + 2*PI) / (PI/16)) & 31];
+}
+
+static float game_anim_length_real(const game_state *oldstate,
+                                   const game_state *newstate, int dir,
+                                   const game_ui *ui)
+{
+    /*
+     * Our game_anim_length doesn't need to modify its game_ui, so
+     * this is the real function which declares ui as const. We must
+     * wrap this for the backend structure with a version that has ui
+     * non-const, but we still need this version to call from within
+     * game_redraw which only has a const ui available.
+     */
+    return (float)(ANIM_PER_BLKSIZE_UNIT * sqrt(newstate->n-1));
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return game_anim_length_real(oldstate, newstate, dir, ui);
+
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    if (!oldstate->completed && newstate->completed &&
+       !oldstate->used_solve && !newstate->used_solve)
+        return 2 * FLASH_FRAME;
+    else
+        return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    return state->completed ? +1 : 0;
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int i, bgcolour;
+    struct rotation srot, *rot;
+    int lastx = -1, lasty = -1, lastr = -1;
+    int cx, cy, cmoved = 0, n = state->n;
+
+    cx = ui->cur_visible ? ui->cur_x : -state->n;
+    cy = ui->cur_visible ? ui->cur_y : -state->n;
+    if (cx != ds->cur_x || cy != ds->cur_y)
+        cmoved = 1;
+
+    if (flashtime > 0) {
+        int frame = (int)(flashtime / FLASH_FRAME);
+        bgcolour = (frame % 2 ? COL_LOWLIGHT : COL_HIGHLIGHT);
+    } else
+        bgcolour = COL_BACKGROUND;
+
+    if (!ds->started) {
+        int coords[10];
+
+       draw_rect(dr, 0, 0,
+                 TILE_SIZE * state->w + 2 * BORDER,
+                 TILE_SIZE * state->h + 2 * BORDER, COL_BACKGROUND);
+       draw_update(dr, 0, 0,
+                   TILE_SIZE * state->w + 2 * BORDER,
+                   TILE_SIZE * state->h + 2 * BORDER);
+
+        /*
+         * Recessed area containing the whole puzzle.
+         */
+        coords[0] = COORD(state->w) + HIGHLIGHT_WIDTH - 1;
+        coords[1] = COORD(state->h) + HIGHLIGHT_WIDTH - 1;
+        coords[2] = COORD(state->w) + HIGHLIGHT_WIDTH - 1;
+        coords[3] = COORD(0) - HIGHLIGHT_WIDTH;
+        coords[4] = coords[2] - TILE_SIZE;
+        coords[5] = coords[3] + TILE_SIZE;
+        coords[8] = COORD(0) - HIGHLIGHT_WIDTH;
+        coords[9] = COORD(state->h) + HIGHLIGHT_WIDTH - 1;
+        coords[6] = coords[8] + TILE_SIZE;
+        coords[7] = coords[9] - TILE_SIZE;
+        draw_polygon(dr, coords, 5, COL_HIGHLIGHT, COL_HIGHLIGHT);
+
+        coords[1] = COORD(0) - HIGHLIGHT_WIDTH;
+        coords[0] = COORD(0) - HIGHLIGHT_WIDTH;
+        draw_polygon(dr, coords, 5, COL_LOWLIGHT, COL_LOWLIGHT);
+
+        ds->started = TRUE;
+    }
+
+    /*
+     * If we're drawing any rotated tiles, sort out the rotation
+     * parameters, and also zap the rotation region to the
+     * background colour before doing anything else.
+     */
+    if (oldstate) {
+       float angle;
+       float anim_max = game_anim_length_real(oldstate, state, dir, ui);
+
+       if (dir > 0) {
+           lastx = state->lastx;
+           lasty = state->lasty;
+           lastr = state->lastr;
+       } else {
+           lastx = oldstate->lastx;
+           lasty = oldstate->lasty;
+           lastr = -oldstate->lastr;
+       }
+
+       rot = &srot;
+       rot->cx = COORD(lastx);
+       rot->cy = COORD(lasty);
+       rot->cw = rot->ch = TILE_SIZE * state->n;
+       rot->ox = rot->cx + rot->cw/2;
+       rot->oy = rot->cy + rot->ch/2;
+       angle = (float)((-PI/2 * lastr) * (1.0 - animtime / anim_max));
+       rot->c = (float)cos(angle);
+       rot->s = (float)sin(angle);
+
+       /*
+        * Sort out the colours of the various sides of the tile.
+        */
+       rot->lc = highlight_colour((float)PI + angle);
+       rot->rc = highlight_colour(angle);
+       rot->tc = highlight_colour((float)(PI/2.0) + angle);
+       rot->bc = highlight_colour((float)(-PI/2.0) + angle);
+
+       draw_rect(dr, rot->cx, rot->cy, rot->cw, rot->ch, bgcolour);
+    } else
+       rot = NULL;
+
+    /*
+     * Now draw each tile.
+     */
+    for (i = 0; i < state->w * state->h; i++) {
+       int t, cc = 0;
+       int tx = i % state->w, ty = i / state->w;
+
+       /*
+        * Figure out what should be displayed at this location.
+        * Usually it will be state->grid[i], unless we're in the
+        * middle of animating an actual rotation and this cell is
+        * within the rotation region, in which case we set -1
+        * (always display).
+        */
+       if (oldstate && lastx >= 0 && lasty >= 0 &&
+           tx >= lastx && tx < lastx + state->n &&
+           ty >= lasty && ty < lasty + state->n)
+           t = -1;
+       else
+           t = state->grid[i];
+
+        if (cmoved) {
+            /* cursor has moved (or changed visibility)... */
+            if (tx == cx || tx == cx+n-1 || ty == cy || ty == cy+n-1)
+                cc = 1; /* ...we're on new cursor, redraw */
+            if (tx == ds->cur_x || tx == ds->cur_x+n-1 ||
+                ty == ds->cur_y || ty == ds->cur_y+n-1)
+                cc = 1; /* ...we were on old cursor, redraw */
+        }
+
+       if (ds->bgcolour != bgcolour ||   /* always redraw when flashing */
+           ds->grid[i] != t || ds->grid[i] == -1 || t == -1 || cc) {
+           int x = COORD(tx), y = COORD(ty);
+            unsigned cedges = 0;
+
+            if (tx == cx     && ty >= cy && ty <= cy+n-1) cedges |= CUR_LEFT;
+            if (ty == cy     && tx >= cx && tx <= cx+n-1) cedges |= CUR_TOP;
+            if (tx == cx+n-1 && ty >= cy && ty <= cy+n-1) cedges |= CUR_RIGHT;
+            if (ty == cy+n-1 && tx >= cx && tx <= cx+n-1) cedges |= CUR_BOTTOM;
+
+           draw_tile(dr, ds, state, x, y, state->grid[i], bgcolour, rot, cedges);
+            ds->grid[i] = t;
+        }
+    }
+    ds->bgcolour = bgcolour;
+    ds->cur_x = cx; ds->cur_y = cy;
+
+    /*
+     * Update the status bar.
+     */
+    {
+       char statusbuf[256];
+
+        /*
+         * Don't show the new status until we're also showing the
+         * new _state_ - after the game animation is complete.
+         */
+        if (oldstate)
+            state = oldstate;
+
+       if (state->used_solve)
+           sprintf(statusbuf, "Moves since auto-solve: %d",
+                   state->movecount - state->completed);
+       else {
+           sprintf(statusbuf, "%sMoves: %d",
+                   (state->completed ? "COMPLETED! " : ""),
+                   (state->completed ? state->completed : state->movecount));
+            if (state->movetarget)
+                sprintf(statusbuf+strlen(statusbuf), " (target %d)",
+                        state->movetarget);
+        }
+
+       status_bar(dr, statusbuf);
+    }
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+}
+
+#ifdef COMBINED
+#define thegame twiddle
+#endif
+
+const struct game thegame = {
+    "Twiddle", "games.twiddle", "twiddle",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    TRUE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    FALSE, FALSE, game_print_size, game_print,
+    TRUE,                             /* wants_statusbar */
+    FALSE, game_timing_state,
+    0,                                /* flags */
+};
+
+/* vim: set shiftwidth=4 tabstop=8: */
diff --git a/undead.R b/undead.R
new file mode 100644 (file)
index 0000000..5907ed6
--- /dev/null
+++ b/undead.R
@@ -0,0 +1,18 @@
+# -*- makefile -*-
+
+undead : [X] GTK COMMON undead undead-icon|no-icon
+undead : [G] WINDOWS COMMON undead undead.res|noicon.res
+
+ALL += undead[COMBINED]
+
+!begin am gtk
+GAMES += undead
+!end
+
+!begin >list.c
+    A(undead) \
+!end
+
+!begin >gamedesc.txt
+undead:undead.exe:Undead:Monster-placing puzzle:Place ghosts, vampires and zombies so that the right numbers of them can be seen in mirrors.
+!end
diff --git a/undead.c b/undead.c
new file mode 100644 (file)
index 0000000..ad0ab79
--- /dev/null
+++ b/undead.c
@@ -0,0 +1,2738 @@
+/*
+ * undead: Implementation of Haunted Mirror Mazes
+ *
+ * http://www.janko.at/Raetsel/Spukschloss/index.htm
+ *
+ * Puzzle definition is the total number of each monster type, the
+ * grid definition, and the list of sightings (clockwise, starting
+ * from top left corner)
+ *
+ * Example: (Janko puzzle No. 1,
+ * http://www.janko.at/Raetsel/Spukschloss/001.a.htm )
+ *
+ *   Ghosts: 0 Vampires: 2 Zombies: 6
+ *
+ *     2 1 1 1
+ *   1 \ \ . / 2
+ *   0 \ . / . 2
+ *   0 / . / . 2
+ *   3 . . . \ 2
+ *     3 3 2 2
+ *
+ *  would be encoded into: 
+ *     4x4:0,2,6,LLaRLaRaRaRdL,2,1,1,1,2,2,2,2,2,2,3,3,3,0,0,1
+ *
+ *  Additionally, the game description can contain monsters fixed at a
+ *  certain grid position. The internal generator does not (yet) use
+ *  this feature, but this is needed to enter puzzles like Janko No.
+ *  14, which is encoded as:
+ *  8x5:12,12,0,LaRbLaRaLaRLbRaVaVaGRaRaRaLbLaRbRLb,0,2,0,2,2,1,2,1,3,1,0,1,8,4,3,0,0,2,3,2,7,2,1,6,2,1
+ * 
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+
+enum {
+    COL_BACKGROUND,
+    COL_GRID,
+    COL_TEXT,
+    COL_ERROR,
+    COL_HIGHLIGHT,
+    COL_FLASH,
+    COL_GHOST,
+    COL_ZOMBIE,
+    COL_VAMPIRE,
+    COL_DONE,
+    NCOLOURS
+};
+
+#define DIFFLIST(A)                             \
+    A(EASY,Easy,e)                              \
+    A(NORMAL,Normal,n)                          \
+    A(TRICKY,Tricky,t)
+#define ENUM(upper,title,lower) DIFF_ ## upper,
+#define TITLE(upper,title,lower) #title,
+#define ENCODE(upper,title,lower) #lower
+#define CONFIG(upper,title,lower) ":" #title
+enum { DIFFLIST(ENUM) DIFFCOUNT };
+static char const *const undead_diffnames[] = { DIFFLIST(TITLE) "(count)" };
+static char const undead_diffchars[] = DIFFLIST(ENCODE);
+#define DIFFCONFIG DIFFLIST(CONFIG)
+
+struct game_params {
+    int w;      /* Grid width */
+    int h;      /* Grid height */
+    int diff;   /* Puzzle difficulty */
+};
+
+static const struct game_params undead_presets[] = {
+    {  4,  4, DIFF_EASY },
+    {  4,  4, DIFF_NORMAL },
+    {  4,  4, DIFF_TRICKY },
+    {  5,  5, DIFF_EASY },
+    {  5,  5, DIFF_NORMAL },
+    {  5,  5, DIFF_TRICKY },
+    {  7,  7, DIFF_EASY },
+    {  7,  7, DIFF_NORMAL }
+};
+
+#define DEFAULT_PRESET 1
+
+static game_params *default_params(void) {
+    game_params *ret = snew(game_params);
+
+    *ret = undead_presets[DEFAULT_PRESET];
+    return ret;
+}
+
+static int game_fetch_preset(int i, char **name, game_params **params) {
+    game_params *ret;
+    char buf[64];
+
+    if (i < 0 || i >= lenof(undead_presets)) return FALSE;
+
+    ret = default_params();
+    *ret = undead_presets[i]; /* struct copy */
+    *params = ret;
+
+    sprintf(buf, "%dx%d %s",
+            undead_presets[i].w, undead_presets[i].h,
+            undead_diffnames[undead_presets[i].diff]);
+    *name = dupstr(buf);
+
+    return TRUE;
+}
+
+static void free_params(game_params *params) {
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;            /* structure copy */
+    return ret;
+}
+
+static void decode_params(game_params *params, char const *string) {
+    params->w = params->h = atoi(string);
+
+    while (*string && isdigit((unsigned char) *string)) ++string;
+    if (*string == 'x') {
+        string++;
+        params->h = atoi(string);
+        while (*string && isdigit((unsigned char)*string)) string++;
+    }
+
+    params->diff = DIFF_NORMAL;
+    if (*string == 'd') {
+        int i;
+        string++;
+        for (i = 0; i < DIFFCOUNT; i++)
+            if (*string == undead_diffchars[i])
+                params->diff = i;
+        if (*string) string++;
+    }
+
+    return;
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char buf[256];
+    sprintf(buf, "%dx%d", params->w, params->h);
+    if (full)
+        sprintf(buf + strlen(buf), "d%c", undead_diffchars[params->diff]);
+    return dupstr(buf);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+    char buf[64];
+
+    ret = snewn(4, config_item);
+
+    ret[0].name = "Width";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->w);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Height";
+    ret[1].type = C_STRING;
+    sprintf(buf, "%d", params->h);
+    ret[1].sval = dupstr(buf);
+    ret[1].ival = 0;
+
+    ret[2].name = "Difficulty";
+    ret[2].type = C_CHOICES;
+    ret[2].sval = DIFFCONFIG;
+    ret[2].ival = params->diff;
+
+    ret[3].name = NULL;
+    ret[3].type = C_END;
+    ret[3].sval = NULL;
+    ret[3].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w = atoi(cfg[0].sval);
+    ret->h = atoi(cfg[1].sval);
+    ret->diff = cfg[2].ival;
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    if ((params->w * params->h ) > 54)  return "Grid is too big";
+    if (params->w < 3)                  return "Width must be at least 3";
+    if (params->h < 3)                  return "Height must be at least 3";
+    if (params->diff >= DIFFCOUNT)      return "Unknown difficulty rating";
+    return NULL;
+}
+
+/* --------------------------------------------------------------- */
+/* Game state allocation, deallocation. */
+
+struct path {
+    int length;
+    int *p;
+    int grid_start;
+    int grid_end;
+    int num_monsters;
+    int *mapping;
+    int sightings_start;
+    int sightings_end;
+    int *xy;
+};
+
+struct game_common {
+    int refcount;
+    struct game_params params;
+    int wh;
+    int num_ghosts,num_vampires,num_zombies,num_total;
+    int num_paths;
+    struct path *paths;
+    int *grid;
+    int *xinfo;
+    int *fixed;
+    int solved;
+};
+
+struct game_state {
+    struct game_common *common;
+    int *guess;
+    unsigned char *pencils;
+    unsigned char *cell_errors;
+    unsigned char *hint_errors;
+    unsigned char *hints_done;
+    unsigned char count_errors[3];
+    int solved;
+    int cheated;
+};
+
+static game_state *new_state(const game_params *params) {
+    int i;
+    game_state *state = snew(game_state);
+    state->common = snew(struct game_common);
+
+    state->common->refcount = 1;
+    state->common->params.w = params->w;
+    state->common->params.h = params->h;
+    state->common->params.diff = params->diff;
+
+    state->common->wh = (state->common->params.w +2) * (state->common->params.h +2);
+
+    state->common->num_ghosts = 0;
+    state->common->num_vampires = 0;
+    state->common->num_zombies = 0;
+    state->common->num_total = 0;
+
+    state->common->grid = snewn(state->common->wh, int);
+    state->common->xinfo = snewn(state->common->wh, int);
+    state->common->fixed = NULL;
+    state->common->solved = FALSE;
+
+    state->common->num_paths =
+        state->common->params.w + state->common->params.h;
+    state->common->paths = snewn(state->common->num_paths, struct path);
+
+    for (i=0;i<state->common->num_paths;i++) {
+        state->common->paths[i].length = 0;
+        state->common->paths[i].grid_start = -1;
+        state->common->paths[i].grid_end = -1;
+        state->common->paths[i].num_monsters = 0;
+        state->common->paths[i].sightings_start = 0;
+        state->common->paths[i].sightings_end = 0;
+        state->common->paths[i].p = snewn(state->common->wh,int);
+        state->common->paths[i].xy = snewn(state->common->wh,int);
+        state->common->paths[i].mapping = snewn(state->common->wh,int);
+    }
+
+    state->guess = NULL;
+    state->pencils = NULL;
+
+    state->cell_errors = snewn(state->common->wh, unsigned char);
+    for (i=0;i<state->common->wh;i++)
+        state->cell_errors[i] = FALSE;
+    state->hint_errors = snewn(2*state->common->num_paths, unsigned char);
+    for (i=0;i<2*state->common->num_paths;i++)
+        state->hint_errors[i] = FALSE;
+    state->hints_done = snewn(2 * state->common->num_paths, unsigned char);
+    memset(state->hints_done, 0,
+           2 * state->common->num_paths * sizeof(unsigned char));
+    for (i=0;i<3;i++)
+        state->count_errors[i] = FALSE;
+
+    state->solved = FALSE;
+    state->cheated = FALSE;
+    
+    return state;
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    game_state *ret = snew(game_state);
+
+    ret->common = state->common;
+    ret->common->refcount++;
+
+    if (state->guess != NULL) {
+        ret->guess = snewn(ret->common->num_total,int);
+        memcpy(ret->guess, state->guess, ret->common->num_total*sizeof(int));
+    }
+    else ret->guess = NULL;
+
+    if (state->pencils != NULL) {
+        ret->pencils = snewn(ret->common->num_total,unsigned char);
+        memcpy(ret->pencils, state->pencils,
+               ret->common->num_total*sizeof(unsigned char));
+    }
+    else ret->pencils = NULL;
+
+    if (state->cell_errors != NULL) {
+        ret->cell_errors = snewn(ret->common->wh,unsigned char);
+        memcpy(ret->cell_errors, state->cell_errors,
+               ret->common->wh*sizeof(unsigned char));
+    }
+    else ret->cell_errors = NULL;
+
+    if (state->hint_errors != NULL) {
+        ret->hint_errors = snewn(2*ret->common->num_paths,unsigned char);
+        memcpy(ret->hint_errors, state->hint_errors,
+               2*ret->common->num_paths*sizeof(unsigned char));
+    }
+    else ret->hint_errors = NULL;
+
+    if (state->hints_done != NULL) {
+        ret->hints_done = snewn(2 * state->common->num_paths, unsigned char);
+        memcpy(ret->hints_done, state->hints_done,
+               2 * state->common->num_paths * sizeof(unsigned char));
+    }
+    else ret->hints_done = NULL;
+
+    ret->count_errors[0] = state->count_errors[0];
+    ret->count_errors[1] = state->count_errors[1];
+    ret->count_errors[2] = state->count_errors[2];
+
+    ret->solved = state->solved;
+    ret->cheated = state->cheated;
+    
+    return ret;
+}
+
+static void free_game(game_state *state) {
+    int i;
+
+    state->common->refcount--;
+    if (state->common->refcount == 0) {
+        for (i=0;i<state->common->num_paths;i++) {
+            sfree(state->common->paths[i].mapping);
+            sfree(state->common->paths[i].xy);
+            sfree(state->common->paths[i].p);
+        }
+        sfree(state->common->paths);
+        sfree(state->common->xinfo);
+        sfree(state->common->grid);
+        if (state->common->fixed != NULL) sfree(state->common->fixed);
+        sfree(state->common);
+    }
+    if (state->hints_done != NULL) sfree(state->hints_done);
+    if (state->hint_errors != NULL) sfree(state->hint_errors);
+    if (state->cell_errors != NULL) sfree(state->cell_errors);
+    if (state->pencils != NULL) sfree(state->pencils);
+    if (state->guess != NULL) sfree(state->guess);
+    sfree(state);
+
+    return;
+}
+
+/* --------------------------------------------------------------- */
+/* Puzzle generator */
+
+/* cell states */
+enum {
+    CELL_EMPTY,
+    CELL_MIRROR_L,
+    CELL_MIRROR_R,
+    CELL_GHOST,
+    CELL_VAMPIRE,
+    CELL_ZOMBIE,
+    CELL_UNDEF
+};
+
+/* grid walk directions */
+enum {
+    DIRECTION_NONE,
+    DIRECTION_UP,
+    DIRECTION_RIGHT,
+    DIRECTION_LEFT,
+    DIRECTION_DOWN
+};
+
+int range2grid(int rangeno, int width, int height, int *x, int *y) {
+
+    if (rangeno < 0) {
+        *x = 0; *y = 0; return DIRECTION_NONE;
+    }
+    if (rangeno < width) {
+        *x = rangeno+1; *y = 0; return DIRECTION_DOWN;
+    }
+    rangeno = rangeno - width;
+    if (rangeno < height) {
+        *x = width+1; *y = rangeno+1; return DIRECTION_LEFT;
+    }
+    rangeno = rangeno - height;
+    if (rangeno < width) {
+        *x = width-rangeno; *y = height+1; return DIRECTION_UP;
+    }
+    rangeno = rangeno - width;
+    if (rangeno < height) {
+        *x = 0; *y = height-rangeno; return DIRECTION_RIGHT;
+    }
+    *x = 0; *y = 0;
+    return DIRECTION_NONE;
+}
+
+int grid2range(int x, int y, int w, int h) {
+    if (x>0 && x<w+1 && y>0 && y<h+1)           return -1;
+    if (x<0 || x>w+1 || y<0 || y>h+1)           return -1;
+    if ((x == 0 || x==w+1) && (y==0 || y==h+1)) return -1;
+    if (y==0)                                   return x-1;
+    if (x==(w+1))                               return y-1+w;
+    if (y==(h+1))                               return 2*w + h - x;
+    return 2*(w+h) - y;
+}
+
+void make_paths(game_state *state) {
+    int i;
+    int count = 0;
+
+    for (i=0;i<2*(state->common->params.w + state->common->params.h);i++) {
+        int x,y,dir;
+        int j,k,num_monsters;
+        int found;
+        int c,p; 
+        found = FALSE;
+        /* Check whether inverse path is already in list */
+        for (j=0;j<count;j++) {
+            if (i == state->common->paths[j].grid_end) {
+                found = TRUE;
+                break;
+            }
+        }
+        if (found) continue;
+
+        /* We found a new path through the mirror maze */
+        state->common->paths[count].grid_start = i;     
+        dir = range2grid(i, state->common->params.w,
+                         state->common->params.h,&x,&y);     
+        state->common->paths[count].sightings_start =
+            state->common->grid[x+y*(state->common->params.w +2)];
+        while (TRUE) {
+            int c,r;
+
+            if      (dir == DIRECTION_DOWN)     y++;
+            else if (dir == DIRECTION_LEFT)     x--;
+            else if (dir == DIRECTION_UP)       y--;
+            else if (dir == DIRECTION_RIGHT)    x++;
+            
+            r = grid2range(x, y, state->common->params.w,
+                           state->common->params.h);
+            if (r != -1) {
+                state->common->paths[count].grid_end = r;               
+                state->common->paths[count].sightings_end =
+                    state->common->grid[x+y*(state->common->params.w +2)];
+                break;
+            }
+
+            c = state->common->grid[x+y*(state->common->params.w+2)];
+            state->common->paths[count].xy[state->common->paths[count].length] =
+                x+y*(state->common->params.w+2);
+            if (c == CELL_MIRROR_L) {
+                state->common->paths[count].p[state->common->paths[count].length] = -1;
+                if (dir == DIRECTION_DOWN)          dir = DIRECTION_RIGHT;
+                else if (dir == DIRECTION_LEFT)     dir = DIRECTION_UP;
+                else if (dir == DIRECTION_UP)       dir = DIRECTION_LEFT;
+                else if (dir == DIRECTION_RIGHT)    dir = DIRECTION_DOWN;
+            }
+            else if (c == CELL_MIRROR_R) {
+                state->common->paths[count].p[state->common->paths[count].length] = -1;
+                if (dir == DIRECTION_DOWN)          dir = DIRECTION_LEFT;
+                else if (dir == DIRECTION_LEFT)     dir = DIRECTION_DOWN;
+                else if (dir == DIRECTION_UP)       dir = DIRECTION_RIGHT;
+                else if (dir == DIRECTION_RIGHT)    dir = DIRECTION_UP;
+            }
+            else {
+                state->common->paths[count].p[state->common->paths[count].length] =
+                    state->common->xinfo[x+y*(state->common->params.w+2)];
+            }
+            state->common->paths[count].length++;
+        }
+        /* Count unique monster entries in each path */
+        state->common->paths[count].num_monsters = 0;
+        for (j=0;j<state->common->num_total;j++) {
+            num_monsters = 0;
+            for (k=0;k<state->common->paths[count].length;k++)
+                if (state->common->paths[count].p[k] == j)
+                    num_monsters++;
+            if (num_monsters > 0)
+                state->common->paths[count].num_monsters++;
+        }
+
+        /* Generate mapping vector */
+        c = 0;
+        for (p=0;p<state->common->paths[count].length;p++) {
+            int m;
+            m = state->common->paths[count].p[p];
+            if (m == -1) continue;
+            found = FALSE;
+            for (j=0; j<c; j++)
+                if (state->common->paths[count].mapping[j] == m) found = TRUE;
+            if (!found) state->common->paths[count].mapping[c++] = m;
+        }
+        count++;
+    }
+    return;
+}
+
+struct guess {
+    int length;
+    int *guess;
+    int *possible;
+};
+
+int next_list(struct guess *g, int pos) {
+
+    if (pos == 0) {
+        if ((g->guess[pos] == 1 && g->possible[pos] == 1) || 
+            (g->guess[pos] == 2 && (g->possible[pos] == 3 ||
+                                    g->possible[pos] == 2)) ||
+            g->guess[pos] == 4)
+            return FALSE;
+        if (g->guess[pos] == 1 && (g->possible[pos] == 3 ||
+                                   g->possible[pos] == 7)) {
+            g->guess[pos] = 2; return TRUE;
+        }
+        if (g->guess[pos] == 1 && g->possible[pos] == 5) {
+            g->guess[pos] = 4; return TRUE;
+        }
+        if (g->guess[pos] == 2 && (g->possible[pos] == 6 || g->possible[pos] == 7)) {
+            g->guess[pos] = 4; return TRUE;
+        }
+    }
+
+    if (g->guess[pos] == 1) {
+        if (g->possible[pos] == 1) {
+            return next_list(g,pos-1);
+        }
+        if (g->possible[pos] == 3 || g->possible[pos] == 7) {
+            g->guess[pos] = 2; return TRUE;
+        }
+        if (g->possible[pos] == 5) {
+            g->guess[pos] = 4; return TRUE;
+        }
+    }
+
+    if (g->guess[pos] == 2) {
+        if (g->possible[pos] == 2) {
+            return next_list(g,pos-1);
+        }
+        if (g->possible[pos] == 3) {
+            g->guess[pos] = 1; return next_list(g,pos-1);
+        }
+        if (g->possible[pos] == 6 || g->possible[pos] == 7) {
+            g->guess[pos] = 4; return TRUE;
+        }
+    }
+
+    if (g->guess[pos] == 4) {
+        if (g->possible[pos] == 5 || g->possible[pos] == 7) {
+            g->guess[pos] = 1; return next_list(g,pos-1);
+        }
+        if (g->possible[pos] == 6) {
+            g->guess[pos] = 2; return next_list(g,pos-1);
+        }
+        if (g->possible[pos] == 4) {
+            return next_list(g,pos-1);
+        }
+    }
+    return FALSE;
+}
+
+void get_unique(game_state *state, int counter, random_state *rs) {
+
+    int p,i,c,pathlimit,count_uniques;
+    struct guess path_guess;
+    int *view_count;
+    
+    struct entry {
+        struct entry *link;
+        int *guess;
+        int start_view;
+        int end_view;
+    };
+
+    struct {
+        struct entry *head;
+        struct entry *node;
+    } views, single_views, test_views;
+
+    struct entry test_entry;
+
+    path_guess.length = state->common->paths[counter].num_monsters;
+    path_guess.guess = snewn(path_guess.length,int);
+    path_guess.possible = snewn(path_guess.length,int);
+    for (i=0;i<path_guess.length;i++) 
+        path_guess.guess[i] = path_guess.possible[i] = 0;
+
+    for (p=0;p<path_guess.length;p++) {
+        path_guess.possible[p] =
+            state->guess[state->common->paths[counter].mapping[p]];
+        switch (path_guess.possible[p]) {
+          case 1: path_guess.guess[p] = 1; break;
+          case 2: path_guess.guess[p] = 2; break;
+          case 3: path_guess.guess[p] = 1; break;
+          case 4: path_guess.guess[p] = 4; break;
+          case 5: path_guess.guess[p] = 1; break;
+          case 6: path_guess.guess[p] = 2; break;
+          case 7: path_guess.guess[p] = 1; break;
+        }
+    }
+
+    views.head = NULL;
+    views.node = NULL;
+
+    pathlimit = state->common->paths[counter].length + 1;
+    view_count = snewn(pathlimit*pathlimit, int);
+    for (i = 0; i < pathlimit*pathlimit; i++)
+        view_count[i] = 0;
+    
+    do {
+        int mirror, start_view, end_view;
+        
+        mirror = FALSE;
+        start_view = 0;
+        for (p=0;p<state->common->paths[counter].length;p++) {
+            if (state->common->paths[counter].p[p] == -1) mirror = TRUE;
+            else {
+                for (i=0;i<path_guess.length;i++) {
+                    if (state->common->paths[counter].p[p] ==
+                        state->common->paths[counter].mapping[i]) {
+                        if (path_guess.guess[i] == 1 && mirror == TRUE)
+                            start_view++;
+                        if (path_guess.guess[i] == 2 && mirror == FALSE)
+                            start_view++;
+                        if (path_guess.guess[i] == 4)
+                            start_view++;
+                        break;
+                    }
+                }
+            }
+        }
+        mirror = FALSE;
+        end_view = 0;
+        for (p=state->common->paths[counter].length-1;p>=0;p--) {
+            if (state->common->paths[counter].p[p] == -1) mirror = TRUE;
+            else {
+                for (i=0;i<path_guess.length;i++) {
+                    if (state->common->paths[counter].p[p] ==
+                        state->common->paths[counter].mapping[i]) {
+                        if (path_guess.guess[i] == 1 && mirror == TRUE)
+                            end_view++;
+                        if (path_guess.guess[i] == 2 && mirror == FALSE)
+                            end_view++;
+                        if (path_guess.guess[i] == 4)
+                            end_view++;
+                        break;
+                    }
+                }
+            }
+        }
+
+        assert(start_view >= 0 && start_view < pathlimit);
+        assert(end_view >= 0 && end_view < pathlimit);
+        i = start_view * pathlimit + end_view;
+        view_count[i]++;
+        if (view_count[i] == 1) {
+            views.node = snewn(1,struct entry);
+            views.node->link = views.head;
+            views.node->guess = snewn(path_guess.length,int);
+            views.head = views.node;
+            views.node->start_view = start_view;
+            views.node->end_view = end_view;
+            memcpy(views.node->guess, path_guess.guess,
+                   path_guess.length*sizeof(int));
+        }
+    } while (next_list(&path_guess, path_guess.length-1));
+
+    /*  extract single entries from view list */
+
+    test_views.head = views.head;
+    test_views.node = views.node;
+    
+    test_entry.guess = snewn(path_guess.length,int);
+
+    single_views.head = NULL;
+    single_views.node = NULL;
+
+    count_uniques = 0;
+    while (test_views.head != NULL) {
+        test_views.node = test_views.head;
+        test_views.head = test_views.head->link;
+        i = test_views.node->start_view * pathlimit + test_views.node->end_view;
+        if (view_count[i] == 1) {
+            single_views.node = snewn(1,struct entry);
+            single_views.node->link = single_views.head;
+            single_views.node->guess = snewn(path_guess.length,int);
+            single_views.head = single_views.node;
+            single_views.node->start_view = test_views.node->start_view;
+            single_views.node->end_view = test_views.node->end_view;
+            memcpy(single_views.node->guess, test_views.node->guess,
+                   path_guess.length*sizeof(int));
+            count_uniques++;
+        }
+    }
+
+    sfree(view_count);
+
+    if (count_uniques > 0) {
+        test_entry.start_view = 0;
+        test_entry.end_view = 0;
+        /* Choose one unique guess per random */
+        /* While we are busy with looping through single_views, we
+         * conveniently free the linked list single_view */
+        c = random_upto(rs,count_uniques);
+        while(single_views.head != NULL) {
+            single_views.node = single_views.head;
+            single_views.head = single_views.head->link;
+            if (c-- == 0) {
+                memcpy(test_entry.guess, single_views.node->guess,
+                       path_guess.length*sizeof(int));
+                test_entry.start_view = single_views.node->start_view;
+                test_entry.end_view = single_views.node->end_view;
+            }
+            sfree(single_views.node->guess);
+            sfree(single_views.node);
+        }
+        
+        /* Modify state_guess according to path_guess.mapping */
+        for (i=0;i<path_guess.length;i++)
+            state->guess[state->common->paths[counter].mapping[i]] =
+                test_entry.guess[i];
+    }
+
+    sfree(test_entry.guess);
+
+    while (views.head != NULL) {
+        views.node = views.head;
+        views.head = views.head->link;
+        sfree(views.node->guess);
+        sfree(views.node);
+    }
+
+    sfree(path_guess.possible);
+    sfree(path_guess.guess);
+
+    return;
+}
+
+int count_monsters(game_state *state,
+                   int *cGhost, int *cVampire, int *cZombie) {
+    int cNone;
+    int i;
+
+    *cGhost = *cVampire = *cZombie = cNone = 0;
+
+    for (i=0;i<state->common->num_total;i++) {
+        if (state->guess[i] == 1) (*cGhost)++;
+        else if (state->guess[i] == 2) (*cVampire)++;
+        else if (state->guess[i] == 4) (*cZombie)++;
+        else cNone++;
+    }
+
+    return cNone;
+}
+
+int check_numbers(game_state *state, int *guess) {
+    int valid;
+    int i;
+    int count_ghosts, count_vampires, count_zombies;
+
+    count_ghosts = count_vampires = count_zombies = 0;  
+    for (i=0;i<state->common->num_total;i++) {
+        if (guess[i] == 1) count_ghosts++;
+        if (guess[i] == 2) count_vampires++;
+        if (guess[i] == 4) count_zombies++;
+    }
+
+    valid = TRUE;
+
+    if (count_ghosts   > state->common->num_ghosts)   valid = FALSE; 
+    if (count_vampires > state->common->num_vampires) valid = FALSE; 
+    if (count_zombies > state->common->num_zombies)   valid = FALSE; 
+
+    return valid;
+}
+
+int check_solution(int *g, struct path path) {
+    int i;
+    int mirror;
+    int count;
+
+    count = 0;
+    mirror = FALSE;
+    for (i=0;i<path.length;i++) {
+        if (path.p[i] == -1) mirror = TRUE;
+        else {
+            if (g[path.p[i]] == 1 && mirror) count++;
+            else if (g[path.p[i]] == 2 && !mirror) count++;
+            else if (g[path.p[i]] == 4) count++;
+        }
+    }
+    if (count != path.sightings_start) return FALSE;    
+
+    count = 0;
+    mirror = FALSE;
+    for (i=path.length-1;i>=0;i--) {
+        if (path.p[i] == -1) mirror = TRUE;
+        else {
+            if (g[path.p[i]] == 1 && mirror) count++;
+            else if (g[path.p[i]] == 2 && !mirror) count++;
+            else if (g[path.p[i]] == 4) count++;
+        }
+    }
+    if (count != path.sightings_end) return FALSE;  
+
+    return TRUE;
+}
+
+int solve_iterative(game_state *state, struct path *paths) {
+    int solved;
+    int p,i,j,count;
+
+    int *guess;
+    int *possible;
+
+    struct guess loop;
+
+    solved = TRUE;
+    loop.length = state->common->num_total;
+    guess = snewn(state->common->num_total,int);
+    possible = snewn(state->common->num_total,int);
+
+    for (i=0;i<state->common->num_total;i++) {
+        guess[i] = state->guess[i];
+        possible[i] = 0;
+    }
+
+    for (p=0;p<state->common->num_paths;p++) {
+        if (paths[p].num_monsters > 0) {
+            loop.length = paths[p].num_monsters;
+            loop.guess = snewn(paths[p].num_monsters,int);
+            loop.possible = snewn(paths[p].num_monsters,int);
+
+            for (i=0;i<paths[p].num_monsters;i++) {
+                switch (state->guess[paths[p].mapping[i]]) {
+                  case 1: loop.guess[i] = 1; break;
+                  case 2: loop.guess[i] = 2; break;
+                  case 3: loop.guess[i] = 1; break;
+                  case 4: loop.guess[i] = 4; break;
+                  case 5: loop.guess[i] = 1; break;
+                  case 6: loop.guess[i] = 2; break;
+                  case 7: loop.guess[i] = 1; break;
+                }
+                loop.possible[i] = state->guess[paths[p].mapping[i]];
+                possible[paths[p].mapping[i]] = 0;
+            }
+
+            while(TRUE) {
+                for (i=0;i<state->common->num_total;i++) {
+                    guess[i] = state->guess[i];
+                }
+                count = 0;
+                for (i=0;i<paths[p].num_monsters;i++) 
+                    guess[paths[p].mapping[i]] = loop.guess[count++];
+                if (check_numbers(state,guess) &&
+                    check_solution(guess,paths[p]))
+                    for (j=0;j<paths[p].num_monsters;j++)
+                        possible[paths[p].mapping[j]] |= loop.guess[j];
+                if (!next_list(&loop,loop.length-1)) break;
+            }
+            for (i=0;i<paths[p].num_monsters;i++)       
+                state->guess[paths[p].mapping[i]] &=
+                    possible[paths[p].mapping[i]];
+            sfree(loop.possible);
+            sfree(loop.guess);
+        }
+    }
+
+    for (i=0;i<state->common->num_total;i++) {
+        if (state->guess[i] == 3 || state->guess[i] == 5 ||
+            state->guess[i] == 6 || state->guess[i] == 7) {
+            solved = FALSE; break;
+        }
+    }
+
+    sfree(possible);
+    sfree(guess);
+
+    return solved;
+}
+
+int solve_bruteforce(game_state *state, struct path *paths) {
+    int solved, correct;
+    int number_solutions;
+    int p,i;
+
+    struct guess loop;
+
+    loop.guess = snewn(state->common->num_total,int);
+    loop.possible = snewn(state->common->num_total,int);
+
+    for (i=0;i<state->common->num_total;i++) {
+        loop.possible[i] = state->guess[i];
+        switch (state->guess[i]) {
+          case 1: loop.guess[i] = 1; break;
+          case 2: loop.guess[i] = 2; break;
+          case 3: loop.guess[i] = 1; break;
+          case 4: loop.guess[i] = 4; break;
+          case 5: loop.guess[i] = 1; break;
+          case 6: loop.guess[i] = 2; break;
+          case 7: loop.guess[i] = 1; break;
+        }
+    }
+
+    solved = FALSE;
+    number_solutions = 0;
+
+    while (TRUE) {
+
+        correct = TRUE;
+        if (!check_numbers(state,loop.guess)) correct = FALSE;
+        else 
+            for (p=0;p<state->common->num_paths;p++)
+                if (!check_solution(loop.guess,paths[p])) {
+                    correct = FALSE; break;
+                }
+        if (correct) {
+            number_solutions++;
+            solved = TRUE;
+            if(number_solutions > 1) {
+                solved = FALSE;
+                break; 
+            }
+            for (i=0;i<state->common->num_total;i++)
+                state->guess[i] = loop.guess[i];
+        }
+        if (!next_list(&loop,state->common->num_total -1)) {
+            break;
+        }
+    }
+
+    sfree(loop.possible);
+    sfree(loop.guess);
+
+    return solved;
+}
+
+int path_cmp(const void *a, const void *b) {
+    const struct path *pa = (const struct path *)a;
+    const struct path *pb = (const struct path *)b;
+    return pa->num_monsters - pb->num_monsters;
+}
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                           char **aux, int interactive) {
+    int i,count,c,w,h,r,p,g;
+    game_state *new;
+
+    /* Variables for puzzle generation algorithm */
+    int filling;
+    int max_length;
+    int count_ghosts, count_vampires, count_zombies;
+    int abort;
+    float ratio;
+    
+    /* Variables for solver algorithm */
+    int solved_iterative, solved_bruteforce, contains_inconsistency,
+        count_ambiguous;
+    int iterative_depth;
+    int *old_guess;
+
+    /* Variables for game description generation */
+    int x,y;
+    char *e;
+    char *desc; 
+
+    i = 0;
+    while (TRUE) {
+        new = new_state(params);
+        abort = FALSE;
+
+        /* Fill grid with random mirrors and (later to be populated)
+         * empty monster cells */
+        count = 0;
+        for (h=1;h<new->common->params.h+1;h++)
+            for (w=1;w<new->common->params.w+1;w++) {
+                c = random_upto(rs,5);
+                if (c >= 2) {
+                    new->common->grid[w+h*(new->common->params.w+2)] = CELL_EMPTY;
+                    new->common->xinfo[w+h*(new->common->params.w+2)] = count++;
+                }
+                else if (c == 0) {
+                    new->common->grid[w+h*(new->common->params.w+2)] = 
+                        CELL_MIRROR_L;
+                    new->common->xinfo[w+h*(new->common->params.w+2)] = -1;
+                }
+                else {
+                    new->common->grid[w+h*(new->common->params.w+2)] =
+                        CELL_MIRROR_R;
+                    new->common->xinfo[w+h*(new->common->params.w+2)] = -1;         
+                }
+            }
+        new->common->num_total = count; /* Total number of monsters in maze */
+
+        /* Puzzle is boring if it has too few monster cells. Discard
+         * grid, make new grid */
+        if (new->common->num_total <= 4) {
+            free_game(new);
+            continue;
+        }
+
+        /* Monsters / Mirrors ratio should be balanced */
+        ratio = (float)new->common->num_total /
+            (float)(new->common->params.w * new->common->params.h);
+        if (ratio < 0.48 || ratio > 0.78) {
+            free_game(new);
+            continue;
+        }        
+
+        /* Assign clue identifiers */   
+        for (r=0;r<2*(new->common->params.w+new->common->params.h);r++) {
+            int x,y,gridno;
+            gridno = range2grid(r,new->common->params.w,new->common->params.h,
+                                &x,&y);
+            new->common->grid[x+y*(new->common->params.w +2)] = gridno;
+            new->common->xinfo[x+y*(new->common->params.w +2)] = 0;
+        }
+        /* The four corners don't matter at all for the game. Set them
+         * all to zero, just to have a nice data structure */
+        new->common->grid[0] = 0;        
+        new->common->xinfo[0] = 0;      
+        new->common->grid[new->common->params.w+1] = 0; 
+        new->common->xinfo[new->common->params.w+1] = 0;
+        new->common->grid[new->common->params.w+1 + (new->common->params.h+1)*(new->common->params.w+2)] = 0; 
+        new->common->xinfo[new->common->params.w+1 + (new->common->params.h+1)*(new->common->params.w+2)] = 0;
+        new->common->grid[(new->common->params.h+1)*(new->common->params.w+2)] = 0; 
+        new->common->xinfo[(new->common->params.h+1)*(new->common->params.w+2)] = 0;
+
+        /* Initialize solution vector */
+        new->guess = snewn(new->common->num_total,int);
+        for (g=0;g<new->common->num_total;g++) new->guess[g] = 7;
+
+        /* Initialize fixed flag from common. Not needed for the
+         * puzzle generator; initialize it for having clean code */
+        new->common->fixed = snewn(new->common->num_total,int);
+        for (g=0;g<new->common->num_total;g++)
+            new->common->fixed[g] = FALSE;
+
+        /* paths generation */
+        make_paths(new);
+
+        /* Grid is invalid if max. path length > threshold. Discard
+         * grid, make new one */
+        switch (new->common->params.diff) {
+          case DIFF_EASY:     max_length = min(new->common->params.w,new->common->params.h) + 1; break;
+          case DIFF_NORMAL:   max_length = (max(new->common->params.w,new->common->params.h) * 3) / 2; break;
+          case DIFF_TRICKY:   max_length = 9; break;
+          default:            max_length = 9; break;
+        }
+
+        for (p=0;p<new->common->num_paths;p++) {
+            if (new->common->paths[p].num_monsters > max_length) {
+                abort = TRUE;
+            }
+        }
+        if (abort) {
+            free_game(new);
+            continue;
+        }
+
+        qsort(new->common->paths, new->common->num_paths,
+              sizeof(struct path), path_cmp);
+
+        /* Grid monster initialization */
+        /*  For easy puzzles, we try to fill nearly the whole grid
+            with unique solution paths (up to 2) For more difficult
+            puzzles, we fill only roughly half the grid, and choose
+            random monsters for the rest For hard puzzles, we fill
+            even less paths with unique solutions */
+
+        switch (new->common->params.diff) {
+          case DIFF_EASY:   filling = 2; break;
+          case DIFF_NORMAL: filling = min( (new->common->params.w+new->common->params.h) , (new->common->num_total)/2 ); break;
+          case DIFF_TRICKY: filling = max( (new->common->params.w+new->common->params.h) , (new->common->num_total)/2 ); break;
+          default:          filling = 0; break;
+        }
+
+        count = 0;
+        while ( (count_monsters(new, &count_ghosts, &count_vampires,
+                                &count_zombies)) > filling) {
+            if ((count) >= new->common->num_paths) break;
+            if (new->common->paths[count].num_monsters == 0) {
+                count++;
+                continue;
+            }
+            get_unique(new,count,rs);
+            count++;
+        }
+
+        /* Fill any remaining ambiguous entries with random monsters */ 
+        for(g=0;g<new->common->num_total;g++) {
+            if (new->guess[g] == 7) {
+                r = random_upto(rs,3);
+                new->guess[g] = (r == 0) ? 1 : ( (r == 1) ? 2 : 4 );        
+            }
+        }
+
+        /*  Determine all hints */
+        count_monsters(new, &new->common->num_ghosts,
+                       &new->common->num_vampires, &new->common->num_zombies);
+
+        /* Puzzle is trivial if it has only one type of monster. Discard. */
+        if ((new->common->num_ghosts == 0 && new->common->num_vampires == 0) ||
+            (new->common->num_ghosts == 0 && new->common->num_zombies == 0) ||
+            (new->common->num_vampires == 0 && new->common->num_zombies == 0)) {
+            free_game(new);
+            continue;
+        }
+
+        /* Discard puzzle if difficulty Tricky, and it has only 1
+         * member of any monster type */
+        if (new->common->params.diff == DIFF_TRICKY && 
+            (new->common->num_ghosts <= 1 ||
+             new->common->num_vampires <= 1 || new->common->num_zombies <= 1)) {
+            free_game(new);
+            continue;
+        }
+
+        for (w=1;w<new->common->params.w+1;w++)
+            for (h=1;h<new->common->params.h+1;h++) {
+                c = new->common->xinfo[w+h*(new->common->params.w+2)];
+                if (c >= 0) {
+                    if (new->guess[c] == 1) new->common->grid[w+h*(new->common->params.w+2)] = CELL_GHOST;
+                    if (new->guess[c] == 2) new->common->grid[w+h*(new->common->params.w+2)] = CELL_VAMPIRE;
+                    if (new->guess[c] == 4) new->common->grid[w+h*(new->common->params.w+2)] = CELL_ZOMBIE;                 
+                }
+            }
+
+        /* Prepare path information needed by the solver (containing all hints) */  
+        for (p=0;p<new->common->num_paths;p++) {
+            int mirror,x,y;
+
+            new->common->paths[p].sightings_start = 0;
+            new->common->paths[p].sightings_end = 0;
+            
+            mirror = FALSE;
+            for (g=0;g<new->common->paths[p].length;g++) {
+
+                if (new->common->paths[p].p[g] == -1) mirror = TRUE;
+                else {
+                    if      (new->guess[new->common->paths[p].p[g]] == 1 && mirror == TRUE)  (new->common->paths[p].sightings_start)++;
+                    else if (new->guess[new->common->paths[p].p[g]] == 2 && mirror == FALSE) (new->common->paths[p].sightings_start)++;
+                    else if (new->guess[new->common->paths[p].p[g]] == 4)                    (new->common->paths[p].sightings_start)++;
+                }
+            }
+
+            mirror = FALSE;
+            for (g=new->common->paths[p].length-1;g>=0;g--) {
+                if (new->common->paths[p].p[g] == -1) mirror = TRUE;
+                else {
+                    if      (new->guess[new->common->paths[p].p[g]] == 1 && mirror == TRUE)  (new->common->paths[p].sightings_end)++;
+                    else if (new->guess[new->common->paths[p].p[g]] == 2 && mirror == FALSE) (new->common->paths[p].sightings_end)++;
+                    else if (new->guess[new->common->paths[p].p[g]] == 4)                    (new->common->paths[p].sightings_end)++;
+                }
+            }
+
+            range2grid(new->common->paths[p].grid_start,
+                       new->common->params.w,new->common->params.h,&x,&y);
+            new->common->grid[x+y*(new->common->params.w +2)] =
+                new->common->paths[p].sightings_start;
+            range2grid(new->common->paths[p].grid_end,
+                       new->common->params.w,new->common->params.h,&x,&y);
+            new->common->grid[x+y*(new->common->params.w +2)] =
+                new->common->paths[p].sightings_end;
+        }
+
+        /* Try to solve the puzzle with the iterative solver */
+        old_guess = snewn(new->common->num_total,int);
+        for (p=0;p<new->common->num_total;p++) {
+            new->guess[p] = 7;
+            old_guess[p] = 7;
+        }
+        iterative_depth = 0;
+        solved_iterative = FALSE;
+        contains_inconsistency = FALSE;
+        count_ambiguous = 0;
+
+        while (TRUE) {
+            int no_change;          
+            no_change = TRUE;
+            solved_iterative = solve_iterative(new,new->common->paths);
+            iterative_depth++;      
+            for (p=0;p<new->common->num_total;p++) {
+                if (new->guess[p] != old_guess[p]) no_change = FALSE;
+                old_guess[p] = new->guess[p];
+                if (new->guess[p] == 0) contains_inconsistency = TRUE;
+            }
+            if (solved_iterative || no_change) break;
+        } 
+
+        /* If necessary, try to solve the puzzle with the brute-force solver */
+        solved_bruteforce = FALSE;  
+        if (new->common->params.diff != DIFF_EASY &&
+            !solved_iterative && !contains_inconsistency) {
+            for (p=0;p<new->common->num_total;p++)
+                if (new->guess[p] != 1 && new->guess[p] != 2 &&
+                    new->guess[p] != 4) count_ambiguous++;
+
+            solved_bruteforce = solve_bruteforce(new, new->common->paths);
+        }   
+
+        /*  Determine puzzle difficulty level */    
+        if (new->common->params.diff == DIFF_EASY && solved_iterative &&
+            iterative_depth <= 3 && !contains_inconsistency) { 
+/*          printf("Puzzle level: EASY Level %d Ratio %f Ambiguous %d (Found after %i tries)\n",iterative_depth, ratio, count_ambiguous, i); */
+            break;
+        }
+
+        if (new->common->params.diff == DIFF_NORMAL &&
+            ((solved_iterative && iterative_depth > 3) ||
+             (solved_bruteforce && count_ambiguous < 4)) &&
+            !contains_inconsistency) {  
+/*          printf("Puzzle level: NORMAL Level %d Ratio %f Ambiguous %d (Found after %d tries)\n", iterative_depth, ratio, count_ambiguous, i); */
+            break;
+        }
+        if (new->common->params.diff == DIFF_TRICKY &&
+            solved_bruteforce && iterative_depth > 0 &&
+            count_ambiguous >= 4 && !contains_inconsistency) {
+/*          printf("Puzzle level: TRICKY Level %d Ratio %f Ambiguous %d (Found after %d tries)\n", iterative_depth, ratio, count_ambiguous, i); */
+            break;
+        }
+
+        /* If puzzle is not solvable or does not satisfy the desired
+         * difficulty level, free memory and start from scratch */    
+        sfree(old_guess);
+        free_game(new);
+        i++;
+    }
+    
+    /* We have a valid puzzle! */
+    
+    desc = snewn(10 + new->common->wh +
+                 6*(new->common->params.w + new->common->params.h), char);
+    e = desc;
+
+    /* Encode monster counts */
+    e += sprintf(e, "%d,", new->common->num_ghosts);
+    e += sprintf(e, "%d,", new->common->num_vampires);
+    e += sprintf(e, "%d,", new->common->num_zombies);
+
+    /* Encode grid */
+    count = 0;
+    for (y=1;y<new->common->params.h+1;y++)
+        for (x=1;x<new->common->params.w+1;x++) {
+            c = new->common->grid[x+y*(new->common->params.w+2)];
+            if (count > 25) {
+                *e++ = 'z';
+                count -= 26;
+            }
+            if (c != CELL_MIRROR_L && c != CELL_MIRROR_R) {
+                count++;
+            }
+            else if (c == CELL_MIRROR_L) {
+                if (count > 0) *e++ = (count-1 + 'a');
+                *e++ = 'L';
+                count = 0;
+            }
+            else {
+                if (count > 0) *e++ = (count-1 + 'a');
+                *e++ = 'R';
+                count = 0;          
+            }
+        }
+    if (count > 0) *e++ = (count-1 + 'a');
+
+    /* Encode hints */
+    for (p=0;p<2*(new->common->params.w + new->common->params.h);p++) {
+        range2grid(p,new->common->params.w,new->common->params.h,&x,&y);
+        e += sprintf(e, ",%d", new->common->grid[x+y*(new->common->params.w+2)]);
+    }
+
+    *e++ = '\0';
+    desc = sresize(desc, e - desc, char);
+
+    sfree(old_guess);
+    free_game(new);
+
+    return desc;
+}
+
+void num2grid(int num, int width, int height, int *x, int *y) {
+    *x = 1+(num%width);
+    *y = 1+(num/width);
+    return;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    int i;
+    int n;
+    int count;
+
+    game_state *state = new_state(params);
+
+    state->common->num_ghosts = atoi(desc);
+    while (*desc && isdigit((unsigned char)*desc)) desc++;
+    desc++;
+    state->common->num_vampires = atoi(desc);
+    while (*desc && isdigit((unsigned char)*desc)) desc++;
+    desc++;
+    state->common->num_zombies = atoi(desc);
+    while (*desc && isdigit((unsigned char)*desc)) desc++;
+    desc++;
+
+    state->common->num_total = state->common->num_ghosts + state->common->num_vampires + state->common->num_zombies;
+
+    state->guess = snewn(state->common->num_total,int);
+    state->pencils = snewn(state->common->num_total,unsigned char);
+    state->common->fixed = snewn(state->common->num_total,int);
+    for (i=0;i<state->common->num_total;i++) {
+        state->guess[i] = 7;
+        state->pencils[i] = 0;
+        state->common->fixed[i] = FALSE;
+    }
+    for (i=0;i<state->common->wh;i++)
+        state->cell_errors[i] = FALSE;
+    for (i=0;i<2*state->common->num_paths;i++)
+        state->hint_errors[i] = FALSE;
+    for (i=0;i<3;i++)
+        state->count_errors[i] = FALSE;
+
+    count = 0;
+    n = 0;
+    while (*desc != ',') {
+        int c;
+        int x,y;
+
+        if (*desc == 'L') {
+            num2grid(n,state->common->params.w,state->common->params.h,&x,&y);
+            state->common->grid[x+y*(state->common->params.w +2)] = CELL_MIRROR_L;
+            state->common->xinfo[x+y*(state->common->params.w+2)] = -1;
+            n++;
+        }
+        else if (*desc == 'R') {
+            num2grid(n,state->common->params.w,state->common->params.h,&x,&y);
+            state->common->grid[x+y*(state->common->params.w +2)] = CELL_MIRROR_R;
+            state->common->xinfo[x+y*(state->common->params.w+2)] = -1;
+            n++;
+        }
+        else if (*desc == 'G') {
+            num2grid(n,state->common->params.w,state->common->params.h,&x,&y);
+            state->common->grid[x+y*(state->common->params.w +2)] = CELL_GHOST;
+            state->common->xinfo[x+y*(state->common->params.w+2)] = count;
+            state->guess[count] = 1;
+            state->common->fixed[count++] = TRUE;
+            n++;
+        }
+        else if (*desc == 'V') {
+            num2grid(n,state->common->params.w,state->common->params.h,&x,&y);
+            state->common->grid[x+y*(state->common->params.w +2)] = CELL_VAMPIRE;
+            state->common->xinfo[x+y*(state->common->params.w+2)] = count;
+            state->guess[count] = 2;
+            state->common->fixed[count++] = TRUE;
+            n++;
+        }
+        else if (*desc == 'Z') {
+            num2grid(n,state->common->params.w,state->common->params.h,&x,&y);
+            state->common->grid[x+y*(state->common->params.w +2)] = CELL_ZOMBIE;
+            state->common->xinfo[x+y*(state->common->params.w+2)] = count;
+            state->guess[count] = 4;
+            state->common->fixed[count++] = TRUE;
+            n++;
+        }
+        else {
+            c = *desc - ('a' -1);
+            while (c-- > 0) {
+                num2grid(n,state->common->params.w,state->common->params.h,&x,&y);
+                state->common->grid[x+y*(state->common->params.w +2)] = CELL_EMPTY;
+                state->common->xinfo[x+y*(state->common->params.w+2)] = count;
+                state->guess[count] = 7;
+                state->common->fixed[count++] = FALSE;
+                n++;
+            }
+        }
+        desc++;
+    }
+    desc++;
+
+    for (i=0;i<2*(state->common->params.w + state->common->params.h);i++) {
+        int x,y;
+        int sights;
+
+        sights = atoi(desc);
+        while (*desc && isdigit((unsigned char)*desc)) desc++;
+        desc++;
+
+
+        range2grid(i,state->common->params.w,state->common->params.h,&x,&y);
+        state->common->grid[x+y*(state->common->params.w +2)] = sights;
+        state->common->xinfo[x+y*(state->common->params.w +2)] = -2;
+    }
+
+    state->common->grid[0] = 0;
+    state->common->xinfo[0] = -2;
+    state->common->grid[state->common->params.w+1] = 0;
+    state->common->xinfo[state->common->params.w+1] = -2;
+    state->common->grid[state->common->params.w+1 + (state->common->params.h+1)*(state->common->params.w+2)] = 0;
+    state->common->xinfo[state->common->params.w+1 + (state->common->params.h+1)*(state->common->params.w+2)] = -2;
+    state->common->grid[(state->common->params.h+1)*(state->common->params.w+2)] = 0;
+    state->common->xinfo[(state->common->params.h+1)*(state->common->params.w+2)] = -2;
+
+    make_paths(state);
+    qsort(state->common->paths, state->common->num_paths, sizeof(struct path), path_cmp);
+
+    return state;
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    int i;
+    int w = params->w, h = params->h;
+    int wh = w*h;
+    int area;
+    int monsters;
+    int monster_count;
+    const char *desc_s = desc;
+        
+    for (i=0;i<3;i++) {
+        if (!*desc) return "Faulty game description";
+        while (*desc && isdigit((unsigned char)*desc)) { desc++; }
+        if (*desc != ',') return "Invalid character in number list";
+        desc++;
+    }
+    desc = desc_s;
+    
+    area = monsters = monster_count = 0;
+    for (i=0;i<3;i++) {
+        monster_count += atoi(desc);
+        while (*desc && isdigit((unsigned char)*desc)) desc++;
+        desc++;
+    }
+    while (*desc && *desc != ',') {
+        if (*desc >= 'a' && *desc <= 'z') {
+            area += *desc - 'a' +1; monsters += *desc - 'a' +1;
+        } else if (*desc == 'G' || *desc == 'V' || *desc == 'Z') {
+            area++; monsters++;
+        } else if (*desc == 'L' || *desc == 'R') {
+            area++;
+        } else
+            return "Invalid character in grid specification";
+        desc++;
+    }
+    if (area < wh) return "Not enough data to fill grid";
+    else if (area > wh) return "Too much data to fill grid";
+    if (monsters != monster_count)
+        return "Monster numbers do not match grid spaces";
+
+    for (i = 0; i < 2*(w+h); i++) {
+        if (!*desc) return "Not enough numbers given after grid specification";
+        else if (*desc != ',') return "Invalid character in number list";
+        desc++;
+        while (*desc && isdigit((unsigned char)*desc)) { desc++; }
+    }
+
+    if (*desc) return "Unexpected additional data at end of game description";
+
+    return NULL;
+}
+
+static char *solve_game(const game_state *state_start, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    int p;
+    int *old_guess;
+    int iterative_depth;
+    int solved_iterative, solved_bruteforce, contains_inconsistency,
+        count_ambiguous;
+
+    int i;
+    char *move, *c;
+
+    game_state *solve_state = dup_game(currstate);
+
+    old_guess = snewn(solve_state->common->num_total,int);
+    for (p=0;p<solve_state->common->num_total;p++) {
+        if (solve_state->common->fixed[p]) {
+            old_guess[p] = solve_state->guess[p] = state_start->guess[p];
+        }
+        else {
+            old_guess[p] = solve_state->guess[p] = 7;
+        }
+    }
+    iterative_depth = 0;
+    solved_iterative = FALSE;
+    contains_inconsistency = FALSE;
+    count_ambiguous = 0;
+    
+    /* Try to solve the puzzle with the iterative solver */
+    while (TRUE) {
+        int no_change;
+        no_change = TRUE;
+        solved_iterative =
+            solve_iterative(solve_state,solve_state->common->paths);
+        iterative_depth++;
+        for (p=0;p<solve_state->common->num_total;p++) {
+            if (solve_state->guess[p] != old_guess[p]) no_change = FALSE;
+            old_guess[p] = solve_state->guess[p];
+            if (solve_state->guess[p] == 0) contains_inconsistency = TRUE;
+        }
+        if (solved_iterative || no_change || contains_inconsistency) break;
+    }
+
+    if (contains_inconsistency) {
+        *error = "Puzzle is inconsistent";
+        sfree(old_guess);
+        free_game(solve_state);
+        return NULL;
+    }
+
+    /* If necessary, try to solve the puzzle with the brute-force solver */
+    solved_bruteforce = FALSE;  
+    if (!solved_iterative) {
+        for (p=0;p<solve_state->common->num_total;p++)
+            if (solve_state->guess[p] != 1 && solve_state->guess[p] != 2 &&
+                solve_state->guess[p] != 4) count_ambiguous++;
+        solved_bruteforce =
+            solve_bruteforce(solve_state, solve_state->common->paths);
+    }
+
+    if (!solved_iterative && !solved_bruteforce) {
+        *error = "Puzzle is unsolvable";
+        sfree(old_guess);
+        free_game(solve_state);
+        return NULL;
+    }
+
+/*  printf("Puzzle solved at level %s, iterations %d, ambiguous %d\n", (solved_bruteforce ? "TRICKY" : "NORMAL"), iterative_depth, count_ambiguous); */
+
+    move = snewn(solve_state->common->num_total * 4 +2, char);
+    c = move;
+    *c++='S';
+    for (i = 0; i < solve_state->common->num_total; i++) {
+        if (solve_state->guess[i] == 1) c += sprintf(c, ";G%d", i);
+        if (solve_state->guess[i] == 2) c += sprintf(c, ";V%d", i);
+        if (solve_state->guess[i] == 4) c += sprintf(c, ";Z%d", i);
+    }
+    *c++ = '\0';
+    move = sresize(move, c - move, char);
+
+    sfree(old_guess);   
+    free_game(solve_state);
+    return move;
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    int w,h,c,r,xi,g;
+    char *ret;
+    char buf[120];
+
+    ret = snewn(50 + 6*(state->common->params.w +2) +
+                6*(state->common->params.h+2) +
+                3*(state->common->params.w * state->common->params.h), char);
+
+    sprintf(ret,"G: %d V: %d Z: %d\n\n",state->common->num_ghosts,
+            state->common->num_vampires, state->common->num_zombies);
+
+    for (h=0;h<state->common->params.h+2;h++) {
+        for (w=0;w<state->common->params.w+2;w++) {
+            c = state->common->grid[w+h*(state->common->params.w+2)];
+            xi = state->common->xinfo[w+h*(state->common->params.w+2)];
+            r = grid2range(w,h,state->common->params.w,state->common->params.h);
+            if (r != -1) {
+                sprintf(buf,"%2d", c);  strcat(ret,buf);
+            } else if (c == CELL_MIRROR_L) {
+                sprintf(buf," \\"); strcat(ret,buf);
+            } else if (c == CELL_MIRROR_R) {
+                sprintf(buf," /"); strcat(ret,buf);
+            } else if (xi >= 0) {
+                g = state->guess[xi];
+                if (g == 1)      { sprintf(buf," G"); strcat(ret,buf); }
+                else if (g == 2) { sprintf(buf," V"); strcat(ret,buf); }
+                else if (g == 4) { sprintf(buf," Z"); strcat(ret,buf); }
+                else             { sprintf(buf," ."); strcat(ret,buf); }
+            } else {
+                sprintf(buf,"  "); strcat(ret,buf);
+            }
+        }
+        sprintf(buf,"\n"); strcat(ret,buf);
+    }
+
+    return ret;
+}
+
+struct game_ui {
+    int hx, hy;                         /* as for solo.c, highlight pos */
+    int hshow, hpencil, hcursor;        /* show state, type, and ?cursor. */
+    int ascii;
+};
+
+static game_ui *new_ui(const game_state *state)
+{
+    game_ui *ui = snew(game_ui);
+    ui->hx = ui->hy = 0;
+    ui->hpencil = ui->hshow = ui->hcursor = 0;
+    ui->ascii = FALSE;
+    return ui;
+}
+
+static void free_ui(game_ui *ui) {
+    sfree(ui);
+    return;
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+    return;
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+    /* See solo.c; if we were pencil-mode highlighting and
+     * somehow a square has just been properly filled, cancel
+     * pencil mode. */
+    if (ui->hshow && ui->hpencil && !ui->hcursor) {
+        int g = newstate->guess[newstate->common->xinfo[ui->hx + ui->hy*(newstate->common->params.w+2)]];
+        if (g == 1 || g == 2 || g == 4)
+            ui->hshow = 0;
+    }
+}
+
+struct game_drawstate {
+    int tilesize, started, solved;
+    int w, h;
+        
+    int *monsters;
+    unsigned char *pencils;
+
+    unsigned char count_errors[3];
+    unsigned char *cell_errors;
+    unsigned char *hint_errors;
+    unsigned char *hints_done;
+
+    int hx, hy, hshow, hpencil; /* as for game_ui. */
+    int hflash;
+    int ascii;
+};
+
+static int is_clue(const game_state *state, int x, int y)
+{
+    int h = state->common->params.h, w = state->common->params.w;
+
+    if (((x == 0 || x == w + 1) && y > 0 && y <= h) ||
+        ((y == 0 || y == h + 1) && x > 0 && x <= w))
+        return TRUE;
+
+    return FALSE;
+}
+
+static int clue_index(const game_state *state, int x, int y)
+{
+    int h = state->common->params.h, w = state->common->params.w;
+
+    if (y == 0)
+        return x - 1;
+    else if (x == w + 1)
+        return w + y - 1;
+    else if (y == h + 1)
+        return 2 * w + h - x;
+    else if (x == 0)
+        return 2 * (w + h) - y;
+
+    return -1;
+}
+
+#define TILESIZE (ds->tilesize)
+#define BORDER (TILESIZE/4)
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    int gx,gy;
+    int g,xi;
+    char buf[80]; 
+
+    gx = ((x-BORDER-1) / TILESIZE );
+    gy = ((y-BORDER-2) / TILESIZE ) - 1;
+
+    if (button == 'a' || button == 'A') {
+        ui->ascii = !ui->ascii;
+        return "";      
+    }
+
+    if (button == 'm' || button == 'M') {
+        return dupstr("M");
+    }
+    
+    if (ui->hshow == 1 && ui->hpencil == 0) {
+        xi = state->common->xinfo[ui->hx + ui->hy*(state->common->params.w+2)];
+        if (xi >= 0 && !state->common->fixed[xi]) {
+            if (button == 'g' || button == 'G' || button == '1') {
+                if (!ui->hcursor) ui->hshow = 0;
+                sprintf(buf,"G%d",xi);
+                return dupstr(buf);
+            }
+            if (button == 'v' || button == 'V' || button == '2') {
+                if (!ui->hcursor) ui->hshow = 0;
+                sprintf(buf,"V%d",xi);
+                return dupstr(buf);
+            }
+            if (button == 'z' || button == 'Z' || button == '3') {
+                if (!ui->hcursor) ui->hshow = 0;
+                sprintf(buf,"Z%d",xi);
+                return dupstr(buf);
+            }
+            if (button == 'e' || button == 'E' || button == CURSOR_SELECT2 ||
+                button == '0' || button == '\b' ) {
+                if (!ui->hcursor) ui->hshow = 0;
+                sprintf(buf,"E%d",xi);
+                return dupstr(buf);
+            }
+        }       
+    }
+
+    if (IS_CURSOR_MOVE(button)) {
+        if (ui->hx == 0 && ui->hy == 0) {
+            ui->hx = 1;
+            ui->hy = 1;
+        }
+        else switch (button) {
+              case CURSOR_UP:     ui->hy -= (ui->hy > 1)     ? 1 : 0; break;
+              case CURSOR_DOWN:   ui->hy += (ui->hy < ds->h) ? 1 : 0; break;
+              case CURSOR_RIGHT:  ui->hx += (ui->hx < ds->w) ? 1 : 0; break;
+              case CURSOR_LEFT:   ui->hx -= (ui->hx > 1)     ? 1 : 0; break;
+            }
+        ui->hshow = ui->hcursor = 1;
+        return "";
+    }
+    if (ui->hshow && button == CURSOR_SELECT) {
+        ui->hpencil = 1 - ui->hpencil;
+        ui->hcursor = 1;
+        return "";
+    }
+
+    if (ui->hshow == 1 && ui->hpencil == 1) {
+        xi = state->common->xinfo[ui->hx + ui->hy*(state->common->params.w+2)];
+        if (xi >= 0 && !state->common->fixed[xi]) {
+            if (button == 'g' || button == 'G' || button == '1') {
+                sprintf(buf,"g%d",xi);
+                if (!ui->hcursor) ui->hpencil = ui->hshow = 0;
+                return dupstr(buf);
+            }
+            if (button == 'v' || button == 'V' || button == '2') {
+                sprintf(buf,"v%d",xi);
+                if (!ui->hcursor) ui->hpencil = ui->hshow = 0;
+                return dupstr(buf);
+            }
+            if (button == 'z' || button == 'Z' || button == '3') {
+                sprintf(buf,"z%d",xi);
+                if (!ui->hcursor) ui->hpencil = ui->hshow = 0;
+                return dupstr(buf);
+            }
+            if (button == 'e' || button == 'E' || button == CURSOR_SELECT2 ||
+                button == '0' || button == '\b') {
+                sprintf(buf,"E%d",xi);
+                if (!ui->hcursor) ui->hpencil = ui->hshow = 0;
+                return dupstr(buf);
+            }
+        }       
+    }
+
+    if (gx > 0 && gx < ds->w+1 && gy > 0 && gy < ds->h+1) {
+        xi = state->common->xinfo[gx+gy*(state->common->params.w+2)];
+        if (xi >= 0 && !state->common->fixed[xi]) {
+            g = state->guess[xi];
+            if (ui->hshow == 0) {
+                if (button == LEFT_BUTTON) {
+                    ui->hshow = 1; ui->hpencil = 0; ui->hcursor = 0;
+                    ui->hx = gx; ui->hy = gy;
+                    return "";
+                }
+                else if (button == RIGHT_BUTTON && g == 7) {
+                    ui->hshow = 1; ui->hpencil = 1; ui->hcursor = 0;
+                    ui->hx = gx; ui->hy = gy;
+                    return "";
+                }
+            }
+            else if (ui->hshow == 1) {
+                if (button == LEFT_BUTTON) {
+                    if (ui->hpencil == 0) {
+                        if (gx == ui->hx && gy == ui->hy) {
+                            ui->hshow = 0; ui->hpencil = 0; ui->hcursor = 0;
+                            ui->hx = 0; ui->hy = 0;
+                            return "";
+                        }
+                        else {
+                            ui->hshow = 1; ui->hpencil = 0; ui->hcursor = 0;
+                            ui->hx = gx; ui->hy = gy;
+                            return "";
+                        }
+                    }
+                    else {
+                        ui->hshow = 1; ui->hpencil = 0; ui->hcursor = 0;
+                        ui->hx = gx; ui->hy = gy;
+                        return "";
+                    }
+                }
+                else if (button == RIGHT_BUTTON) {
+                    if (ui->hpencil == 0 && g == 7) {
+                        ui->hshow = 1; ui->hpencil = 1; ui->hcursor = 0;
+                        ui->hx = gx; ui->hy = gy;
+                        return "";
+                    }
+                    else {
+                        if (gx == ui->hx && gy == ui->hy) {
+                            ui->hshow = 0; ui->hpencil = 0; ui->hcursor = 0;
+                            ui->hx = 0; ui->hy = 0;
+                            return "";
+                        }
+                        else if (g == 7) {
+                            ui->hshow = 1; ui->hpencil = 1; ui->hcursor = 0;
+                            ui->hx = gx; ui->hy = gy;
+                            return "";
+                        }
+                    }
+                }
+            }
+        }
+    } else if (button == LEFT_BUTTON) {
+        if (is_clue(state, gx, gy)) {
+            sprintf(buf, "D%d,%d", gx, gy);
+            return dupstr(buf);
+        }
+    }
+
+    return NULL;
+}
+
+int check_numbers_draw(game_state *state, int *guess) {
+    int valid, filled;
+    int i,x,y,xy;
+    int count_ghosts, count_vampires, count_zombies;
+
+    count_ghosts = count_vampires = count_zombies = 0;  
+    for (i=0;i<state->common->num_total;i++) {
+        if (guess[i] == 1) count_ghosts++;
+        if (guess[i] == 2) count_vampires++;
+        if (guess[i] == 4) count_zombies++;
+    }
+
+    valid = TRUE;
+    filled = (count_ghosts + count_vampires + count_zombies >=
+              state->common->num_total);
+
+    if (count_ghosts > state->common->num_ghosts ||
+        (filled && count_ghosts != state->common->num_ghosts) ) {
+        valid = FALSE; 
+        state->count_errors[0] = TRUE; 
+        for (x=1;x<state->common->params.w+1;x++)
+            for (y=1;y<state->common->params.h+1;y++) {
+                xy = x+y*(state->common->params.w+2);
+                if (state->common->xinfo[xy] >= 0 &&
+                    guess[state->common->xinfo[xy]] == 1)
+                    state->cell_errors[xy] = TRUE;
+            }
+    }
+    if (count_vampires > state->common->num_vampires ||
+        (filled && count_vampires != state->common->num_vampires) ) {
+        valid = FALSE; 
+        state->count_errors[1] = TRUE; 
+        for (x=1;x<state->common->params.w+1;x++)
+            for (y=1;y<state->common->params.h+1;y++) {
+                xy = x+y*(state->common->params.w+2);
+                if (state->common->xinfo[xy] >= 0 &&
+                    guess[state->common->xinfo[xy]] == 2)
+                    state->cell_errors[xy] = TRUE;
+            }
+    }
+    if (count_zombies > state->common->num_zombies ||
+        (filled && count_zombies != state->common->num_zombies) )  {
+        valid = FALSE; 
+        state->count_errors[2] = TRUE; 
+        for (x=1;x<state->common->params.w+1;x++)
+            for (y=1;y<state->common->params.h+1;y++) {
+                xy = x+y*(state->common->params.w+2);
+                if (state->common->xinfo[xy] >= 0 &&
+                    guess[state->common->xinfo[xy]] == 4)
+                    state->cell_errors[xy] = TRUE;
+            }
+    }
+
+    return valid;
+}
+
+int check_path_solution(game_state *state, int p) {
+    int i;
+    int mirror;
+    int count;
+    int correct;
+    int unfilled;
+
+    count = 0;
+    mirror = FALSE;
+    correct = TRUE;
+
+    unfilled = 0;
+    for (i=0;i<state->common->paths[p].length;i++) {
+        if (state->common->paths[p].p[i] == -1) mirror = TRUE;
+        else {
+            if (state->guess[state->common->paths[p].p[i]] == 1 && mirror)
+                count++;
+            else if (state->guess[state->common->paths[p].p[i]] == 2 && !mirror)
+                count++;
+            else if (state->guess[state->common->paths[p].p[i]] == 4)
+                count++;
+            else if (state->guess[state->common->paths[p].p[i]] == 7)
+                unfilled++;
+        }
+    }
+
+    if (count            > state->common->paths[p].sightings_start ||
+        count + unfilled < state->common->paths[p].sightings_start)
+    {
+        correct = FALSE;
+        state->hint_errors[state->common->paths[p].grid_start] = TRUE;
+    }
+
+    count = 0;
+    mirror = FALSE;
+    unfilled = 0;
+    for (i=state->common->paths[p].length-1;i>=0;i--) {
+        if (state->common->paths[p].p[i] == -1) mirror = TRUE;
+        else {
+            if (state->guess[state->common->paths[p].p[i]] == 1 && mirror)
+                count++;
+            else if (state->guess[state->common->paths[p].p[i]] == 2 && !mirror)
+                count++;
+            else if (state->guess[state->common->paths[p].p[i]] == 4)
+                count++;
+            else if (state->guess[state->common->paths[p].p[i]] == 7)
+                unfilled++;
+        }
+    }
+
+    if (count            > state->common->paths[p].sightings_end ||
+        count + unfilled < state->common->paths[p].sightings_end)
+    {
+        correct = FALSE;
+        state->hint_errors[state->common->paths[p].grid_end] = TRUE;
+    }
+
+    if (!correct) {
+        for (i=0;i<state->common->paths[p].length;i++) 
+            state->cell_errors[state->common->paths[p].xy[i]] = TRUE;
+    }
+
+    return correct;
+}
+
+static game_state *execute_move(const game_state *state, const char *move)
+{
+    int x,y,n,p,i;
+    char c;
+    int correct; 
+    int solver; 
+
+    game_state *ret = dup_game(state);
+    solver = FALSE;
+
+    while (*move) {
+        c = *move;
+        if (c == 'S') {
+            move++;
+            solver = TRUE;
+        }
+        if (c == 'G' || c == 'V' || c == 'Z' || c == 'E' ||
+            c == 'g' || c == 'v' || c == 'z') {
+            move++;
+            sscanf(move, "%d%n", &x, &n);
+            if (c == 'G') ret->guess[x] = 1;
+            if (c == 'V') ret->guess[x] = 2;
+            if (c == 'Z') ret->guess[x] = 4;
+            if (c == 'E') { ret->guess[x] = 7; ret->pencils[x] = 0; }
+            if (c == 'g') ret->pencils[x] ^= 1;
+            if (c == 'v') ret->pencils[x] ^= 2;
+            if (c == 'z') ret->pencils[x] ^= 4;
+            move += n;
+        }
+        if (c == 'D' && sscanf(move + 1, "%d,%d%n", &x, &y, &n) == 2 &&
+            is_clue(ret, x, y)) {
+            ret->hints_done[clue_index(ret, x, y)] ^= 1;
+            move += n + 1;
+        }
+        if (c == 'M') {
+            /*
+             * Fill in absolutely all pencil marks in unfilled
+             * squares, for those who like to play by the rigorous
+             * approach of starting off in that state and eliminating
+             * things.
+             */
+            for (i = 0; i < ret->common->wh; i++)
+                if (ret->guess[i] == 7)
+                    ret->pencils[i] = 7;
+            move++;
+        }
+        if (*move == ';') move++;
+    }
+
+    correct = TRUE;
+
+    for (i=0;i<ret->common->wh;i++) ret->cell_errors[i] = FALSE;
+    for (i=0;i<2*ret->common->num_paths;i++) ret->hint_errors[i] = FALSE;
+    for (i=0;i<3;i++) ret->count_errors[i] = FALSE;
+
+    if (!check_numbers_draw(ret,ret->guess)) correct = FALSE;
+
+    for (p=0;p<state->common->num_paths;p++)
+        if (!check_path_solution(ret,p)) correct = FALSE;
+
+    for (i=0;i<state->common->num_total;i++)
+        if (!(ret->guess[i] == 1 || ret->guess[i] == 2 ||
+              ret->guess[i] == 4)) correct = FALSE;
+
+    if (correct && !solver) ret->solved = TRUE;
+    if (solver) ret->cheated = TRUE;
+
+    return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+#define PREFERRED_TILE_SIZE 64
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    struct { int tilesize; } ads, *ds = &ads;
+    ads.tilesize = tilesize;
+
+    *x = 2*BORDER+(params->w+2)*TILESIZE;
+    *y = 2*BORDER+(params->h+3)*TILESIZE;
+    return;
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+    return;
+}
+
+#define COLOUR(ret, i, r, g, b)     ((ret[3*(i)+0] = (r)), (ret[3*(i)+1] = (g)), (ret[3*(i)+2] = (b)))
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+
+    frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
+
+    ret[COL_GRID * 3 + 0] = 0.0F;
+    ret[COL_GRID * 3 + 1] = 0.0F;
+    ret[COL_GRID * 3 + 2] = 0.0F;
+
+    ret[COL_TEXT * 3 + 0] = 0.0F;
+    ret[COL_TEXT * 3 + 1] = 0.0F;
+    ret[COL_TEXT * 3 + 2] = 0.0F;
+
+    ret[COL_ERROR * 3 + 0] = 1.0F;
+    ret[COL_ERROR * 3 + 1] = 0.0F;
+    ret[COL_ERROR * 3 + 2] = 0.0F;
+
+    ret[COL_HIGHLIGHT * 3 + 0] = 0.78F * ret[COL_BACKGROUND * 3 + 0];
+    ret[COL_HIGHLIGHT * 3 + 1] = 0.78F * ret[COL_BACKGROUND * 3 + 1];
+    ret[COL_HIGHLIGHT * 3 + 2] = 0.78F * ret[COL_BACKGROUND * 3 + 2];
+
+    ret[COL_FLASH * 3 + 0] = 1.0F;
+    ret[COL_FLASH * 3 + 1] = 1.0F;
+    ret[COL_FLASH * 3 + 2] = 1.0F;
+
+    ret[COL_GHOST * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 0.5F;
+    ret[COL_GHOST * 3 + 1] = ret[COL_BACKGROUND * 3 + 0];
+    ret[COL_GHOST * 3 + 2] = ret[COL_BACKGROUND * 3 + 0];
+
+    ret[COL_ZOMBIE * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 0.5F;
+    ret[COL_ZOMBIE * 3 + 1] = ret[COL_BACKGROUND * 3 + 0];
+    ret[COL_ZOMBIE * 3 + 2] = ret[COL_BACKGROUND * 3 + 0] * 0.5F;
+
+    ret[COL_VAMPIRE * 3 + 0] = ret[COL_BACKGROUND * 3 + 0];
+    ret[COL_VAMPIRE * 3 + 1] = ret[COL_BACKGROUND * 3 + 0] * 0.9F;
+    ret[COL_VAMPIRE * 3 + 2] = ret[COL_BACKGROUND * 3 + 0] * 0.9F;
+
+    ret[COL_DONE * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] / 1.5F;
+    ret[COL_DONE * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] / 1.5F;
+    ret[COL_DONE * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] / 1.5F;
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    int i;
+    struct game_drawstate *ds = snew(struct game_drawstate);
+
+    ds->tilesize = 0;
+    ds->started = ds->solved = FALSE;
+    ds->w = state->common->params.w;
+    ds->h = state->common->params.h;
+    ds->ascii = FALSE;
+    
+    ds->count_errors[0] = FALSE;
+    ds->count_errors[1] = FALSE;
+    ds->count_errors[2] = FALSE;
+
+    ds->monsters = snewn(state->common->num_total,int);
+    for (i=0;i<(state->common->num_total);i++)
+        ds->monsters[i] = 7;
+    ds->pencils = snewn(state->common->num_total,unsigned char);
+    for (i=0;i<state->common->num_total;i++)
+        ds->pencils[i] = 0;
+
+    ds->cell_errors = snewn(state->common->wh,unsigned char);
+    for (i=0;i<state->common->wh;i++)
+        ds->cell_errors[i] = FALSE;
+    ds->hint_errors = snewn(2*state->common->num_paths,unsigned char);
+    for (i=0;i<2*state->common->num_paths;i++)
+        ds->hint_errors[i] = FALSE;
+    ds->hints_done = snewn(2 * state->common->num_paths, unsigned char);
+    memset(ds->hints_done, 0,
+           2 * state->common->num_paths * sizeof(unsigned char));
+
+    ds->hshow = ds->hpencil = ds->hflash = 0;
+    ds->hx = ds->hy = 0;
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds) {
+    sfree(ds->hints_done);
+    sfree(ds->hint_errors);
+    sfree(ds->cell_errors);
+    sfree(ds->pencils);
+    sfree(ds->monsters);
+    sfree(ds);
+    return;
+}
+
+static void draw_cell_background(drawing *dr, game_drawstate *ds,
+                                 const game_state *state, const game_ui *ui,
+                                 int x, int y) {
+
+    int hon;
+    int dx,dy;
+    dx = BORDER+(x* ds->tilesize)+(TILESIZE/2);
+    dy = BORDER+(y* ds->tilesize)+(TILESIZE/2)+TILESIZE;
+
+    hon = (ui->hshow && x == ui->hx && y == ui->hy);
+    draw_rect(dr,dx-(TILESIZE/2)+1,dy-(TILESIZE/2)+1,TILESIZE-1,TILESIZE-1,(hon && !ui->hpencil) ? COL_HIGHLIGHT : COL_BACKGROUND);
+
+    if (hon && ui->hpencil) {
+        int coords[6];
+        coords[0] = dx-(TILESIZE/2)+1;
+        coords[1] = dy-(TILESIZE/2)+1;
+        coords[2] = coords[0] + TILESIZE/2;
+        coords[3] = coords[1];
+        coords[4] = coords[0];
+        coords[5] = coords[1] + TILESIZE/2;
+        draw_polygon(dr, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT);
+    }
+
+    draw_update(dr,dx-(TILESIZE/2)+1,dy-(TILESIZE/2)+1,TILESIZE-1,TILESIZE-1);
+
+    return;
+}
+
+static void draw_circle_or_point(drawing *dr, int cx, int cy, int radius,
+                                 int colour)
+{
+    if (radius > 0)
+        draw_circle(dr, cx, cy, radius, colour, colour);
+    else
+        draw_rect(dr, cx, cy, 1, 1, colour);
+}
+
+static void draw_monster(drawing *dr, game_drawstate *ds, int x, int y,
+                         int tilesize, int hflash, int monster)
+{
+    int black = (hflash ? COL_FLASH : COL_TEXT);
+    
+    if (monster == 1) {                /* ghost */
+        int poly[80], i, j;
+
+        clip(dr,x-(tilesize/2)+2,y-(tilesize/2)+2,tilesize-3,tilesize/2+1);
+        draw_circle(dr,x,y,2*tilesize/5, COL_GHOST,black);
+        unclip(dr);
+
+        i = 0;
+        poly[i++] = x - 2*tilesize/5;
+        poly[i++] = y-2;
+        poly[i++] = x - 2*tilesize/5;
+        poly[i++] = y + 2*tilesize/5;
+
+        for (j = 0; j < 3; j++) {
+            int total = (2*tilesize/5) * 2;
+            int before = total * j / 3;
+            int after = total * (j+1) / 3;
+            int mid = (before + after) / 2;
+            poly[i++] = x - 2*tilesize/5 + mid;
+            poly[i++] = y + 2*tilesize/5 - (total / 6);
+            poly[i++] = x - 2*tilesize/5 + after;
+            poly[i++] = y + 2*tilesize/5;
+        }
+
+        poly[i++] = x + 2*tilesize/5;
+        poly[i++] = y-2;
+
+        clip(dr,x-(tilesize/2)+2,y,tilesize-3,tilesize-(tilesize/2)-1);
+        draw_polygon(dr, poly, i/2, COL_GHOST, black);
+        unclip(dr);
+
+        draw_circle(dr,x-tilesize/6,y-tilesize/12,tilesize/10,
+                    COL_BACKGROUND,black);
+        draw_circle(dr,x+tilesize/6,y-tilesize/12,tilesize/10,
+                    COL_BACKGROUND,black);
+        
+        draw_circle_or_point(dr,x-tilesize/6+1+tilesize/48,y-tilesize/12,
+                             tilesize/48,black);
+        draw_circle_or_point(dr,x+tilesize/6+1+tilesize/48,y-tilesize/12,
+                             tilesize/48,black);
+        
+    } else if (monster == 2) {         /* vampire */
+        int poly[80], i;
+
+        clip(dr,x-(tilesize/2)+2,y-(tilesize/2)+2,tilesize-3,tilesize/2);
+        draw_circle(dr,x,y,2*tilesize/5,black,black);
+        unclip(dr);
+
+        clip(dr,x-(tilesize/2)+2,y-(tilesize/2)+2,tilesize/2+1,tilesize/2);
+        draw_circle(dr,x-tilesize/7,y,2*tilesize/5-tilesize/7,
+                    COL_VAMPIRE,black);
+        unclip(dr);
+        clip(dr,x,y-(tilesize/2)+2,tilesize/2+1,tilesize/2);
+        draw_circle(dr,x+tilesize/7,y,2*tilesize/5-tilesize/7,
+                    COL_VAMPIRE,black);
+        unclip(dr);
+
+        clip(dr,x-(tilesize/2)+2,y,tilesize-3,tilesize/2);
+        draw_circle(dr,x,y,2*tilesize/5, COL_VAMPIRE,black);
+        unclip(dr);
+
+        draw_circle(dr, x-tilesize/7, y-tilesize/16, tilesize/16,
+                    COL_BACKGROUND, black);
+        draw_circle(dr, x+tilesize/7, y-tilesize/16, tilesize/16,
+                    COL_BACKGROUND, black);
+        draw_circle_or_point(dr, x-tilesize/7, y-tilesize/16, tilesize/48,
+                             black);
+        draw_circle_or_point(dr, x+tilesize/7, y-tilesize/16, tilesize/48,
+                             black);
+
+        clip(dr, x-(tilesize/2)+2, y+tilesize/8, tilesize-3, tilesize/4);
+
+        i = 0;
+        poly[i++] = x-3*tilesize/16;
+        poly[i++] = y+1*tilesize/8;
+        poly[i++] = x-2*tilesize/16;
+        poly[i++] = y+7*tilesize/24;
+        poly[i++] = x-1*tilesize/16;
+        poly[i++] = y+1*tilesize/8;
+        draw_polygon(dr, poly, i/2, COL_BACKGROUND, black);
+        i = 0;
+        poly[i++] = x+3*tilesize/16;
+        poly[i++] = y+1*tilesize/8;
+        poly[i++] = x+2*tilesize/16;
+        poly[i++] = y+7*tilesize/24;
+        poly[i++] = x+1*tilesize/16;
+        poly[i++] = y+1*tilesize/8;
+        draw_polygon(dr, poly, i/2, COL_BACKGROUND, black);
+
+        draw_circle(dr, x, y-tilesize/5, 2*tilesize/5, COL_VAMPIRE, black);
+        unclip(dr);
+
+    } else if (monster == 4) {         /* zombie */
+        draw_circle(dr,x,y,2*tilesize/5, COL_ZOMBIE,black);
+
+        draw_line(dr,
+                  x-tilesize/7-tilesize/16, y-tilesize/12-tilesize/16,
+                  x-tilesize/7+tilesize/16, y-tilesize/12+tilesize/16,
+                  black);
+        draw_line(dr,
+                  x-tilesize/7+tilesize/16, y-tilesize/12-tilesize/16,
+                  x-tilesize/7-tilesize/16, y-tilesize/12+tilesize/16,
+                  black);
+        draw_line(dr,
+                  x+tilesize/7-tilesize/16, y-tilesize/12-tilesize/16,
+                  x+tilesize/7+tilesize/16, y-tilesize/12+tilesize/16,
+                  black);
+        draw_line(dr,
+                  x+tilesize/7+tilesize/16, y-tilesize/12-tilesize/16,
+                  x+tilesize/7-tilesize/16, y-tilesize/12+tilesize/16,
+                  black);
+
+        clip(dr, x-tilesize/5, y+tilesize/6, 2*tilesize/5+1, tilesize/2);
+        draw_circle(dr, x-tilesize/15, y+tilesize/6, tilesize/12,
+                    COL_BACKGROUND, black);
+        unclip(dr);
+
+        draw_line(dr, x-tilesize/5, y+tilesize/6, x+tilesize/5, y+tilesize/6,
+                  black);
+    }
+
+    draw_update(dr,x-(tilesize/2)+2,y-(tilesize/2)+2,tilesize-3,tilesize-3);
+}
+
+static void draw_monster_count(drawing *dr, game_drawstate *ds,
+                               const game_state *state, int c, int hflash) {
+    int dx,dy;
+    char buf[8];
+    char bufm[8];
+    
+    dy = TILESIZE/4;
+    dx = BORDER+(ds->w+2)*TILESIZE/2+TILESIZE/4;
+    switch (c) {
+      case 0: 
+        sprintf(buf,"%d",state->common->num_ghosts);
+        sprintf(bufm,"G");
+        dx -= 3*TILESIZE/2;
+        break;
+      case 1: 
+        sprintf(buf,"%d",state->common->num_vampires); 
+        sprintf(bufm,"V");
+        break;
+      case 2: 
+        sprintf(buf,"%d",state->common->num_zombies); 
+        sprintf(bufm,"Z");
+        dx += 3*TILESIZE/2;
+        break;
+    }
+
+    draw_rect(dr, dx-2*TILESIZE/3, dy, 3*TILESIZE/2, TILESIZE,
+              COL_BACKGROUND);
+    if (!ds->ascii) { 
+        draw_monster(dr, ds, dx-TILESIZE/3, dy+TILESIZE/2,
+                     2*TILESIZE/3, hflash, 1<<c);
+    } else {
+        draw_text(dr, dx-TILESIZE/3,dy+TILESIZE/2,FONT_VARIABLE,TILESIZE/2,
+                  ALIGN_HCENTRE|ALIGN_VCENTRE,
+                  hflash ? COL_FLASH : COL_TEXT, bufm);
+    }
+    draw_text(dr, dx, dy+TILESIZE/2, FONT_VARIABLE, TILESIZE/2,
+              ALIGN_HLEFT|ALIGN_VCENTRE,
+              (state->count_errors[c] ? COL_ERROR :
+               hflash ? COL_FLASH : COL_TEXT), buf);
+    draw_update(dr, dx-2*TILESIZE/3, dy, 3*TILESIZE/2, TILESIZE);
+
+    return;
+}
+
+static void draw_path_hint(drawing *dr, game_drawstate *ds,
+                           const struct game_params *params,
+                           int hint_index, int hflash, int hint) {
+    int x, y, color, dx, dy, text_dx, text_dy, text_size;
+    char buf[4];
+
+    if (ds->hint_errors[hint_index])
+        color = COL_ERROR;
+    else if (hflash)
+        color = COL_FLASH;
+    else if (ds->hints_done[hint_index])
+        color = COL_DONE;
+    else
+        color = COL_TEXT;
+
+    range2grid(hint_index, params->w, params->h, &x, &y);
+    /* Upper-left corner of the "tile" */
+    dx = BORDER + x * TILESIZE;
+    dy = BORDER + y * TILESIZE + TILESIZE;
+    /* Center of the "tile" */
+    text_dx = dx + TILESIZE / 2;
+    text_dy = dy +  TILESIZE / 2;
+    /* Avoid wiping out the borders of the puzzle */
+    dx += 2;
+    dy += 2;
+    text_size = TILESIZE - 3;
+
+    sprintf(buf,"%d", hint);
+    draw_rect(dr, dx, dy, text_size, text_size, COL_BACKGROUND);
+    draw_text(dr, text_dx, text_dy, FONT_FIXED, TILESIZE / 2,
+              ALIGN_HCENTRE | ALIGN_VCENTRE, color, buf);
+    draw_update(dr, dx, dy, text_size, text_size);
+
+    return;
+}
+
+static void draw_mirror(drawing *dr, game_drawstate *ds,
+                        const game_state *state, int x, int y,
+                        int hflash, int mirror) {
+    int dx,dy,mx1,my1,mx2,my2;
+    dx = BORDER+(x* ds->tilesize)+(TILESIZE/2);
+    dy = BORDER+(y* ds->tilesize)+(TILESIZE/2)+TILESIZE;
+
+    if (mirror == CELL_MIRROR_L) {
+        mx1 = dx-(TILESIZE/4);
+        my1 = dy-(TILESIZE/4);
+        mx2 = dx+(TILESIZE/4);
+        my2 = dy+(TILESIZE/4);
+    }
+    else {
+        mx1 = dx-(TILESIZE/4);
+        my1 = dy+(TILESIZE/4);
+        mx2 = dx+(TILESIZE/4);
+        my2 = dy-(TILESIZE/4);
+    }
+    draw_thick_line(dr,(float)(TILESIZE/16),mx1,my1,mx2,my2,
+                    hflash ? COL_FLASH : COL_TEXT);
+    draw_update(dr,dx-(TILESIZE/2)+1,dy-(TILESIZE/2)+1,TILESIZE-1,TILESIZE-1);
+
+    return;
+}
+
+static void draw_big_monster(drawing *dr, game_drawstate *ds,
+                             const game_state *state, int x, int y,
+                             int hflash, int monster)
+{
+    int dx,dy;
+    char buf[10];
+    dx = BORDER+(x* ds->tilesize)+(TILESIZE/2);
+    dy = BORDER+(y* ds->tilesize)+(TILESIZE/2)+TILESIZE;
+    if (ds->ascii) {
+        if (monster == 1) sprintf(buf,"G");
+        else if (monster == 2) sprintf(buf,"V");
+        else if (monster == 4) sprintf(buf,"Z");
+        else sprintf(buf," ");
+        draw_text(dr,dx,dy,FONT_FIXED,TILESIZE/2,ALIGN_HCENTRE|ALIGN_VCENTRE,
+                  hflash ? COL_FLASH : COL_TEXT,buf);
+        draw_update(dr,dx-(TILESIZE/2)+2,dy-(TILESIZE/2)+2,TILESIZE-3,
+                    TILESIZE-3);
+    }
+    else {
+        draw_monster(dr, ds, dx, dy, 3*TILESIZE/4, hflash, monster);
+    }
+    return;
+}
+
+static void draw_pencils(drawing *dr, game_drawstate *ds,
+                         const game_state *state, int x, int y, int pencil)
+{
+    int dx, dy;
+    int monsters[4];
+    int i, j, px, py;
+    char buf[10];
+    dx = BORDER+(x* ds->tilesize)+(TILESIZE/4);
+    dy = BORDER+(y* ds->tilesize)+(TILESIZE/4)+TILESIZE;
+
+    for (i = 0, j = 1; j < 8; j *= 2)
+        if (pencil & j)
+            monsters[i++] = j;
+    while (i < 4)
+        monsters[i++] = 0;
+
+    for (py = 0; py < 2; py++)
+        for (px = 0; px < 2; px++)
+            if (monsters[py*2+px]) {
+                if (!ds->ascii) {
+                    draw_monster(dr, ds,
+                                 dx + TILESIZE/2 * px, dy + TILESIZE/2 * py,
+                                 TILESIZE/2, 0, monsters[py*2+px]);
+                }
+                else {
+                    switch (monsters[py*2+px]) {
+                      case 1: sprintf(buf,"G"); break;
+                      case 2: sprintf(buf,"V"); break;
+                      case 4: sprintf(buf,"Z"); break;
+                    }
+                    draw_text(dr,dx + TILESIZE/2 * px,dy + TILESIZE/2 * py,
+                              FONT_FIXED,TILESIZE/4,ALIGN_HCENTRE|ALIGN_VCENTRE,
+                              COL_TEXT,buf);
+                }
+            }
+    draw_update(dr,dx-(TILESIZE/4)+2,dy-(TILESIZE/4)+2,
+                (TILESIZE/2)-3,(TILESIZE/2)-3);
+
+    return;
+}
+
+#define FLASH_TIME 0.7F
+
+static int is_hint_stale(const game_drawstate *ds, int hflash,
+                         const game_state *state, int index)
+{
+    int ret = FALSE;
+    if (!ds->started) ret = TRUE;
+    if (ds->hflash != hflash) ret = TRUE;
+
+    if (ds->hint_errors[index] != state->hint_errors[index]) {
+        ds->hint_errors[index] = state->hint_errors[index];
+        ret = TRUE;
+    }
+
+    if (ds->hints_done[index] != state->hints_done[index]) {
+        ds->hints_done[index] = state->hints_done[index];
+        ret = TRUE;
+    }
+
+    return ret;
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int i,j,x,y,xy;
+    int stale, xi, c, hflash, hchanged, changed_ascii;
+
+    hflash = (int)(flashtime * 5 / FLASH_TIME) % 2;
+
+    /* Draw static grid components at startup */    
+    if (!ds->started) { 
+        draw_rect(dr, 0, 0, 2*BORDER+(ds->w+2)*TILESIZE,
+                  2*BORDER+(ds->h+3)*TILESIZE, COL_BACKGROUND);
+        draw_rect(dr, BORDER+TILESIZE-1, BORDER+2*TILESIZE-1,
+                  (ds->w)*TILESIZE +3, (ds->h)*TILESIZE +3, COL_GRID);
+        for (i=0;i<ds->w;i++)
+            for (j=0;j<ds->h;j++)
+                draw_rect(dr, BORDER+(ds->tilesize*(i+1))+1,
+                          BORDER+(ds->tilesize*(j+2))+1, ds->tilesize-1,
+                          ds->tilesize-1, COL_BACKGROUND);
+        draw_update(dr, 0, 0, 2*BORDER+(ds->w+2)*TILESIZE,
+                    2*BORDER+(ds->h+3)*TILESIZE);
+    }
+
+    hchanged = FALSE;
+    if (ds->hx != ui->hx || ds->hy != ui->hy ||
+        ds->hshow != ui->hshow || ds->hpencil != ui->hpencil)
+        hchanged = TRUE;
+
+    if (ds->ascii != ui->ascii) {
+        ds->ascii = ui->ascii;
+        changed_ascii = TRUE;
+    } else
+        changed_ascii = FALSE;
+
+    /* Draw monster count hints */
+
+    for (i=0;i<3;i++) {
+        stale = FALSE;
+        if (!ds->started) stale = TRUE;
+        if (ds->hflash != hflash) stale = TRUE;
+        if (changed_ascii) stale = TRUE;
+        if (ds->count_errors[i] != state->count_errors[i]) {
+            stale = TRUE;
+            ds->count_errors[i] = state->count_errors[i];
+        }
+        
+        if (stale) {
+            draw_monster_count(dr, ds, state, i, hflash);
+        }
+    }
+
+    /* Draw path count hints */
+    for (i=0;i<state->common->num_paths;i++) {
+        struct path *path = &state->common->paths[i];
+        
+        if (is_hint_stale(ds, hflash, state, path->grid_start)) {
+            draw_path_hint(dr, ds, &state->common->params, path->grid_start,
+                           hflash, path->sightings_start);
+        }
+
+        if (is_hint_stale(ds, hflash, state, path->grid_end)) {
+            draw_path_hint(dr, ds, &state->common->params, path->grid_end,
+                           hflash, path->sightings_end);
+        }
+    }
+
+    /* Draw puzzle grid contents */
+    for (x = 1; x < ds->w+1; x++)
+        for (y = 1; y < ds->h+1; y++) {
+            stale = FALSE;
+            xy = x+y*(state->common->params.w+2);
+            xi = state->common->xinfo[xy];
+            c = state->common->grid[xy];
+    
+            if (!ds->started) stale = TRUE;
+            if (ds->hflash != hflash) stale = TRUE;
+            if (changed_ascii) stale = TRUE;
+        
+            if (hchanged) {
+                if ((x == ui->hx && y == ui->hy) ||
+                    (x == ds->hx && y == ds->hy))
+                    stale = TRUE;
+            }
+
+            if (xi >= 0 && (state->guess[xi] != ds->monsters[xi]) ) {
+                stale = TRUE;
+                ds->monsters[xi] = state->guess[xi];
+            }
+        
+            if (xi >= 0 && (state->pencils[xi] != ds->pencils[xi]) ) {
+                stale = TRUE;
+                ds->pencils[xi] = state->pencils[xi];
+            }
+
+            if (state->cell_errors[xy] != ds->cell_errors[xy]) {
+                stale = TRUE;
+                ds->cell_errors[xy] = state->cell_errors[xy];
+            }
+                
+            if (stale) {
+                draw_cell_background(dr, ds, state, ui, x, y);
+                if (xi < 0) 
+                    draw_mirror(dr, ds, state, x, y, hflash, c);
+                else if (state->guess[xi] == 1 || state->guess[xi] == 2 ||
+                         state->guess[xi] == 4)
+                    draw_big_monster(dr, ds, state, x, y, hflash, state->guess[xi]);
+                else 
+                    draw_pencils(dr, ds, state, x, y, state->pencils[xi]);
+            }
+        }
+
+    ds->hx = ui->hx; ds->hy = ui->hy;
+    ds->hshow = ui->hshow;
+    ds->hpencil = ui->hpencil;
+    ds->hflash = hflash;
+    ds->started = TRUE;
+    return;
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return 0.0F;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    return (!oldstate->solved && newstate->solved && !oldstate->cheated &&
+            !newstate->cheated) ? FLASH_TIME : 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    return state->solved;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+}
+
+#ifdef COMBINED
+#define thegame undead
+#endif
+
+const struct game thegame = {
+    "Undead", "games.undead", "undead",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    TRUE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    FALSE, FALSE, game_print_size, game_print,
+    FALSE,                 /* wants_statusbar */
+    FALSE, game_timing_state,
+    0,                     /* flags */
+};
diff --git a/unequal.R b/unequal.R
new file mode 100644 (file)
index 0000000..a061582
--- /dev/null
+++ b/unequal.R
@@ -0,0 +1,27 @@
+# -*- makefile -*-
+
+UNEQUAL_EXTRA = latin tree234 maxflow
+
+unequal  : [X] GTK COMMON unequal UNEQUAL_EXTRA unequal-icon|no-icon
+
+unequal  : [G] WINDOWS COMMON unequal UNEQUAL_EXTRA unequal.res|noicon.res
+
+unequalsolver : [U] unequal[STANDALONE_SOLVER] latin[STANDALONE_SOLVER] tree234 maxflow STANDALONE
+unequalsolver : [C] unequal[STANDALONE_SOLVER] latin[STANDALONE_SOLVER] tree234 maxflow STANDALONE
+
+latincheck : [U] latin[STANDALONE_LATIN_TEST] tree234 maxflow STANDALONE
+latincheck : [C] latin[STANDALONE_LATIN_TEST] tree234 maxflow STANDALONE
+
+ALL += unequal[COMBINED] UNEQUAL_EXTRA
+
+!begin am gtk
+GAMES += unequal
+!end
+
+!begin >list.c
+    A(unequal) \
+!end
+
+!begin >gamedesc.txt
+unequal:unequal.exe:Unequal:Latin square puzzle:Complete the latin square in accordance with the > signs.
+!end
diff --git a/unequal.c b/unequal.c
new file mode 100644 (file)
index 0000000..3664788
--- /dev/null
+++ b/unequal.c
@@ -0,0 +1,2267 @@
+/*
+ * unequal.c
+ *
+ * Implementation of 'Futoshiki', a puzzle featured in the Guardian.
+ *
+ * TTD:
+   * add multiple-links-on-same-col/row solver nous
+   * Optimise set solver to use bit operations instead
+ *
+ * Guardian puzzles of note:
+   * #1: 5:0,0L,0L,0,0,0R,0,0L,0D,0L,0R,0,2,0D,0,0,0,0,0,0,0U,0,0,0,0U,
+   * #2: 5:0,0,0,4L,0L,0,2LU,0L,0U,0,0,0U,0,0,0,0,0D,0,3LRUD,0,0R,3,0L,0,0,
+   * #3: (reprint of #2)
+   * #4: 
+   * #5: 5:0,0,0,0,0,0,2,0U,3U,0U,0,0,3,0,0,0,3,0D,4,0,0,0L,0R,0,0,
+   * #6: 5:0D,0L,0,0R,0,0,0D,0,3,0D,0,0R,0,0R,0D,0U,0L,0,1,2,0,0,0U,0,0L,
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+#include "latin.h" /* contains typedef for digit */
+
+/* ----------------------------------------------------------
+ * Constant and structure definitions
+ */
+
+#define FLASH_TIME 0.4F
+
+#define PREFERRED_TILE_SIZE 32
+
+#define TILE_SIZE (ds->tilesize)
+#define GAP_SIZE  (TILE_SIZE/2)
+#define SQUARE_SIZE (TILE_SIZE + GAP_SIZE)
+
+#define BORDER    (TILE_SIZE / 2)
+
+#define COORD(x)  ( (x) * SQUARE_SIZE + BORDER )
+#define FROMCOORD(x)  ( ((x) - BORDER + SQUARE_SIZE) / SQUARE_SIZE - 1 )
+
+#define GRID(p,w,x,y) ((p)->w[((y)*(p)->order)+(x)])
+#define GRID3(p,w,x,y,z) ((p)->w[ (((x)*(p)->order+(y))*(p)->order+(z)) ])
+#define HINT(p,x,y,n) GRID3(p, hints, x, y, n)
+
+enum {
+    COL_BACKGROUND,
+    COL_GRID,
+    COL_TEXT, COL_GUESS, COL_ERROR, COL_PENCIL,
+    COL_HIGHLIGHT, COL_LOWLIGHT, COL_SPENT = COL_LOWLIGHT,
+    NCOLOURS
+};
+
+struct game_params {
+    int order;          /* Size of latin square */
+    int diff;           /* Difficulty */
+    int adjacent;       /* Puzzle indicators are 'adjacent number'
+                            not 'greater-than'. */
+};
+
+#define F_IMMUTABLE     1       /* passed in as game description */
+#define F_ADJ_UP        2
+#define F_ADJ_RIGHT     4
+#define F_ADJ_DOWN      8
+#define F_ADJ_LEFT      16
+#define F_ERROR         32
+#define F_ERROR_UP      64
+#define F_ERROR_RIGHT   128
+#define F_ERROR_DOWN    256
+#define F_ERROR_LEFT    512
+#define F_SPENT_UP      1024
+#define F_SPENT_RIGHT   2048
+#define F_SPENT_DOWN    4096
+#define F_SPENT_LEFT    8192
+
+#define ADJ_TO_SPENT(x) ((x) << 9)
+
+#define F_ERROR_MASK (F_ERROR|F_ERROR_UP|F_ERROR_RIGHT|F_ERROR_DOWN|F_ERROR_LEFT)
+
+struct game_state {
+    int order, completed, cheated, adjacent;
+    digit *nums;                 /* actual numbers (size order^2) */
+    unsigned char *hints;        /* remaining possiblities (size order^3) */
+    unsigned int *flags;         /* flags (size order^2) */
+};
+
+/* ----------------------------------------------------------
+ * Game parameters and presets
+ */
+
+/* Steal the method from map.c for difficulty levels. */
+#define DIFFLIST(A)               \
+    A(LATIN,Trivial,NULL,t)       \
+    A(EASY,Easy,solver_easy, e)   \
+    A(SET,Tricky,solver_set, k)   \
+    A(EXTREME,Extreme,NULL,x)     \
+    A(RECURSIVE,Recursive,NULL,r)
+
+#define ENUM(upper,title,func,lower) DIFF_ ## upper,
+#define TITLE(upper,title,func,lower) #title,
+#define ENCODE(upper,title,func,lower) #lower
+#define CONFIG(upper,title,func,lower) ":" #title
+enum { DIFFLIST(ENUM) DIFFCOUNT, DIFF_IMPOSSIBLE = diff_impossible, DIFF_AMBIGUOUS = diff_ambiguous, DIFF_UNFINISHED = diff_unfinished };
+static char const *const unequal_diffnames[] = { DIFFLIST(TITLE) };
+static char const unequal_diffchars[] = DIFFLIST(ENCODE);
+#define DIFFCONFIG DIFFLIST(CONFIG)
+
+#define DEFAULT_PRESET 0
+
+const static struct game_params unequal_presets[] = {
+    {  4, DIFF_EASY,    0 },
+    {  5, DIFF_EASY,    0 },
+    {  5, DIFF_SET,     0 },
+    {  5, DIFF_SET,     1 },
+    {  5, DIFF_EXTREME, 0 },
+    {  6, DIFF_EASY,    0 },
+    {  6, DIFF_SET,     0 },
+    {  6, DIFF_SET,     1 },
+    {  6, DIFF_EXTREME, 0 },
+    {  7, DIFF_SET,     0 },
+    {  7, DIFF_SET,     1 },
+    {  7, DIFF_EXTREME, 0 }
+};
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params *ret;
+    char buf[80];
+
+    if (i < 0 || i >= lenof(unequal_presets))
+        return FALSE;
+
+    ret = snew(game_params);
+    *ret = unequal_presets[i]; /* structure copy */
+
+    sprintf(buf, "%s: %dx%d %s",
+            ret->adjacent ? "Adjacent" : "Unequal",
+            ret->order, ret->order,
+            unequal_diffnames[ret->diff]);
+
+    *name = dupstr(buf);
+    *params = ret;
+    return TRUE;
+}
+
+static game_params *default_params(void)
+{
+    game_params *ret;
+    char *name;
+
+    if (!game_fetch_preset(DEFAULT_PRESET, &name, &ret)) return NULL;
+    sfree(name);
+    return ret;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;       /* structure copy */
+    return ret;
+}
+
+static void decode_params(game_params *ret, char const *string)
+{
+    char const *p = string;
+
+    ret->order = atoi(p);
+    while (*p && isdigit((unsigned char)*p)) p++;
+
+    if (*p == 'a') {
+        p++;
+        ret->adjacent = 1;
+    } else
+        ret->adjacent = 0;
+
+    if (*p == 'd') {
+        int i;
+        p++;
+        ret->diff = DIFFCOUNT+1; /* ...which is invalid */
+        if (*p) {
+            for (i = 0; i < DIFFCOUNT; i++) {
+                if (*p == unequal_diffchars[i])
+                    ret->diff = i;
+            }
+            p++;
+        }
+    }
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char ret[80];
+
+    sprintf(ret, "%d", params->order);
+    if (params->adjacent)
+        sprintf(ret + strlen(ret), "a");
+    if (full)
+        sprintf(ret + strlen(ret), "d%c", unequal_diffchars[params->diff]);
+
+    return dupstr(ret);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+    char buf[80];
+
+    ret = snewn(4, config_item);
+
+    ret[0].name = "Mode";
+    ret[0].type = C_CHOICES;
+    ret[0].sval = ":Unequal:Adjacent";
+    ret[0].ival = params->adjacent;
+
+    ret[1].name = "Size (s*s)";
+    ret[1].type = C_STRING;
+    sprintf(buf, "%d", params->order);
+    ret[1].sval = dupstr(buf);
+    ret[1].ival = 0;
+
+    ret[2].name = "Difficulty";
+    ret[2].type = C_CHOICES;
+    ret[2].sval = DIFFCONFIG;
+    ret[2].ival = params->diff;
+
+    ret[3].name = NULL;
+    ret[3].type = C_END;
+    ret[3].sval = NULL;
+    ret[3].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->adjacent = cfg[0].ival;
+    ret->order = atoi(cfg[1].sval);
+    ret->diff = cfg[2].ival;
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    if (params->order < 3 || params->order > 32)
+        return "Order must be between 3 and 32";
+    if (params->diff >= DIFFCOUNT)
+        return "Unknown difficulty rating";
+    if (params->order < 5 && params->adjacent &&
+        params->diff >= DIFF_SET)
+        return "Order must be at least 5 for Adjacent puzzles of this difficulty.";
+    return NULL;
+}
+
+/* ----------------------------------------------------------
+ * Various utility functions
+ */
+
+static const struct { unsigned int f, fo, fe; int dx, dy; char c, ac; } adjthan[] = {
+    { F_ADJ_UP,    F_ADJ_DOWN,  F_ERROR_UP,     0, -1, '^', '-' },
+    { F_ADJ_RIGHT, F_ADJ_LEFT,  F_ERROR_RIGHT,  1,  0, '>', '|' },
+    { F_ADJ_DOWN,  F_ADJ_UP,    F_ERROR_DOWN,   0,  1, 'v', '-' },
+    { F_ADJ_LEFT,  F_ADJ_RIGHT, F_ERROR_LEFT,  -1,  0, '<', '|' }
+};
+
+static game_state *blank_game(int order, int adjacent)
+{
+    game_state *state = snew(game_state);
+    int o2 = order*order, o3 = o2*order;
+
+    state->order = order;
+    state->adjacent = adjacent;
+    state->completed = state->cheated = 0;
+
+    state->nums = snewn(o2, digit);
+    state->hints = snewn(o3, unsigned char);
+    state->flags = snewn(o2, unsigned int);
+
+    memset(state->nums, 0, o2 * sizeof(digit));
+    memset(state->hints, 0, o3);
+    memset(state->flags, 0, o2 * sizeof(unsigned int));
+
+    return state;
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    game_state *ret = blank_game(state->order, state->adjacent);
+    int o2 = state->order*state->order, o3 = o2*state->order;
+
+    memcpy(ret->nums, state->nums, o2 * sizeof(digit));
+    memcpy(ret->hints, state->hints, o3);
+    memcpy(ret->flags, state->flags, o2 * sizeof(unsigned int));
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    sfree(state->nums);
+    sfree(state->hints);
+    sfree(state->flags);
+    sfree(state);
+}
+
+#define CHECKG(x,y) grid[(y)*o+(x)]
+
+/* Returns 0 if it finds an error, 1 otherwise. */
+static int check_num_adj(digit *grid, game_state *state,
+                        int x, int y, int me)
+{
+    unsigned int f = GRID(state, flags, x, y);
+    int ret = 1, i, o = state->order;
+
+    for (i = 0; i < 4; i++) {
+        int dx = adjthan[i].dx, dy = adjthan[i].dy, n, dn;
+
+        if (x+dx < 0 || x+dx >= o || y+dy < 0 || y+dy >= o)
+            continue;
+
+        n = CHECKG(x, y);
+        dn = CHECKG(x+dx, y+dy);
+
+        assert (n != 0);
+        if (dn == 0) continue;
+
+        if (state->adjacent) {
+            int gd = abs(n-dn);
+
+            if ((f & adjthan[i].f) && (gd != 1)) {
+                debug(("check_adj error (%d,%d):%d should be | (%d,%d):%d",
+                       x, y, n, x+dx, y+dy, dn));
+                if (me) GRID(state, flags, x, y) |= adjthan[i].fe;
+                ret = 0;
+            }
+            if (!(f & adjthan[i].f) && (gd == 1)) {
+                debug(("check_adj error (%d,%d):%d should not be | (%d,%d):%d",
+                       x, y, n, x+dx, y+dy, dn));
+                if (me) GRID(state, flags, x, y) |= adjthan[i].fe;
+                ret = 0;
+            }
+
+        } else {
+            if ((f & adjthan[i].f) && (n <= dn)) {
+                debug(("check_adj error (%d,%d):%d not > (%d,%d):%d",
+                       x, y, n, x+dx, y+dy, dn));
+                if (me) GRID(state, flags, x, y) |= adjthan[i].fe;
+                ret = 0;
+            }
+        }
+    }
+    return ret;
+}
+
+/* Returns 0 if it finds an error, 1 otherwise. */
+static int check_num_error(digit *grid, game_state *state,
+                           int x, int y, int mark_errors)
+{
+    int o = state->order;
+    int xx, yy, val = CHECKG(x,y), ret = 1;
+
+    assert(val != 0);
+
+    /* check for dups in same column. */
+    for (yy = 0; yy < state->order; yy++) {
+        if (yy == y) continue;
+        if (CHECKG(x,yy) == val) ret = 0;
+    }
+
+    /* check for dups in same row. */
+    for (xx = 0; xx < state->order; xx++) {
+        if (xx == x) continue;
+        if (CHECKG(xx,y) == val) ret = 0;
+    }
+
+    if (!ret) {
+        debug(("check_num_error (%d,%d) duplicate %d", x, y, val));
+        if (mark_errors) GRID(state, flags, x, y) |= F_ERROR;
+    }
+    return ret;
+}
+
+/* Returns:     -1 for 'wrong'
+ *               0 for 'incomplete'
+ *               1 for 'complete and correct'
+ */
+static int check_complete(digit *grid, game_state *state, int mark_errors)
+{
+    int x, y, ret = 1, o = state->order;
+
+    if (mark_errors)
+        assert(grid == state->nums);
+
+    for (x = 0; x < state->order; x++) {
+        for (y = 0; y < state->order; y++) {
+            if (mark_errors)
+                GRID(state, flags, x, y) &= ~F_ERROR_MASK;
+            if (grid[y*o+x] == 0) {
+                ret = 0;
+            } else {
+                if (!check_num_error(grid, state, x, y, mark_errors)) ret = -1;
+                if (!check_num_adj(grid, state, x, y, mark_errors)) ret = -1;
+            }
+        }
+    }
+    if (ret == 1 && latin_check(grid, o))
+        ret = -1;
+    return ret;
+}
+
+static char n2c(digit n, int order) {
+    if (n == 0)         return ' ';
+    if (order < 10) {
+        if (n < 10)     return '0' + n;
+    } else {
+        if (n < 11)     return '0' + n-1;
+        n -= 11;
+        if (n <= 26)    return 'A' + n;
+    }
+    return '?';
+}
+
+/* should be 'digit', but includes -1 for 'not a digit'.
+ * Includes keypresses (0 especially) for interpret_move. */
+static int c2n(int c, int order) {
+    if (c < 0 || c > 0xff)
+        return -1;
+    if (c == ' ' || c == '\b')
+        return 0;
+    if (order < 10) {
+        if (c >= '0' && c <= '9')
+            return (int)(c - '0');
+    } else {
+        if (c >= '0' && c <= '9')
+            return (int)(c - '0' + 1);
+        if (c >= 'A' && c <= 'Z')
+            return (int)(c - 'A' + 11);
+        if (c >= 'a' && c <= 'z')
+            return (int)(c - 'a' + 11);
+    }
+    return -1;
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    int x, y, len, n;
+    char *ret, *p;
+
+    len = (state->order*2) * (state->order*2-1) + 1;
+    ret = snewn(len, char);
+    p = ret;
+
+    for (y = 0; y < state->order; y++) {
+        for (x = 0; x < state->order; x++) {
+            n = GRID(state, nums, x, y);
+            *p++ = n > 0 ? n2c(n, state->order) : '.';
+
+            if (x < (state->order-1)) {
+                if (state->adjacent) {
+                    *p++ = (GRID(state, flags, x, y) & F_ADJ_RIGHT) ? '|' : ' ';
+                } else {
+                    if (GRID(state, flags, x, y) & F_ADJ_RIGHT)
+                        *p++ = '>';
+                    else if (GRID(state, flags, x+1, y) & F_ADJ_LEFT)
+                        *p++ = '<';
+                    else
+                        *p++ = ' ';
+                }
+            }
+        }
+        *p++ = '\n';
+
+        if (y < (state->order-1)) {
+            for (x = 0; x < state->order; x++) {
+                if (state->adjacent) {
+                    *p++ = (GRID(state, flags, x, y) & F_ADJ_DOWN) ? '-' : ' ';
+                } else {
+                    if (GRID(state, flags, x, y) & F_ADJ_DOWN)
+                        *p++ = 'v';
+                    else if (GRID(state, flags, x, y+1) & F_ADJ_UP)
+                        *p++ = '^';
+                    else
+                        *p++ = ' ';
+                }
+
+                if (x < state->order-1)
+                  *p++ = ' ';
+            }
+            *p++ = '\n';
+        }
+    }
+    *p++ = '\0';
+
+    assert(p - ret == len);
+    return ret;
+}
+
+#ifdef STANDALONE_SOLVER
+static void game_debug(game_state *state)
+{
+    char *dbg = game_text_format(state);
+    printf("%s", dbg);
+    sfree(dbg);
+}
+#endif
+
+/* ----------------------------------------------------------
+ * Solver.
+ */
+
+struct solver_link {
+    int len, gx, gy, lx, ly;
+};
+
+struct solver_ctx {
+    game_state *state;
+
+    int nlinks, alinks;
+    struct solver_link *links;
+};
+
+static void solver_add_link(struct solver_ctx *ctx,
+                            int gx, int gy, int lx, int ly, int len)
+{
+    if (ctx->alinks < ctx->nlinks+1) {
+        ctx->alinks = ctx->alinks*2 + 1;
+        /*debug(("resizing ctx->links, new size %d", ctx->alinks));*/
+        ctx->links = sresize(ctx->links, ctx->alinks, struct solver_link);
+    }
+    ctx->links[ctx->nlinks].gx = gx;
+    ctx->links[ctx->nlinks].gy = gy;
+    ctx->links[ctx->nlinks].lx = lx;
+    ctx->links[ctx->nlinks].ly = ly;
+    ctx->links[ctx->nlinks].len = len;
+    ctx->nlinks++;
+    /*debug(("Adding new link: len %d (%d,%d) < (%d,%d), nlinks now %d",
+           len, lx, ly, gx, gy, ctx->nlinks));*/
+}
+
+static struct solver_ctx *new_ctx(game_state *state)
+{
+    struct solver_ctx *ctx = snew(struct solver_ctx);
+    int o = state->order;
+    int i, x, y;
+    unsigned int f;
+
+    ctx->nlinks = ctx->alinks = 0;
+    ctx->links = NULL;
+    ctx->state = state;
+
+    if (state->adjacent) return ctx; /* adjacent mode doesn't use links. */
+
+    for (x = 0; x < o; x++) {
+        for (y = 0; y < o; y++) {
+            f = GRID(state, flags, x, y);
+            for (i = 0; i < 4; i++) {
+                if (f & adjthan[i].f)
+                    solver_add_link(ctx, x, y, x+adjthan[i].dx, y+adjthan[i].dy, 1);
+            }
+        }
+    }
+
+    return ctx;
+}
+
+static void *clone_ctx(void *vctx)
+{
+    struct solver_ctx *ctx = (struct solver_ctx *)vctx;
+    return new_ctx(ctx->state);
+}
+
+static void free_ctx(void *vctx)
+{
+    struct solver_ctx *ctx = (struct solver_ctx *)vctx;
+    if (ctx->links) sfree(ctx->links);
+    sfree(ctx);
+}
+
+static void solver_nminmax(struct latin_solver *solver,
+                           int x, int y, int *min_r, int *max_r,
+                           unsigned char **ns_r)
+{
+    int o = solver->o, min = o, max = 0, n;
+    unsigned char *ns;
+
+    assert(x >= 0 && y >= 0 && x < o && y < o);
+
+    ns = solver->cube + cubepos(x,y,1);
+
+    if (grid(x,y) > 0) {
+        min = max = grid(x,y)-1;
+    } else {
+        for (n = 0; n < o; n++) {
+            if (ns[n]) {
+                if (n > max) max = n;
+                if (n < min) min = n;
+            }
+        }
+    }
+    if (min_r) *min_r = min;
+    if (max_r) *max_r = max;
+    if (ns_r) *ns_r = ns;
+}
+
+static int solver_links(struct latin_solver *solver, void *vctx)
+{
+    struct solver_ctx *ctx = (struct solver_ctx *)vctx;
+    int i, j, lmin, gmax, nchanged = 0;
+    unsigned char *gns, *lns;
+    struct solver_link *link;
+
+    for (i = 0; i < ctx->nlinks; i++) {
+        link = &ctx->links[i];
+        solver_nminmax(solver, link->gx, link->gy, NULL, &gmax, &gns);
+        solver_nminmax(solver, link->lx, link->ly, &lmin, NULL, &lns);
+
+        for (j = 0; j < solver->o; j++) {
+            /* For the 'greater' end of the link, discount all numbers
+             * too small to satisfy the inequality. */
+            if (gns[j]) {
+                if (j < (lmin+link->len)) {
+#ifdef STANDALONE_SOLVER
+                    if (solver_show_working) {
+                        printf("%*slink elimination, (%d,%d) > (%d,%d):\n",
+                               solver_recurse_depth*4, "",
+                               link->gx+1, link->gy+1, link->lx+1, link->ly+1);
+                        printf("%*s  ruling out %d at (%d,%d)\n",
+                               solver_recurse_depth*4, "",
+                               j+1, link->gx+1, link->gy+1);
+                    }
+#endif
+                    cube(link->gx, link->gy, j+1) = FALSE;
+                    nchanged++;
+                }
+            }
+            /* For the 'lesser' end of the link, discount all numbers
+             * too large to satisfy inequality. */
+            if (lns[j]) {
+                if (j > (gmax-link->len)) {
+#ifdef STANDALONE_SOLVER
+                    if (solver_show_working) {
+                        printf("%*slink elimination, (%d,%d) > (%d,%d):\n",
+                               solver_recurse_depth*4, "",
+                               link->gx+1, link->gy+1, link->lx+1, link->ly+1);
+                        printf("%*s  ruling out %d at (%d,%d)\n",
+                               solver_recurse_depth*4, "",
+                               j+1, link->lx+1, link->ly+1);
+                    }
+#endif
+                    cube(link->lx, link->ly, j+1) = FALSE;
+                    nchanged++;
+                }
+            }
+        }
+    }
+    return nchanged;
+}
+
+static int solver_adjacent(struct latin_solver *solver, void *vctx)
+{
+    struct solver_ctx *ctx = (struct solver_ctx *)vctx;
+    int nchanged = 0, x, y, i, n, o = solver->o, nx, ny, gd;
+
+    /* Update possible values based on known values and adjacency clues. */
+
+    for (x = 0; x < o; x++) {
+        for (y = 0; y < o; y++) {
+            if (grid(x, y) == 0) continue;
+
+            /* We have a definite number here. Make sure that any
+             * adjacent possibles reflect the adjacent/non-adjacent clue. */
+
+            for (i = 0; i < 4; i++) {
+                int isadjacent = (GRID(ctx->state, flags, x, y) & adjthan[i].f);
+
+                nx = x + adjthan[i].dx, ny = y + adjthan[i].dy;
+                if (nx < 0 || ny < 0 || nx >= o || ny >= o)
+                    continue;
+
+                for (n = 0; n < o; n++) {
+                    /* Continue past numbers the adjacent square _could_ be,
+                     * given the clue we have. */
+                    gd = abs((n+1) - grid(x, y));
+                    if (isadjacent && (gd == 1)) continue;
+                    if (!isadjacent && (gd != 1)) continue;
+
+                    if (cube(nx, ny, n+1) == FALSE)
+                        continue; /* already discounted this possibility. */
+
+#ifdef STANDALONE_SOLVER
+                    if (solver_show_working) {
+                        printf("%*sadjacent elimination, (%d,%d):%d %s (%d,%d):\n",
+                               solver_recurse_depth*4, "",
+                               x+1, y+1, grid(x, y), isadjacent ? "|" : "!|", nx+1, ny+1);
+                        printf("%*s  ruling out %d at (%d,%d)\n",
+                               solver_recurse_depth*4, "", n+1, nx+1, ny+1);
+                    }
+#endif
+                    cube(nx, ny, n+1) = FALSE;
+                    nchanged++;
+                }
+            }
+        }
+    }
+
+    return nchanged;
+}
+
+static int solver_adjacent_set(struct latin_solver *solver, void *vctx)
+{
+    struct solver_ctx *ctx = (struct solver_ctx *)vctx;
+    int x, y, i, n, nn, o = solver->o, nx, ny, gd;
+    int nchanged = 0, *scratch = snewn(o, int);
+
+    /* Update possible values based on other possible values
+     * of adjacent squares, and adjacency clues. */
+
+    for (x = 0; x < o; x++) {
+        for (y = 0; y < o; y++) {
+            for (i = 0; i < 4; i++) {
+                int isadjacent = (GRID(ctx->state, flags, x, y) & adjthan[i].f);
+
+                nx = x + adjthan[i].dx, ny = y + adjthan[i].dy;
+                if (nx < 0 || ny < 0 || nx >= o || ny >= o)
+                    continue;
+
+                /* We know the current possibles for the square (x,y)
+                 * and also the adjacency clue from (x,y) to (nx,ny).
+                 * Construct a maximum set of possibles for (nx,ny)
+                 * in scratch, based on these constraints... */
+
+                memset(scratch, 0, o*sizeof(int));
+
+                for (n = 0; n < o; n++) {
+                    if (cube(x, y, n+1) == FALSE) continue;
+
+                    for (nn = 0; nn < o; nn++) {
+                        if (n == nn) continue;
+
+                        gd = abs(nn - n);
+                        if (isadjacent && (gd != 1)) continue;
+                        if (!isadjacent && (gd == 1)) continue;
+
+                        scratch[nn] = 1;
+                    }
+                }
+
+                /* ...and remove any possibilities for (nx,ny) that are
+                 * currently set but are not indicated in scratch. */
+                for (n = 0; n < o; n++) {
+                    if (scratch[n] == 1) continue;
+                    if (cube(nx, ny, n+1) == FALSE) continue;
+
+#ifdef STANDALONE_SOLVER
+                    if (solver_show_working) {
+                        printf("%*sadjacent possible elimination, (%d,%d) %s (%d,%d):\n",
+                               solver_recurse_depth*4, "",
+                               x+1, y+1, isadjacent ? "|" : "!|", nx+1, ny+1);
+                        printf("%*s  ruling out %d at (%d,%d)\n",
+                               solver_recurse_depth*4, "", n+1, nx+1, ny+1);
+                    }
+#endif
+                    cube(nx, ny, n+1) = FALSE;
+                    nchanged++;
+                }
+            }
+        }
+    }
+
+    return nchanged;
+}
+
+static int solver_easy(struct latin_solver *solver, void *vctx)
+{
+    struct solver_ctx *ctx = (struct solver_ctx *)vctx;
+    if (ctx->state->adjacent)
+       return solver_adjacent(solver, vctx);
+    else
+       return solver_links(solver, vctx);
+}
+
+static int solver_set(struct latin_solver *solver, void *vctx)
+{
+    struct solver_ctx *ctx = (struct solver_ctx *)vctx;
+    if (ctx->state->adjacent)
+       return solver_adjacent_set(solver, vctx);
+    else
+       return 0;
+}
+
+#define SOLVER(upper,title,func,lower) func,
+static usersolver_t const unequal_solvers[] = { DIFFLIST(SOLVER) };
+
+static int solver_state(game_state *state, int maxdiff)
+{
+    struct solver_ctx *ctx = new_ctx(state);
+    struct latin_solver solver;
+    int diff;
+
+    latin_solver_alloc(&solver, state->nums, state->order);
+
+    diff = latin_solver_main(&solver, maxdiff,
+                            DIFF_LATIN, DIFF_SET, DIFF_EXTREME,
+                            DIFF_EXTREME, DIFF_RECURSIVE,
+                            unequal_solvers, ctx, clone_ctx, free_ctx);
+
+    memcpy(state->hints, solver.cube, state->order*state->order*state->order);
+
+    free_ctx(ctx);
+
+    latin_solver_free(&solver);
+
+    if (diff == DIFF_IMPOSSIBLE)
+        return -1;
+    if (diff == DIFF_UNFINISHED)
+        return 0;
+    if (diff == DIFF_AMBIGUOUS)
+        return 2;
+    return 1;
+}
+
+static game_state *solver_hint(const game_state *state, int *diff_r,
+                               int mindiff, int maxdiff)
+{
+    game_state *ret = dup_game(state);
+    int diff, r = 0;
+
+    for (diff = mindiff; diff <= maxdiff; diff++) {
+        r = solver_state(ret, diff);
+        debug(("solver_state after %s %d", unequal_diffnames[diff], r));
+        if (r != 0) goto done;
+    }
+
+done:
+    if (diff_r) *diff_r = (r > 0) ? diff : -1;
+    return ret;
+}
+
+/* ----------------------------------------------------------
+ * Game generation.
+ */
+
+static char *latin_desc(digit *sq, size_t order)
+{
+    int o2 = order*order, i;
+    char *soln = snewn(o2+2, char);
+
+    soln[0] = 'S';
+    for (i = 0; i < o2; i++)
+        soln[i+1] = n2c(sq[i], order);
+    soln[o2+1] = '\0';
+
+    return soln;
+}
+
+/* returns non-zero if it placed (or could have placed) clue. */
+static int gg_place_clue(game_state *state, int ccode, digit *latin, int checkonly)
+{
+    int loc = ccode / 5, which = ccode % 5;
+    int x = loc % state->order, y = loc / state->order;
+
+    assert(loc < state->order*state->order);
+
+    if (which == 4) {           /* add number */
+        if (state->nums[loc] != 0) {
+#ifdef STANDALONE_SOLVER
+            if (state->nums[loc] != latin[loc]) {
+                printf("inconsistency for (%d,%d): state %d latin %d\n",
+                       x+1, y+1, state->nums[loc], latin[loc]);
+            }
+#endif
+            assert(state->nums[loc] == latin[loc]);
+            return 0;
+        }
+        if (!checkonly) {
+            state->nums[loc] = latin[loc];
+        }
+    } else {                    /* add flag */
+        int lx, ly, lloc;
+
+        if (state->adjacent)
+            return 0; /* never add flag clues in adjacent mode (they're always
+                         all present) */
+
+        if (state->flags[loc] & adjthan[which].f)
+            return 0; /* already has flag. */
+
+        lx = x + adjthan[which].dx;
+        ly = y + adjthan[which].dy;
+        if (lx < 0 || ly < 0 || lx >= state->order || ly >= state->order)
+            return 0; /* flag compares to off grid */
+
+        lloc = loc + adjthan[which].dx + adjthan[which].dy*state->order;
+        if (latin[loc] <= latin[lloc])
+            return 0; /* flag would be incorrect */
+
+        if (!checkonly) {
+            state->flags[loc] |= adjthan[which].f;
+        }
+    }
+    return 1;
+}
+
+/* returns non-zero if it removed (or could have removed) the clue. */
+static int gg_remove_clue(game_state *state, int ccode, int checkonly)
+{
+    int loc = ccode / 5, which = ccode % 5;
+#ifdef STANDALONE_SOLVER
+    int x = loc % state->order, y = loc / state->order;
+#endif
+
+    assert(loc < state->order*state->order);
+
+    if (which == 4) {           /* remove number. */
+        if (state->nums[loc] == 0) return 0;
+        if (!checkonly) {
+#ifdef STANDALONE_SOLVER
+            if (solver_show_working)
+                printf("gg_remove_clue: removing %d at (%d,%d)",
+                       state->nums[loc], x+1, y+1);
+#endif
+            state->nums[loc] = 0;
+        }
+    } else {                    /* remove flag */
+        if (state->adjacent)
+            return 0; /* never remove clues in adjacent mode. */
+
+        if (!(state->flags[loc] & adjthan[which].f)) return 0;
+        if (!checkonly) {
+#ifdef STANDALONE_SOLVER
+            if (solver_show_working)
+               printf("gg_remove_clue: removing %c at (%d,%d)",
+                       adjthan[which].c, x+1, y+1);
+#endif
+            state->flags[loc] &= ~adjthan[which].f;
+        }
+    }
+    return 1;
+}
+
+static int gg_best_clue(game_state *state, int *scratch, digit *latin)
+{
+    int ls = state->order * state->order * 5;
+    int maxposs = 0, minclues = 5, best = -1, i, j;
+    int nposs, nclues, loc;
+
+#ifdef STANDALONE_SOLVER
+    if (solver_show_working) {
+        game_debug(state);
+        latin_solver_debug(state->hints, state->order);
+    }
+#endif
+
+    for (i = ls; i-- > 0 ;) {
+        if (!gg_place_clue(state, scratch[i], latin, 1)) continue;
+
+        loc = scratch[i] / 5;
+        for (j = nposs = 0; j < state->order; j++) {
+            if (state->hints[loc*state->order + j]) nposs++;
+        }
+        for (j = nclues = 0; j < 4; j++) {
+            if (state->flags[loc] & adjthan[j].f) nclues++;
+        }
+        if ((nposs > maxposs) ||
+            (nposs == maxposs && nclues < minclues)) {
+            best = i; maxposs = nposs; minclues = nclues;
+#ifdef STANDALONE_SOLVER
+            if (solver_show_working) {
+                int x = loc % state->order, y = loc / state->order;
+                printf("gg_best_clue: b%d (%d,%d) new best [%d poss, %d clues].\n",
+                       best, x+1, y+1, nposs, nclues);
+            }
+#endif
+        }
+    }
+    /* if we didn't solve, we must have 1 clue to place! */
+    assert(best != -1);
+    return best;
+}
+
+#ifdef STANDALONE_SOLVER
+int maxtries;
+#define MAXTRIES maxtries
+#else
+#define MAXTRIES 50
+#endif
+int gg_solved;
+
+static int game_assemble(game_state *new, int *scratch, digit *latin,
+                         int difficulty)
+{
+    game_state *copy = dup_game(new);
+    int best;
+
+    if (difficulty >= DIFF_RECURSIVE) {
+        /* We mustn't use any solver that might guess answers;
+         * if it guesses wrongly but solves, gg_place_clue will
+         * get mighty confused. We will always trim clues down
+         * (making it more difficult) in game_strip, which doesn't
+         * have this problem. */
+        difficulty = DIFF_RECURSIVE-1;
+    }
+
+#ifdef STANDALONE_SOLVER
+    if (solver_show_working) {
+        game_debug(new);
+        latin_solver_debug(new->hints, new->order);
+    }
+#endif
+
+    while(1) {
+        gg_solved++;
+        if (solver_state(copy, difficulty) == 1) break;
+
+        best = gg_best_clue(copy, scratch, latin);
+        gg_place_clue(new, scratch[best], latin, 0);
+        gg_place_clue(copy, scratch[best], latin, 0);
+    }
+    free_game(copy);
+#ifdef STANDALONE_SOLVER
+    if (solver_show_working) {
+        char *dbg = game_text_format(new);
+        printf("game_assemble: done, %d solver iterations:\n%s\n", gg_solved, dbg);
+        sfree(dbg);
+    }
+#endif
+    return 0;
+}
+
+static void game_strip(game_state *new, int *scratch, digit *latin,
+                       int difficulty)
+{
+    int o = new->order, o2 = o*o, lscratch = o2*5, i;
+    game_state *copy = blank_game(new->order, new->adjacent);
+
+    /* For each symbol (if it exists in new), try and remove it and
+     * solve again; if we couldn't solve without it put it back. */
+    for (i = 0; i < lscratch; i++) {
+        if (!gg_remove_clue(new, scratch[i], 0)) continue;
+
+        memcpy(copy->nums,  new->nums,  o2 * sizeof(digit));
+        memcpy(copy->flags, new->flags, o2 * sizeof(unsigned int));
+        gg_solved++;
+        if (solver_state(copy, difficulty) != 1) {
+            /* put clue back, we can't solve without it. */
+            int ret = gg_place_clue(new, scratch[i], latin, 0);
+            assert(ret == 1);
+        } else {
+#ifdef STANDALONE_SOLVER
+            if (solver_show_working)
+                printf("game_strip: clue was redundant.");
+#endif
+        }
+    }
+    free_game(copy);
+#ifdef STANDALONE_SOLVER
+    if (solver_show_working) {
+        char *dbg = game_text_format(new);
+        debug(("game_strip: done, %d solver iterations.", gg_solved));
+        debug(("%s", dbg));
+        sfree(dbg);
+    }
+#endif
+}
+
+static void add_adjacent_flags(game_state *state, digit *latin)
+{
+    int x, y, o = state->order;
+
+    /* All clues in adjacent mode are always present (the only variables are
+     * the numbers). This adds all the flags to state based on the supplied
+     * latin square. */
+
+    for (y = 0; y < o; y++) {
+        for (x = 0; x < o; x++) {
+            if (x < (o-1) && (abs(latin[y*o+x] - latin[y*o+x+1]) == 1)) {
+                GRID(state, flags, x, y) |= F_ADJ_RIGHT;
+                GRID(state, flags, x+1, y) |= F_ADJ_LEFT;
+            }
+            if (y < (o-1) && (abs(latin[y*o+x] - latin[(y+1)*o+x]) == 1)) {
+                GRID(state, flags, x, y) |= F_ADJ_DOWN;
+                GRID(state, flags, x, y+1) |= F_ADJ_UP;
+            }
+        }
+    }
+}
+
+static char *new_game_desc(const game_params *params_in, random_state *rs,
+                          char **aux, int interactive)
+{
+    game_params params_copy = *params_in; /* structure copy */
+    game_params *params = &params_copy;
+    digit *sq = NULL;
+    int i, x, y, retlen, k, nsol;
+    int o2 = params->order * params->order, ntries = 1;
+    int *scratch, lscratch = o2*5;
+    char *ret, buf[80];
+    game_state *state = blank_game(params->order, params->adjacent);
+
+    /* Generate a list of 'things to strip' (randomised later) */
+    scratch = snewn(lscratch, int);
+    /* Put the numbers (4 mod 5) before the inequalities (0-3 mod 5) */
+    for (i = 0; i < lscratch; i++) scratch[i] = (i%o2)*5 + 4 - (i/o2);
+
+generate:
+#ifdef STANDALONE_SOLVER
+    if (solver_show_working)
+        printf("new_game_desc: generating %s puzzle, ntries so far %d\n",
+               unequal_diffnames[params->diff], ntries);
+#endif
+    if (sq) sfree(sq);
+    sq = latin_generate(params->order, rs);
+    latin_debug(sq, params->order);
+    /* Separately shuffle the numeric and inequality clues */
+    shuffle(scratch, lscratch/5, sizeof(int), rs);
+    shuffle(scratch+lscratch/5, 4*lscratch/5, sizeof(int), rs);
+
+    memset(state->nums, 0, o2 * sizeof(digit));
+    memset(state->flags, 0, o2 * sizeof(unsigned int));
+
+    if (state->adjacent) {
+        /* All adjacency flags are always present. */
+        add_adjacent_flags(state, sq);
+    }
+
+    gg_solved = 0;
+    if (game_assemble(state, scratch, sq, params->diff) < 0)
+        goto generate;
+    game_strip(state, scratch, sq, params->diff);
+
+    if (params->diff > 0) {
+        game_state *copy = dup_game(state);
+        nsol = solver_state(copy, params->diff-1);
+        free_game(copy);
+        if (nsol > 0) {
+#ifdef STANDALONE_SOLVER
+            if (solver_show_working)
+                printf("game_assemble: puzzle as generated is too easy.\n");
+#endif
+            if (ntries < MAXTRIES) {
+                ntries++;
+                goto generate;
+            }
+#ifdef STANDALONE_SOLVER
+            if (solver_show_working)
+                printf("Unable to generate %s %dx%d after %d attempts.\n",
+                       unequal_diffnames[params->diff],
+                       params->order, params->order, MAXTRIES);
+#endif
+            params->diff--;
+        }
+    }
+#ifdef STANDALONE_SOLVER
+    if (solver_show_working)
+        printf("new_game_desc: generated %s puzzle; %d attempts (%d solver).\n",
+               unequal_diffnames[params->diff], ntries, gg_solved);
+#endif
+
+    ret = NULL; retlen = 0;
+    for (y = 0; y < params->order; y++) {
+        for (x = 0; x < params->order; x++) {
+            unsigned int f = GRID(state, flags, x, y);
+            k = sprintf(buf, "%d%s%s%s%s,",
+                        GRID(state, nums, x, y),
+                        (f & F_ADJ_UP)    ? "U" : "",
+                        (f & F_ADJ_RIGHT) ? "R" : "",
+                        (f & F_ADJ_DOWN)  ? "D" : "",
+                        (f & F_ADJ_LEFT)  ? "L" : "");
+
+            ret = sresize(ret, retlen + k + 1, char);
+            strcpy(ret + retlen, buf);
+            retlen += k;
+        }
+    }
+    *aux = latin_desc(sq, params->order);
+
+    free_game(state);
+    sfree(sq);
+    sfree(scratch);
+
+    return ret;
+}
+
+static game_state *load_game(const game_params *params, const char *desc,
+                             char **why_r)
+{
+    game_state *state = blank_game(params->order, params->adjacent);
+    const char *p = desc;
+    int i = 0, n, o = params->order, x, y;
+    char *why = NULL;
+
+    while (*p) {
+        while (*p >= 'a' && *p <= 'z') {
+            i += *p - 'a' + 1;
+            p++;
+        }
+        if (i >= o*o) {
+            why = "Too much data to fill grid"; goto fail;
+        }
+
+        if (*p < '0' || *p > '9') {
+            why = "Expecting number in game description"; goto fail;
+        }
+        n = atoi(p);
+        if (n < 0 || n > o) {
+            why = "Out-of-range number in game description"; goto fail;
+        }
+        state->nums[i] = (digit)n;
+        while (*p >= '0' && *p <= '9') p++; /* skip number */
+
+        if (state->nums[i] != 0)
+            state->flags[i] |= F_IMMUTABLE; /* === number set by game description */
+
+        while (*p == 'U' || *p == 'R' || *p == 'D' || *p == 'L') {
+            switch (*p) {
+            case 'U': state->flags[i] |= F_ADJ_UP;    break;
+            case 'R': state->flags[i] |= F_ADJ_RIGHT; break;
+            case 'D': state->flags[i] |= F_ADJ_DOWN;  break;
+            case 'L': state->flags[i] |= F_ADJ_LEFT;  break;
+            default: why = "Expecting flag URDL in game description"; goto fail;
+            }
+            p++;
+        }
+        i++;
+        if (i < o*o && *p != ',') {
+            why = "Missing separator"; goto fail;
+        }
+        if (*p == ',') p++;
+    }
+    if (i < o*o) {
+        why = "Not enough data to fill grid"; goto fail;
+    }
+    i = 0;
+    for (y = 0; y < o; y++) {
+        for (x = 0; x < o; x++) {
+            for (n = 0; n < 4; n++) {
+                if (GRID(state, flags, x, y) & adjthan[n].f) {
+                    int nx = x + adjthan[n].dx;
+                    int ny = y + adjthan[n].dy;
+                    /* a flag must not point us off the grid. */
+                    if (nx < 0 || ny < 0 || nx >= o || ny >= o) {
+                        why = "Flags go off grid"; goto fail;
+                    }
+                    if (params->adjacent) {
+                        /* if one cell is adjacent to another, the other must
+                         * also be adjacent to the first. */
+                        if (!(GRID(state, flags, nx, ny) & adjthan[n].fo)) {
+                            why = "Flags contradicting each other"; goto fail;
+                        }
+                    } else {
+                        /* if one cell is GT another, the other must _not_ also
+                         * be GT the first. */
+                        if (GRID(state, flags, nx, ny) & adjthan[n].fo) {
+                            why = "Flags contradicting each other"; goto fail;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    return state;
+
+fail:
+    free_game(state);
+    if (why_r) *why_r = why;
+    return NULL;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    game_state *state = load_game(params, desc, NULL);
+    if (!state) {
+        assert("Unable to load ?validated game.");
+        return NULL;
+    }
+    return state;
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    char *why = NULL;
+    game_state *dummy = load_game(params, desc, &why);
+    if (dummy) {
+        free_game(dummy);
+        assert(!why);
+    } else
+        assert(why);
+    return why;
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    game_state *solved;
+    int r;
+    char *ret = NULL;
+
+    if (aux) return dupstr(aux);
+
+    solved = dup_game(state);
+    for (r = 0; r < state->order*state->order; r++) {
+        if (!(solved->flags[r] & F_IMMUTABLE))
+            solved->nums[r] = 0;
+    }
+    r = solver_state(solved, DIFFCOUNT-1);   /* always use full solver */
+    if (r > 0) ret = latin_desc(solved->nums, solved->order);
+    free_game(solved);
+    return ret;
+}
+
+/* ----------------------------------------------------------
+ * Game UI input processing.
+ */
+
+struct game_ui {
+    int hx, hy;                         /* as for solo.c, highlight pos */
+    int hshow, hpencil, hcursor;        /* show state, type, and ?cursor. */
+};
+
+static game_ui *new_ui(const game_state *state)
+{
+    game_ui *ui = snew(game_ui);
+
+    ui->hx = ui->hy = 0;
+    ui->hpencil = ui->hshow = ui->hcursor = 0;
+
+    return ui;
+}
+
+static void free_ui(game_ui *ui)
+{
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+    /* See solo.c; if we were pencil-mode highlighting and
+     * somehow a square has just been properly filled, cancel
+     * pencil mode. */
+    if (ui->hshow && ui->hpencil && !ui->hcursor &&
+        GRID(newstate, nums, ui->hx, ui->hy) != 0) {
+        ui->hshow = 0;
+    }
+}
+
+struct game_drawstate {
+    int tilesize, order, started, adjacent;
+    digit *nums;                /* copy of nums, o^2 */
+    unsigned char *hints;       /* copy of hints, o^3 */
+    unsigned int *flags;        /* o^2 */
+
+    int hx, hy, hshow, hpencil; /* as for game_ui. */
+    int hflash;
+};
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int ox, int oy, int button)
+{
+    int x = FROMCOORD(ox), y = FROMCOORD(oy), n;
+    char buf[80];
+    int shift_or_control = button & (MOD_SHFT | MOD_CTRL);
+
+    button &= ~MOD_MASK;
+
+    if (x >= 0 && x < ds->order && y >= 0 && y < ds->order && IS_MOUSE_DOWN(button)) {
+       if (oy - COORD(y) > TILE_SIZE && ox - COORD(x) > TILE_SIZE)
+           return NULL;
+
+       if (oy - COORD(y) > TILE_SIZE) {
+           if (GRID(state, flags, x, y) & F_ADJ_DOWN)
+               sprintf(buf, "F%d,%d,%d", x, y, F_SPENT_DOWN);
+           else if (y + 1 < ds->order && GRID(state, flags, x, y + 1) & F_ADJ_UP)
+               sprintf(buf, "F%d,%d,%d", x, y + 1, F_SPENT_UP);
+           else return NULL;
+           return dupstr(buf);
+       }
+
+       if (ox - COORD(x) > TILE_SIZE) {
+           if (GRID(state, flags, x, y) & F_ADJ_RIGHT)
+               sprintf(buf, "F%d,%d,%d", x, y, F_SPENT_RIGHT);
+           else if (x + 1 < ds->order && GRID(state, flags, x + 1, y) & F_ADJ_LEFT)
+               sprintf(buf, "F%d,%d,%d", x + 1, y, F_SPENT_LEFT);
+           else return NULL;
+           return dupstr(buf);
+       }
+
+        if (button == LEFT_BUTTON) {
+            /* normal highlighting for non-immutable squares */
+            if (GRID(state, flags, x, y) & F_IMMUTABLE)
+                ui->hshow = 0;
+            else if (x == ui->hx && y == ui->hy &&
+                     ui->hshow && ui->hpencil == 0)
+                ui->hshow = 0;
+            else {
+                ui->hx = x; ui->hy = y; ui->hpencil = 0;
+                ui->hshow = 1;
+            }
+            ui->hcursor = 0;
+            return "";
+        }
+        if (button == RIGHT_BUTTON) {
+            /* pencil highlighting for non-filled squares */
+            if (GRID(state, nums, x, y) != 0)
+                ui->hshow = 0;
+            else if (x == ui->hx && y == ui->hy &&
+                     ui->hshow && ui->hpencil)
+                ui->hshow = 0;
+            else {
+                ui->hx = x; ui->hy = y; ui->hpencil = 1;
+                ui->hshow = 1;
+            }
+            ui->hcursor = 0;
+            return "";
+        }
+    }
+
+    if (IS_CURSOR_MOVE(button)) {
+       if (shift_or_control) {
+           int nx = ui->hx, ny = ui->hy, i, self;
+           move_cursor(button, &nx, &ny, ds->order, ds->order, FALSE);
+           ui->hshow = ui->hcursor = 1;
+
+           for (i = 0; i < 4 && (nx != ui->hx + adjthan[i].dx ||
+                                 ny != ui->hy + adjthan[i].dy); ++i);
+
+           if (i == 4)
+               return ""; /* invalid direction, i.e. out of the board */
+
+           if (!(GRID(state, flags, ui->hx, ui->hy) & adjthan[i].f ||
+                 GRID(state, flags, nx,     ny    ) & adjthan[i].fo))
+               return ""; /* no clue to toggle */
+
+           if (state->adjacent)
+               self = (adjthan[i].dx >= 0 && adjthan[i].dy >= 0);
+           else
+               self = (GRID(state, flags, ui->hx, ui->hy) & adjthan[i].f);
+
+           if (self)
+               sprintf(buf, "F%d,%d,%d", ui->hx, ui->hy,
+                       ADJ_TO_SPENT(adjthan[i].f));
+           else
+               sprintf(buf, "F%d,%d,%d", nx, ny,
+                       ADJ_TO_SPENT(adjthan[i].fo));
+
+           return dupstr(buf);
+       } else {
+           move_cursor(button, &ui->hx, &ui->hy, ds->order, ds->order, FALSE);
+           ui->hshow = ui->hcursor = 1;
+           return "";
+       }
+    }
+    if (ui->hshow && IS_CURSOR_SELECT(button)) {
+        ui->hpencil = 1 - ui->hpencil;
+        ui->hcursor = 1;
+        return "";
+    }
+
+    n = c2n(button, state->order);
+    if (ui->hshow && n >= 0 && n <= ds->order) {
+        debug(("button %d, cbutton %d", button, (int)((char)button)));
+
+        debug(("n %d, h (%d,%d) p %d flags 0x%x nums %d",
+               n, ui->hx, ui->hy, ui->hpencil,
+               GRID(state, flags, ui->hx, ui->hy),
+               GRID(state, nums, ui->hx, ui->hy)));
+
+        if (GRID(state, flags, ui->hx, ui->hy) & F_IMMUTABLE)
+            return NULL;        /* can't edit immutable square (!) */
+        if (ui->hpencil && GRID(state, nums, ui->hx, ui->hy) > 0)
+            return NULL;        /* can't change hints on filled square (!) */
+
+
+        sprintf(buf, "%c%d,%d,%d",
+                (char)(ui->hpencil && n > 0 ? 'P' : 'R'), ui->hx, ui->hy, n);
+
+        if (!ui->hcursor) ui->hshow = 0;
+
+        return dupstr(buf);
+    }
+
+    if (button == 'H' || button == 'h')
+        return dupstr("H");
+    if (button == 'M' || button == 'm')
+        return dupstr("M");
+
+    return NULL;
+}
+
+static game_state *execute_move(const game_state *state, const char *move)
+{
+    game_state *ret = NULL;
+    int x, y, n, i, rc;
+
+    debug(("execute_move: %s", move));
+
+    if ((move[0] == 'P' || move[0] == 'R') &&
+        sscanf(move+1, "%d,%d,%d", &x, &y, &n) == 3 &&
+        x >= 0 && x < state->order && y >= 0 && y < state->order &&
+        n >= 0 && n <= state->order) {
+        ret = dup_game(state);
+        if (move[0] == 'P' && n > 0)
+            HINT(ret, x, y, n-1) = !HINT(ret, x, y, n-1);
+        else {
+            GRID(ret, nums, x, y) = n;
+            for (i = 0; i < state->order; i++)
+                HINT(ret, x, y, i) = 0;
+
+            /* real change to grid; check for completion */
+            if (!ret->completed && check_complete(ret->nums, ret, 1) > 0)
+                ret->completed = TRUE;
+        }
+        return ret;
+    } else if (move[0] == 'S') {
+        const char *p;
+
+        ret = dup_game(state);
+        ret->completed = ret->cheated = TRUE;
+
+        p = move+1;
+        for (i = 0; i < state->order*state->order; i++) {
+            n = c2n((int)*p, state->order);
+            if (!*p || n <= 0 || n > state->order)
+                goto badmove;
+            ret->nums[i] = n;
+            p++;
+        }
+        if (*p) goto badmove;
+        rc = check_complete(ret->nums, ret, 1);
+       assert(rc > 0);
+        return ret;
+    } else if (move[0] == 'M') {
+        ret = dup_game(state);
+        for (x = 0; x < state->order; x++) {
+            for (y = 0; y < state->order; y++) {
+                for (n = 0; n < state->order; n++) {
+                    HINT(ret, x, y, n) = 1;
+                }
+            }
+        }
+        return ret;
+    } else if (move[0] == 'H') {
+        return solver_hint(state, NULL, DIFF_EASY, DIFF_EASY);
+    } else if (move[0] == 'F' && sscanf(move+1, "%d,%d,%d", &x, &y, &n) == 3 &&
+              x >= 0 && x < state->order && y >= 0 && y < state->order) {
+       ret = dup_game(state);
+       GRID(ret, flags, x, y) ^= n;
+       return ret;
+    }
+
+badmove:
+    if (ret) free_game(ret);
+    return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing/printing routines.
+ */
+
+#define DRAW_SIZE (TILE_SIZE*ds->order + GAP_SIZE*(ds->order-1) + BORDER*2)
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    struct { int tilesize, order; } ads, *ds = &ads;
+    ads.tilesize = tilesize;
+    ads.order = params->order;
+
+    *x = *y = DRAW_SIZE;
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+    int i;
+
+    game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT);
+
+    for (i = 0; i < 3; i++) {
+        ret[COL_TEXT * 3 + i] = 0.0F;
+        ret[COL_GRID * 3 + i] = 0.5F;
+    }
+
+    /* Lots of these were taken from solo.c. */
+    ret[COL_GUESS * 3 + 0] = 0.0F;
+    ret[COL_GUESS * 3 + 1] = 0.6F * ret[COL_BACKGROUND * 3 + 1];
+    ret[COL_GUESS * 3 + 2] = 0.0F;
+
+    ret[COL_ERROR * 3 + 0] = 1.0F;
+    ret[COL_ERROR * 3 + 1] = 0.0F;
+    ret[COL_ERROR * 3 + 2] = 0.0F;
+
+    ret[COL_PENCIL * 3 + 0] = 0.5F * ret[COL_BACKGROUND * 3 + 0];
+    ret[COL_PENCIL * 3 + 1] = 0.5F * ret[COL_BACKGROUND * 3 + 1];
+    ret[COL_PENCIL * 3 + 2] = ret[COL_BACKGROUND * 3 + 2];
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    struct game_drawstate *ds = snew(struct game_drawstate);
+    int o2 = state->order*state->order, o3 = o2*state->order;
+
+    ds->tilesize = 0;
+    ds->order = state->order;
+    ds->adjacent = state->adjacent;
+
+    ds->nums = snewn(o2, digit);
+    ds->hints = snewn(o3, unsigned char);
+    ds->flags = snewn(o2, unsigned int);
+    memset(ds->nums, 0, o2*sizeof(digit));
+    memset(ds->hints, 0, o3);
+    memset(ds->flags, 0, o2*sizeof(unsigned int));
+
+    ds->hx = ds->hy = 0;
+    ds->started = ds->hshow = ds->hpencil = ds->hflash = 0;
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds->nums);
+    sfree(ds->hints);
+    sfree(ds->flags);
+    sfree(ds);
+}
+
+static void draw_gt(drawing *dr, int ox, int oy,
+                    int dx1, int dy1, int dx2, int dy2, int col)
+{
+    int coords[12];
+    int xdx = (dx1+dx2 ? 0 : 1), xdy = (dx1+dx2 ? 1 : 0);
+    coords[0] = ox + xdx;
+    coords[1] = oy + xdy;
+    coords[2] = ox + xdx + dx1;
+    coords[3] = oy + xdy + dy1;
+    coords[4] = ox + xdx + dx1 + dx2;
+    coords[5] = oy + xdy + dy1 + dy2;
+    coords[6] = ox - xdx + dx1 + dx2;
+    coords[7] = oy - xdy + dy1 + dy2;
+    coords[8] = ox - xdx + dx1;
+    coords[9] = oy - xdy + dy1;
+    coords[10] = ox - xdx;
+    coords[11] = oy - xdy;
+    draw_polygon(dr, coords, 6, col, col);
+}
+
+#define COLOUR(direction) (f & (F_ERROR_##direction) ? COL_ERROR : \
+                          f & (F_SPENT_##direction) ? COL_SPENT : fg)
+
+static void draw_gts(drawing *dr, game_drawstate *ds, int ox, int oy,
+                     unsigned int f, int bg, int fg)
+{
+    int g = GAP_SIZE, g2 = (g+1)/2, g4 = (g+1)/4;
+
+    /* Draw all the greater-than signs emanating from this tile. */
+
+    if (f & F_ADJ_UP) {
+       if (bg >= 0) draw_rect(dr, ox, oy - g, TILE_SIZE, g, bg);
+        draw_gt(dr, ox+g2, oy-g4, g2, -g2, g2, g2, COLOUR(UP));
+        draw_update(dr, ox, oy-g, TILE_SIZE, g);
+    }
+    if (f & F_ADJ_RIGHT) {
+       if (bg >= 0) draw_rect(dr, ox + TILE_SIZE, oy, g, TILE_SIZE, bg);
+        draw_gt(dr, ox+TILE_SIZE+g4, oy+g2, g2, g2, -g2, g2, COLOUR(RIGHT));
+        draw_update(dr, ox+TILE_SIZE, oy, g, TILE_SIZE);
+    }
+    if (f & F_ADJ_DOWN) {
+       if (bg >= 0) draw_rect(dr, ox, oy + TILE_SIZE, TILE_SIZE, g, bg);
+        draw_gt(dr, ox+g2, oy+TILE_SIZE+g4, g2, g2, g2, -g2, COLOUR(DOWN));
+        draw_update(dr, ox, oy+TILE_SIZE, TILE_SIZE, g);
+    }
+    if (f & F_ADJ_LEFT) {
+       if (bg >= 0) draw_rect(dr, ox - g, oy, g, TILE_SIZE, bg);
+        draw_gt(dr, ox-g4, oy+g2, -g2, g2, g2, g2, COLOUR(LEFT));
+        draw_update(dr, ox-g, oy, g, TILE_SIZE);
+    }
+}
+
+static void draw_adjs(drawing *dr, game_drawstate *ds, int ox, int oy,
+                      unsigned int f, int bg, int fg)
+{
+    int g = GAP_SIZE, g38 = 3*(g+1)/8, g4 = (g+1)/4;
+
+    /* Draw all the adjacency bars relevant to this tile; we only have
+     * to worry about F_ADJ_RIGHT and F_ADJ_DOWN.
+     *
+     * If we _only_ have the error flag set (i.e. it's not supposed to be
+     * adjacent, but adjacent numbers were entered) draw an outline red bar.
+     */
+
+    if (f & (F_ADJ_RIGHT|F_ERROR_RIGHT)) {
+        if (f & F_ADJ_RIGHT) {
+            draw_rect(dr, ox+TILE_SIZE+g38, oy, g4, TILE_SIZE, COLOUR(RIGHT));
+        } else {
+            draw_rect_outline(dr, ox+TILE_SIZE+g38, oy, g4, TILE_SIZE, COL_ERROR);
+        }
+    } else if (bg >= 0) {
+        draw_rect(dr, ox+TILE_SIZE+g38, oy, g4, TILE_SIZE, bg);
+    }
+    draw_update(dr, ox+TILE_SIZE, oy, g, TILE_SIZE);
+
+    if (f & (F_ADJ_DOWN|F_ERROR_DOWN)) {
+        if (f & F_ADJ_DOWN) {
+            draw_rect(dr, ox, oy+TILE_SIZE+g38, TILE_SIZE, g4, COLOUR(DOWN));
+        } else {
+            draw_rect_outline(dr, ox, oy+TILE_SIZE+g38, TILE_SIZE, g4, COL_ERROR);
+        }
+    } else if (bg >= 0) {
+        draw_rect(dr, ox, oy+TILE_SIZE+g38, TILE_SIZE, g4, bg);
+    }
+    draw_update(dr, ox, oy+TILE_SIZE, TILE_SIZE, g);
+}
+
+static void draw_furniture(drawing *dr, game_drawstate *ds,
+                           const game_state *state, const game_ui *ui,
+                           int x, int y, int hflash)
+{
+    int ox = COORD(x), oy = COORD(y), bg, hon;
+    unsigned int f = GRID(state, flags, x, y);
+
+    bg = hflash ? COL_HIGHLIGHT : COL_BACKGROUND;
+
+    hon = (ui->hshow && x == ui->hx && y == ui->hy);
+
+    /* Clear square. */
+    draw_rect(dr, ox, oy, TILE_SIZE, TILE_SIZE,
+              (hon && !ui->hpencil) ? COL_HIGHLIGHT : bg);
+
+    /* Draw the highlight (pencil or full), if we're the highlight */
+    if (hon && ui->hpencil) {
+        int coords[6];
+        coords[0] = ox;
+        coords[1] = oy;
+        coords[2] = ox + TILE_SIZE/2;
+        coords[3] = oy;
+        coords[4] = ox;
+        coords[5] = oy + TILE_SIZE/2;
+        draw_polygon(dr, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT);
+    }
+
+    /* Draw the square outline (which is the cursor, if we're the cursor). */
+    draw_rect_outline(dr, ox, oy, TILE_SIZE, TILE_SIZE, COL_GRID);
+
+    draw_update(dr, ox, oy, TILE_SIZE, TILE_SIZE);
+
+    /* Draw the adjacent clue signs. */
+    if (ds->adjacent)
+        draw_adjs(dr, ds, ox, oy, f, COL_BACKGROUND, COL_GRID);
+    else
+        draw_gts(dr, ds, ox, oy, f, COL_BACKGROUND, COL_TEXT);
+}
+
+static void draw_num(drawing *dr, game_drawstate *ds, int x, int y)
+{
+    int ox = COORD(x), oy = COORD(y);
+    unsigned int f = GRID(ds,flags,x,y);
+    char str[2];
+
+    /* (can assume square has just been cleared) */
+
+    /* Draw number, choosing appropriate colour */
+    str[0] = n2c(GRID(ds, nums, x, y), ds->order);
+    str[1] = '\0';
+    draw_text(dr, ox + TILE_SIZE/2, oy + TILE_SIZE/2,
+              FONT_VARIABLE, 3*TILE_SIZE/4, ALIGN_VCENTRE | ALIGN_HCENTRE,
+              (f & F_IMMUTABLE) ? COL_TEXT : (f & F_ERROR) ? COL_ERROR : COL_GUESS, str);
+}
+
+static void draw_hints(drawing *dr, game_drawstate *ds, int x, int y)
+{
+    int ox = COORD(x), oy = COORD(y);
+    int nhints, i, j, hw, hh, hmax, fontsz;
+    char str[2];
+
+    /* (can assume square has just been cleared) */
+
+    /* Draw hints; steal ingenious algorithm (basically)
+     * from solo.c:draw_number() */
+    for (i = nhints = 0; i < ds->order; i++) {
+        if (HINT(ds, x, y, i)) nhints++;
+    }
+
+    for (hw = 1; hw * hw < nhints; hw++);
+    if (hw < 3) hw = 3;
+    hh = (nhints + hw - 1) / hw;
+    if (hh < 2) hh = 2;
+    hmax = max(hw, hh);
+    fontsz = TILE_SIZE/(hmax*(11-hmax)/8);
+
+    for (i = j = 0; i < ds->order; i++) {
+        if (HINT(ds,x,y,i)) {
+            int hx = j % hw, hy = j / hw;
+
+            str[0] = n2c(i+1, ds->order);
+            str[1] = '\0';
+            draw_text(dr,
+                      ox + (4*hx+3) * TILE_SIZE / (4*hw+2),
+                      oy + (4*hy+3) * TILE_SIZE / (4*hh+2),
+                      FONT_VARIABLE, fontsz,
+                      ALIGN_VCENTRE | ALIGN_HCENTRE, COL_PENCIL, str);
+            j++;
+        }
+    }
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int x, y, i, hchanged = 0, stale, hflash = 0;
+
+    debug(("highlight old (%d,%d), new (%d,%d)", ds->hx, ds->hy, ui->hx, ui->hy));
+
+    if (flashtime > 0 &&
+        (flashtime <= FLASH_TIME/3 || flashtime >= FLASH_TIME*2/3))
+         hflash = 1;
+
+    if (!ds->started) {
+        draw_rect(dr, 0, 0, DRAW_SIZE, DRAW_SIZE, COL_BACKGROUND);
+        draw_update(dr, 0, 0, DRAW_SIZE, DRAW_SIZE);
+    }
+    if (ds->hx != ui->hx || ds->hy != ui->hy ||
+        ds->hshow != ui->hshow || ds->hpencil != ui->hpencil)
+        hchanged = 1;
+
+    for (x = 0; x < ds->order; x++) {
+        for (y = 0; y < ds->order; y++) {
+            if (!ds->started)
+                stale = 1;
+            else if (hflash != ds->hflash)
+                stale = 1;
+            else
+                stale = 0;
+
+            if (hchanged) {
+                if ((x == ui->hx && y == ui->hy) ||
+                    (x == ds->hx && y == ds->hy))
+                    stale = 1;
+            }
+
+            if (GRID(state, nums, x, y) != GRID(ds, nums, x, y)) {
+                GRID(ds, nums, x, y) = GRID(state, nums, x, y);
+                stale = 1;
+            }
+            if (GRID(state, flags, x, y) != GRID(ds, flags, x, y)) {
+                GRID(ds, flags, x, y) = GRID(state, flags, x, y);
+                stale = 1;
+            }
+            if (GRID(ds, nums, x, y) == 0) {
+                /* We're not a number square (therefore we might
+                 * display hints); do we need to update? */
+                for (i = 0; i < ds->order; i++) {
+                    if (HINT(state, x, y, i) != HINT(ds, x, y, i)) {
+                        HINT(ds, x, y, i) = HINT(state, x, y, i);
+                        stale = 1;
+                    }
+                }
+            }
+            if (stale) {
+                draw_furniture(dr, ds, state, ui, x, y, hflash);
+                if (GRID(ds, nums, x, y) > 0)
+                    draw_num(dr, ds, x, y);
+                else
+                    draw_hints(dr, ds, x, y);
+            }
+        }
+    }
+    ds->hx = ui->hx; ds->hy = ui->hy;
+    ds->hshow = ui->hshow;
+    ds->hpencil = ui->hpencil;
+
+    ds->started = 1;
+    ds->hflash = hflash;
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return 0.0F;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    if (!oldstate->completed && newstate->completed &&
+        !oldstate->cheated && !newstate->cheated)
+        return FLASH_TIME;
+    return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    return state->completed ? +1 : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+    int pw, ph;
+
+    /* 10mm squares by default, roughly the same as Grauniad. */
+    game_compute_size(params, 1000, &pw, &ph);
+    *x = pw / 100.0F;
+    *y = ph / 100.0F;
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+    int ink = print_mono_colour(dr, 0);
+    int x, y, o = state->order, ox, oy, n;
+    char str[2];
+
+    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
+    game_drawstate ads, *ds = &ads;
+    game_set_size(dr, ds, NULL, tilesize);
+
+    print_line_width(dr, 2 * TILE_SIZE / 40);
+
+    /* Squares, numbers, gt signs */
+    for (y = 0; y < o; y++) {
+        for (x = 0; x < o; x++) {
+            ox = COORD(x); oy = COORD(y);
+            n = GRID(state, nums, x, y);
+
+            draw_rect_outline(dr, ox, oy, TILE_SIZE, TILE_SIZE, ink);
+
+            str[0] = n ? n2c(n, state->order) : ' ';
+            str[1] = '\0';
+            draw_text(dr, ox + TILE_SIZE/2, oy + TILE_SIZE/2,
+                      FONT_VARIABLE, TILE_SIZE/2, ALIGN_VCENTRE | ALIGN_HCENTRE,
+                      ink, str);
+
+            if (state->adjacent)
+                draw_adjs(dr, ds, ox, oy, GRID(state, flags, x, y), -1, ink);
+            else
+                draw_gts(dr, ds, ox, oy, GRID(state, flags, x, y), -1, ink);
+        }
+    }
+}
+
+/* ----------------------------------------------------------------------
+ * Housekeeping.
+ */
+
+#ifdef COMBINED
+#define thegame unequal
+#endif
+
+const struct game thegame = {
+    "Unequal", "games.unequal", "unequal",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    TRUE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    TRUE, FALSE, game_print_size, game_print,
+    FALSE,                            /* wants_statusbar */
+    FALSE, game_timing_state,
+    REQUIRE_RBUTTON | REQUIRE_NUMPAD,  /* flags */
+};
+
+/* ----------------------------------------------------------------------
+ * Standalone solver.
+ */
+
+#ifdef STANDALONE_SOLVER
+
+#include <time.h>
+#include <stdarg.h>
+
+const char *quis = NULL;
+
+#if 0 /* currently unused */
+
+static void debug_printf(char *fmt, ...)
+{
+    char buf[4096];
+    va_list ap;
+
+    va_start(ap, fmt);
+    vsprintf(buf, fmt, ap);
+    puts(buf);
+    va_end(ap);
+}
+
+static void game_printf(game_state *state)
+{
+    char *dbg = game_text_format(state);
+    printf("%s", dbg);
+    sfree(dbg);
+}
+
+static void game_printf_wide(game_state *state)
+{
+    int x, y, i, n;
+
+    for (y = 0; y < state->order; y++) {
+        for (x = 0; x < state->order; x++) {
+            n = GRID(state, nums, x, y);
+            for (i = 0; i < state->order; i++) {
+                if (n > 0)
+                    printf("%c", n2c(n, state->order));
+                else if (HINT(state, x, y, i))
+                    printf("%c", n2c(i+1, state->order));
+                else
+                    printf(".");
+            }
+            printf(" ");
+        }
+        printf("\n");
+    }
+    printf("\n");
+}
+
+#endif
+
+static void pdiff(int diff)
+{
+    if (diff == DIFF_IMPOSSIBLE)
+        printf("Game is impossible.\n");
+    else if (diff == DIFF_UNFINISHED)
+        printf("Game has incomplete.\n");
+    else if (diff == DIFF_AMBIGUOUS)
+        printf("Game has multiple solutions.\n");
+    else
+        printf("Game has difficulty %s.\n", unequal_diffnames[diff]);
+}
+
+static int solve(game_params *p, char *desc, int debug)
+{
+    game_state *state = new_game(NULL, p, desc);
+    struct solver_ctx *ctx = new_ctx(state);
+    struct latin_solver solver;
+    int diff;
+
+    solver_show_working = debug;
+    game_debug(state);
+
+    latin_solver_alloc(&solver, state->nums, state->order);
+
+    diff = latin_solver_main(&solver, DIFF_RECURSIVE,
+                            DIFF_LATIN, DIFF_SET, DIFF_EXTREME,
+                            DIFF_EXTREME, DIFF_RECURSIVE,
+                            unequal_solvers, ctx, clone_ctx, free_ctx);
+
+    free_ctx(ctx);
+
+    latin_solver_free(&solver);
+
+    if (debug) pdiff(diff);
+
+    game_debug(state);
+    free_game(state);
+    return diff;
+}
+
+static void check(game_params *p)
+{
+    char *msg = validate_params(p, 1);
+    if (msg) {
+        fprintf(stderr, "%s: %s", quis, msg);
+        exit(1);
+    }
+}
+
+static int gen(game_params *p, random_state *rs, int debug)
+{
+    char *desc, *aux;
+    int diff;
+
+    check(p);
+
+    solver_show_working = debug;
+    desc = new_game_desc(p, rs, &aux, 0);
+    diff = solve(p, desc, debug);
+    sfree(aux);
+    sfree(desc);
+
+    return diff;
+}
+
+static void soak(game_params *p, random_state *rs)
+{
+    time_t tt_start, tt_now, tt_last;
+    char *aux, *desc;
+    game_state *st;
+    int n = 0, neasy = 0, realdiff = p->diff;
+
+    check(p);
+
+    solver_show_working = 0;
+    maxtries = 1;
+
+    tt_start = tt_now = time(NULL);
+
+    printf("Soak-generating an %s %dx%d grid, difficulty %s.\n",
+           p->adjacent ? "adjacent" : "unequal",
+           p->order, p->order, unequal_diffnames[p->diff]);
+
+    while (1) {
+        p->diff = realdiff;
+        desc = new_game_desc(p, rs, &aux, 0);
+        st = new_game(NULL, p, desc);
+        solver_state(st, DIFF_RECURSIVE);
+        free_game(st);
+        sfree(aux);
+        sfree(desc);
+
+        n++;
+        if (realdiff != p->diff) neasy++;
+
+        tt_last = time(NULL);
+        if (tt_last > tt_now) {
+            tt_now = tt_last;
+            printf("%d total, %3.1f/s; %d/%2.1f%% easy, %3.1f/s good.\n",
+                   n, (double)n / ((double)tt_now - tt_start),
+                   neasy, (double)neasy*100.0/(double)n,
+                   (double)(n - neasy) / ((double)tt_now - tt_start));
+        }
+    }
+}
+
+static void usage_exit(const char *msg)
+{
+    if (msg)
+        fprintf(stderr, "%s: %s\n", quis, msg);
+    fprintf(stderr, "Usage: %s [--seed SEED] --soak <params> | [game_id [game_id ...]]\n", quis);
+    exit(1);
+}
+
+int main(int argc, const char *argv[])
+{
+    random_state *rs;
+    time_t seed = time(NULL);
+    int do_soak = 0, diff;
+
+    game_params *p;
+
+    maxtries = 50;
+
+    quis = argv[0];
+    while (--argc > 0) {
+        const char *p = *++argv;
+        if (!strcmp(p, "--soak"))
+            do_soak = 1;
+        else if (!strcmp(p, "--seed")) {
+            if (argc == 0)
+                usage_exit("--seed needs an argument");
+            seed = (time_t)atoi(*++argv);
+            argc--;
+        } else if (*p == '-')
+            usage_exit("unrecognised option");
+        else
+            break;
+    }
+    rs = random_new((void*)&seed, sizeof(time_t));
+
+    if (do_soak == 1) {
+        if (argc != 1) usage_exit("only one argument for --soak");
+        p = default_params();
+        decode_params(p, *argv);
+        soak(p, rs);
+    } else if (argc > 0) {
+        int i;
+        for (i = 0; i < argc; i++) {
+            const char *id = *argv++;
+            char *desc = strchr(id, ':'), *err;
+            p = default_params();
+            if (desc) {
+                *desc++ = '\0';
+                decode_params(p, id);
+                err = validate_desc(p, desc);
+                if (err) {
+                    fprintf(stderr, "%s: %s\n", quis, err);
+                    exit(1);
+                }
+                solve(p, desc, 1);
+            } else {
+                decode_params(p, id);
+                diff = gen(p, rs, 1);
+            }
+        }
+    } else {
+        while(1) {
+            p = default_params();
+            p->order = random_upto(rs, 7) + 3;
+            p->diff = random_upto(rs, 4);
+            diff = gen(p, rs, 0);
+            pdiff(diff);
+        }
+    }
+
+    return 0;
+}
+
+#endif
+
+/* vim: set shiftwidth=4 tabstop=8: */
diff --git a/unruly.R b/unruly.R
new file mode 100644 (file)
index 0000000..064ccc3
--- /dev/null
+++ b/unruly.R
@@ -0,0 +1,21 @@
+# -*- makefile -*-
+
+unruly : [X] GTK COMMON unruly unruly-icon|no-icon
+unruly : [G] WINDOWS COMMON unruly unruly.res|noicon.res
+
+unrulysolver : [U] unruly[STANDALONE_SOLVER] STANDALONE
+unrulysolver : [C] unruly[STANDALONE_SOLVER] STANDALONE
+
+ALL += unruly[COMBINED]
+
+!begin am gtk
+GAMES += unruly
+!end
+
+!begin >list.c
+    A(unruly) \
+!end
+
+!begin >gamedesc.txt
+unruly:unruly.exe:Unruly:Black and white grid puzzle:Fill in the black and white grid to avoid runs of three.
+!end
diff --git a/unruly.c b/unruly.c
new file mode 100644 (file)
index 0000000..616e5e5
--- /dev/null
+++ b/unruly.c
@@ -0,0 +1,2071 @@
+/*
+ * unruly.c: Implementation for Binary Puzzles.
+ * (C) 2012 Lennard Sprong
+ * Created for Simon Tatham's Portable Puzzle Collection
+ * See LICENCE for licence details
+ *
+ * Objective of the game: Fill the grid with zeros and ones, with the
+ * following rules:
+ * - There can't be a run of three or more equal numbers.
+ * - Each row and column contains an equal amount of zeros and ones.
+ *
+ * This puzzle type is known under several names, including
+ * Tohu-Wa-Vohu, One and Two and Binairo.
+ *
+ * Some variants include an extra constraint, stating that no two rows or two
+ * columns may contain the same exact sequence of zeros and ones.
+ * This rule is rarely used, so it is not enabled in the default presets
+ * (but it can be selected via the Custom configurer).
+ *
+ * More information:
+ * http://www.janko.at/Raetsel/Tohu-Wa-Vohu/index.htm
+ */
+
+/*
+ * Possible future improvements:
+ *
+ * More solver cleverness
+ *
+ *  - a counting-based deduction in which you find groups of squares
+ *    which must each contain at least one of a given colour, plus
+ *    other squares which are already known to be that colour, and see
+ *    if you have any squares left over when you've worked out where
+ *    they all have to be. This is a generalisation of the current
+ *    check_near_complete: where that only covers rows with three
+ *    unfilled squares, this would handle more, such as
+ *        0 . . 1 0 1 . . 0 .
+ *    in which each of the two-square gaps must contain a 0, and there
+ *    are three 0s placed, and that means the rightmost square can't
+ *    be a 0.
+ *
+ *  - an 'Unreasonable' difficulty level, supporting recursion and
+ *    backtracking.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+
+#ifdef STANDALONE_SOLVER
+int solver_verbose = FALSE;
+#endif
+
+enum {
+    COL_BACKGROUND,
+    COL_GRID,
+    COL_EMPTY,
+    /*
+     * When editing this enum, maintain the invariants
+     *   COL_n_HIGHLIGHT = COL_n + 1
+     *   COL_n_LOWLIGHT = COL_n + 2
+     */
+    COL_0,
+    COL_0_HIGHLIGHT,
+    COL_0_LOWLIGHT,
+    COL_1,
+    COL_1_HIGHLIGHT,
+    COL_1_LOWLIGHT,
+    COL_CURSOR,
+    COL_ERROR,
+    NCOLOURS
+};
+
+struct game_params {
+    int w2, h2;        /* full grid width and height respectively */
+    int unique;        /* should row and column patterns be unique? */
+    int diff;
+};
+#define DIFFLIST(A)                             \
+    A(EASY,Easy, e)                             \
+    A(NORMAL,Normal, n)                         \
+
+#define ENUM(upper,title,lower) DIFF_ ## upper,
+#define TITLE(upper,title,lower) #title,
+#define ENCODE(upper,title,lower) #lower
+#define CONFIG(upper,title,lower) ":" #title
+enum { DIFFLIST(ENUM) DIFFCOUNT };
+static char const *const unruly_diffnames[] = { DIFFLIST(TITLE) };
+
+static char const unruly_diffchars[] = DIFFLIST(ENCODE);
+#define DIFFCONFIG DIFFLIST(CONFIG)
+
+const static struct game_params unruly_presets[] = {
+    { 8,  8, FALSE, DIFF_EASY},
+    { 8,  8, FALSE, DIFF_NORMAL},
+    {10, 10, FALSE, DIFF_EASY},
+    {10, 10, FALSE, DIFF_NORMAL},
+    {14, 14, FALSE, DIFF_EASY},
+    {14, 14, FALSE, DIFF_NORMAL}
+};
+
+#define DEFAULT_PRESET 0
+
+enum {
+    EMPTY,
+    N_ONE,
+    N_ZERO,
+    BOGUS
+};
+
+#define FE_HOR_ROW_LEFT   0x0001
+#define FE_HOR_ROW_MID    0x0003
+#define FE_HOR_ROW_RIGHT  0x0002
+
+#define FE_VER_ROW_TOP    0x0004
+#define FE_VER_ROW_MID    0x000C
+#define FE_VER_ROW_BOTTOM 0x0008
+
+#define FE_COUNT          0x0010
+
+#define FE_ROW_MATCH      0x0020
+#define FE_COL_MATCH      0x0040
+
+#define FF_ONE            0x0080
+#define FF_ZERO           0x0100
+#define FF_CURSOR         0x0200
+
+#define FF_FLASH1         0x0400
+#define FF_FLASH2         0x0800
+#define FF_IMMUTABLE      0x1000
+
+struct game_state {
+    int w2, h2;
+    int unique;
+    char *grid;
+    unsigned char *immutable;
+
+    int completed, cheated;
+};
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+
+    *ret = unruly_presets[DEFAULT_PRESET];        /* structure copy */
+
+    return ret;
+}
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params *ret;
+    char buf[80];
+
+    if (i < 0 || i >= lenof(unruly_presets))
+        return FALSE;
+
+    ret = snew(game_params);
+    *ret = unruly_presets[i];     /* structure copy */
+
+    sprintf(buf, "%dx%d %s", ret->w2, ret->h2, unruly_diffnames[ret->diff]);
+
+    *name = dupstr(buf);
+    *params = ret;
+    return TRUE;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;             /* structure copy */
+    return ret;
+}
+
+static void decode_params(game_params *params, char const *string)
+{
+    char const *p = string;
+
+    params->unique = FALSE;
+
+    params->w2 = atoi(p);
+    while (*p && isdigit((unsigned char)*p)) p++;
+    if (*p == 'x') {
+        p++;
+        params->h2 = atoi(p);
+        while (*p && isdigit((unsigned char)*p)) p++;
+    } else {
+        params->h2 = params->w2;
+    }
+
+    if (*p == 'u') {
+        p++;
+        params->unique = TRUE;
+    }
+
+    if (*p == 'd') {
+        int i;
+        p++;
+        params->diff = DIFFCOUNT + 1;   /* ...which is invalid */
+        if (*p) {
+            for (i = 0; i < DIFFCOUNT; i++) {
+                if (*p == unruly_diffchars[i])
+                    params->diff = i;
+            }
+            p++;
+        }
+    }
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char buf[80];
+
+    sprintf(buf, "%dx%d", params->w2, params->h2);
+    if (params->unique)
+        strcat(buf, "u");
+    if (full)
+        sprintf(buf + strlen(buf), "d%c", unruly_diffchars[params->diff]);
+
+    return dupstr(buf);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+    char buf[80];
+
+    ret = snewn(5, config_item);
+
+    ret[0].name = "Width";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->w2);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = "Height";
+    ret[1].type = C_STRING;
+    sprintf(buf, "%d", params->h2);
+    ret[1].sval = dupstr(buf);
+    ret[1].ival = 0;
+
+    ret[2].name = "Unique rows and columns";
+    ret[2].type = C_BOOLEAN;
+    ret[2].ival = params->unique;
+
+    ret[3].name = "Difficulty";
+    ret[3].type = C_CHOICES;
+    ret[3].sval = DIFFCONFIG;
+    ret[3].ival = params->diff;
+
+    ret[4].name = NULL;
+    ret[4].type = C_END;
+    ret[4].sval = NULL;
+    ret[4].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->w2 = atoi(cfg[0].sval);
+    ret->h2 = atoi(cfg[1].sval);
+    ret->unique = cfg[2].ival;
+    ret->diff = cfg[3].ival;
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    if ((params->w2 & 1) || (params->h2 & 1))
+        return "Width and height must both be even";
+    if (params->w2 < 6 || params->h2 < 6)
+        return "Width and height must be at least 6";
+    if (params->unique) {
+        static const long A177790[] = {
+            /*
+             * The nth element of this array gives the number of
+             * distinct possible Unruly rows of length 2n (that is,
+             * containing exactly n 1s and n 0s and not containing
+             * three consecutive elements the same) for as long as
+             * those numbers fit in a 32-bit signed int.
+             *
+             * So in unique-rows mode, if the puzzle width is 2n, then
+             * the height must be at most (this array)[n], and vice
+             * versa.
+             *
+             * This is sequence A177790 in the Online Encyclopedia of
+             * Integer Sequences: http://oeis.org/A177790
+             */
+            1L, 2L, 6L, 14L, 34L, 84L, 208L, 518L, 1296L, 3254L,
+            8196L, 20700L, 52404L, 132942L, 337878L, 860142L,
+            2192902L, 5598144L, 14308378L, 36610970L, 93770358L,
+            240390602L, 616787116L, 1583765724L
+        };
+        if (params->w2 < 2*lenof(A177790) &&
+            params->h2 > A177790[params->w2/2]) {
+            return "Puzzle is too tall for unique-rows mode";
+        }
+        if (params->h2 < 2*lenof(A177790) &&
+            params->w2 > A177790[params->h2/2]) {
+            return "Puzzle is too long for unique-rows mode";
+        }
+    }
+    if (params->diff >= DIFFCOUNT)
+        return "Unknown difficulty rating";
+
+    return NULL;
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    int w2 = params->w2, h2 = params->h2;
+    int s = w2 * h2;
+
+    const char *p = desc;
+    int pos = 0;
+
+    while (*p) {
+        if (*p >= 'a' && *p < 'z')
+            pos += 1 + (*p - 'a');
+        else if (*p >= 'A' && *p < 'Z')
+            pos += 1 + (*p - 'A');
+        else if (*p == 'Z' || *p == 'z')
+            pos += 25;
+        else
+            return "Description contains invalid characters";
+
+        ++p;
+    }
+
+    if (pos < s+1)
+        return "Description too short";
+    if (pos > s+1)
+        return "Description too long";
+
+    return NULL;
+}
+
+static game_state *blank_state(int w2, int h2, int unique)
+{
+    game_state *state = snew(game_state);
+    int s = w2 * h2;
+
+    state->w2 = w2;
+    state->h2 = h2;
+    state->unique = unique;
+    state->grid = snewn(s, char);
+    state->immutable = snewn(s, unsigned char);
+
+    memset(state->grid, EMPTY, s);
+    memset(state->immutable, FALSE, s);
+
+    state->completed = state->cheated = FALSE;
+
+    return state;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    int w2 = params->w2, h2 = params->h2;
+    int s = w2 * h2;
+
+    game_state *state = blank_state(w2, h2, params->unique);
+
+    const char *p = desc;
+    int pos = 0;
+
+    while (*p) {
+        if (*p >= 'a' && *p < 'z') {
+            pos += (*p - 'a');
+            if (pos < s) {
+                state->grid[pos] = N_ZERO;
+                state->immutable[pos] = TRUE;
+            }
+            pos++;
+        } else if (*p >= 'A' && *p < 'Z') {
+            pos += (*p - 'A');
+            if (pos < s) {
+                state->grid[pos] = N_ONE;
+                state->immutable[pos] = TRUE;
+            }
+            pos++;
+        } else if (*p == 'Z' || *p == 'z') {
+            pos += 25;
+        } else
+            assert(!"Description contains invalid characters");
+
+        ++p;
+    }
+    assert(pos == s+1);
+
+    return state;
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    int w2 = state->w2, h2 = state->h2;
+    int s = w2 * h2;
+
+    game_state *ret = blank_state(w2, h2, state->unique);
+
+    memcpy(ret->grid, state->grid, s);
+    memcpy(ret->immutable, state->immutable, s);
+
+    ret->completed = state->completed;
+    ret->cheated = state->cheated;
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    sfree(state->grid);
+    sfree(state->immutable);
+
+    sfree(state);
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    int w2 = state->w2, h2 = state->h2;
+    int lr = w2*2 + 1;
+
+    char *ret = snewn(lr * h2 + 1, char);
+    char *p = ret;
+
+    int x, y;
+    for (y = 0; y < h2; y++) {
+        for (x = 0; x < w2; x++) {
+            /* Place number */
+            char c = state->grid[y * w2 + x];
+            *p++ = (c == N_ONE ? '1' : c == N_ZERO ? '0' : '.');
+            *p++ = ' ';
+        }
+        /* End line */
+        *p++ = '\n';
+    }
+    /* End with NUL */
+    *p++ = '\0';
+
+    return ret;
+}
+
+/* ****** *
+ * Solver *
+ * ****** */
+
+struct unruly_scratch {
+    int *ones_rows;
+    int *ones_cols;
+    int *zeros_rows;
+    int *zeros_cols;
+};
+
+static void unruly_solver_update_remaining(const game_state *state,
+                                           struct unruly_scratch *scratch)
+{
+    int w2 = state->w2, h2 = state->h2;
+    int x, y;
+
+    /* Reset all scratch data */
+    memset(scratch->ones_rows, 0, h2 * sizeof(int));
+    memset(scratch->ones_cols, 0, w2 * sizeof(int));
+    memset(scratch->zeros_rows, 0, h2 * sizeof(int));
+    memset(scratch->zeros_cols, 0, w2 * sizeof(int));
+
+    for (x = 0; x < w2; x++)
+        for (y = 0; y < h2; y++) {
+            if (state->grid[y * w2 + x] == N_ONE) {
+                scratch->ones_rows[y]++;
+                scratch->ones_cols[x]++;
+            } else if (state->grid[y * w2 + x] == N_ZERO) {
+                scratch->zeros_rows[y]++;
+                scratch->zeros_cols[x]++;
+            }
+        }
+}
+
+static struct unruly_scratch *unruly_new_scratch(const game_state *state)
+{
+    int w2 = state->w2, h2 = state->h2;
+
+    struct unruly_scratch *ret = snew(struct unruly_scratch);
+
+    ret->ones_rows = snewn(h2, int);
+    ret->ones_cols = snewn(w2, int);
+    ret->zeros_rows = snewn(h2, int);
+    ret->zeros_cols = snewn(w2, int);
+
+    unruly_solver_update_remaining(state, ret);
+
+    return ret;
+}
+
+static void unruly_free_scratch(struct unruly_scratch *scratch)
+{
+    sfree(scratch->ones_rows);
+    sfree(scratch->ones_cols);
+    sfree(scratch->zeros_rows);
+    sfree(scratch->zeros_cols);
+
+    sfree(scratch);
+}
+
+static int unruly_solver_check_threes(game_state *state, int *rowcount,
+                                      int *colcount, int horizontal,
+                                      char check, char block)
+{
+    int w2 = state->w2, h2 = state->h2;
+
+    int dx = horizontal ? 1 : 0, dy = 1 - dx;
+    int sx = dx, sy = dy;
+    int ex = w2 - dx, ey = h2 - dy;
+
+    int x, y;
+    int ret = 0;
+
+    /* Check for any three squares which almost form three in a row */
+    for (y = sy; y < ey; y++) {
+        for (x = sx; x < ex; x++) {
+            int i1 = (y-dy) * w2 + (x-dx);
+            int i2 = y * w2 + x;
+            int i3 = (y+dy) * w2 + (x+dx);
+
+            if (state->grid[i1] == check && state->grid[i2] == check
+                && state->grid[i3] == EMPTY) {
+                ret++;
+#ifdef STANDALONE_SOLVER
+                if (solver_verbose) {
+                    printf("Solver: %i,%i and %i,%i confirm %c at %i,%i\n",
+                           i1 % w2, i1 / w2, i2 % w2, i2 / w2,
+                           (block == N_ONE ? '1' : '0'), i3 % w2,
+                           i3 / w2);
+                }
+#endif
+                state->grid[i3] = block;
+                rowcount[i3 / w2]++;
+                colcount[i3 % w2]++;
+            }
+            if (state->grid[i1] == check && state->grid[i2] == EMPTY
+                && state->grid[i3] == check) {
+                ret++;
+#ifdef STANDALONE_SOLVER
+                if (solver_verbose) {
+                    printf("Solver: %i,%i and %i,%i confirm %c at %i,%i\n",
+                           i1 % w2, i1 / w2, i3 % w2, i3 / w2,
+                           (block == N_ONE ? '1' : '0'), i2 % w2,
+                           i2 / w2);
+                }
+#endif
+                state->grid[i2] = block;
+                rowcount[i2 / w2]++;
+                colcount[i2 % w2]++;
+            }
+            if (state->grid[i1] == EMPTY && state->grid[i2] == check
+                && state->grid[i3] == check) {
+                ret++;
+#ifdef STANDALONE_SOLVER
+                if (solver_verbose) {
+                    printf("Solver: %i,%i and %i,%i confirm %c at %i,%i\n",
+                           i2 % w2, i2 / w2, i3 % w2, i3 / w2,
+                           (block == N_ONE ? '1' : '0'), i1 % w2,
+                           i1 / w2);
+                }
+#endif
+                state->grid[i1] = block;
+                rowcount[i1 / w2]++;
+                colcount[i1 % w2]++;
+            }
+        }
+    }
+
+    return ret;
+}
+
+static int unruly_solver_check_all_threes(game_state *state,
+                                          struct unruly_scratch *scratch)
+{
+    int ret = 0;
+
+    ret +=
+        unruly_solver_check_threes(state, scratch->zeros_rows,
+                                   scratch->zeros_cols, TRUE, N_ONE, N_ZERO);
+    ret +=
+        unruly_solver_check_threes(state, scratch->ones_rows,
+                                   scratch->ones_cols, TRUE, N_ZERO, N_ONE);
+    ret +=
+        unruly_solver_check_threes(state, scratch->zeros_rows,
+                                   scratch->zeros_cols, FALSE, N_ONE,
+                                   N_ZERO);
+    ret +=
+        unruly_solver_check_threes(state, scratch->ones_rows,
+                                   scratch->ones_cols, FALSE, N_ZERO, N_ONE);
+
+    return ret;
+}
+
+static int unruly_solver_check_uniques(game_state *state, int *rowcount,
+                                       int horizontal, char check, char block,
+                                       struct unruly_scratch *scratch)
+{
+    int w2 = state->w2, h2 = state->h2;
+
+    int rmult = (horizontal ? w2 : 1);
+    int cmult = (horizontal ? 1 : w2);
+    int nr = (horizontal ? h2 : w2);
+    int nc = (horizontal ? w2 : h2);
+    int max = nc / 2;
+
+    int r, r2, c;
+    int ret = 0;
+
+    /*
+     * Find each row that has max entries of type 'check', and see if
+     * all those entries match those in any row with max-1 entries. If
+     * so, set the last non-matching entry of the latter row to ensure
+     * that it's different.
+     */
+    for (r = 0; r < nr; r++) {
+        if (rowcount[r] != max)
+            continue;
+        for (r2 = 0; r2 < nr; r2++) {
+            int nmatch = 0, nonmatch = -1;
+            if (rowcount[r2] != max-1)
+                continue;
+            for (c = 0; c < nc; c++) {
+                if (state->grid[r*rmult + c*cmult] == check) {
+                    if (state->grid[r2*rmult + c*cmult] == check)
+                        nmatch++;
+                    else
+                        nonmatch = c;
+                }
+            }
+            if (nmatch == max-1) {
+                int i1 = r2 * rmult + nonmatch * cmult;
+                assert(nonmatch != -1);
+                if (state->grid[i1] == block)
+                    continue;
+                assert(state->grid[i1] == EMPTY);
+#ifdef STANDALONE_SOLVER
+                if (solver_verbose) {
+                    printf("Solver: matching %s %i, %i gives %c at %i,%i\n",
+                           horizontal ? "rows" : "cols",
+                           r, r2, (block == N_ONE ? '1' : '0'), i1 % w2,
+                           i1 / w2);
+                }
+#endif
+                state->grid[i1] = block;
+                if (block == N_ONE) {
+                    scratch->ones_rows[i1 / w2]++;
+                    scratch->ones_cols[i1 % w2]++;
+                } else {
+                    scratch->zeros_rows[i1 / w2]++;
+                    scratch->zeros_cols[i1 % w2]++;
+                }
+                ret++;
+            }
+        }
+    }
+    return ret;
+}
+
+static int unruly_solver_check_all_uniques(game_state *state,
+                                           struct unruly_scratch *scratch)
+{
+    int ret = 0;
+
+    ret += unruly_solver_check_uniques(state, scratch->ones_rows,
+                                       TRUE, N_ONE, N_ZERO, scratch);
+    ret += unruly_solver_check_uniques(state, scratch->zeros_rows,
+                                       TRUE, N_ZERO, N_ONE, scratch);
+    ret += unruly_solver_check_uniques(state, scratch->ones_cols,
+                                       FALSE, N_ONE, N_ZERO, scratch);
+    ret += unruly_solver_check_uniques(state, scratch->zeros_cols,
+                                       FALSE, N_ZERO, N_ONE, scratch);
+
+    return ret;
+}
+
+static int unruly_solver_fill_row(game_state *state, int i, int horizontal,
+                                  int *rowcount, int *colcount, char fill)
+{
+    int ret = 0;
+    int w2 = state->w2, h2 = state->h2;
+    int j;
+
+#ifdef STANDALONE_SOLVER
+    if (solver_verbose) {
+        printf("Solver: Filling %s %i with %c:",
+               (horizontal ? "Row" : "Col"), i,
+               (fill == N_ZERO ? '0' : '1'));
+    }
+#endif
+    /* Place a number in every empty square in a row/column */
+    for (j = 0; j < (horizontal ? w2 : h2); j++) {
+        int p = (horizontal ? i * w2 + j : j * w2 + i);
+
+        if (state->grid[p] == EMPTY) {
+#ifdef STANDALONE_SOLVER
+            if (solver_verbose) {
+                printf(" (%i,%i)", (horizontal ? j : i),
+                       (horizontal ? i : j));
+            }
+#endif
+            ret++;
+            state->grid[p] = fill;
+            rowcount[(horizontal ? i : j)]++;
+            colcount[(horizontal ? j : i)]++;
+        }
+    }
+
+#ifdef STANDALONE_SOLVER
+    if (solver_verbose) {
+        printf("\n");
+    }
+#endif
+
+    return ret;
+}
+
+static int unruly_solver_check_complete_nums(game_state *state,
+                                             int *complete, int horizontal,
+                                             int *rowcount, int *colcount,
+                                             char fill)
+{
+    int w2 = state->w2, h2 = state->h2;
+    int count = (horizontal ? h2 : w2); /* number of rows to check */
+    int target = (horizontal ? w2 : h2) / 2; /* target number of 0s/1s */
+    int *other = (horizontal ? rowcount : colcount);
+
+    int ret = 0;
+
+    int i;
+    /* Check for completed rows/cols for one number, then fill in the rest */
+    for (i = 0; i < count; i++) {
+        if (complete[i] == target && other[i] < target) {
+#ifdef STANDALONE_SOLVER
+            if (solver_verbose) {
+                printf("Solver: Row %i satisfied for %c\n", i,
+                       (fill != N_ZERO ? '0' : '1'));
+            }
+#endif
+            ret += unruly_solver_fill_row(state, i, horizontal, rowcount,
+                                          colcount, fill);
+        }
+    }
+
+    return ret;
+}
+
+static int unruly_solver_check_all_complete_nums(game_state *state,
+                                                 struct unruly_scratch *scratch)
+{
+    int ret = 0;
+
+    ret +=
+        unruly_solver_check_complete_nums(state, scratch->ones_rows, TRUE,
+                                          scratch->zeros_rows,
+                                          scratch->zeros_cols, N_ZERO);
+    ret +=
+        unruly_solver_check_complete_nums(state, scratch->ones_cols, FALSE,
+                                          scratch->zeros_rows,
+                                          scratch->zeros_cols, N_ZERO);
+    ret +=
+        unruly_solver_check_complete_nums(state, scratch->zeros_rows, TRUE,
+                                          scratch->ones_rows,
+                                          scratch->ones_cols, N_ONE);
+    ret +=
+        unruly_solver_check_complete_nums(state, scratch->zeros_cols, FALSE,
+                                          scratch->ones_rows,
+                                          scratch->ones_cols, N_ONE);
+
+    return ret;
+}
+
+static int unruly_solver_check_near_complete(game_state *state,
+                                             int *complete, int horizontal,
+                                             int *rowcount, int *colcount,
+                                             char fill)
+{
+    int w2 = state->w2, h2 = state->h2;
+    int w = w2/2, h = h2/2;
+
+    int dx = horizontal ? 1 : 0, dy = 1 - dx;
+
+    int sx = dx, sy = dy;
+    int ex = w2 - dx, ey = h2 - dy;
+
+    int x, y;
+    int ret = 0;
+
+    /*
+     * This function checks for a row with one Y remaining, then looks
+     * for positions that could cause the remaining squares in the row
+     * to make 3 X's in a row. Example:
+     *
+     * Consider the following row:
+     * 1 1 0 . . .
+     * If the last 1 was placed in the last square, the remaining
+     * squares would be 0:
+     * 1 1 0 0 0 1
+     * This violates the 3 in a row rule. We now know that the last 1
+     * shouldn't be in the last cell.
+     * 1 1 0 . . 0
+     */
+
+    /* Check for any two blank and one filled square */
+    for (y = sy; y < ey; y++) {
+        /* One type must have 1 remaining, the other at least 2 */
+        if (horizontal && (complete[y] < w - 1 || rowcount[y] > w - 2))
+            continue;
+
+        for (x = sx; x < ex; x++) {
+            int i, i1, i2, i3;
+            if (!horizontal
+                && (complete[x] < h - 1 || colcount[x] > h - 2))
+                continue;
+
+            i = (horizontal ? y : x);
+            i1 = (y-dy) * w2 + (x-dx);
+            i2 = y * w2 + x;
+            i3 = (y+dy) * w2 + (x+dx);
+
+            if (state->grid[i1] == fill && state->grid[i2] == EMPTY
+                && state->grid[i3] == EMPTY) {
+                /*
+                 * Temporarily fill the empty spaces with something else.
+                 * This avoids raising the counts for the row and column
+                 */
+                state->grid[i2] = BOGUS;
+                state->grid[i3] = BOGUS;
+
+#ifdef STANDALONE_SOLVER
+                if (solver_verbose) {
+                    printf("Solver: Row %i nearly satisfied for %c\n", i,
+                           (fill != N_ZERO ? '0' : '1'));
+                }
+#endif
+                ret +=
+                    unruly_solver_fill_row(state, i, horizontal, rowcount,
+                                         colcount, fill);
+
+                state->grid[i2] = EMPTY;
+                state->grid[i3] = EMPTY;
+            }
+
+            else if (state->grid[i1] == EMPTY && state->grid[i2] == fill
+                     && state->grid[i3] == EMPTY) {
+                state->grid[i1] = BOGUS;
+                state->grid[i3] = BOGUS;
+
+#ifdef STANDALONE_SOLVER
+                if (solver_verbose) {
+                    printf("Solver: Row %i nearly satisfied for %c\n", i,
+                           (fill != N_ZERO ? '0' : '1'));
+                }
+#endif
+                ret +=
+                    unruly_solver_fill_row(state, i, horizontal, rowcount,
+                                         colcount, fill);
+
+                state->grid[i1] = EMPTY;
+                state->grid[i3] = EMPTY;
+            }
+
+            else if (state->grid[i1] == EMPTY && state->grid[i2] == EMPTY
+                     && state->grid[i3] == fill) {
+                state->grid[i1] = BOGUS;
+                state->grid[i2] = BOGUS;
+
+#ifdef STANDALONE_SOLVER
+                if (solver_verbose) {
+                    printf("Solver: Row %i nearly satisfied for %c\n", i,
+                           (fill != N_ZERO ? '0' : '1'));
+                }
+#endif
+                ret +=
+                    unruly_solver_fill_row(state, i, horizontal, rowcount,
+                                         colcount, fill);
+
+                state->grid[i1] = EMPTY;
+                state->grid[i2] = EMPTY;
+            }
+
+            else if (state->grid[i1] == EMPTY && state->grid[i2] == EMPTY
+                     && state->grid[i3] == EMPTY) {
+                state->grid[i1] = BOGUS;
+                state->grid[i2] = BOGUS;
+                state->grid[i3] = BOGUS;
+
+#ifdef STANDALONE_SOLVER
+                if (solver_verbose) {
+                    printf("Solver: Row %i nearly satisfied for %c\n", i,
+                           (fill != N_ZERO ? '0' : '1'));
+                }
+#endif
+                ret +=
+                    unruly_solver_fill_row(state, i, horizontal, rowcount,
+                                         colcount, fill);
+
+                state->grid[i1] = EMPTY;
+                state->grid[i2] = EMPTY;
+                state->grid[i3] = EMPTY;
+            }
+        }
+    }
+
+    return ret;
+}
+
+static int unruly_solver_check_all_near_complete(game_state *state,
+                                                 struct unruly_scratch *scratch)
+{
+    int ret = 0;
+
+    ret +=
+        unruly_solver_check_near_complete(state, scratch->ones_rows, TRUE,
+                                        scratch->zeros_rows,
+                                        scratch->zeros_cols, N_ZERO);
+    ret +=
+        unruly_solver_check_near_complete(state, scratch->ones_cols, FALSE,
+                                        scratch->zeros_rows,
+                                        scratch->zeros_cols, N_ZERO);
+    ret +=
+        unruly_solver_check_near_complete(state, scratch->zeros_rows, TRUE,
+                                        scratch->ones_rows,
+                                        scratch->ones_cols, N_ONE);
+    ret +=
+        unruly_solver_check_near_complete(state, scratch->zeros_cols, FALSE,
+                                        scratch->ones_rows,
+                                        scratch->ones_cols, N_ONE);
+
+    return ret;
+}
+
+static int unruly_validate_rows(const game_state *state, int horizontal,
+                                char check, int *errors)
+{
+    int w2 = state->w2, h2 = state->h2;
+
+    int dx = horizontal ? 1 : 0, dy = 1 - dx;
+
+    int sx = dx, sy = dy;
+    int ex = w2 - dx, ey = h2 - dy;
+
+    int x, y;
+    int ret = 0;
+
+    int err1 = (horizontal ? FE_HOR_ROW_LEFT : FE_VER_ROW_TOP);
+    int err2 = (horizontal ? FE_HOR_ROW_MID : FE_VER_ROW_MID);
+    int err3 = (horizontal ? FE_HOR_ROW_RIGHT : FE_VER_ROW_BOTTOM);
+
+    /* Check for any three in a row, and mark errors accordingly (if
+     * required) */
+    for (y = sy; y < ey; y++) {
+        for (x = sx; x < ex; x++) {
+            int i1 = (y-dy) * w2 + (x-dx);
+            int i2 = y * w2 + x;
+            int i3 = (y+dy) * w2 + (x+dx);
+
+            if (state->grid[i1] == check && state->grid[i2] == check
+                && state->grid[i3] == check) {
+                ret++;
+                if (errors) {
+                    errors[i1] |= err1;
+                    errors[i2] |= err2;
+                    errors[i3] |= err3;
+                }
+            }
+        }
+    }
+
+    return ret;
+}
+
+static int unruly_validate_unique(const game_state *state, int horizontal,
+                                  int *errors)
+{
+    int w2 = state->w2, h2 = state->h2;
+
+    int rmult = (horizontal ? w2 : 1);
+    int cmult = (horizontal ? 1 : w2);
+    int nr = (horizontal ? h2 : w2);
+    int nc = (horizontal ? w2 : h2);
+    int err = (horizontal ? FE_ROW_MATCH : FE_COL_MATCH);
+
+    int r, r2, c;
+    int ret = 0;
+
+    /* Check for any two full rows matching exactly, and mark errors
+     * accordingly (if required) */
+    for (r = 0; r < nr; r++) {
+        int nfull = 0;
+        for (c = 0; c < nc; c++)
+            if (state->grid[r*rmult + c*cmult] != EMPTY)
+                nfull++;
+        if (nfull != nc)
+            continue;
+        for (r2 = r+1; r2 < nr; r2++) {
+            int match = TRUE;
+            for (c = 0; c < nc; c++)
+                if (state->grid[r*rmult + c*cmult] !=
+                    state->grid[r2*rmult + c*cmult])
+                    match = FALSE;
+            if (match) {
+                if (errors) {
+                    for (c = 0; c < nc; c++) {
+                        errors[r*rmult + c*cmult] |= err;
+                        errors[r2*rmult + c*cmult] |= err;
+                    }
+                }
+                ret++;
+            }
+        }
+    }
+
+    return ret;
+}
+
+static int unruly_validate_all_rows(const game_state *state, int *errors)
+{
+    int errcount = 0;
+
+    errcount += unruly_validate_rows(state, TRUE, N_ONE, errors);
+    errcount += unruly_validate_rows(state, FALSE, N_ONE, errors);
+    errcount += unruly_validate_rows(state, TRUE, N_ZERO, errors);
+    errcount += unruly_validate_rows(state, FALSE, N_ZERO, errors);
+
+    if (state->unique) {
+        errcount += unruly_validate_unique(state, TRUE, errors);
+        errcount += unruly_validate_unique(state, FALSE, errors);
+    }
+
+    if (errcount)
+        return -1;
+    return 0;
+}
+
+static int unruly_validate_counts(const game_state *state,
+                                  struct unruly_scratch *scratch, int *errors)
+{
+    int w2 = state->w2, h2 = state->h2;
+    int w = w2/2, h = h2/2;
+    char below = FALSE;
+    char above = FALSE;
+    int i;
+
+    /* See if all rows/columns are satisfied. If one is exceeded,
+     * mark it as an error (if required)
+     */
+
+    char hasscratch = TRUE;
+    if (!scratch) {
+        scratch = unruly_new_scratch(state);
+        hasscratch = FALSE;
+    }
+
+    for (i = 0; i < w2; i++) {
+        if (scratch->ones_cols[i] < h)
+            below = TRUE;
+        if (scratch->zeros_cols[i] < h)
+            below = TRUE;
+
+        if (scratch->ones_cols[i] > h) {
+            above = TRUE;
+            if (errors)
+                errors[2*h2 + i] = TRUE;
+        } else if (errors)
+            errors[2*h2 + i] = FALSE;
+
+        if (scratch->zeros_cols[i] > h) {
+            above = TRUE;
+            if (errors)
+                errors[2*h2 + w2 + i] = TRUE;
+        } else if (errors)
+            errors[2*h2 + w2 + i] = FALSE;
+    }
+    for (i = 0; i < h2; i++) {
+        if (scratch->ones_rows[i] < w)
+            below = TRUE;
+        if (scratch->zeros_rows[i] < w)
+            below = TRUE;
+
+        if (scratch->ones_rows[i] > w) {
+            above = TRUE;
+            if (errors)
+                errors[i] = TRUE;
+        } else if (errors)
+            errors[i] = FALSE;
+
+        if (scratch->zeros_rows[i] > w) {
+            above = TRUE;
+            if (errors)
+                errors[h2 + i] = TRUE;
+        } else if (errors)
+            errors[h2 + i] = FALSE;
+    }
+
+    if (!hasscratch)
+        unruly_free_scratch(scratch);
+
+    return (above ? -1 : below ? 1 : 0);
+}
+
+static int unruly_solve_game(game_state *state,
+                             struct unruly_scratch *scratch, int diff)
+{
+    int done, maxdiff = -1;
+
+    while (TRUE) {
+        done = 0;
+
+        /* Check for impending 3's */
+        done += unruly_solver_check_all_threes(state, scratch);
+
+        /* Keep using the simpler techniques while they produce results */
+        if (done) {
+            if (maxdiff < DIFF_EASY)
+                maxdiff = DIFF_EASY;
+            continue;
+        }
+
+        /* Check for completed rows */
+        done += unruly_solver_check_all_complete_nums(state, scratch);
+
+        if (done) {
+            if (maxdiff < DIFF_EASY)
+                maxdiff = DIFF_EASY;
+            continue;
+        }
+
+        /* Check for impending failures of row/column uniqueness, if
+         * it's enabled in this game mode */
+        if (state->unique) {
+            done += unruly_solver_check_all_uniques(state, scratch);
+
+            if (done) {
+                if (maxdiff < DIFF_EASY)
+                    maxdiff = DIFF_EASY;
+                continue;
+            }
+        }
+
+        /* Normal techniques */
+        if (diff < DIFF_NORMAL)
+            break;
+
+        /* Check for nearly completed rows */
+        done += unruly_solver_check_all_near_complete(state, scratch);
+
+        if (done) {
+            if (maxdiff < DIFF_NORMAL)
+                maxdiff = DIFF_NORMAL;
+            continue;
+        }
+
+        break;
+    }
+    return maxdiff;
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    game_state *solved = dup_game(state);
+    struct unruly_scratch *scratch = unruly_new_scratch(solved);
+    char *ret = NULL;
+    int result;
+
+    unruly_solve_game(solved, scratch, DIFFCOUNT);
+
+    result = unruly_validate_counts(solved, scratch, NULL);
+    if (unruly_validate_all_rows(solved, NULL) == -1)
+        result = -1;
+
+    if (result == 0) {
+        int w2 = solved->w2, h2 = solved->h2;
+        int s = w2 * h2;
+        char *p;
+        int i;
+
+        ret = snewn(s + 2, char);
+        p = ret;
+        *p++ = 'S';
+
+        for (i = 0; i < s; i++)
+            *p++ = (solved->grid[i] == N_ONE ? '1' : '0');
+
+        *p++ = '\0';
+    } else if (result == 1)
+        *error = "No solution found.";
+    else if (result == -1)
+        *error = "Puzzle is invalid.";
+
+    free_game(solved);
+    unruly_free_scratch(scratch);
+    return ret;
+}
+
+/* ********* *
+ * Generator *
+ * ********* */
+
+static int unruly_fill_game(game_state *state, struct unruly_scratch *scratch,
+                            random_state *rs)
+{
+
+    int w2 = state->w2, h2 = state->h2;
+    int s = w2 * h2;
+    int i, j;
+    int *spaces;
+
+#ifdef STANDALONE_SOLVER
+    if (solver_verbose) {
+        printf("Generator: Attempt to fill grid\n");
+    }
+#endif
+
+    /* Generate random array of spaces */
+    spaces = snewn(s, int);
+    for (i = 0; i < s; i++)
+        spaces[i] = i;
+    shuffle(spaces, s, sizeof(*spaces), rs);
+
+    /*
+     * Construct a valid filled grid by repeatedly picking an unfilled
+     * space and fill it, then calling the solver to fill in any
+     * spaces forced by the change.
+     */
+    for (j = 0; j < s; j++) {
+        i = spaces[j];
+
+        if (state->grid[i] != EMPTY)
+            continue;
+
+        if (random_upto(rs, 2)) {
+            state->grid[i] = N_ONE;
+            scratch->ones_rows[i / w2]++;
+            scratch->ones_cols[i % w2]++;
+        } else {
+            state->grid[i] = N_ZERO;
+            scratch->zeros_rows[i / w2]++;
+            scratch->zeros_cols[i % w2]++;
+        }
+
+        unruly_solve_game(state, scratch, DIFFCOUNT);
+    }
+    sfree(spaces);
+
+    if (unruly_validate_all_rows(state, NULL) != 0
+        || unruly_validate_counts(state, scratch, NULL) != 0)
+        return FALSE;
+
+    return TRUE;
+}
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                           char **aux, int interactive)
+{
+#ifdef STANDALONE_SOLVER
+    char *debug;
+    int temp_verbose = FALSE;
+#endif
+
+    int w2 = params->w2, h2 = params->h2;
+    int s = w2 * h2;
+    int *spaces;
+    int i, j, run;
+    char *ret, *p;
+
+    game_state *state;
+    struct unruly_scratch *scratch;
+
+    int attempts = 0;
+
+    while (1) {
+
+        while (TRUE) {
+            attempts++;
+            state = blank_state(w2, h2, params->unique);
+            scratch = unruly_new_scratch(state);
+            if (unruly_fill_game(state, scratch, rs))
+                break;
+            free_game(state);
+            unruly_free_scratch(scratch);
+        }
+
+#ifdef STANDALONE_SOLVER
+        if (solver_verbose) {
+            printf("Puzzle generated in %i attempts\n", attempts);
+            debug = game_text_format(state);
+            fputs(debug, stdout);
+            sfree(debug);
+
+            temp_verbose = solver_verbose;
+            solver_verbose = FALSE;
+        }
+#endif
+
+        unruly_free_scratch(scratch);
+
+        /* Generate random array of spaces */
+        spaces = snewn(s, int);
+        for (i = 0; i < s; i++)
+            spaces[i] = i;
+        shuffle(spaces, s, sizeof(*spaces), rs);
+
+        /*
+         * Winnow the clues by starting from our filled grid, repeatedly
+         * picking a filled space and emptying it, as long as the solver
+         * reports that the puzzle can still be solved after doing so.
+         */
+        for (j = 0; j < s; j++) {
+            char c;
+            game_state *solver;
+
+            i = spaces[j];
+
+            c = state->grid[i];
+            state->grid[i] = EMPTY;
+
+            solver = dup_game(state);
+            scratch = unruly_new_scratch(state);
+
+            unruly_solve_game(solver, scratch, params->diff);
+
+            if (unruly_validate_counts(solver, scratch, NULL) != 0)
+                state->grid[i] = c;
+
+            free_game(solver);
+            unruly_free_scratch(scratch);
+        }
+        sfree(spaces);
+
+#ifdef STANDALONE_SOLVER
+        if (temp_verbose) {
+            solver_verbose = TRUE;
+
+            printf("Final puzzle:\n");
+            debug = game_text_format(state);
+            fputs(debug, stdout);
+            sfree(debug);
+        }
+#endif
+
+        /*
+         * See if the game has accidentally come out too easy.
+         */
+        if (params->diff > 0) {
+            int ok;
+            game_state *solver;
+
+            solver = dup_game(state);
+            scratch = unruly_new_scratch(state);
+
+            unruly_solve_game(solver, scratch, params->diff - 1);
+
+            ok = unruly_validate_counts(solver, scratch, NULL);
+
+            free_game(solver);
+            unruly_free_scratch(scratch);
+
+            if (ok)
+                break;
+        } else {
+            /*
+             * Puzzles of the easiest difficulty can't be too easy.
+             */
+            break;
+        }
+    }
+
+    /* Encode description */
+    ret = snewn(s + 1, char);
+    p = ret;
+    run = 0;
+    for (i = 0; i < s+1; i++) {
+        if (i == s || state->grid[i] == N_ZERO) {
+            while (run > 24) {
+                *p++ = 'z';
+                run -= 25;
+            }
+            *p++ = 'a' + run;
+            run = 0;
+        } else if (state->grid[i] == N_ONE) {
+            while (run > 24) {
+                *p++ = 'Z';
+                run -= 25;
+            }
+            *p++ = 'A' + run;
+            run = 0;
+        } else {
+            run++;
+        }
+    }
+    *p = '\0';
+
+    free_game(state);
+
+    return ret;
+}
+
+/* ************** *
+ * User Interface *
+ * ************** */
+
+struct game_ui {
+    int cx, cy;
+    char cursor;
+};
+
+static game_ui *new_ui(const game_state *state)
+{
+    game_ui *ret = snew(game_ui);
+
+    ret->cx = ret->cy = 0;
+    ret->cursor = FALSE;
+
+    return ret;
+}
+
+static void free_ui(game_ui *ui)
+{
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+}
+
+struct game_drawstate {
+    int tilesize;
+    int w2, h2;
+    int started;
+
+    int *gridfs;
+    int *rowfs;
+
+    int *grid;
+};
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    struct game_drawstate *ds = snew(struct game_drawstate);
+
+    int w2 = state->w2, h2 = state->h2;
+    int s = w2 * h2;
+    int i;
+
+    ds->tilesize = 0;
+    ds->w2 = w2;
+    ds->h2 = h2;
+    ds->started = FALSE;
+
+    ds->gridfs = snewn(s, int);
+    ds->rowfs = snewn(2 * (w2 + h2), int);
+
+    ds->grid = snewn(s, int);
+    for (i = 0; i < s; i++)
+        ds->grid[i] = -1;
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds->gridfs);
+    sfree(ds->rowfs);
+    sfree(ds->grid);
+    sfree(ds);
+}
+
+#define COORD(x)     ( (x) * ds->tilesize + ds->tilesize/2 )
+#define FROMCOORD(x) ( ((x)-(ds->tilesize/2)) / ds->tilesize )
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int ox, int oy, int button)
+{
+    int hx = ui->cx;
+    int hy = ui->cy;
+
+    int gx = FROMCOORD(ox);
+    int gy = FROMCOORD(oy);
+
+    int w2 = state->w2, h2 = state->h2;
+
+    button &= ~MOD_MASK;
+
+    /* Mouse click */
+    if (button == LEFT_BUTTON || button == RIGHT_BUTTON ||
+        button == MIDDLE_BUTTON) {
+        if (ox >= (ds->tilesize / 2) && gx < w2
+            && oy >= (ds->tilesize / 2) && gy < h2) {
+            hx = gx;
+            hy = gy;
+            ui->cursor = FALSE;
+        } else
+            return NULL;
+    }
+
+    /* Keyboard move */
+    if (IS_CURSOR_MOVE(button)) {
+        move_cursor(button, &ui->cx, &ui->cy, w2, h2, 0);
+        ui->cursor = TRUE;
+        return "";
+    }
+
+    /* Place one */
+    if ((ui->cursor && (button == CURSOR_SELECT || button == CURSOR_SELECT2
+                        || button == '\b' || button == '0' || button == '1'
+                        || button == '2')) ||
+        button == LEFT_BUTTON || button == RIGHT_BUTTON ||
+        button == MIDDLE_BUTTON) {
+        char buf[80];
+        char c, i;
+
+        if (state->immutable[hy * w2 + hx])
+            return NULL;
+
+        c = '-';
+        i = state->grid[hy * w2 + hx];
+
+        if (button == '0' || button == '2')
+            c = '0';
+        else if (button == '1')
+            c = '1';
+        else if (button == MIDDLE_BUTTON)
+            c = '-';
+
+        /* Cycle through options */
+        else if (button == CURSOR_SELECT2 || button == RIGHT_BUTTON)
+            c = (i == EMPTY ? '0' : i == N_ZERO ? '1' : '-');
+        else if (button == CURSOR_SELECT || button == LEFT_BUTTON)
+            c = (i == EMPTY ? '1' : i == N_ONE ? '0' : '-');
+
+        if (state->grid[hy * w2 + hx] ==
+            (c == '0' ? N_ZERO : c == '1' ? N_ONE : EMPTY))
+            return NULL;               /* don't put no-ops on the undo chain */
+
+        sprintf(buf, "P%c,%d,%d", c, hx, hy);
+
+        return dupstr(buf);
+    }
+    return NULL;
+}
+
+static game_state *execute_move(const game_state *state, const char *move)
+{
+    int w2 = state->w2, h2 = state->h2;
+    int s = w2 * h2;
+    int x, y, i;
+    char c;
+
+    game_state *ret;
+
+    if (move[0] == 'S') {
+        const char *p;
+
+        ret = dup_game(state);
+        p = move + 1;
+
+        for (i = 0; i < s; i++) {
+
+            if (!*p || !(*p == '1' || *p == '0')) {
+                free_game(ret);
+                return NULL;
+            }
+
+            ret->grid[i] = (*p == '1' ? N_ONE : N_ZERO);
+            p++;
+        }
+
+        ret->completed = ret->cheated = TRUE;
+        return ret;
+    } else if (move[0] == 'P'
+               && sscanf(move + 1, "%c,%d,%d", &c, &x, &y) == 3 && x >= 0
+               && x < w2 && y >= 0 && y < h2 && (c == '-' || c == '0'
+                                                 || c == '1')) {
+        ret = dup_game(state);
+        i = y * w2 + x;
+
+        if (state->immutable[i]) {
+            free_game(ret);
+            return NULL;
+        }
+
+        ret->grid[i] = (c == '1' ? N_ONE : c == '0' ? N_ZERO : EMPTY);
+
+        if (!ret->completed && unruly_validate_counts(ret, NULL, NULL) == 0
+            && (unruly_validate_all_rows(ret, NULL) == 0))
+            ret->completed = TRUE;
+
+        return ret;
+    }
+
+    return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    *x = tilesize * (params->w2 + 1);
+    *y = tilesize * (params->h2 + 1);
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+    int i;
+
+    frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
+
+    for (i = 0; i < 3; i++) {
+        ret[COL_1 * 3 + i] = 0.2F;
+        ret[COL_1_HIGHLIGHT * 3 + i] = 0.4F;
+        ret[COL_1_LOWLIGHT * 3 + i] = 0.0F;
+        ret[COL_0 * 3 + i] = 0.95F;
+        ret[COL_0_HIGHLIGHT * 3 + i] = 1.0F;
+        ret[COL_0_LOWLIGHT * 3 + i] = 0.9F;
+        ret[COL_EMPTY * 3 + i] = 0.5F;
+        ret[COL_GRID * 3 + i] = 0.3F;
+    }
+    game_mkhighlight_specific(fe, ret, COL_0, COL_0_HIGHLIGHT, COL_0_LOWLIGHT);
+    game_mkhighlight_specific(fe, ret, COL_1, COL_1_HIGHLIGHT, COL_1_LOWLIGHT);
+
+    ret[COL_ERROR * 3 + 0] = 1.0F;
+    ret[COL_ERROR * 3 + 1] = 0.0F;
+    ret[COL_ERROR * 3 + 2] = 0.0F;
+
+    ret[COL_CURSOR * 3 + 0] = 0.0F;
+    ret[COL_CURSOR * 3 + 1] = 0.7F;
+    ret[COL_CURSOR * 3 + 2] = 0.0F;
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static void unruly_draw_err_rectangle(drawing *dr, int x, int y, int w, int h,
+                                      int tilesize)
+{
+    double thick = tilesize / 10;
+    double margin = tilesize / 20;
+
+    draw_rect(dr, x+margin, y+margin, w-2*margin, thick, COL_ERROR);
+    draw_rect(dr, x+margin, y+margin, thick, h-2*margin, COL_ERROR);
+    draw_rect(dr, x+margin, y+h-margin-thick, w-2*margin, thick, COL_ERROR);
+    draw_rect(dr, x+w-margin-thick, y+margin, thick, h-2*margin, COL_ERROR);
+}
+
+static void unruly_draw_tile(drawing *dr, int x, int y, int tilesize, int tile)
+{
+    clip(dr, x, y, tilesize, tilesize);
+
+    /* Draw the grid edge first, so the tile can overwrite it */
+    draw_rect(dr, x, y, tilesize, tilesize, COL_GRID);
+
+    /* Background of the tile */
+    {
+        int val = (tile & FF_ZERO ? 0 : tile & FF_ONE ? 2 : 1);
+        val = (val == 0 ? COL_0 : val == 2 ? COL_1 : COL_EMPTY);
+
+        if ((tile & (FF_FLASH1 | FF_FLASH2)) &&
+             (val == COL_0 || val == COL_1)) {
+            val += (tile & FF_FLASH1 ? 1 : 2);
+        }
+
+        draw_rect(dr, x, y, tilesize-1, tilesize-1, val);
+
+        if ((val == COL_0 || val == COL_1) && (tile & FF_IMMUTABLE)) {
+            draw_rect(dr, x + tilesize/6, y + tilesize/6,
+                      tilesize - 2*(tilesize/6) - 2, 1, val + 2);
+            draw_rect(dr, x + tilesize/6, y + tilesize/6,
+                      1, tilesize - 2*(tilesize/6) - 2, val + 2);
+            draw_rect(dr, x + tilesize/6 + 1, y + tilesize - tilesize/6 - 2,
+                      tilesize - 2*(tilesize/6) - 2, 1, val + 1);
+            draw_rect(dr, x + tilesize - tilesize/6 - 2, y + tilesize/6 + 1,
+                      1, tilesize - 2*(tilesize/6) - 2, val + 1);
+        }
+    }
+
+    /* 3-in-a-row errors */
+    if (tile & (FE_HOR_ROW_LEFT | FE_HOR_ROW_RIGHT)) {
+        int left = x, right = x + tilesize - 1;
+        if ((tile & FE_HOR_ROW_LEFT))
+            right += tilesize/2;
+        if ((tile & FE_HOR_ROW_RIGHT))
+            left -= tilesize/2;
+        unruly_draw_err_rectangle(dr, left, y, right-left, tilesize-1, tilesize);
+    }
+    if (tile & (FE_VER_ROW_TOP | FE_VER_ROW_BOTTOM)) {
+        int top = y, bottom = y + tilesize - 1;
+        if ((tile & FE_VER_ROW_TOP))
+            bottom += tilesize/2;
+        if ((tile & FE_VER_ROW_BOTTOM))
+            top -= tilesize/2;
+        unruly_draw_err_rectangle(dr, x, top, tilesize-1, bottom-top, tilesize);
+    }
+
+    /* Count errors */
+    if (tile & FE_COUNT) {
+        draw_text(dr, x + tilesize/2, y + tilesize/2, FONT_VARIABLE,
+                  tilesize/2, ALIGN_HCENTRE | ALIGN_VCENTRE, COL_ERROR, "!");
+    }
+
+    /* Row-match errors */
+    if (tile & FE_ROW_MATCH) {
+        draw_rect(dr, x, y+tilesize/2-tilesize/12,
+                  tilesize, 2*(tilesize/12), COL_ERROR);
+    }
+    if (tile & FE_COL_MATCH) {
+        draw_rect(dr, x+tilesize/2-tilesize/12, y,
+                  2*(tilesize/12), tilesize, COL_ERROR);
+    }
+
+    /* Cursor rectangle */
+    if (tile & FF_CURSOR) {
+        draw_rect(dr, x, y, tilesize/12, tilesize-1, COL_CURSOR);
+        draw_rect(dr, x, y, tilesize-1, tilesize/12, COL_CURSOR);
+        draw_rect(dr, x+tilesize-1-tilesize/12, y, tilesize/12, tilesize-1,
+                  COL_CURSOR);
+        draw_rect(dr, x, y+tilesize-1-tilesize/12, tilesize-1, tilesize/12,
+                  COL_CURSOR);
+    }
+
+    unclip(dr);
+    draw_update(dr, x, y, tilesize, tilesize);
+}
+
+#define TILE_SIZE (ds->tilesize)
+#define DEFAULT_TILE_SIZE 32
+#define FLASH_FRAME 0.12F
+#define FLASH_TIME (FLASH_FRAME * 3)
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int w2 = state->w2, h2 = state->h2;
+    int s = w2 * h2;
+    int flash;
+    int x, y, i;
+
+    if (!ds->started) {
+        /* Main window background */
+        draw_rect(dr, 0, 0, TILE_SIZE * (w2+1), TILE_SIZE * (h2+1),
+                  COL_BACKGROUND);
+        /* Outer edge of grid */
+        draw_rect(dr, COORD(0)-TILE_SIZE/10, COORD(0)-TILE_SIZE/10,
+                  TILE_SIZE*w2 + 2*(TILE_SIZE/10) - 1,
+                  TILE_SIZE*h2 + 2*(TILE_SIZE/10) - 1, COL_GRID);
+
+        draw_update(dr, 0, 0, TILE_SIZE * (w2+1), TILE_SIZE * (h2+1));
+        ds->started = TRUE;
+    }
+
+    flash = 0;
+    if (flashtime > 0)
+        flash = (int)(flashtime / FLASH_FRAME) == 1 ? FF_FLASH2 : FF_FLASH1;
+
+    for (i = 0; i < s; i++)
+        ds->gridfs[i] = 0;
+    unruly_validate_all_rows(state, ds->gridfs);
+    for (i = 0; i < 2 * (h2 + w2); i++)
+        ds->rowfs[i] = 0;
+    unruly_validate_counts(state, NULL, ds->rowfs);
+
+    for (y = 0; y < h2; y++) {
+        for (x = 0; x < w2; x++) {
+            int tile;
+
+            i = y * w2 + x;
+
+            tile = ds->gridfs[i];
+
+            if (state->grid[i] == N_ONE) {
+                tile |= FF_ONE;
+                if (ds->rowfs[y] || ds->rowfs[2*h2 + x])
+                    tile |= FE_COUNT;
+            } else if (state->grid[i] == N_ZERO) {
+                tile |= FF_ZERO;
+                if (ds->rowfs[h2 + y] || ds->rowfs[2*h2 + w2 + x])
+                    tile |= FE_COUNT;
+            }
+
+            tile |= flash;
+
+            if (state->immutable[i])
+                tile |= FF_IMMUTABLE;
+
+            if (ui->cursor && ui->cx == x && ui->cy == y)
+                tile |= FF_CURSOR;
+
+            if (ds->grid[i] != tile) {
+                ds->grid[i] = tile;
+                unruly_draw_tile(dr, COORD(x), COORD(y), TILE_SIZE, tile);
+            }
+        }
+    }
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    return 0.0F;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    if (!oldstate->completed && newstate->completed &&
+        !oldstate->cheated && !newstate->cheated)
+        return FLASH_TIME;
+    return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    return state->completed ? +1 : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+    int pw, ph;
+
+    /* Using 7mm squares */
+    game_compute_size(params, 700, &pw, &ph);
+    *x = pw / 100.0F;
+    *y = ph / 100.0F;
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+    int w2 = state->w2, h2 = state->h2;
+    int x, y;
+
+    int ink = print_mono_colour(dr, 0);
+
+    for (y = 0; y < h2; y++)
+        for (x = 0; x < w2; x++) {
+            int tx = x * tilesize + (tilesize / 2);
+            int ty = y * tilesize + (tilesize / 2);
+
+            /* Draw the border */
+            int coords[8];
+            coords[0] = tx;
+            coords[1] = ty - 1;
+            coords[2] = tx + tilesize;
+            coords[3] = ty - 1;
+            coords[4] = tx + tilesize;
+            coords[5] = ty + tilesize - 1;
+            coords[6] = tx;
+            coords[7] = ty + tilesize - 1;
+            draw_polygon(dr, coords, 4, -1, ink);
+
+            if (state->grid[y * w2 + x] == N_ONE)
+                draw_rect(dr, tx, ty, tilesize, tilesize, ink);
+            else if (state->grid[y * w2 + x] == N_ZERO)
+                draw_circle(dr, tx + tilesize/2, ty + tilesize/2,
+                            tilesize/12, ink, ink);
+        }
+}
+
+#ifdef COMBINED
+#define thegame unruly
+#endif
+
+const struct game thegame = {
+    "Unruly", "games.unruly", "unruly",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    TRUE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    DEFAULT_TILE_SIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    TRUE, FALSE, game_print_size, game_print,
+    FALSE,                      /* wants_statusbar */
+    FALSE, game_timing_state,
+    0,                          /* flags */
+};
+
+/* ***************** *
+ * Standalone solver *
+ * ***************** */
+
+#ifdef STANDALONE_SOLVER
+#include <time.h>
+#include <stdarg.h>
+
+/* Most of the standalone solver code was copied from unequal.c and singles.c */
+
+const char *quis;
+
+static void usage_exit(const char *msg)
+{
+    if (msg)
+        fprintf(stderr, "%s: %s\n", quis, msg);
+    fprintf(stderr,
+            "Usage: %s [-v] [--seed SEED] <params> | [game_id [game_id ...]]\n",
+            quis);
+    exit(1);
+}
+
+int main(int argc, char *argv[])
+{
+    random_state *rs;
+    time_t seed = time(NULL);
+
+    game_params *params = NULL;
+
+    char *id = NULL, *desc = NULL, *err;
+
+    quis = argv[0];
+
+    while (--argc > 0) {
+        char *p = *++argv;
+        if (!strcmp(p, "--seed")) {
+            if (argc == 0)
+                usage_exit("--seed needs an argument");
+            seed = (time_t) atoi(*++argv);
+            argc--;
+        } else if (!strcmp(p, "-v"))
+            solver_verbose = TRUE;
+        else if (*p == '-')
+            usage_exit("unrecognised option");
+        else
+            id = p;
+    }
+
+    if (id) {
+        desc = strchr(id, ':');
+        if (desc)
+            *desc++ = '\0';
+
+        params = default_params();
+        decode_params(params, id);
+        err = validate_params(params, TRUE);
+        if (err) {
+            fprintf(stderr, "Parameters are invalid\n");
+            fprintf(stderr, "%s: %s", argv[0], err);
+            exit(1);
+        }
+    }
+
+    if (!desc) {
+        char *desc_gen, *aux;
+        rs = random_new((void *) &seed, sizeof(time_t));
+        if (!params)
+            params = default_params();
+        printf("Generating puzzle with parameters %s\n",
+               encode_params(params, TRUE));
+        desc_gen = new_game_desc(params, rs, &aux, FALSE);
+
+        if (!solver_verbose) {
+            char *fmt = game_text_format(new_game(NULL, params, desc_gen));
+            fputs(fmt, stdout);
+            sfree(fmt);
+        }
+
+        printf("Game ID: %s\n", desc_gen);
+    } else {
+        game_state *input;
+        struct unruly_scratch *scratch;
+        int maxdiff, errcode;
+
+        err = validate_desc(params, desc);
+        if (err) {
+            fprintf(stderr, "Description is invalid\n");
+            fprintf(stderr, "%s", err);
+            exit(1);
+        }
+
+        input = new_game(NULL, params, desc);
+        scratch = unruly_new_scratch(input);
+
+        maxdiff = unruly_solve_game(input, scratch, DIFFCOUNT);
+
+        errcode = unruly_validate_counts(input, scratch, NULL);
+        if (unruly_validate_all_rows(input, NULL) == -1)
+            errcode = -1;
+
+        if (errcode != -1) {
+            char *fmt = game_text_format(input);
+            fputs(fmt, stdout);
+            sfree(fmt);
+            if (maxdiff < 0)
+                printf("Difficulty: already solved!\n");
+            else
+                printf("Difficulty: %s\n", unruly_diffnames[maxdiff]);
+        }
+
+        if (errcode == 1)
+            printf("No solution found.\n");
+        else if (errcode == -1)
+            printf("Puzzle is invalid.\n");
+
+        free_game(input);
+        unruly_free_scratch(scratch);
+    }
+
+    return 0;
+}
+#endif
diff --git a/untangle.R b/untangle.R
new file mode 100644 (file)
index 0000000..a57f1e5
--- /dev/null
@@ -0,0 +1,21 @@
+# -*- makefile -*-
+
+UNTANGLE_EXTRA = tree234
+
+untangle : [X] GTK COMMON untangle UNTANGLE_EXTRA untangle-icon|no-icon
+
+untangle : [G] WINDOWS COMMON untangle UNTANGLE_EXTRA untangle.res|noicon.res
+
+ALL += untangle[COMBINED] UNTANGLE_EXTRA
+
+!begin am gtk
+GAMES += untangle
+!end
+
+!begin >list.c
+    A(untangle) \
+!end
+
+!begin >gamedesc.txt
+untangle:untangle.exe:Untangle:Planar graph layout puzzle:Reposition the points so that the lines do not cross.
+!end
diff --git a/untangle.c b/untangle.c
new file mode 100644 (file)
index 0000000..49366b1
--- /dev/null
@@ -0,0 +1,1491 @@
+/*
+ * untangle.c: Game about planar graphs. You are given a graph
+ * represented by points and straight lines, with some lines
+ * crossing; your task is to drag the points into a configuration
+ * where none of the lines cross.
+ * 
+ * Cloned from a Flash game called `Planarity', by John Tantalo.
+ * <http://home.cwru.edu/~jnt5/Planarity> at the time of writing
+ * this. The Flash game had a fixed set of levels; my added value,
+ * as usual, is automatic generation of random games to order.
+ */
+
+/*
+ * TODO:
+ *
+ *  - This puzzle, perhaps uniquely among the collection, could use
+ *    support for non-aspect-ratio-preserving resizes. This would
+ *    require some sort of fairly large redesign, unfortunately (since
+ *    it would invalidate the basic assumption that puzzles' size
+ *    requirements are adequately expressed by a single scalar tile
+ *    size), and probably complicate the rest of the puzzles' API as a
+ *    result. So I'm not sure I really want to do it.
+ *
+ *  - It would be nice if we could somehow auto-detect a real `long
+ *    long' type on the host platform and use it in place of my
+ *    hand-hacked int64s. It'd be faster and more reliable.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+
+#include "puzzles.h"
+#include "tree234.h"
+
+#define CIRCLE_RADIUS 6
+#define DRAG_THRESHOLD (CIRCLE_RADIUS * 2)
+#define PREFERRED_TILESIZE 64
+
+#define FLASH_TIME 0.30F
+#define ANIM_TIME 0.13F
+#define SOLVEANIM_TIME 0.50F
+
+enum {
+    COL_SYSBACKGROUND,
+    COL_BACKGROUND,
+    COL_LINE,
+#ifdef SHOW_CROSSINGS
+    COL_CROSSEDLINE,
+#endif
+    COL_OUTLINE,
+    COL_POINT,
+    COL_DRAGPOINT,
+    COL_NEIGHBOUR,
+    COL_FLASH1,
+    COL_FLASH2,
+    NCOLOURS
+};
+
+typedef struct point {
+    /*
+     * Points are stored using rational coordinates, with the same
+     * denominator for both coordinates.
+     */
+    long x, y, d;
+} point;
+
+typedef struct edge {
+    /*
+     * This structure is implicitly associated with a particular
+     * point set, so all it has to do is to store two point
+     * indices. It is required to store them in the order (lower,
+     * higher), i.e. a < b always.
+     */
+    int a, b;
+} edge;
+
+struct game_params {
+    int n;                            /* number of points */
+};
+
+struct graph {
+    int refcount;                     /* for deallocation */
+    tree234 *edges;                   /* stores `edge' structures */
+};
+
+struct game_state {
+    game_params params;
+    int w, h;                         /* extent of coordinate system only */
+    point *pts;
+#ifdef SHOW_CROSSINGS
+    int *crosses;                     /* mark edges which are crossed */
+#endif
+    struct graph *graph;
+    int completed, cheated, just_solved;
+};
+
+static int edgecmpC(const void *av, const void *bv)
+{
+    const edge *a = (const edge *)av;
+    const edge *b = (const edge *)bv;
+
+    if (a->a < b->a)
+       return -1;
+    else if (a->a > b->a)
+       return +1;
+    else if (a->b < b->b)
+       return -1;
+    else if (a->b > b->b)
+       return +1;
+    return 0;
+}
+
+static int edgecmp(void *av, void *bv) { return edgecmpC(av, bv); }
+
+static game_params *default_params(void)
+{
+    game_params *ret = snew(game_params);
+
+    ret->n = 10;
+
+    return ret;
+}
+
+static int game_fetch_preset(int i, char **name, game_params **params)
+{
+    game_params *ret;
+    int n;
+    char buf[80];
+
+    switch (i) {
+      case 0: n = 6; break;
+      case 1: n = 10; break;
+      case 2: n = 15; break;
+      case 3: n = 20; break;
+      case 4: n = 25; break;
+      default: return FALSE;
+    }
+
+    sprintf(buf, "%d points", n);
+    *name = dupstr(buf);
+
+    *params = ret = snew(game_params);
+    ret->n = n;
+
+    return TRUE;
+}
+
+static void free_params(game_params *params)
+{
+    sfree(params);
+}
+
+static game_params *dup_params(const game_params *params)
+{
+    game_params *ret = snew(game_params);
+    *ret = *params;                   /* structure copy */
+    return ret;
+}
+
+static void decode_params(game_params *params, char const *string)
+{
+    params->n = atoi(string);
+}
+
+static char *encode_params(const game_params *params, int full)
+{
+    char buf[80];
+
+    sprintf(buf, "%d", params->n);
+
+    return dupstr(buf);
+}
+
+static config_item *game_configure(const game_params *params)
+{
+    config_item *ret;
+    char buf[80];
+
+    ret = snewn(3, config_item);
+
+    ret[0].name = "Number of points";
+    ret[0].type = C_STRING;
+    sprintf(buf, "%d", params->n);
+    ret[0].sval = dupstr(buf);
+    ret[0].ival = 0;
+
+    ret[1].name = NULL;
+    ret[1].type = C_END;
+    ret[1].sval = NULL;
+    ret[1].ival = 0;
+
+    return ret;
+}
+
+static game_params *custom_params(const config_item *cfg)
+{
+    game_params *ret = snew(game_params);
+
+    ret->n = atoi(cfg[0].sval);
+
+    return ret;
+}
+
+static char *validate_params(const game_params *params, int full)
+{
+    if (params->n < 4)
+        return "Number of points must be at least four";
+    return NULL;
+}
+
+/* ----------------------------------------------------------------------
+ * Small number of 64-bit integer arithmetic operations, to prevent
+ * integer overflow at the very core of cross().
+ */
+
+typedef struct {
+    long hi;
+    unsigned long lo;
+} int64;
+
+#define greater64(i,j) ( (i).hi>(j).hi || ((i).hi==(j).hi && (i).lo>(j).lo))
+#define sign64(i) ((i).hi < 0 ? -1 : (i).hi==0 && (i).lo==0 ? 0 : +1)
+
+static int64 mulu32to64(unsigned long x, unsigned long y)
+{
+    unsigned long a, b, c, d, t;
+    int64 ret;
+
+    a = (x & 0xFFFF) * (y & 0xFFFF);
+    b = (x & 0xFFFF) * (y >> 16);
+    c = (x >> 16) * (y & 0xFFFF);
+    d = (x >> 16) * (y >> 16);
+
+    ret.lo = a;
+    ret.hi = d + (b >> 16) + (c >> 16);
+    t = (b & 0xFFFF) << 16;
+    ret.lo += t;
+    if (ret.lo < t)
+       ret.hi++;
+    t = (c & 0xFFFF) << 16;
+    ret.lo += t;
+    if (ret.lo < t)
+       ret.hi++;
+
+#ifdef DIAGNOSTIC_VIA_LONGLONG
+    assert(((unsigned long long)ret.hi << 32) + ret.lo ==
+          (unsigned long long)x * y);
+#endif
+
+    return ret;
+}
+
+static int64 mul32to64(long x, long y)
+{
+    int sign = +1;
+    int64 ret;
+#ifdef DIAGNOSTIC_VIA_LONGLONG
+    long long realret = (long long)x * y;
+#endif
+
+    if (x < 0)
+       x = -x, sign = -sign;
+    if (y < 0)
+       y = -y, sign = -sign;
+
+    ret = mulu32to64(x, y);
+
+    if (sign < 0) {
+       ret.hi = -ret.hi;
+       ret.lo = -ret.lo;
+       if (ret.lo)
+           ret.hi--;
+    }
+
+#ifdef DIAGNOSTIC_VIA_LONGLONG
+    assert(((unsigned long long)ret.hi << 32) + ret.lo == realret);
+#endif
+
+    return ret;
+}
+
+static int64 dotprod64(long a, long b, long p, long q)
+{
+    int64 ab, pq;
+
+    ab = mul32to64(a, b);
+    pq = mul32to64(p, q);
+    ab.hi += pq.hi;
+    ab.lo += pq.lo;
+    if (ab.lo < pq.lo)
+       ab.hi++;
+    return ab;
+}
+
+/*
+ * Determine whether the line segments between a1 and a2, and
+ * between b1 and b2, intersect. We count it as an intersection if
+ * any of the endpoints lies _on_ the other line.
+ */
+static int cross(point a1, point a2, point b1, point b2)
+{
+    long b1x, b1y, b2x, b2y, px, py;
+    int64 d1, d2, d3;
+
+    /*
+     * The condition for crossing is that b1 and b2 are on opposite
+     * sides of the line a1-a2, and vice versa. We determine this
+     * by taking the dot product of b1-a1 with a vector
+     * perpendicular to a2-a1, and similarly with b2-a1, and seeing
+     * if they have different signs.
+     */
+
+    /*
+     * Construct the vector b1-a1. We don't have to worry too much
+     * about the denominator, because we're only going to check the
+     * sign of this vector; we just need to get the numerator
+     * right.
+     */
+    b1x = b1.x * a1.d - a1.x * b1.d;
+    b1y = b1.y * a1.d - a1.y * b1.d;
+    /* Now construct b2-a1, and a vector perpendicular to a2-a1,
+     * in the same way. */
+    b2x = b2.x * a1.d - a1.x * b2.d;
+    b2y = b2.y * a1.d - a1.y * b2.d;
+    px = a1.y * a2.d - a2.y * a1.d;
+    py = a2.x * a1.d - a1.x * a2.d;
+    /* Take the dot products. Here we resort to 64-bit arithmetic. */
+    d1 = dotprod64(b1x, px, b1y, py);
+    d2 = dotprod64(b2x, px, b2y, py);
+    /* If they have the same non-zero sign, the lines do not cross. */
+    if ((sign64(d1) > 0 && sign64(d2) > 0) ||
+       (sign64(d1) < 0 && sign64(d2) < 0))
+       return FALSE;
+
+    /*
+     * If the dot products are both exactly zero, then the two line
+     * segments are collinear. At this point the intersection
+     * condition becomes whether or not they overlap within their
+     * line.
+     */
+    if (sign64(d1) == 0 && sign64(d2) == 0) {
+       /* Construct the vector a2-a1. */
+       px = a2.x * a1.d - a1.x * a2.d;
+       py = a2.y * a1.d - a1.y * a2.d;
+       /* Determine the dot products of b1-a1 and b2-a1 with this. */
+       d1 = dotprod64(b1x, px, b1y, py);
+       d2 = dotprod64(b2x, px, b2y, py);
+       /* If they're both strictly negative, the lines do not cross. */
+       if (sign64(d1) < 0 && sign64(d2) < 0)
+           return FALSE;
+       /* Otherwise, take the dot product of a2-a1 with itself. If
+        * the other two dot products both exceed this, the lines do
+        * not cross. */
+       d3 = dotprod64(px, px, py, py);
+       if (greater64(d1, d3) && greater64(d2, d3))
+           return FALSE;
+    }
+
+    /*
+     * We've eliminated the only important special case, and we
+     * have determined that b1 and b2 are on opposite sides of the
+     * line a1-a2. Now do the same thing the other way round and
+     * we're done.
+     */
+    b1x = a1.x * b1.d - b1.x * a1.d;
+    b1y = a1.y * b1.d - b1.y * a1.d;
+    b2x = a2.x * b1.d - b1.x * a2.d;
+    b2y = a2.y * b1.d - b1.y * a2.d;
+    px = b1.y * b2.d - b2.y * b1.d;
+    py = b2.x * b1.d - b1.x * b2.d;
+    d1 = dotprod64(b1x, px, b1y, py);
+    d2 = dotprod64(b2x, px, b2y, py);
+    if ((sign64(d1) > 0 && sign64(d2) > 0) ||
+       (sign64(d1) < 0 && sign64(d2) < 0))
+       return FALSE;
+
+    /*
+     * The lines must cross.
+     */
+    return TRUE;
+}
+
+static unsigned long squarert(unsigned long n) {
+    unsigned long d, a, b, di;
+
+    d = n;
+    a = 0;
+    b = 1L << 30;                     /* largest available power of 4 */
+    do {
+        a >>= 1;
+        di = 2*a + b;
+        if (di <= d) {
+            d -= di;
+            a += b;
+        }
+        b >>= 2;
+    } while (b);
+
+    return a;
+}
+
+/*
+ * Our solutions are arranged on a square grid big enough that n
+ * points occupy about 1/POINTDENSITY of the grid.
+ */
+#define POINTDENSITY 3
+#define MAXDEGREE 4
+#define COORDLIMIT(n) squarert((n) * POINTDENSITY)
+
+static void addedge(tree234 *edges, int a, int b)
+{
+    edge *e = snew(edge);
+
+    assert(a != b);
+
+    e->a = min(a, b);
+    e->b = max(a, b);
+
+    add234(edges, e);
+}
+
+static int isedge(tree234 *edges, int a, int b)
+{
+    edge e;
+
+    assert(a != b);
+
+    e.a = min(a, b);
+    e.b = max(a, b);
+
+    return find234(edges, &e, NULL) != NULL;
+}
+
+typedef struct vertex {
+    int param;
+    int vindex;
+} vertex;
+
+static int vertcmpC(const void *av, const void *bv)
+{
+    const vertex *a = (vertex *)av;
+    const vertex *b = (vertex *)bv;
+
+    if (a->param < b->param)
+       return -1;
+    else if (a->param > b->param)
+       return +1;
+    else if (a->vindex < b->vindex)
+       return -1;
+    else if (a->vindex > b->vindex)
+       return +1;
+    return 0;
+}
+static int vertcmp(void *av, void *bv) { return vertcmpC(av, bv); }
+
+/*
+ * Construct point coordinates for n points arranged in a circle,
+ * within the bounding box (0,0) to (w,w).
+ */
+static void make_circle(point *pts, int n, int w)
+{
+    long d, r, c, i;
+
+    /*
+     * First, decide on a denominator. Although in principle it
+     * would be nice to set this really high so as to finely
+     * distinguish all the points on the circle, I'm going to set
+     * it at a fixed size to prevent integer overflow problems.
+     */
+    d = PREFERRED_TILESIZE;
+
+    /*
+     * Leave a little space outside the circle.
+     */
+    c = d * w / 2;
+    r = d * w * 3 / 7;
+
+    /*
+     * Place the points.
+     */
+    for (i = 0; i < n; i++) {
+       double angle = i * 2 * PI / n;
+       double x = r * sin(angle), y = - r * cos(angle);
+       pts[i].x = (long)(c + x + 0.5);
+       pts[i].y = (long)(c + y + 0.5);
+       pts[i].d = d;
+    }
+}
+
+static char *new_game_desc(const game_params *params, random_state *rs,
+                          char **aux, int interactive)
+{
+    int n = params->n, i;
+    long w, h, j, k, m;
+    point *pts, *pts2;
+    long *tmp;
+    tree234 *edges, *vertices;
+    edge *e, *e2;
+    vertex *v, *vs, *vlist;
+    char *ret;
+
+    w = h = COORDLIMIT(n);
+
+    /*
+     * Choose n points from this grid.
+     */
+    pts = snewn(n, point);
+    tmp = snewn(w*h, long);
+    for (i = 0; i < w*h; i++)
+       tmp[i] = i;
+    shuffle(tmp, w*h, sizeof(*tmp), rs);
+    for (i = 0; i < n; i++) {
+       pts[i].x = tmp[i] % w;
+       pts[i].y = tmp[i] / w;
+       pts[i].d = 1;
+    }
+    sfree(tmp);
+
+    /*
+     * Now start adding edges between the points.
+     * 
+     * At all times, we attempt to add an edge to the lowest-degree
+     * vertex we currently have, and we try the other vertices as
+     * candidate second endpoints in order of distance from this
+     * one. We stop as soon as we find an edge which
+     * 
+     *  (a) does not increase any vertex's degree beyond MAXDEGREE
+     *  (b) does not cross any existing edges
+     *  (c) does not intersect any actual point.
+     */
+    vs = snewn(n, vertex);
+    vertices = newtree234(vertcmp);
+    for (i = 0; i < n; i++) {
+       v = vs + i;
+       v->param = 0;                  /* in this tree, param is the degree */
+       v->vindex = i;
+       add234(vertices, v);
+    }
+    edges = newtree234(edgecmp);
+    vlist = snewn(n, vertex);
+    while (1) {
+       int added = FALSE;
+
+       for (i = 0; i < n; i++) {
+           v = index234(vertices, i);
+           j = v->vindex;
+
+           if (v->param >= MAXDEGREE)
+               break;                 /* nothing left to add! */
+
+           /*
+            * Sort the other vertices into order of their distance
+            * from this one. Don't bother looking below i, because
+            * we've already tried those edges the other way round.
+            * Also here we rule out target vertices with too high
+            * a degree, and (of course) ones to which we already
+            * have an edge.
+            */
+           m = 0;
+           for (k = i+1; k < n; k++) {
+               vertex *kv = index234(vertices, k);
+               int ki = kv->vindex;
+               int dx, dy;
+
+               if (kv->param >= MAXDEGREE || isedge(edges, ki, j))
+                   continue;
+
+               vlist[m].vindex = ki;
+               dx = pts[ki].x - pts[j].x;
+               dy = pts[ki].y - pts[j].y;
+               vlist[m].param = dx*dx + dy*dy;
+               m++;
+           }
+
+           qsort(vlist, m, sizeof(*vlist), vertcmpC);
+
+           for (k = 0; k < m; k++) {
+               int p;
+               int ki = vlist[k].vindex;
+
+               /*
+                * Check to see whether this edge intersects any
+                * existing edge or point.
+                */
+               for (p = 0; p < n; p++)
+                   if (p != ki && p != j && cross(pts[ki], pts[j],
+                                                  pts[p], pts[p]))
+                       break;
+               if (p < n)
+                   continue;
+               for (p = 0; (e = index234(edges, p)) != NULL; p++)
+                   if (e->a != ki && e->a != j &&
+                       e->b != ki && e->b != j &&
+                       cross(pts[ki], pts[j], pts[e->a], pts[e->b]))
+                       break;
+               if (e)
+                   continue;
+
+               /*
+                * We're done! Add this edge, modify the degrees of
+                * the two vertices involved, and break.
+                */
+               addedge(edges, j, ki);
+               added = TRUE;
+               del234(vertices, vs+j);
+               vs[j].param++;
+               add234(vertices, vs+j);
+               del234(vertices, vs+ki);
+               vs[ki].param++;
+               add234(vertices, vs+ki);
+               break;
+           }
+
+           if (k < m)
+               break;
+       }
+
+       if (!added)
+           break;                     /* we're done. */
+    }
+
+    /*
+     * That's our graph. Now shuffle the points, making sure that
+     * they come out with at least one crossed line when arranged
+     * in a circle (so that the puzzle isn't immediately solved!).
+     */
+    tmp = snewn(n, long);
+    for (i = 0; i < n; i++)
+       tmp[i] = i;
+    pts2 = snewn(n, point);
+    make_circle(pts2, n, w);
+    while (1) {
+       shuffle(tmp, n, sizeof(*tmp), rs);
+       for (i = 0; (e = index234(edges, i)) != NULL; i++) {
+           for (j = i+1; (e2 = index234(edges, j)) != NULL; j++) {
+               if (e2->a == e->a || e2->a == e->b ||
+                   e2->b == e->a || e2->b == e->b)
+                   continue;
+               if (cross(pts2[tmp[e2->a]], pts2[tmp[e2->b]],
+                         pts2[tmp[e->a]], pts2[tmp[e->b]]))
+                   break;
+           }
+           if (e2)
+               break;
+       }
+       if (e)
+           break;                     /* we've found a crossing */
+    }
+
+    /*
+     * We're done. Now encode the graph in a string format. Let's
+     * use a comma-separated list of dash-separated vertex number
+     * pairs, numbered from zero. We'll sort the list to prevent
+     * side channels.
+     */
+    ret = NULL;
+    {
+       char *sep;
+       char buf[80];
+       int retlen;
+       edge *ea;
+
+       retlen = 0;
+       m = count234(edges);
+       ea = snewn(m, edge);
+       for (i = 0; (e = index234(edges, i)) != NULL; i++) {
+           assert(i < m);
+           ea[i].a = min(tmp[e->a], tmp[e->b]);
+           ea[i].b = max(tmp[e->a], tmp[e->b]);
+           retlen += 1 + sprintf(buf, "%d-%d", ea[i].a, ea[i].b);
+       }
+       assert(i == m);
+       qsort(ea, m, sizeof(*ea), edgecmpC);
+
+       ret = snewn(retlen, char);
+       sep = "";
+       k = 0;
+
+       for (i = 0; i < m; i++) {
+           k += sprintf(ret + k, "%s%d-%d", sep, ea[i].a, ea[i].b);
+           sep = ",";
+       }
+       assert(k < retlen);
+
+       sfree(ea);
+    }
+
+    /*
+     * Encode the solution we started with as an aux_info string.
+     */
+    {
+       char buf[80];
+       char *auxstr;
+       int auxlen;
+
+       auxlen = 2;                    /* leading 'S' and trailing '\0' */
+       for (i = 0; i < n; i++) {
+           j = tmp[i];
+           pts2[j] = pts[i];
+           if (pts2[j].d & 1) {
+               pts2[j].x *= 2;
+               pts2[j].y *= 2;
+               pts2[j].d *= 2;
+           }
+           pts2[j].x += pts2[j].d / 2;
+           pts2[j].y += pts2[j].d / 2;
+           auxlen += sprintf(buf, ";P%d:%ld,%ld/%ld", i,
+                             pts2[j].x, pts2[j].y, pts2[j].d);
+       }
+       k = 0;
+       auxstr = snewn(auxlen, char);
+       auxstr[k++] = 'S';
+       for (i = 0; i < n; i++)
+           k += sprintf(auxstr+k, ";P%d:%ld,%ld/%ld", i,
+                        pts2[i].x, pts2[i].y, pts2[i].d);
+       assert(k < auxlen);
+       *aux = auxstr;
+    }
+    sfree(pts2);
+
+    sfree(tmp);
+    sfree(vlist);
+    freetree234(vertices);
+    sfree(vs);
+    while ((e = delpos234(edges, 0)) != NULL)
+       sfree(e);
+    freetree234(edges);
+    sfree(pts);
+
+    return ret;
+}
+
+static char *validate_desc(const game_params *params, const char *desc)
+{
+    int a, b;
+
+    while (*desc) {
+       a = atoi(desc);
+       if (a < 0 || a >= params->n)
+           return "Number out of range in game description";
+       while (*desc && isdigit((unsigned char)*desc)) desc++;
+       if (*desc != '-')
+           return "Expected '-' after number in game description";
+       desc++;                        /* eat dash */
+       b = atoi(desc);
+       if (b < 0 || b >= params->n)
+           return "Number out of range in game description";
+       while (*desc && isdigit((unsigned char)*desc)) desc++;
+       if (*desc) {
+           if (*desc != ',')
+               return "Expected ',' after number in game description";
+           desc++;                    /* eat comma */
+       }
+    }
+
+    return NULL;
+}
+
+static void mark_crossings(game_state *state)
+{
+    int ok = TRUE;
+    int i, j;
+    edge *e, *e2;
+
+#ifdef SHOW_CROSSINGS
+    for (i = 0; (e = index234(state->graph->edges, i)) != NULL; i++)
+       state->crosses[i] = FALSE;
+#endif
+
+    /*
+     * Check correctness: for every pair of edges, see whether they
+     * cross.
+     */
+    for (i = 0; (e = index234(state->graph->edges, i)) != NULL; i++) {
+       for (j = i+1; (e2 = index234(state->graph->edges, j)) != NULL; j++) {
+           if (e2->a == e->a || e2->a == e->b ||
+               e2->b == e->a || e2->b == e->b)
+               continue;
+           if (cross(state->pts[e2->a], state->pts[e2->b],
+                     state->pts[e->a], state->pts[e->b])) {
+               ok = FALSE;
+#ifdef SHOW_CROSSINGS
+               state->crosses[i] = state->crosses[j] = TRUE;
+#else
+               goto done;             /* multi-level break - sorry */
+#endif
+           }
+       }
+    }
+
+    /*
+     * e == NULL if we've gone through all the edge pairs
+     * without finding a crossing.
+     */
+#ifndef SHOW_CROSSINGS
+    done:
+#endif
+    if (ok)
+       state->completed = TRUE;
+}
+
+static game_state *new_game(midend *me, const game_params *params,
+                            const char *desc)
+{
+    int n = params->n;
+    game_state *state = snew(game_state);
+    int a, b;
+
+    state->params = *params;
+    state->w = state->h = COORDLIMIT(n);
+    state->pts = snewn(n, point);
+    make_circle(state->pts, n, state->w);
+    state->graph = snew(struct graph);
+    state->graph->refcount = 1;
+    state->graph->edges = newtree234(edgecmp);
+    state->completed = state->cheated = state->just_solved = FALSE;
+
+    while (*desc) {
+       a = atoi(desc);
+       assert(a >= 0 && a < params->n);
+       while (*desc && isdigit((unsigned char)*desc)) desc++;
+       assert(*desc == '-');
+       desc++;                        /* eat dash */
+       b = atoi(desc);
+       assert(b >= 0 && b < params->n);
+       while (*desc && isdigit((unsigned char)*desc)) desc++;
+       if (*desc) {
+           assert(*desc == ',');
+           desc++;                    /* eat comma */
+       }
+       addedge(state->graph->edges, a, b);
+    }
+
+#ifdef SHOW_CROSSINGS
+    state->crosses = snewn(count234(state->graph->edges), int);
+    mark_crossings(state);            /* sets up `crosses' and `completed' */
+#endif
+
+    return state;
+}
+
+static game_state *dup_game(const game_state *state)
+{
+    int n = state->params.n;
+    game_state *ret = snew(game_state);
+
+    ret->params = state->params;
+    ret->w = state->w;
+    ret->h = state->h;
+    ret->pts = snewn(n, point);
+    memcpy(ret->pts, state->pts, n * sizeof(point));
+    ret->graph = state->graph;
+    ret->graph->refcount++;
+    ret->completed = state->completed;
+    ret->cheated = state->cheated;
+    ret->just_solved = state->just_solved;
+#ifdef SHOW_CROSSINGS
+    ret->crosses = snewn(count234(ret->graph->edges), int);
+    memcpy(ret->crosses, state->crosses,
+          count234(ret->graph->edges) * sizeof(int));
+#endif
+
+    return ret;
+}
+
+static void free_game(game_state *state)
+{
+    if (--state->graph->refcount <= 0) {
+       edge *e;
+       while ((e = delpos234(state->graph->edges, 0)) != NULL)
+           sfree(e);
+       freetree234(state->graph->edges);
+       sfree(state->graph);
+    }
+    sfree(state->pts);
+    sfree(state);
+}
+
+static char *solve_game(const game_state *state, const game_state *currstate,
+                        const char *aux, char **error)
+{
+    int n = state->params.n;
+    int matrix[4];
+    point *pts;
+    int i, j, besti;
+    float bestd;
+    char buf[80], *ret;
+    int retlen, retsize;
+
+    if (!aux) {
+       *error = "Solution not known for this puzzle";
+       return NULL;
+    }
+
+    /*
+     * Decode the aux_info to get the original point positions.
+     */
+    pts = snewn(n, point);
+    aux++;                             /* eat 'S' */
+    for (i = 0; i < n; i++) {
+        int p, k;
+        long x, y, d;
+       int ret = sscanf(aux, ";P%d:%ld,%ld/%ld%n", &p, &x, &y, &d, &k);
+        if (ret != 4 || p != i) {
+            *error = "Internal error: aux_info badly formatted";
+            sfree(pts);
+            return NULL;
+        }
+        pts[i].x = x;
+        pts[i].y = y;
+        pts[i].d = d;
+        aux += k;
+    }
+
+    /*
+     * Now go through eight possible symmetries of the point set.
+     * For each one, work out the sum of the Euclidean distances
+     * between the points' current positions and their new ones.
+     * 
+     * We're squaring distances here, which means we're at risk of
+     * integer overflow. Fortunately, there's no real need to be
+     * massively careful about rounding errors, since this is a
+     * non-essential bit of the code; so I'll just work in floats
+     * internally.
+     */
+    besti = -1;
+    bestd = 0.0F;
+
+    for (i = 0; i < 8; i++) {
+        float d;
+
+        matrix[0] = matrix[1] = matrix[2] = matrix[3] = 0;
+        matrix[i & 1] = (i & 2) ? +1 : -1;
+        matrix[3-(i&1)] = (i & 4) ? +1 : -1;
+
+        d = 0.0F;
+        for (j = 0; j < n; j++) {
+            float px = (float)pts[j].x / pts[j].d;
+            float py = (float)pts[j].y / pts[j].d;
+            float sx = (float)currstate->pts[j].x / currstate->pts[j].d;
+            float sy = (float)currstate->pts[j].y / currstate->pts[j].d;
+            float cx = (float)currstate->w / 2;
+            float cy = (float)currstate->h / 2;
+            float ox, oy, dx, dy;
+
+            px -= cx;
+            py -= cy;
+
+            ox = matrix[0] * px + matrix[1] * py;
+            oy = matrix[2] * px + matrix[3] * py;
+
+            ox += cx;
+            oy += cy;
+
+            dx = ox - sx;
+            dy = oy - sy;
+
+            d += dx*dx + dy*dy;
+        }
+
+        if (besti < 0 || bestd > d) {
+            besti = i;
+            bestd = d;
+        }
+    }
+
+    assert(besti >= 0);
+
+    /*
+     * Now we know which symmetry is closest to the points' current
+     * positions. Use it.
+     */
+    matrix[0] = matrix[1] = matrix[2] = matrix[3] = 0;
+    matrix[besti & 1] = (besti & 2) ? +1 : -1;
+    matrix[3-(besti&1)] = (besti & 4) ? +1 : -1;
+
+    retsize = 256;
+    ret = snewn(retsize, char);
+    retlen = 0;
+    ret[retlen++] = 'S';
+    ret[retlen] = '\0';
+
+    for (i = 0; i < n; i++) {
+        float px = (float)pts[i].x / pts[i].d;
+        float py = (float)pts[i].y / pts[i].d;
+        float cx = (float)currstate->w / 2;
+        float cy = (float)currstate->h / 2;
+        float ox, oy;
+        int extra;
+
+        px -= cx;
+        py -= cy;
+
+        ox = matrix[0] * px + matrix[1] * py;
+        oy = matrix[2] * px + matrix[3] * py;
+
+        ox += cx;
+        oy += cy;
+
+        /*
+         * Use a fixed denominator of 2, because we know the
+         * original points were on an integer grid offset by 1/2.
+         */
+        pts[i].d = 2;
+        ox *= pts[i].d;
+        oy *= pts[i].d;
+        pts[i].x = (long)(ox + 0.5F);
+        pts[i].y = (long)(oy + 0.5F);
+
+        extra = sprintf(buf, ";P%d:%ld,%ld/%ld", i,
+                        pts[i].x, pts[i].y, pts[i].d);
+        if (retlen + extra >= retsize) {
+            retsize = retlen + extra + 256;
+            ret = sresize(ret, retsize, char);
+        }
+        strcpy(ret + retlen, buf);
+        retlen += extra;
+    }
+
+    sfree(pts);
+
+    return ret;
+}
+
+static int game_can_format_as_text_now(const game_params *params)
+{
+    return TRUE;
+}
+
+static char *game_text_format(const game_state *state)
+{
+    return NULL;
+}
+
+struct game_ui {
+    int dragpoint;                    /* point being dragged; -1 if none */
+    point newpoint;                   /* where it's been dragged to so far */
+    int just_dragged;                 /* reset in game_changed_state */
+    int just_moved;                   /* _set_ in game_changed_state */
+    float anim_length;
+};
+
+static game_ui *new_ui(const game_state *state)
+{
+    game_ui *ui = snew(game_ui);
+    ui->dragpoint = -1;
+    ui->just_moved = ui->just_dragged = FALSE;
+    return ui;
+}
+
+static void free_ui(game_ui *ui)
+{
+    sfree(ui);
+}
+
+static char *encode_ui(const game_ui *ui)
+{
+    return NULL;
+}
+
+static void decode_ui(game_ui *ui, const char *encoding)
+{
+}
+
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+                               const game_state *newstate)
+{
+    ui->dragpoint = -1;
+    ui->just_moved = ui->just_dragged;
+    ui->just_dragged = FALSE;
+}
+
+struct game_drawstate {
+    long tilesize;
+    int bg, dragpoint;
+    long *x, *y;
+};
+
+static char *interpret_move(const game_state *state, game_ui *ui,
+                            const game_drawstate *ds,
+                            int x, int y, int button)
+{
+    int n = state->params.n;
+
+    if (IS_MOUSE_DOWN(button)) {
+       int i, best;
+        long bestd;
+
+       /*
+        * Begin drag. We drag the vertex _nearest_ to the pointer,
+        * just in case one is nearly on top of another and we want
+        * to drag the latter. However, we drag nothing at all if
+        * the nearest vertex is outside DRAG_THRESHOLD.
+        */
+       best = -1;
+       bestd = 0;
+
+       for (i = 0; i < n; i++) {
+           long px = state->pts[i].x * ds->tilesize / state->pts[i].d;
+           long py = state->pts[i].y * ds->tilesize / state->pts[i].d;
+           long dx = px - x;
+           long dy = py - y;
+           long d = dx*dx + dy*dy;
+
+           if (best == -1 || bestd > d) {
+               best = i;
+               bestd = d;
+           }
+       }
+
+       if (bestd <= DRAG_THRESHOLD * DRAG_THRESHOLD) {
+           ui->dragpoint = best;
+           ui->newpoint.x = x;
+           ui->newpoint.y = y;
+           ui->newpoint.d = ds->tilesize;
+           return "";
+       }
+
+    } else if (IS_MOUSE_DRAG(button) && ui->dragpoint >= 0) {
+       ui->newpoint.x = x;
+       ui->newpoint.y = y;
+       ui->newpoint.d = ds->tilesize;
+       return "";
+    } else if (IS_MOUSE_RELEASE(button) && ui->dragpoint >= 0) {
+       int p = ui->dragpoint;
+       char buf[80];
+
+       ui->dragpoint = -1;            /* terminate drag, no matter what */
+
+       /*
+        * First, see if we're within range. The user can cancel a
+        * drag by dragging the point right off the window.
+        */
+       if (ui->newpoint.x < 0 ||
+            ui->newpoint.x >= (long)state->w*ui->newpoint.d ||
+           ui->newpoint.y < 0 ||
+            ui->newpoint.y >= (long)state->h*ui->newpoint.d)
+           return "";
+
+       /*
+        * We aren't cancelling the drag. Construct a move string
+        * indicating where this point is going to.
+        */
+       sprintf(buf, "P%d:%ld,%ld/%ld", p,
+               ui->newpoint.x, ui->newpoint.y, ui->newpoint.d);
+       ui->just_dragged = TRUE;
+       return dupstr(buf);
+    }
+
+    return NULL;
+}
+
+static game_state *execute_move(const game_state *state, const char *move)
+{
+    int n = state->params.n;
+    int p, k;
+    long x, y, d;
+    game_state *ret = dup_game(state);
+
+    ret->just_solved = FALSE;
+
+    while (*move) {
+       if (*move == 'S') {
+           move++;
+           if (*move == ';') move++;
+           ret->cheated = ret->just_solved = TRUE;
+       }
+       if (*move == 'P' &&
+           sscanf(move+1, "%d:%ld,%ld/%ld%n", &p, &x, &y, &d, &k) == 4 &&
+           p >= 0 && p < n && d > 0) {
+           ret->pts[p].x = x;
+           ret->pts[p].y = y;
+           ret->pts[p].d = d;
+
+           move += k+1;
+           if (*move == ';') move++;
+       } else {
+           free_game(ret);
+           return NULL;
+       }
+    }
+
+    mark_crossings(ret);
+
+    return ret;
+}
+
+/* ----------------------------------------------------------------------
+ * Drawing routines.
+ */
+
+static void game_compute_size(const game_params *params, int tilesize,
+                              int *x, int *y)
+{
+    *x = *y = COORDLIMIT(params->n) * tilesize;
+}
+
+static void game_set_size(drawing *dr, game_drawstate *ds,
+                          const game_params *params, int tilesize)
+{
+    ds->tilesize = tilesize;
+}
+
+static float *game_colours(frontend *fe, int *ncolours)
+{
+    float *ret = snewn(3 * NCOLOURS, float);
+
+    /*
+     * COL_BACKGROUND is what we use as the normal background colour.
+     * Unusually, though, it isn't colour #0: COL_SYSBACKGROUND, a bit
+     * darker, takes that place. This means that if the user resizes
+     * an Untangle window so as to change its aspect ratio, the
+     * still-square playable area will be distinguished from the dead
+     * space around it.
+     */
+    game_mkhighlight(fe, ret, COL_BACKGROUND, -1, COL_SYSBACKGROUND);
+
+    ret[COL_LINE * 3 + 0] = 0.0F;
+    ret[COL_LINE * 3 + 1] = 0.0F;
+    ret[COL_LINE * 3 + 2] = 0.0F;
+
+#ifdef SHOW_CROSSINGS
+    ret[COL_CROSSEDLINE * 3 + 0] = 1.0F;
+    ret[COL_CROSSEDLINE * 3 + 1] = 0.0F;
+    ret[COL_CROSSEDLINE * 3 + 2] = 0.0F;
+#endif
+
+    ret[COL_OUTLINE * 3 + 0] = 0.0F;
+    ret[COL_OUTLINE * 3 + 1] = 0.0F;
+    ret[COL_OUTLINE * 3 + 2] = 0.0F;
+
+    ret[COL_POINT * 3 + 0] = 0.0F;
+    ret[COL_POINT * 3 + 1] = 0.0F;
+    ret[COL_POINT * 3 + 2] = 1.0F;
+
+    ret[COL_DRAGPOINT * 3 + 0] = 1.0F;
+    ret[COL_DRAGPOINT * 3 + 1] = 1.0F;
+    ret[COL_DRAGPOINT * 3 + 2] = 1.0F;
+
+    ret[COL_NEIGHBOUR * 3 + 0] = 1.0F;
+    ret[COL_NEIGHBOUR * 3 + 1] = 0.0F;
+    ret[COL_NEIGHBOUR * 3 + 2] = 0.0F;
+
+    ret[COL_FLASH1 * 3 + 0] = 0.5F;
+    ret[COL_FLASH1 * 3 + 1] = 0.5F;
+    ret[COL_FLASH1 * 3 + 2] = 0.5F;
+
+    ret[COL_FLASH2 * 3 + 0] = 1.0F;
+    ret[COL_FLASH2 * 3 + 1] = 1.0F;
+    ret[COL_FLASH2 * 3 + 2] = 1.0F;
+
+    *ncolours = NCOLOURS;
+    return ret;
+}
+
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
+{
+    struct game_drawstate *ds = snew(struct game_drawstate);
+    int i;
+
+    ds->tilesize = 0;
+    ds->x = snewn(state->params.n, long);
+    ds->y = snewn(state->params.n, long);
+    for (i = 0; i < state->params.n; i++)
+        ds->x[i] = ds->y[i] = -1;
+    ds->bg = -1;
+    ds->dragpoint = -1;
+
+    return ds;
+}
+
+static void game_free_drawstate(drawing *dr, game_drawstate *ds)
+{
+    sfree(ds->y);
+    sfree(ds->x);
+    sfree(ds);
+}
+
+static point mix(point a, point b, float distance)
+{
+    point ret;
+
+    ret.d = a.d * b.d;
+    ret.x = (long)(a.x * b.d + distance * (b.x * a.d - a.x * b.d));
+    ret.y = (long)(a.y * b.d + distance * (b.y * a.d - a.y * b.d));
+
+    return ret;
+}
+
+static void game_redraw(drawing *dr, game_drawstate *ds,
+                        const game_state *oldstate, const game_state *state,
+                        int dir, const game_ui *ui,
+                        float animtime, float flashtime)
+{
+    int w, h;
+    edge *e;
+    int i, j;
+    int bg, points_moved;
+
+    /*
+     * There's no terribly sensible way to do partial redraws of
+     * this game, so I'm going to have to resort to redrawing the
+     * whole thing every time.
+     */
+
+    if (flashtime == 0)
+        bg = COL_BACKGROUND;
+    else if ((int)(flashtime * 4 / FLASH_TIME) % 2 == 0)
+        bg = COL_FLASH1;
+    else
+        bg = COL_FLASH2;
+
+    /*
+     * To prevent excessive spinning on redraw during a completion
+     * flash, we first check to see if _either_ the flash
+     * background colour has changed _or_ at least one point has
+     * moved _or_ a drag has begun or ended, and abandon the redraw
+     * if neither is the case.
+     * 
+     * Also in this loop we work out the coordinates of all the
+     * points for this redraw.
+     */
+    points_moved = FALSE;
+    for (i = 0; i < state->params.n; i++) {
+        point p = state->pts[i];
+        long x, y;
+
+        if (ui->dragpoint == i)
+            p = ui->newpoint;
+
+        if (oldstate)
+            p = mix(oldstate->pts[i], p, animtime / ui->anim_length);
+
+       x = p.x * ds->tilesize / p.d;
+       y = p.y * ds->tilesize / p.d;
+
+        if (ds->x[i] != x || ds->y[i] != y)
+            points_moved = TRUE;
+
+        ds->x[i] = x;
+        ds->y[i] = y;
+    }
+
+    if (ds->bg == bg && ds->dragpoint == ui->dragpoint && !points_moved)
+        return;                        /* nothing to do */
+
+    ds->dragpoint = ui->dragpoint;
+    ds->bg = bg;
+
+    game_compute_size(&state->params, ds->tilesize, &w, &h);
+    draw_rect(dr, 0, 0, w, h, bg);
+
+    /*
+     * Draw the edges.
+     */
+
+    for (i = 0; (e = index234(state->graph->edges, i)) != NULL; i++) {
+       draw_line(dr, ds->x[e->a], ds->y[e->a], ds->x[e->b], ds->y[e->b],
+#ifdef SHOW_CROSSINGS
+                 (oldstate?oldstate:state)->crosses[i] ?
+                 COL_CROSSEDLINE :
+#endif
+                 COL_LINE);
+    }
+
+    /*
+     * Draw the points.
+     * 
+     * When dragging, we should not only vary the colours, but
+     * leave the point being dragged until last.
+     */
+    for (j = 0; j < 3; j++) {
+       int thisc = (j == 0 ? COL_POINT :
+                    j == 1 ? COL_NEIGHBOUR : COL_DRAGPOINT);
+       for (i = 0; i < state->params.n; i++) {
+            int c;
+
+           if (ui->dragpoint == i) {
+               c = COL_DRAGPOINT;
+           } else if (ui->dragpoint >= 0 &&
+                      isedge(state->graph->edges, ui->dragpoint, i)) {
+               c = COL_NEIGHBOUR;
+           } else {
+               c = COL_POINT;
+           }
+
+           if (c == thisc) {
+#ifdef VERTEX_NUMBERS
+               draw_circle(dr, ds->x[i], ds->y[i], DRAG_THRESHOLD, bg, bg);
+               {
+                   char buf[80];
+                   sprintf(buf, "%d", i);
+                   draw_text(dr, ds->x[i], ds->y[i], FONT_VARIABLE,
+                              DRAG_THRESHOLD*3/2,
+                             ALIGN_VCENTRE|ALIGN_HCENTRE, c, buf);
+               }
+#else
+               draw_circle(dr, ds->x[i], ds->y[i], CIRCLE_RADIUS,
+                            c, COL_OUTLINE);
+#endif
+           }
+       }
+    }
+
+    draw_update(dr, 0, 0, w, h);
+}
+
+static float game_anim_length(const game_state *oldstate,
+                              const game_state *newstate, int dir, game_ui *ui)
+{
+    if (ui->just_moved)
+       return 0.0F;
+    if ((dir < 0 ? oldstate : newstate)->just_solved)
+       ui->anim_length = SOLVEANIM_TIME;
+    else
+       ui->anim_length = ANIM_TIME;
+    return ui->anim_length;
+}
+
+static float game_flash_length(const game_state *oldstate,
+                               const game_state *newstate, int dir, game_ui *ui)
+{
+    if (!oldstate->completed && newstate->completed &&
+       !oldstate->cheated && !newstate->cheated)
+        return FLASH_TIME;
+    return 0.0F;
+}
+
+static int game_status(const game_state *state)
+{
+    return state->completed ? +1 : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
+{
+    return TRUE;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
+{
+}
+
+static void game_print(drawing *dr, const game_state *state, int tilesize)
+{
+}
+
+#ifdef COMBINED
+#define thegame untangle
+#endif
+
+const struct game thegame = {
+    "Untangle", "games.untangle", "untangle",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_desc,
+    validate_desc,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    FALSE, game_can_format_as_text_now, game_text_format,
+    new_ui,
+    free_ui,
+    encode_ui,
+    decode_ui,
+    game_changed_state,
+    interpret_move,
+    execute_move,
+    PREFERRED_TILESIZE, game_compute_size, game_set_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_status,
+    FALSE, FALSE, game_print_size, game_print,
+    FALSE,                            /* wants_statusbar */
+    FALSE, game_timing_state,
+    SOLVE_ANIMATES,                   /* flags */
+};
diff --git a/version.c b/version.c
new file mode 100644 (file)
index 0000000..1cef29f
--- /dev/null
+++ b/version.c
@@ -0,0 +1,7 @@
+/*
+ * Puzzles version numbering.
+ */
+
+#include "version.h"
+
+char ver[] = VER;
diff --git a/version.h b/version.h
new file mode 100644 (file)
index 0000000..0495829
--- /dev/null
+++ b/version.h
@@ -0,0 +1,2 @@
+/* Generated by automated build script */
+#define VER "Version 20161228.7cae89f"
diff --git a/windows.c b/windows.c
new file mode 100644 (file)
index 0000000..9cc66e2
--- /dev/null
+++ b/windows.c
@@ -0,0 +1,3760 @@
+/*
+ * windows.c: Windows front end for my puzzle collection.
+ */
+
+#include <windows.h>
+#include <commctrl.h>
+#ifndef NO_HTMLHELP
+#include <htmlhelp.h>
+#endif /* NO_HTMLHELP */
+
+#ifdef _WIN32_WCE
+#include <commdlg.h>
+#include <aygshell.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <time.h>
+
+#include "puzzles.h"
+
+#ifdef _WIN32_WCE
+#include "resource.h"
+#endif
+
+#define IDM_NEW       0x0010
+#define IDM_RESTART   0x0020
+#define IDM_UNDO      0x0030
+#define IDM_REDO      0x0040
+#define IDM_COPY      0x0050
+#define IDM_SOLVE     0x0060
+#define IDM_QUIT      0x0070
+#define IDM_CONFIG    0x0080
+#define IDM_DESC      0x0090
+#define IDM_SEED      0x00A0
+#define IDM_HELPC     0x00B0
+#define IDM_GAMEHELP  0x00C0
+#define IDM_ABOUT     0x00D0
+#define IDM_SAVE      0x00E0
+#define IDM_LOAD      0x00F0
+#define IDM_PRINT     0x0100
+#define IDM_PRESETS   0x0110
+#define IDM_GAMES     0x0300
+
+#define IDM_KEYEMUL   0x0400
+
+#define HELP_FILE_NAME  "puzzles.hlp"
+#define HELP_CNT_NAME   "puzzles.cnt"
+#ifndef NO_HTMLHELP
+#define CHM_FILE_NAME   "puzzles.chm"
+#endif /* NO_HTMLHELP */
+
+#ifndef NO_HTMLHELP
+typedef HWND (CALLBACK *htmlhelp_t)(HWND, LPCSTR, UINT, DWORD);
+static htmlhelp_t htmlhelp;
+static HINSTANCE hh_dll;
+#endif /* NO_HTMLHELP */
+enum { NONE, HLP, CHM } help_type;
+char *help_path;
+int help_has_contents;
+
+#ifndef FILENAME_MAX
+#define        FILENAME_MAX    (260)
+#endif
+
+#ifndef HGDI_ERROR
+#define HGDI_ERROR ((HANDLE)GDI_ERROR)
+#endif
+
+#ifdef COMBINED
+#define CLASSNAME "Puzzles"
+#else
+#define CLASSNAME thegame.name
+#endif
+
+#ifdef _WIN32_WCE
+
+/*
+ * Wrapper implementations of functions not supplied by the
+ * PocketPC API.
+ */
+
+#define SHGetSubMenu(hWndMB,ID_MENU) (HMENU)SendMessage((hWndMB), SHCMBM_GETSUBMENU, (WPARAM)0, (LPARAM)ID_MENU)
+
+#undef MessageBox
+
+int MessageBox(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType)
+{
+    TCHAR wText[2048];
+    TCHAR wCaption[2048];
+
+    MultiByteToWideChar (CP_ACP, 0, lpText,    -1, wText,    2048);
+    MultiByteToWideChar (CP_ACP, 0, lpCaption, -1, wCaption, 2048);
+
+    return MessageBoxW (hWnd, wText, wCaption, uType);
+}
+
+BOOL SetDlgItemTextA(HWND hDlg, int nIDDlgItem, LPCSTR lpString)
+{
+    TCHAR wText[256];
+
+    MultiByteToWideChar (CP_ACP, 0, lpString, -1, wText, 256);
+    return SetDlgItemTextW(hDlg, nIDDlgItem, wText);
+}
+
+LPCSTR getenv(LPCSTR buf)
+{
+    return NULL;
+}
+
+BOOL GetKeyboardState(PBYTE pb)
+{
+  return FALSE;
+}
+
+static TCHAR wClassName[256], wGameName[256];
+
+#endif
+
+#ifdef DEBUGGING
+static FILE *debug_fp = NULL;
+static HANDLE debug_hdl = INVALID_HANDLE_VALUE;
+static int debug_got_console = 0;
+
+void dputs(char *buf)
+{
+    /*DWORD dw;
+
+    if (!debug_got_console) {
+       if (AllocConsole()) {
+           debug_got_console = 1;
+           debug_hdl = GetStdHandle(STD_OUTPUT_HANDLE);
+       }
+    }
+    if (!debug_fp) {
+       debug_fp = fopen("debug.log", "w");
+    }
+
+    if (debug_hdl != INVALID_HANDLE_VALUE) {
+       WriteFile(debug_hdl, buf, strlen(buf), &dw, NULL);
+    }
+    if (debug_fp) {
+      fputs(buf, debug_fp);
+      fflush(debug_fp);
+    }*/
+    OutputDebugString(buf);
+}
+
+void debug_printf(char *fmt, ...)
+{
+    char buf[4096];
+    va_list ap;
+    static int debugging = -1;
+
+    if (debugging == -1)
+        debugging = getenv("DEBUG_PUZZLES") ? 1 : 0;
+
+    if (debugging) {
+        va_start(ap, fmt);
+        _vsnprintf(buf, 4095, fmt, ap);
+       dputs(buf);
+        va_end(ap);
+    }
+}
+#endif
+
+#ifndef _WIN32_WCE
+#define WINFLAGS (WS_OVERLAPPEDWINDOW &~ \
+                     (WS_MAXIMIZEBOX | WS_OVERLAPPED))
+#else
+#define WINFLAGS (WS_CAPTION | WS_SYSMENU)
+#endif
+
+static void new_game_size(frontend *fe, float scale);
+
+struct font {
+    HFONT font;
+    int type;
+    int size;
+};
+
+struct cfg_aux {
+    int ctlid;
+};
+
+struct blitter {
+    HBITMAP bitmap;
+    frontend *fe;
+    int x, y, w, h;
+};
+
+enum { CFG_PRINT = CFG_FRONTEND_SPECIFIC };
+
+struct frontend {
+    const game *game;
+    midend *me;
+    HWND hwnd, statusbar, cfgbox;
+#ifdef _WIN32_WCE
+    HWND numpad;  /* window handle for the numeric pad */
+#endif
+    HINSTANCE inst;
+    HBITMAP bitmap, prevbm;
+    RECT bitmapPosition;  /* game bitmap position within game window */
+    HDC hdc;
+    COLORREF *colours;
+    HBRUSH *brushes;
+    HPEN *pens;
+    HRGN clip;
+    HMENU gamemenu, typemenu;
+    UINT timer;
+    DWORD timer_last_tickcount;
+    int npresets;
+    game_params **presets;
+    struct font *fonts;
+    int nfonts, fontsize;
+    config_item *cfg;
+    struct cfg_aux *cfgaux;
+    int cfg_which, dlg_done;
+    HFONT cfgfont;
+    HBRUSH oldbr;
+    HPEN oldpen;
+    int help_running;
+    enum { DRAWING, PRINTING, NOTHING } drawstatus;
+    DOCINFO di;
+    int printcount, printw, printh, printsolns, printcurr, printcolour;
+    float printscale;
+    int printoffsetx, printoffsety;
+    float printpixelscale;
+    int fontstart;
+    int linewidth, linedotted;
+    drawing *dr;
+    int xmin, ymin;
+    float puzz_scale;
+};
+
+void frontend_free(frontend *fe)
+{
+    midend_free(fe->me);
+
+    sfree(fe->colours);
+    sfree(fe->brushes);
+    sfree(fe->pens);
+    sfree(fe->presets);
+    sfree(fe->fonts);
+
+    sfree(fe);
+}
+
+static void update_type_menu_tick(frontend *fe);
+static void update_copy_menu_greying(frontend *fe);
+
+void fatal(char *fmt, ...)
+{
+    char buf[2048];
+    va_list ap;
+
+    va_start(ap, fmt);
+    vsprintf(buf, fmt, ap);
+    va_end(ap);
+
+    MessageBox(NULL, buf, "Fatal error", MB_ICONEXCLAMATION | MB_OK);
+
+    exit(1);
+}
+
+char *geterrstr(void)
+{
+    LPVOID lpMsgBuf;
+    DWORD dw = GetLastError();
+    char *ret;
+
+    FormatMessage(
+        FORMAT_MESSAGE_ALLOCATE_BUFFER | 
+        FORMAT_MESSAGE_FROM_SYSTEM,
+        NULL,
+        dw,
+        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+        (LPTSTR) &lpMsgBuf,
+        0, NULL );
+
+    ret = dupstr(lpMsgBuf);
+
+    LocalFree(lpMsgBuf);
+
+    return ret;
+}
+
+void get_random_seed(void **randseed, int *randseedsize)
+{
+    SYSTEMTIME *st = snew(SYSTEMTIME);
+
+    GetLocalTime(st);
+
+    *randseed = (void *)st;
+    *randseedsize = sizeof(SYSTEMTIME);
+}
+
+static void win_status_bar(void *handle, char *text)
+{
+#ifdef _WIN32_WCE
+    TCHAR wText[255];
+#endif
+    frontend *fe = (frontend *)handle;
+
+#ifdef _WIN32_WCE
+    MultiByteToWideChar (CP_ACP, 0, text, -1, wText, 255);
+    SendMessage(fe->statusbar, SB_SETTEXT,
+                (WPARAM) 255 | SBT_NOBORDERS,
+                (LPARAM) wText);
+#else
+    SetWindowText(fe->statusbar, text);
+#endif
+}
+
+static blitter *win_blitter_new(void *handle, int w, int h)
+{
+    blitter *bl = snew(blitter);
+
+    memset(bl, 0, sizeof(blitter));
+    bl->w = w;
+    bl->h = h;
+    bl->bitmap = 0;
+
+    return bl;
+}
+
+static void win_blitter_free(void *handle, blitter *bl)
+{
+    if (bl->bitmap) DeleteObject(bl->bitmap);
+    sfree(bl);
+}
+
+static void blitter_mkbitmap(frontend *fe, blitter *bl)
+{
+    HDC hdc = GetDC(fe->hwnd);
+    bl->bitmap = CreateCompatibleBitmap(hdc, bl->w, bl->h);
+    ReleaseDC(fe->hwnd, hdc);
+}
+
+/* BitBlt(dstDC, dstX, dstY, dstW, dstH, srcDC, srcX, srcY, dType) */
+
+static void win_blitter_save(void *handle, blitter *bl, int x, int y)
+{
+    frontend *fe = (frontend *)handle;
+    HDC hdc_win, hdc_blit;
+    HBITMAP prev_blit;
+
+    assert(fe->drawstatus == DRAWING);
+
+    if (!bl->bitmap) blitter_mkbitmap(fe, bl);
+
+    bl->x = x; bl->y = y;
+
+    hdc_win = GetDC(fe->hwnd);
+    hdc_blit = CreateCompatibleDC(hdc_win);
+    if (!hdc_blit) fatal("hdc_blit failed: 0x%x", GetLastError());
+
+    prev_blit = SelectObject(hdc_blit, bl->bitmap);
+    if (prev_blit == NULL || prev_blit == HGDI_ERROR)
+        fatal("SelectObject for hdc_main failed: 0x%x", GetLastError());
+
+    if (!BitBlt(hdc_blit, 0, 0, bl->w, bl->h,
+                fe->hdc, x, y, SRCCOPY))
+        fatal("BitBlt failed: 0x%x", GetLastError());
+
+    SelectObject(hdc_blit, prev_blit);
+    DeleteDC(hdc_blit);
+    ReleaseDC(fe->hwnd, hdc_win);
+}
+
+static void win_blitter_load(void *handle, blitter *bl, int x, int y)
+{
+    frontend *fe = (frontend *)handle;
+    HDC hdc_win, hdc_blit;
+    HBITMAP prev_blit;
+
+    assert(fe->drawstatus == DRAWING);
+
+    assert(bl->bitmap); /* we should always have saved before loading */
+
+    if (x == BLITTER_FROMSAVED) x = bl->x;
+    if (y == BLITTER_FROMSAVED) y = bl->y;
+
+    hdc_win = GetDC(fe->hwnd);
+    hdc_blit = CreateCompatibleDC(hdc_win);
+
+    prev_blit = SelectObject(hdc_blit, bl->bitmap);
+
+    BitBlt(fe->hdc, x, y, bl->w, bl->h,
+           hdc_blit, 0, 0, SRCCOPY);
+
+    SelectObject(hdc_blit, prev_blit);
+    DeleteDC(hdc_blit);
+    ReleaseDC(fe->hwnd, hdc_win);
+}
+
+void frontend_default_colour(frontend *fe, float *output)
+{
+    DWORD c = GetSysColor(COLOR_MENU); /* ick */
+
+    output[0] = (float)(GetRValue(c) / 255.0);
+    output[1] = (float)(GetGValue(c) / 255.0);
+    output[2] = (float)(GetBValue(c) / 255.0);
+}
+
+static POINT win_transform_point(frontend *fe, int x, int y)
+{
+    POINT ret;
+
+    assert(fe->drawstatus != NOTHING);
+
+    if (fe->drawstatus == PRINTING) {
+       ret.x = (int)(fe->printoffsetx + fe->printpixelscale * x);
+       ret.y = (int)(fe->printoffsety + fe->printpixelscale * y);
+    } else {
+       ret.x = x;
+       ret.y = y;
+    }
+
+    return ret;
+}
+
+static void win_text_colour(frontend *fe, int colour)
+{
+    assert(fe->drawstatus != NOTHING);
+
+    if (fe->drawstatus == PRINTING) {
+       int hatch;
+       float r, g, b;
+       print_get_colour(fe->dr, colour, fe->printcolour, &hatch, &r, &g, &b);
+
+       /*
+        * Displaying text in hatched colours is not permitted.
+        */
+       assert(hatch < 0);
+
+       SetTextColor(fe->hdc, RGB(r * 255, g * 255, b * 255));
+    } else {
+       SetTextColor(fe->hdc, fe->colours[colour]);
+    }
+}
+
+static void win_set_brush(frontend *fe, int colour)
+{
+    HBRUSH br;
+    assert(fe->drawstatus != NOTHING);
+
+    if (fe->drawstatus == PRINTING) {
+       int hatch;
+       float r, g, b;
+       print_get_colour(fe->dr, colour, fe->printcolour, &hatch, &r, &g, &b);
+
+       if (hatch < 0) {
+           br = CreateSolidBrush(RGB(r * 255, g * 255, b * 255));
+       } else {
+#ifdef _WIN32_WCE
+           /*
+            * This is only ever required during printing, and the
+            * PocketPC port doesn't support printing.
+            */
+           fatal("CreateHatchBrush not supported");
+#else
+           br = CreateHatchBrush(hatch == HATCH_BACKSLASH ? HS_FDIAGONAL :
+                                 hatch == HATCH_SLASH ? HS_BDIAGONAL :
+                                 hatch == HATCH_HORIZ ? HS_HORIZONTAL :
+                                 hatch == HATCH_VERT ? HS_VERTICAL :
+                                 hatch == HATCH_PLUS ? HS_CROSS :
+                                 /* hatch == HATCH_X ? */ HS_DIAGCROSS,
+                                 RGB(0,0,0));
+#endif
+       }
+    } else {
+       br = fe->brushes[colour];
+    }
+    fe->oldbr = SelectObject(fe->hdc, br);
+}
+
+static void win_reset_brush(frontend *fe)
+{
+    HBRUSH br;
+
+    assert(fe->drawstatus != NOTHING);
+
+    br = SelectObject(fe->hdc, fe->oldbr);
+    if (fe->drawstatus == PRINTING)
+       DeleteObject(br);
+}
+
+static void win_set_pen(frontend *fe, int colour, int thin)
+{
+    HPEN pen;
+    assert(fe->drawstatus != NOTHING);
+
+    if (fe->drawstatus == PRINTING) {
+       int hatch;
+       float r, g, b;
+       int width = thin ? 0 : fe->linewidth;
+
+       if (fe->linedotted)
+           width = 0;
+
+       print_get_colour(fe->dr, colour, fe->printcolour, &hatch, &r, &g, &b);
+       /*
+        * Stroking in hatched colours is not permitted.
+        */
+       assert(hatch < 0);
+       pen = CreatePen(fe->linedotted ? PS_DOT : PS_SOLID,
+                       width, RGB(r * 255, g * 255, b * 255));
+    } else {
+       pen = fe->pens[colour];
+    }
+    fe->oldpen = SelectObject(fe->hdc, pen);
+}
+
+static void win_reset_pen(frontend *fe)
+{
+    HPEN pen;
+
+    assert(fe->drawstatus != NOTHING);
+
+    pen = SelectObject(fe->hdc, fe->oldpen);
+    if (fe->drawstatus == PRINTING)
+       DeleteObject(pen);
+}
+
+static void win_clip(void *handle, int x, int y, int w, int h)
+{
+    frontend *fe = (frontend *)handle;
+    POINT p, q;
+
+    if (fe->drawstatus == NOTHING)
+       return;
+
+    p = win_transform_point(fe, x, y);
+    q = win_transform_point(fe, x+w, y+h);
+    IntersectClipRect(fe->hdc, p.x, p.y, q.x, q.y);
+}
+
+static void win_unclip(void *handle)
+{
+    frontend *fe = (frontend *)handle;
+
+    if (fe->drawstatus == NOTHING)
+       return;
+
+    SelectClipRgn(fe->hdc, NULL);
+}
+
+static void win_draw_text(void *handle, int x, int y, int fonttype,
+                         int fontsize, int align, int colour, char *text)
+{
+    frontend *fe = (frontend *)handle;
+    POINT xy;
+    int i;
+    LOGFONT lf;
+
+    if (fe->drawstatus == NOTHING)
+       return;
+
+    if (fe->drawstatus == PRINTING)
+       fontsize = (int)(fontsize * fe->printpixelscale);
+
+    xy = win_transform_point(fe, x, y);
+
+    /*
+     * Find or create the font.
+     */
+    for (i = fe->fontstart; i < fe->nfonts; i++)
+        if (fe->fonts[i].type == fonttype && fe->fonts[i].size == fontsize)
+            break;
+
+    if (i == fe->nfonts) {
+        if (fe->fontsize <= fe->nfonts) {
+            fe->fontsize = fe->nfonts + 10;
+            fe->fonts = sresize(fe->fonts, fe->fontsize, struct font);
+        }
+
+        fe->nfonts++;
+
+        fe->fonts[i].type = fonttype;
+        fe->fonts[i].size = fontsize;
+
+        memset (&lf, 0, sizeof(LOGFONT));
+        lf.lfHeight = -fontsize;
+        lf.lfWeight = (fe->drawstatus == PRINTING ? 0 : FW_BOLD);
+        lf.lfCharSet = DEFAULT_CHARSET;
+        lf.lfOutPrecision = OUT_DEFAULT_PRECIS;
+        lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
+        lf.lfQuality = DEFAULT_QUALITY;
+        lf.lfPitchAndFamily = (fonttype == FONT_FIXED ?
+                               FIXED_PITCH | FF_DONTCARE :
+                               VARIABLE_PITCH | FF_SWISS);
+#ifdef _WIN32_WCE
+        wcscpy(lf.lfFaceName, TEXT("Tahoma"));
+#endif
+
+        fe->fonts[i].font = CreateFontIndirect(&lf);
+    }
+
+    /*
+     * Position and draw the text.
+     */
+    {
+       HFONT oldfont;
+       TEXTMETRIC tm;
+       SIZE size;
+       WCHAR wText[256];
+       MultiByteToWideChar (CP_UTF8, 0, text, -1, wText, 256);
+
+       oldfont = SelectObject(fe->hdc, fe->fonts[i].font);
+       if (GetTextMetrics(fe->hdc, &tm)) {
+           if (align & ALIGN_VCENTRE)
+               xy.y -= (tm.tmAscent+tm.tmDescent)/2;
+           else
+               xy.y -= tm.tmAscent;
+       }
+       if (GetTextExtentPoint32W(fe->hdc, wText, wcslen(wText), &size))
+       {
+           if (align & ALIGN_HCENTRE)
+               xy.x -= size.cx / 2;
+           else if (align & ALIGN_HRIGHT)
+               xy.x -= size.cx;
+       }
+       SetBkMode(fe->hdc, TRANSPARENT);
+       win_text_colour(fe, colour);
+       ExtTextOutW(fe->hdc, xy.x, xy.y, 0, NULL, wText, wcslen(wText), NULL);
+       SelectObject(fe->hdc, oldfont);
+    }
+}
+
+static void win_draw_rect(void *handle, int x, int y, int w, int h, int colour)
+{
+    frontend *fe = (frontend *)handle;
+    POINT p, q;
+
+    if (fe->drawstatus == NOTHING)
+       return;
+
+    if (fe->drawstatus == DRAWING && w == 1 && h == 1) {
+       /*
+        * Rectangle() appears to get uppity if asked to draw a 1x1
+        * rectangle, presumably on the grounds that that's beneath
+        * its dignity and you ought to be using SetPixel instead.
+        * So I will.
+        */
+       SetPixel(fe->hdc, x, y, fe->colours[colour]);
+    } else {
+       win_set_brush(fe, colour);
+       win_set_pen(fe, colour, TRUE);
+       p = win_transform_point(fe, x, y);
+       q = win_transform_point(fe, x+w, y+h);
+       Rectangle(fe->hdc, p.x, p.y, q.x, q.y);
+       win_reset_brush(fe);
+       win_reset_pen(fe);
+    }
+}
+
+static void win_draw_line(void *handle, int x1, int y1, int x2, int y2, int colour)
+{
+    frontend *fe = (frontend *)handle;
+    POINT pp[2];
+
+    if (fe->drawstatus == NOTHING)
+       return;
+
+    win_set_pen(fe, colour, FALSE);
+    pp[0] = win_transform_point(fe, x1, y1);
+    pp[1] = win_transform_point(fe, x2, y2);
+    Polyline(fe->hdc, pp, 2);
+    if (fe->drawstatus == DRAWING)
+       SetPixel(fe->hdc, pp[1].x, pp[1].y, fe->colours[colour]);
+    win_reset_pen(fe);
+}
+
+static void win_draw_circle(void *handle, int cx, int cy, int radius,
+                           int fillcolour, int outlinecolour)
+{
+    frontend *fe = (frontend *)handle;
+    POINT p, q;
+
+    assert(outlinecolour >= 0);
+
+    if (fe->drawstatus == NOTHING)
+       return;
+
+    if (fillcolour >= 0)
+       win_set_brush(fe, fillcolour);
+    else
+       fe->oldbr = SelectObject(fe->hdc, GetStockObject(NULL_BRUSH));
+
+    win_set_pen(fe, outlinecolour, FALSE);
+    p = win_transform_point(fe, cx - radius, cy - radius);
+    q = win_transform_point(fe, cx + radius, cy + radius);
+    Ellipse(fe->hdc, p.x, p.y, q.x+1, q.y+1);
+    win_reset_brush(fe);
+    win_reset_pen(fe);
+}
+
+static void win_draw_polygon(void *handle, int *coords, int npoints,
+                            int fillcolour, int outlinecolour)
+{
+    frontend *fe = (frontend *)handle;
+    POINT *pts;
+    int i;
+
+    if (fe->drawstatus == NOTHING)
+       return;
+
+    pts = snewn(npoints+1, POINT);
+
+    for (i = 0; i <= npoints; i++) {
+       int j = (i < npoints ? i : 0);
+       pts[i] = win_transform_point(fe, coords[j*2], coords[j*2+1]);
+    }
+
+    assert(outlinecolour >= 0);
+
+    if (fillcolour >= 0) {
+       win_set_brush(fe, fillcolour);
+       win_set_pen(fe, outlinecolour, FALSE);
+       Polygon(fe->hdc, pts, npoints);
+       win_reset_brush(fe);
+       win_reset_pen(fe);
+    } else {
+       win_set_pen(fe, outlinecolour, FALSE);
+       Polyline(fe->hdc, pts, npoints+1);
+       win_reset_pen(fe);
+    }
+
+    sfree(pts);
+}
+
+static void win_start_draw(void *handle)
+{
+    frontend *fe = (frontend *)handle;
+    HDC hdc_win;
+
+    assert(fe->drawstatus == NOTHING);
+
+    hdc_win = GetDC(fe->hwnd);
+    fe->hdc = CreateCompatibleDC(hdc_win);
+    fe->prevbm = SelectObject(fe->hdc, fe->bitmap);
+    ReleaseDC(fe->hwnd, hdc_win);
+    fe->clip = NULL;
+#ifndef _WIN32_WCE
+    SetMapMode(fe->hdc, MM_TEXT);
+#endif
+    fe->drawstatus = DRAWING;
+}
+
+static void win_draw_update(void *handle, int x, int y, int w, int h)
+{
+    frontend *fe = (frontend *)handle;
+    RECT r;
+
+    if (fe->drawstatus != DRAWING)
+       return;
+
+    r.left = x;
+    r.top = y;
+    r.right = x + w;
+    r.bottom = y + h;
+
+    OffsetRect(&r, fe->bitmapPosition.left, fe->bitmapPosition.top);
+    InvalidateRect(fe->hwnd, &r, FALSE);
+}
+
+static void win_end_draw(void *handle)
+{
+    frontend *fe = (frontend *)handle;
+    assert(fe->drawstatus == DRAWING);
+    SelectObject(fe->hdc, fe->prevbm);
+    DeleteDC(fe->hdc);
+    if (fe->clip) {
+       DeleteObject(fe->clip);
+       fe->clip = NULL;
+    }
+    fe->drawstatus = NOTHING;
+}
+
+static void win_line_width(void *handle, float width)
+{
+    frontend *fe = (frontend *)handle;
+
+    assert(fe->drawstatus != DRAWING);
+    if (fe->drawstatus == NOTHING)
+       return;
+
+    fe->linewidth = (int)(width * fe->printpixelscale);
+}
+
+static void win_line_dotted(void *handle, int dotted)
+{
+    frontend *fe = (frontend *)handle;
+
+    assert(fe->drawstatus != DRAWING);
+    if (fe->drawstatus == NOTHING)
+       return;
+
+    fe->linedotted = dotted;
+}
+
+static void win_begin_doc(void *handle, int pages)
+{
+    frontend *fe = (frontend *)handle;
+
+    assert(fe->drawstatus != DRAWING);
+    if (fe->drawstatus == NOTHING)
+       return;
+
+    if (StartDoc(fe->hdc, &fe->di) <= 0) {
+       char *e = geterrstr();
+       MessageBox(fe->hwnd, e, "Error starting to print",
+                  MB_ICONERROR | MB_OK);
+       sfree(e);
+       fe->drawstatus = NOTHING;
+    }
+
+    /*
+     * Push a marker on the font stack so that we won't use the
+     * same fonts for printing and drawing. (This is because
+     * drawing seems to look generally better in bold, but printing
+     * is better not in bold.)
+     */
+    fe->fontstart = fe->nfonts;
+}
+
+static void win_begin_page(void *handle, int number)
+{
+    frontend *fe = (frontend *)handle;
+
+    assert(fe->drawstatus != DRAWING);
+    if (fe->drawstatus == NOTHING)
+       return;
+
+    if (StartPage(fe->hdc) <= 0) {
+       char *e = geterrstr();
+       MessageBox(fe->hwnd, e, "Error starting a page",
+                  MB_ICONERROR | MB_OK);
+       sfree(e);
+       fe->drawstatus = NOTHING;
+    }
+}
+
+static void win_begin_puzzle(void *handle, float xm, float xc,
+                            float ym, float yc, int pw, int ph, float wmm)
+{
+    frontend *fe = (frontend *)handle;
+    int ppw, pph, pox, poy;
+    float mmpw, mmph, mmox, mmoy;
+    float scale;
+
+    assert(fe->drawstatus != DRAWING);
+    if (fe->drawstatus == NOTHING)
+       return;
+
+    ppw = GetDeviceCaps(fe->hdc, HORZRES);
+    pph = GetDeviceCaps(fe->hdc, VERTRES);
+    mmpw = (float)GetDeviceCaps(fe->hdc, HORZSIZE);
+    mmph = (float)GetDeviceCaps(fe->hdc, VERTSIZE);
+
+    /*
+     * Compute the puzzle's position on the logical page.
+     */
+    mmox = xm * mmpw + xc;
+    mmoy = ym * mmph + yc;
+
+    /*
+     * Work out what that comes to in pixels.
+     */
+    pox = (int)(mmox * (float)ppw / mmpw);
+    poy = (int)(mmoy * (float)pph / mmph);
+
+    /*
+     * And determine the scale.
+     * 
+     * I need a scale such that the maximum puzzle-coordinate
+     * extent of the rectangle (pw * scale) is equal to the pixel
+     * equivalent of the puzzle's millimetre width (wmm * ppw /
+     * mmpw).
+     */
+    scale = (wmm * ppw) / (mmpw * pw);
+
+    /*
+     * Now store pox, poy and scale for use in the main drawing
+     * functions.
+     */
+    fe->printoffsetx = pox;
+    fe->printoffsety = poy;
+    fe->printpixelscale = scale;
+
+    fe->linewidth = 1;
+    fe->linedotted = FALSE;
+}
+
+static void win_end_puzzle(void *handle)
+{
+    /* Nothing needs to be done here. */
+}
+
+static void win_end_page(void *handle, int number)
+{
+    frontend *fe = (frontend *)handle;
+
+    assert(fe->drawstatus != DRAWING);
+
+    if (fe->drawstatus == NOTHING)
+       return;
+
+    if (EndPage(fe->hdc) <= 0) {
+       char *e = geterrstr();
+       MessageBox(fe->hwnd, e, "Error finishing a page",
+                  MB_ICONERROR | MB_OK);
+       sfree(e);
+       fe->drawstatus = NOTHING;
+    }
+}
+
+static void win_end_doc(void *handle)
+{
+    frontend *fe = (frontend *)handle;
+
+    assert(fe->drawstatus != DRAWING);
+
+    /*
+     * Free all the fonts created since we began printing.
+     */
+    while (fe->nfonts > fe->fontstart) {
+       fe->nfonts--;
+       DeleteObject(fe->fonts[fe->nfonts].font);
+    }
+    fe->fontstart = 0;
+
+    /*
+     * The MSDN web site sample code doesn't bother to call EndDoc
+     * if an error occurs half way through printing. I expect doing
+     * so would cause the erroneous document to actually be
+     * printed, or something equally undesirable.
+     */
+    if (fe->drawstatus == NOTHING)
+       return;
+
+    if (EndDoc(fe->hdc) <= 0) {
+       char *e = geterrstr();
+       MessageBox(fe->hwnd, e, "Error finishing printing",
+                  MB_ICONERROR | MB_OK);
+       sfree(e);
+       fe->drawstatus = NOTHING;
+    }
+}
+
+char *win_text_fallback(void *handle, const char *const *strings, int nstrings)
+{
+    /*
+     * We assume Windows can cope with any UTF-8 likely to be
+     * emitted by a puzzle.
+     */
+    return dupstr(strings[0]);
+}
+
+const struct drawing_api win_drawing = {
+    win_draw_text,
+    win_draw_rect,
+    win_draw_line,
+    win_draw_polygon,
+    win_draw_circle,
+    win_draw_update,
+    win_clip,
+    win_unclip,
+    win_start_draw,
+    win_end_draw,
+    win_status_bar,
+    win_blitter_new,
+    win_blitter_free,
+    win_blitter_save,
+    win_blitter_load,
+    win_begin_doc,
+    win_begin_page,
+    win_begin_puzzle,
+    win_end_puzzle,
+    win_end_page,
+    win_end_doc,
+    win_line_width,
+    win_line_dotted,
+    win_text_fallback,
+};
+
+void print(frontend *fe)
+{
+#ifndef _WIN32_WCE
+    PRINTDLG pd;
+    char doctitle[256];
+    document *doc;
+    midend *nme = NULL;  /* non-interactive midend for bulk puzzle generation */
+    int i;
+    char *err = NULL;
+
+    /*
+     * Create our document structure and fill it up with puzzles.
+     */
+    doc = document_new(fe->printw, fe->printh, fe->printscale / 100.0F);
+    for (i = 0; i < fe->printcount; i++) {
+       if (i == 0 && fe->printcurr) {
+           err = midend_print_puzzle(fe->me, doc, fe->printsolns);
+       } else {
+           if (!nme) {
+               game_params *params;
+
+               nme = midend_new(NULL, fe->game, NULL, NULL);
+
+               /*
+                * Set the non-interactive mid-end to have the same
+                * parameters as the standard one.
+                */
+               params = midend_get_params(fe->me);
+               midend_set_params(nme, params);
+               fe->game->free_params(params);
+           }
+
+           midend_new_game(nme);
+           err = midend_print_puzzle(nme, doc, fe->printsolns);
+       }
+       if (err)
+           break;
+    }
+    if (nme)
+       midend_free(nme);
+
+    if (err) {
+       MessageBox(fe->hwnd, err, "Error preparing puzzles for printing",
+                  MB_ICONERROR | MB_OK);
+       document_free(doc);
+       return;
+    }
+
+    memset(&pd, 0, sizeof(pd));
+    pd.lStructSize = sizeof(pd);
+    pd.hwndOwner = fe->hwnd;
+    pd.hDevMode = NULL;
+    pd.hDevNames = NULL;
+    pd.Flags = PD_USEDEVMODECOPIESANDCOLLATE | PD_RETURNDC |
+       PD_NOPAGENUMS | PD_NOSELECTION;
+    pd.nCopies = 1;
+    pd.nFromPage = pd.nToPage = 0xFFFF;
+    pd.nMinPage = pd.nMaxPage = 1;
+
+    if (!PrintDlg(&pd)) {
+       document_free(doc);
+       return;
+    }
+
+    /*
+     * Now pd.hDC is a device context for the printer.
+     */
+
+    /*
+     * FIXME: IWBNI we put up an Abort box here.
+     */
+
+    memset(&fe->di, 0, sizeof(fe->di));
+    fe->di.cbSize = sizeof(fe->di);
+    sprintf(doctitle, "Printed puzzles from %s (from Simon Tatham's"
+           " Portable Puzzle Collection)", fe->game->name);
+    fe->di.lpszDocName = doctitle;
+    fe->di.lpszOutput = NULL;
+    fe->di.lpszDatatype = NULL;
+    fe->di.fwType = 0;
+
+    fe->drawstatus = PRINTING;
+    fe->hdc = pd.hDC;
+
+    fe->dr = drawing_new(&win_drawing, NULL, fe);
+    document_print(doc, fe->dr);
+    drawing_free(fe->dr);
+    fe->dr = NULL;
+
+    fe->drawstatus = NOTHING;
+
+    DeleteDC(pd.hDC);
+    document_free(doc);
+#endif
+}
+
+void deactivate_timer(frontend *fe)
+{
+    if (!fe)
+       return;                        /* for non-interactive midend */
+    if (fe->hwnd) KillTimer(fe->hwnd, fe->timer);
+    fe->timer = 0;
+}
+
+void activate_timer(frontend *fe)
+{
+    if (!fe)
+       return;                        /* for non-interactive midend */
+    if (!fe->timer) {
+       fe->timer = SetTimer(fe->hwnd, 1, 20, NULL);
+       fe->timer_last_tickcount = GetTickCount();
+    }
+}
+
+void write_clip(HWND hwnd, char *data)
+{
+    HGLOBAL clipdata;
+    int len, i, j;
+    char *data2;
+    void *lock;
+
+    /*
+     * Windows expects CRLF in the clipboard, so we must convert
+     * any \n that has come out of the puzzle backend.
+     */
+    len = 0;
+    for (i = 0; data[i]; i++) {
+       if (data[i] == '\n')
+           len++;
+       len++;
+    }
+    data2 = snewn(len+1, char);
+    j = 0;
+    for (i = 0; data[i]; i++) {
+       if (data[i] == '\n')
+           data2[j++] = '\r';
+       data2[j++] = data[i];
+    }
+    assert(j == len);
+    data2[j] = '\0';
+
+    clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len + 1);
+    if (!clipdata) {
+        sfree(data2);
+       return;
+    }
+    lock = GlobalLock(clipdata);
+    if (!lock) {
+        GlobalFree(clipdata);
+        sfree(data2);
+       return;
+    }
+    memcpy(lock, data2, len);
+    ((unsigned char *) lock)[len] = 0;
+    GlobalUnlock(clipdata);
+
+    if (OpenClipboard(hwnd)) {
+       EmptyClipboard();
+       SetClipboardData(CF_TEXT, clipdata);
+       CloseClipboard();
+    } else
+       GlobalFree(clipdata);
+
+    sfree(data2);
+}
+
+/*
+ * Set up Help and see if we can find a help file.
+ */
+static void init_help(void)
+{
+#ifndef _WIN32_WCE
+    char b[2048], *p, *q, *r;
+    FILE *fp;
+
+    /*
+     * Find the executable file path, so we can look alongside
+     * it for help files. Trim the filename off the end.
+     */
+    GetModuleFileName(NULL, b, sizeof(b) - 1);
+    r = b;
+    p = strrchr(b, '\\');
+    if (p && p >= r) r = p+1;
+    q = strrchr(b, ':');
+    if (q && q >= r) r = q+1;
+
+#ifndef NO_HTMLHELP
+    /*
+     * Try HTML Help first.
+     */
+    strcpy(r, CHM_FILE_NAME);
+    if ( (fp = fopen(b, "r")) != NULL) {
+       fclose(fp);
+
+       /*
+        * We have a .CHM. See if we can use it.
+        */
+       hh_dll = LoadLibrary("hhctrl.ocx");
+       if (hh_dll) {
+           htmlhelp = (htmlhelp_t)GetProcAddress(hh_dll, "HtmlHelpA");
+           if (!htmlhelp)
+               FreeLibrary(hh_dll);
+       }
+       if (htmlhelp) {
+           help_path = dupstr(b);
+           help_type = CHM;
+           return;
+       }
+    }
+#endif /* NO_HTMLHELP */
+
+    /*
+     * Now try old-style .HLP.
+     */
+    strcpy(r, HELP_FILE_NAME);
+    if ( (fp = fopen(b, "r")) != NULL) {
+       fclose(fp);
+
+       help_path = dupstr(b);
+       help_type = HLP;
+
+       /*
+        * See if there's a .CNT file alongside it.
+        */
+       strcpy(r, HELP_CNT_NAME);
+       if ( (fp = fopen(b, "r")) != NULL) {
+           fclose(fp);
+           help_has_contents = TRUE;
+       } else
+           help_has_contents = FALSE;
+
+       return;
+    }
+
+    help_type = NONE;         /* didn't find any */
+#endif
+}
+
+#ifndef _WIN32_WCE
+
+/*
+ * Start Help.
+ */
+static void start_help(frontend *fe, const char *topic)
+{
+    char *str = NULL;
+    int cmd;
+
+    switch (help_type) {
+      case HLP:
+       assert(help_path);
+       if (topic) {
+           str = snewn(10+strlen(topic), char);
+           sprintf(str, "JI(`',`%s')", topic);
+           cmd = HELP_COMMAND;
+       } else if (help_has_contents) {
+           cmd = HELP_FINDER;
+       } else {
+           cmd = HELP_CONTENTS;
+       }
+       WinHelp(fe->hwnd, help_path, cmd, (DWORD)str);
+       fe->help_running = TRUE;
+       break;
+      case CHM:
+#ifndef NO_HTMLHELP
+       assert(help_path);
+       assert(htmlhelp);
+       if (topic) {
+           str = snewn(20 + strlen(topic) + strlen(help_path), char);
+           sprintf(str, "%s::/%s.html>main", help_path, topic);
+       } else {
+           str = dupstr(help_path);
+       }
+       htmlhelp(fe->hwnd, str, HH_DISPLAY_TOPIC, 0);
+       fe->help_running = TRUE;
+       break;
+#endif /* NO_HTMLHELP */
+      case NONE:
+       assert(!"This shouldn't happen");
+       break;
+    }
+
+    sfree(str);
+}
+
+/*
+ * Stop Help on window cleanup.
+ */
+static void stop_help(frontend *fe)
+{
+    if (fe->help_running) {
+       switch (help_type) {
+         case HLP:
+           WinHelp(fe->hwnd, help_path, HELP_QUIT, 0);
+           break;
+         case CHM:
+#ifndef NO_HTMLHELP
+           assert(htmlhelp);
+           htmlhelp(NULL, NULL, HH_CLOSE_ALL, 0);
+           break;
+#endif /* NO_HTMLHELP */
+         case NONE:
+           assert(!"This shouldn't happen");
+           break;
+       }
+       fe->help_running = FALSE;
+    }
+}
+
+#endif
+
+/*
+ * Terminate Help on process exit.
+ */
+static void cleanup_help(void)
+{
+    /* Nothing to do currently.
+     * (If we were running HTML Help single-threaded, this is where we'd
+     * call HH_UNINITIALIZE.) */
+}
+
+static int get_statusbar_height(frontend *fe)
+{
+    int sy;
+    if (fe->statusbar) {
+       RECT sr;
+       GetWindowRect(fe->statusbar, &sr);
+       sy = sr.bottom - sr.top;
+    } else {
+       sy = 0;
+    }
+    return sy;
+}
+
+static void adjust_statusbar(frontend *fe, RECT *r)
+{
+    int sy;
+
+    if (!fe->statusbar) return;
+
+    sy = get_statusbar_height(fe);
+#ifndef _WIN32_WCE
+    SetWindowPos(fe->statusbar, NULL, 0, r->bottom-r->top-sy, r->right-r->left,
+                 sy, SWP_NOZORDER);
+#endif
+}
+
+static void get_menu_size(HWND wh, RECT *r)
+{
+    HMENU bar = GetMenu(wh);
+    RECT rect;
+    int i;
+
+    SetRect(r, 0, 0, 0, 0);
+    for (i = 0; i < GetMenuItemCount(bar); i++) {
+        GetMenuItemRect(wh, bar, i, &rect);
+        UnionRect(r, r, &rect);
+    }
+}
+
+/*
+ * Given a proposed new puzzle size (cx,cy), work out the actual
+ * puzzle size that would be (px,py) and the window size including
+ * furniture (wx,wy).
+ */
+
+static int check_window_resize(frontend *fe, int cx, int cy,
+                               int *px, int *py,
+                               int *wx, int *wy)
+{
+    RECT r;
+    int x, y, sy = get_statusbar_height(fe), changed = 0;
+
+    /* disallow making window thinner than menu bar */
+    x = max(cx, fe->xmin);
+    y = max(cy - sy, fe->ymin);
+
+    /*
+     * See if we actually got the window size we wanted, and adjust
+     * the puzzle size if not.
+     */
+    midend_size(fe->me, &x, &y, TRUE);
+    if (x != cx || y != cy) {
+        /*
+         * Resize the window, now we know what size we _really_
+         * want it to be.
+         */
+        r.left = r.top = 0;
+        r.right = x;
+        r.bottom = y + sy;
+        AdjustWindowRectEx(&r, WINFLAGS, TRUE, 0);
+        *wx = r.right - r.left;
+        *wy = r.bottom - r.top;
+        changed = 1;
+    }
+
+    *px = x;
+    *py = y;
+
+    fe->puzz_scale =
+      (float)midend_tilesize(fe->me) / (float)fe->game->preferred_tilesize;
+
+    return changed;
+}
+
+/*
+ * Given the current window size, make sure it's sane for the
+ * current puzzle and resize if necessary.
+ */
+
+static void check_window_size(frontend *fe, int *px, int *py)
+{
+    RECT r;
+    int wx, wy, cx, cy;
+
+    GetClientRect(fe->hwnd, &r);
+    cx = r.right - r.left;
+    cy = r.bottom - r.top;
+
+    if (check_window_resize(fe, cx, cy, px, py, &wx, &wy)) {
+#ifdef _WIN32_WCE
+        SetWindowPos(fe->hwnd, NULL, 0, 0, wx, wy,
+                    SWP_NOMOVE | SWP_NOZORDER);
+#endif
+        ;
+    }
+
+    GetClientRect(fe->hwnd, &r);
+    adjust_statusbar(fe, &r);
+}
+
+static void get_max_puzzle_size(frontend *fe, int *x, int *y)
+{
+    RECT r, sr;
+
+    if (SystemParametersInfo(SPI_GETWORKAREA, 0, &sr, FALSE)) {
+       *x = sr.right - sr.left;
+       *y = sr.bottom - sr.top;
+       r.left = 100;
+       r.right = 200;
+       r.top = 100;
+       r.bottom = 200;
+       AdjustWindowRectEx(&r, WINFLAGS, TRUE, 0);
+       *x -= r.right - r.left - 100;
+       *y -= r.bottom - r.top - 100;
+    } else {
+       *x = *y = INT_MAX;
+    }
+
+    if (fe->statusbar != NULL) {
+       GetWindowRect(fe->statusbar, &sr);
+       *y -= sr.bottom - sr.top;
+    }
+}
+
+#ifdef _WIN32_WCE
+/* Toolbar buttons on the numeric pad */
+static TBBUTTON tbNumpadButtons[] =
+{
+    {0, IDM_KEYEMUL + '1', TBSTATE_ENABLED, TBSTYLE_BUTTON,  0, -1},
+    {1, IDM_KEYEMUL + '2', TBSTATE_ENABLED, TBSTYLE_BUTTON,  0, -1},
+    {2, IDM_KEYEMUL + '3', TBSTATE_ENABLED, TBSTYLE_BUTTON,  0, -1},
+    {3, IDM_KEYEMUL + '4', TBSTATE_ENABLED, TBSTYLE_BUTTON,  0, -1},
+    {4, IDM_KEYEMUL + '5', TBSTATE_ENABLED, TBSTYLE_BUTTON,  0, -1},
+    {5, IDM_KEYEMUL + '6', TBSTATE_ENABLED, TBSTYLE_BUTTON,  0, -1},
+    {6, IDM_KEYEMUL + '7', TBSTATE_ENABLED, TBSTYLE_BUTTON,  0, -1},
+    {7, IDM_KEYEMUL + '8', TBSTATE_ENABLED, TBSTYLE_BUTTON,  0, -1},
+    {8, IDM_KEYEMUL + '9', TBSTATE_ENABLED, TBSTYLE_BUTTON,  0, -1},
+    {9, IDM_KEYEMUL + ' ', TBSTATE_ENABLED, TBSTYLE_BUTTON,  0, -1}
+};
+#endif
+
+/*
+ * Allocate a new frontend structure and create its main window.
+ */
+static frontend *frontend_new(HINSTANCE inst)
+{
+    frontend *fe;
+    const char *nogame = "Puzzles (no game selected)";
+
+    fe = snew(frontend);
+
+    fe->inst = inst;
+
+    fe->game = NULL;
+    fe->me = NULL;
+
+    fe->timer = 0;
+    fe->hwnd = NULL;
+
+    fe->help_running = FALSE;
+
+    fe->drawstatus = NOTHING;
+    fe->dr = NULL;
+    fe->fontstart = 0;
+
+    fe->fonts = NULL;
+    fe->nfonts = fe->fontsize = 0;
+
+    fe->colours = NULL;
+    fe->brushes = NULL;
+    fe->pens = NULL;
+
+    fe->puzz_scale = 1.0;
+
+    #ifdef _WIN32_WCE
+    MultiByteToWideChar (CP_ACP, 0, nogame, -1, wGameName, 256);
+    fe->hwnd = CreateWindowEx(0, wClassName, wGameName,
+                             WS_VISIBLE,
+                             CW_USEDEFAULT, CW_USEDEFAULT,
+                             CW_USEDEFAULT, CW_USEDEFAULT,
+                             NULL, NULL, inst, NULL);
+
+    {
+       SHMENUBARINFO mbi;
+       RECT rc, rcBar, rcTB, rcClient;
+
+       memset (&mbi, 0, sizeof(SHMENUBARINFO));
+       mbi.cbSize     = sizeof(SHMENUBARINFO);
+       mbi.hwndParent = fe->hwnd;
+       mbi.nToolBarId = IDR_MENUBAR1;
+       mbi.hInstRes   = inst;
+
+       SHCreateMenuBar(&mbi);
+
+       GetWindowRect(fe->hwnd, &rc);
+       GetWindowRect(mbi.hwndMB, &rcBar);
+       rc.bottom -= rcBar.bottom - rcBar.top;
+       MoveWindow(fe->hwnd, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, FALSE);
+
+        fe->numpad = NULL;
+    }
+#else
+    fe->hwnd = CreateWindowEx(0, CLASSNAME, nogame,
+                             WS_OVERLAPPEDWINDOW &~
+                             (WS_MAXIMIZEBOX),
+                             CW_USEDEFAULT, CW_USEDEFAULT,
+                             CW_USEDEFAULT, CW_USEDEFAULT,
+                             NULL, NULL, inst, NULL);
+    if (!fe->hwnd) {
+        DWORD lerr = GetLastError();
+        printf("no window: 0x%x\n", lerr);
+    }
+#endif
+
+    fe->gamemenu = NULL;
+    fe->presets = NULL;
+
+    fe->statusbar = NULL;
+    fe->bitmap = NULL;
+
+    SetWindowLong(fe->hwnd, GWL_USERDATA, (LONG)fe);
+
+    return fe;
+}
+
+static void savefile_write(void *wctx, void *buf, int len)
+{
+    FILE *fp = (FILE *)wctx;
+    fwrite(buf, 1, len, fp);
+}
+
+static int savefile_read(void *wctx, void *buf, int len)
+{
+    FILE *fp = (FILE *)wctx;
+    int ret;
+
+    ret = fread(buf, 1, len, fp);
+    return (ret == len);
+}
+
+/*
+ * Create an appropriate midend structure to go in a puzzle window,
+ * given a game type and/or a command-line argument.
+ *
+ * 'arg' can be either a game ID string (descriptive, random, or a
+ * plain set of parameters) or the filename of a save file. The two
+ * boolean flag arguments indicate which possibilities are
+ * permissible.
+ */
+static midend *midend_for_new_game(frontend *fe, const game *cgame,
+                                   char *arg, int maybe_game_id,
+                                   int maybe_save_file, char **error)
+{
+    midend *me = NULL;
+
+    if (!arg) {
+        if (me) midend_free(me);
+        me = midend_new(fe, cgame, &win_drawing, fe);
+        midend_new_game(me);
+    } else {
+        FILE *fp;
+        char *err_param, *err_load;
+
+        /*
+         * See if arg is a valid filename of a save game file.
+         */
+        err_load = NULL;
+        if (maybe_save_file && (fp = fopen(arg, "r")) != NULL) {
+            const game *loadgame;
+
+#ifdef COMBINED
+            /*
+             * Find out what kind of game is stored in the save
+             * file; if we're going to end up loading that, it
+             * will have to override our caller's judgment as to
+             * what game to initialise our midend with.
+             */
+            char *id_name;
+            err_load = identify_game(&id_name, savefile_read, fp);
+            if (!err_load) {
+                int i;
+                for (i = 0; i < gamecount; i++)
+                    if (!strcmp(id_name, gamelist[i]->name))
+                        break;
+                if (i == gamecount) {
+                    err_load = "Save file is for a game not supported by"
+                        " this program";
+                } else {
+                    loadgame = gamelist[i];
+                    rewind(fp); /* go back to the start for actual load */
+                }
+            }
+#else
+            loadgame = cgame;
+#endif
+            if (!err_load) {
+                if (me) midend_free(me);
+                me = midend_new(fe, loadgame, &win_drawing, fe);
+                err_load = midend_deserialise(me, savefile_read, fp);
+            }
+        } else {
+            err_load = "Unable to open file";
+        }
+
+        if (maybe_game_id && (!maybe_save_file || err_load)) {
+            /*
+             * See if arg is a game description.
+             */
+            if (me) midend_free(me);
+            me = midend_new(fe, cgame, &win_drawing, fe);
+            err_param = midend_game_id(me, arg);
+            if (!err_param) {
+                midend_new_game(me);
+            } else {
+                if (maybe_save_file) {
+                    *error = snewn(256 + strlen(arg) + strlen(err_param) +
+                                   strlen(err_load), char);
+                    sprintf(*error, "Supplied argument \"%s\" is neither a"
+                            " game ID (%s) nor a save file (%s)",
+                            arg, err_param, err_load);
+                } else {
+                    *error = dupstr(err_param);
+                }
+                midend_free(me);
+                sfree(fe);
+                return NULL;
+            }
+        } else if (err_load) {
+            *error = dupstr(err_load);
+            midend_free(me);
+            sfree(fe);
+            return NULL;
+        }
+    }
+
+    return me;
+}
+
+/*
+ * Populate a frontend structure with a new midend structure, and
+ * create any window furniture that it needs.
+ *
+ * Previously-allocated memory and window furniture will be freed by
+ * this function.
+ *
+ */
+static int fe_set_midend(frontend *fe, midend *me)
+{
+    int x, y;
+    RECT r;
+
+    if (fe->me) midend_free(fe->me);
+    fe->me = me;
+    fe->game = midend_which_game(fe->me);
+
+    {
+       int i, ncolours;
+        float *colours;
+
+        colours = midend_colours(fe->me, &ncolours);
+
+        if (fe->colours) sfree(fe->colours);
+        if (fe->brushes) sfree(fe->brushes);
+        if (fe->pens) sfree(fe->pens);
+
+       fe->colours = snewn(ncolours, COLORREF);
+       fe->brushes = snewn(ncolours, HBRUSH);
+       fe->pens = snewn(ncolours, HPEN);
+
+       for (i = 0; i < ncolours; i++) {
+           fe->colours[i] = RGB(255 * colours[i*3+0],
+                                255 * colours[i*3+1],
+                                255 * colours[i*3+2]);
+           fe->brushes[i] = CreateSolidBrush(fe->colours[i]);
+           fe->pens[i] = CreatePen(PS_SOLID, 1, fe->colours[i]);
+       }
+        sfree(colours);
+    }
+
+    if (fe->statusbar)
+        DestroyWindow(fe->statusbar);
+    if (midend_wants_statusbar(fe->me)) {
+       fe->statusbar = CreateWindowEx(0, STATUSCLASSNAME,
+                                      TEXT(DEFAULT_STATUSBAR_TEXT),
+                                      WS_CHILD | WS_VISIBLE,
+                                      0, 0, 0, 0, /* status bar does these */
+                                      NULL, NULL, fe->inst, NULL);
+    } else
+        fe->statusbar = NULL;
+
+    get_max_puzzle_size(fe, &x, &y);
+    midend_size(fe->me, &x, &y, FALSE);
+
+    r.left = r.top = 0;
+    r.right = x;
+    r.bottom = y;
+    AdjustWindowRectEx(&r, WINFLAGS, TRUE, 0);
+
+#ifdef _WIN32_WCE
+    if (fe->numpad)
+        DestroyWindow(fe->numpad);
+    if (fe->game->flags & REQUIRE_NUMPAD)
+    {
+        fe->numpad = CreateToolbarEx (fe->hwnd,
+                                      WS_VISIBLE | WS_CHILD | CCS_NOPARENTALIGN | TBSTYLE_FLAT,
+                                      0, 10, fe->inst, IDR_PADTOOLBAR,
+                                      tbNumpadButtons, sizeof (tbNumpadButtons) / sizeof (TBBUTTON),
+                                      0, 0, 14, 15, sizeof (TBBUTTON));
+        GetWindowRect(fe->numpad, &rcTB);
+        GetClientRect(fe->hwnd, &rcClient);
+        MoveWindow(fe->numpad, 
+                   0, 
+                   rcClient.bottom - (rcTB.bottom - rcTB.top) - 1,
+                   rcClient.right,
+                   rcTB.bottom - rcTB.top,
+                   FALSE);
+        SendMessage(fe->numpad, TB_SETINDENT, (rcClient.right - (10 * 21)) / 2, 0);
+    }
+    else {
+       fe->numpad = NULL;
+    }
+    MultiByteToWideChar (CP_ACP, 0, fe->game->name, -1, wGameName, 256);
+    SetWindowText(fe->hwnd, wGameName);
+#else
+    SetWindowText(fe->hwnd, fe->game->name);
+#endif
+
+    if (fe->statusbar)
+        DestroyWindow(fe->statusbar);
+    if (midend_wants_statusbar(fe->me)) {
+       RECT sr;
+       fe->statusbar = CreateWindowEx(0, STATUSCLASSNAME, TEXT("ooh"),
+                                      WS_CHILD | WS_VISIBLE,
+                                      0, 0, 0, 0, /* status bar does these */
+                                      fe->hwnd, NULL, fe->inst, NULL);
+#ifdef _WIN32_WCE
+       /* Flat status bar looks better on the Pocket PC */
+       SendMessage(fe->statusbar, SB_SIMPLE, (WPARAM) TRUE, 0);
+       SendMessage(fe->statusbar, SB_SETTEXT,
+                               (WPARAM) 255 | SBT_NOBORDERS,
+                               (LPARAM) L"");
+#endif
+
+       /*
+        * Now resize the window to take account of the status bar.
+        */
+       GetWindowRect(fe->statusbar, &sr);
+       GetWindowRect(fe->hwnd, &r);
+#ifndef _WIN32_WCE
+       SetWindowPos(fe->hwnd, NULL, 0, 0, r.right - r.left,
+                    r.bottom - r.top + sr.bottom - sr.top,
+                    SWP_NOMOVE | SWP_NOZORDER);
+#endif
+    } else {
+       fe->statusbar = NULL;
+    }
+
+    {
+        HMENU oldmenu = GetMenu(fe->hwnd);
+
+#ifndef _WIN32_WCE
+       HMENU bar = CreateMenu();
+       HMENU menu = CreateMenu();
+        RECT menusize;
+
+       AppendMenu(bar, MF_ENABLED|MF_POPUP, (UINT)menu, "&Game");
+#else
+       HMENU menu = SHGetSubMenu(SHFindMenuBar(fe->hwnd), ID_GAME);
+       DeleteMenu(menu, 0, MF_BYPOSITION);
+#endif
+       fe->gamemenu = menu;
+       AppendMenu(menu, MF_ENABLED, IDM_NEW, TEXT("&New"));
+       AppendMenu(menu, MF_ENABLED, IDM_RESTART, TEXT("&Restart"));
+#ifndef _WIN32_WCE
+        /* ...here I run out of sensible accelerator characters. */
+       AppendMenu(menu, MF_ENABLED, IDM_DESC, TEXT("Speci&fic..."));
+       AppendMenu(menu, MF_ENABLED, IDM_SEED, TEXT("Rando&m Seed..."));
+#endif
+
+        if (fe->presets)
+            sfree(fe->presets);
+       if ((fe->npresets = midend_num_presets(fe->me)) > 0 ||
+           fe->game->can_configure) {
+           int i;
+#ifndef _WIN32_WCE
+           HMENU sub = CreateMenu();
+
+           AppendMenu(bar, MF_ENABLED|MF_POPUP, (UINT)sub, "&Type");
+#else
+           HMENU sub = SHGetSubMenu(SHFindMenuBar(fe->hwnd), ID_TYPE);
+           DeleteMenu(sub, 0, MF_BYPOSITION);
+#endif
+           fe->presets = snewn(fe->npresets, game_params *);
+
+           for (i = 0; i < fe->npresets; i++) {
+               char *name;
+#ifdef _WIN32_WCE
+               TCHAR wName[255];
+#endif
+
+               midend_fetch_preset(fe->me, i, &name, &fe->presets[i]);
+
+               /*
+                * FIXME: we ought to go through and do something
+                * with ampersands here.
+                */
+
+#ifndef _WIN32_WCE
+               AppendMenu(sub, MF_ENABLED, IDM_PRESETS + 0x10 * i, name);
+#else
+               MultiByteToWideChar (CP_ACP, 0, name, -1, wName, 255);
+               AppendMenu(sub, MF_ENABLED, IDM_PRESETS + 0x10 * i, wName);
+#endif
+           }
+           if (fe->game->can_configure) {
+               AppendMenu(sub, MF_ENABLED, IDM_CONFIG, TEXT("&Custom..."));
+           }
+
+           fe->typemenu = sub;
+       } else {
+           fe->typemenu = INVALID_HANDLE_VALUE;
+            fe->presets = NULL;
+        }
+
+#ifdef COMBINED
+#ifdef _WIN32_WCE
+#error Windows CE does not support COMBINED build.
+#endif
+        {
+            HMENU games = CreateMenu();
+            int i;
+
+            AppendMenu(menu, MF_SEPARATOR, 0, 0);
+            AppendMenu(menu, MF_ENABLED|MF_POPUP, (UINT)games, "&Other");
+            for (i = 0; i < gamecount; i++) {
+                if (strcmp(gamelist[i]->name, fe->game->name) != 0) {
+                    /* only include those games that aren't the same as the
+                     * game we're currently playing. */
+                    AppendMenu(games, MF_ENABLED, IDM_GAMES + i, gamelist[i]->name);
+                }
+            }
+        }
+#endif
+
+       AppendMenu(menu, MF_SEPARATOR, 0, 0);
+#ifndef _WIN32_WCE
+       AppendMenu(menu, MF_ENABLED, IDM_LOAD, TEXT("&Load..."));
+       AppendMenu(menu, MF_ENABLED, IDM_SAVE, TEXT("&Save..."));
+       AppendMenu(menu, MF_SEPARATOR, 0, 0);
+       if (fe->game->can_print) {
+           AppendMenu(menu, MF_ENABLED, IDM_PRINT, TEXT("&Print..."));
+           AppendMenu(menu, MF_SEPARATOR, 0, 0);
+       }
+#endif
+       AppendMenu(menu, MF_ENABLED, IDM_UNDO, TEXT("Undo"));
+       AppendMenu(menu, MF_ENABLED, IDM_REDO, TEXT("Redo"));
+#ifndef _WIN32_WCE
+       if (fe->game->can_format_as_text_ever) {
+           AppendMenu(menu, MF_SEPARATOR, 0, 0);
+           AppendMenu(menu, MF_ENABLED, IDM_COPY, TEXT("&Copy"));
+       }
+#endif
+       if (fe->game->can_solve) {
+           AppendMenu(menu, MF_SEPARATOR, 0, 0);
+           AppendMenu(menu, MF_ENABLED, IDM_SOLVE, TEXT("Sol&ve"));
+       }
+       AppendMenu(menu, MF_SEPARATOR, 0, 0);
+#ifndef _WIN32_WCE
+       AppendMenu(menu, MF_ENABLED, IDM_QUIT, TEXT("E&xit"));
+       menu = CreateMenu();
+       AppendMenu(bar, MF_ENABLED|MF_POPUP, (UINT)menu, TEXT("&Help"));
+#endif
+       AppendMenu(menu, MF_ENABLED, IDM_ABOUT, TEXT("&About"));
+#ifndef _WIN32_WCE
+        if (help_type != NONE) {
+            char *item;
+            AppendMenu(menu, MF_SEPARATOR, 0, 0);
+            AppendMenu(menu, MF_ENABLED, IDM_HELPC, TEXT("&Contents"));
+            assert(fe->game->name);
+            item = snewn(10+strlen(fe->game->name), char); /*ick*/
+            sprintf(item, "&Help on %s", fe->game->name);
+            AppendMenu(menu, MF_ENABLED, IDM_GAMEHELP, item);
+            sfree(item);
+        }
+        DestroyMenu(oldmenu);
+       SetMenu(fe->hwnd, bar);
+        get_menu_size(fe->hwnd, &menusize);
+        fe->xmin = (menusize.right - menusize.left) + 25;
+#endif
+    }
+
+    if (fe->bitmap) DeleteObject(fe->bitmap);
+    fe->bitmap = NULL;
+    new_game_size(fe, fe->puzz_scale); /* initialises fe->bitmap */
+
+    return 0;
+}
+
+static void show_window(frontend *fe)
+{
+    ShowWindow(fe->hwnd, SW_SHOWNORMAL);
+    SetForegroundWindow(fe->hwnd);
+
+    update_type_menu_tick(fe);
+    update_copy_menu_greying(fe);
+
+    midend_redraw(fe->me);
+}
+
+#ifdef _WIN32_WCE
+static HFONT dialog_title_font()
+{
+    static HFONT hf = NULL;
+    LOGFONT lf;
+
+    if (hf)
+       return hf;
+
+    memset (&lf, 0, sizeof(LOGFONT));
+    lf.lfHeight = -11; /* - ((8 * GetDeviceCaps(hdc, LOGPIXELSY)) / 72) */
+    lf.lfWeight = FW_BOLD;
+    wcscpy(lf.lfFaceName, TEXT("Tahoma"));
+
+    return hf = CreateFontIndirect(&lf);
+}
+
+static void make_dialog_full_screen(HWND hwnd)
+{
+    SHINITDLGINFO shidi;
+
+    /* Make dialog full screen */
+    shidi.dwMask = SHIDIM_FLAGS;
+    shidi.dwFlags = SHIDIF_DONEBUTTON | SHIDIF_SIZEDLGFULLSCREEN |
+                    SHIDIF_EMPTYMENU;
+    shidi.hDlg = hwnd;
+    SHInitDialog(&shidi);
+}
+#endif
+
+static int CALLBACK AboutDlgProc(HWND hwnd, UINT msg,
+                                WPARAM wParam, LPARAM lParam)
+{
+    frontend *fe = (frontend *)GetWindowLong(hwnd, GWL_USERDATA);
+
+    switch (msg) {
+      case WM_INITDIALOG:
+#ifdef _WIN32_WCE
+       {
+           char title[256];
+
+           make_dialog_full_screen(hwnd);
+
+           sprintf(title, "About %.250s", fe->game->name);
+           SetDlgItemTextA(hwnd, IDC_ABOUT_CAPTION, title);
+
+           SendDlgItemMessage(hwnd, IDC_ABOUT_CAPTION, WM_SETFONT,
+                              (WPARAM) dialog_title_font(), 0);
+
+           SetDlgItemTextA(hwnd, IDC_ABOUT_GAME, fe->game->name);
+           SetDlgItemTextA(hwnd, IDC_ABOUT_VERSION, ver);
+       }
+#endif
+       return TRUE;
+
+      case WM_COMMAND:
+       if (LOWORD(wParam) == IDOK)
+#ifdef _WIN32_WCE
+           EndDialog(hwnd, 1);
+#else
+           fe->dlg_done = 1;
+#endif
+       return 0;
+
+      case WM_CLOSE:
+#ifdef _WIN32_WCE
+       EndDialog(hwnd, 1);
+#else
+       fe->dlg_done = 1;
+#endif
+       return 0;
+    }
+
+    return 0;
+}
+
+/*
+ * Wrappers on midend_{get,set}_config, which extend the CFG_*
+ * enumeration to add CFG_PRINT.
+ */
+static config_item *frontend_get_config(frontend *fe, int which,
+                                       char **wintitle)
+{
+    if (which < CFG_FRONTEND_SPECIFIC) {
+       return midend_get_config(fe->me, which, wintitle);
+    } else if (which == CFG_PRINT) {
+       config_item *ret;
+       int i;
+
+       *wintitle = snewn(40 + strlen(fe->game->name), char);
+       sprintf(*wintitle, "%s print setup", fe->game->name);
+
+       ret = snewn(8, config_item);
+
+       i = 0;
+
+       ret[i].name = "Number of puzzles to print";
+       ret[i].type = C_STRING;
+       ret[i].sval = dupstr("1");
+       ret[i].ival = 0;
+       i++;
+
+       ret[i].name = "Number of puzzles across the page";
+       ret[i].type = C_STRING;
+       ret[i].sval = dupstr("1");
+       ret[i].ival = 0;
+       i++;
+
+       ret[i].name = "Number of puzzles down the page";
+       ret[i].type = C_STRING;
+       ret[i].sval = dupstr("1");
+       ret[i].ival = 0;
+       i++;
+
+       ret[i].name = "Percentage of standard size";
+       ret[i].type = C_STRING;
+       ret[i].sval = dupstr("100.0");
+       ret[i].ival = 0;
+       i++;
+
+       ret[i].name = "Include currently shown puzzle";
+       ret[i].type = C_BOOLEAN;
+       ret[i].sval = NULL;
+       ret[i].ival = TRUE;
+       i++;
+
+       ret[i].name = "Print solutions";
+       ret[i].type = C_BOOLEAN;
+       ret[i].sval = NULL;
+       ret[i].ival = FALSE;
+       i++;
+
+       if (fe->game->can_print_in_colour) {
+           ret[i].name = "Print in colour";
+           ret[i].type = C_BOOLEAN;
+           ret[i].sval = NULL;
+           ret[i].ival = FALSE;
+           i++;
+       }
+
+       ret[i].name = NULL;
+       ret[i].type = C_END;
+       ret[i].sval = NULL;
+       ret[i].ival = 0;
+       i++;
+
+       return ret;
+    } else {
+       assert(!"We should never get here");
+       return NULL;
+    }
+}
+
+static char *frontend_set_config(frontend *fe, int which, config_item *cfg)
+{
+    if (which < CFG_FRONTEND_SPECIFIC) {
+       return midend_set_config(fe->me, which, cfg);
+    } else if (which == CFG_PRINT) {
+       if ((fe->printcount = atoi(cfg[0].sval)) <= 0)
+           return "Number of puzzles to print should be at least one";
+       if ((fe->printw = atoi(cfg[1].sval)) <= 0)
+           return "Number of puzzles across the page should be at least one";
+       if ((fe->printh = atoi(cfg[2].sval)) <= 0)
+           return "Number of puzzles down the page should be at least one";
+       if ((fe->printscale = (float)atof(cfg[3].sval)) <= 0)
+           return "Print size should be positive";
+       fe->printcurr = cfg[4].ival;
+       fe->printsolns = cfg[5].ival;
+       fe->printcolour = fe->game->can_print_in_colour && cfg[6].ival;
+       return NULL;
+    } else {
+       assert(!"We should never get here");
+       return "Internal error";
+    }
+}
+
+#ifdef _WIN32_WCE
+/* Separate version of mkctrl function for the Pocket PC. */
+/* Control coordinates should be specified in dialog units. */
+HWND mkctrl(frontend *fe, int x1, int x2, int y1, int y2,
+           LPCTSTR wclass, int wstyle,
+           int exstyle, const char *wtext, int wid)
+{
+    RECT rc;
+    TCHAR wwtext[256];
+
+    /* Convert dialog units into pixels */
+    rc.left = x1;  rc.right  = x2;
+    rc.top  = y1;  rc.bottom = y2;
+    MapDialogRect(fe->cfgbox, &rc);
+
+    MultiByteToWideChar (CP_ACP, 0, wtext, -1, wwtext, 256);
+
+    return CreateWindowEx(exstyle, wclass, wwtext,
+                         wstyle | WS_CHILD | WS_VISIBLE,
+                         rc.left, rc.top,
+                         rc.right - rc.left, rc.bottom - rc.top,
+                         fe->cfgbox, (HMENU) wid, fe->inst, NULL);
+}
+
+static void create_config_controls(frontend * fe)
+{
+    int id, nctrls;
+    int col1l, col1r, col2l, col2r, y;
+    config_item *i;
+    struct cfg_aux *j;
+    HWND ctl;
+
+    /* Control placement done in dialog units */
+    col1l = 4;   col1r = 96;   /* Label column */
+    col2l = 100; col2r = 154;  /* Input column (edit boxes and combo boxes) */
+
+    /*
+     * Count the controls so we can allocate cfgaux.
+     */
+    for (nctrls = 0, i = fe->cfg; i->type != C_END; i++)
+       nctrls++;
+    fe->cfgaux = snewn(nctrls, struct cfg_aux);
+
+    id = 1000;
+    y = 22; /* Leave some room for the dialog title */
+    for (i = fe->cfg, j = fe->cfgaux; i->type != C_END; i++, j++) {
+       switch (i->type) {
+         case C_STRING:
+           /*
+            * Edit box with a label beside it.
+            */
+           mkctrl(fe, col1l, col1r, y + 1, y + 11,
+                  TEXT("Static"), SS_LEFTNOWORDWRAP, 0, i->name, id++);
+           mkctrl(fe, col2l, col2r, y, y + 12,
+                  TEXT("EDIT"), WS_BORDER | WS_TABSTOP | ES_AUTOHSCROLL,
+                  0, "", (j->ctlid = id++));
+           SetDlgItemTextA(fe->cfgbox, j->ctlid, i->sval);
+           break;
+
+         case C_BOOLEAN:
+           /*
+            * Simple checkbox.
+            */
+           mkctrl(fe, col1l, col2r, y + 1, y + 11, TEXT("BUTTON"),
+                  BS_NOTIFY | BS_AUTOCHECKBOX | WS_TABSTOP,
+                  0, i->name, (j->ctlid = id++));
+           CheckDlgButton(fe->cfgbox, j->ctlid, (i->ival != 0));
+           break;
+
+         case C_CHOICES:
+           /*
+            * Drop-down list with a label beside it.
+            */
+           mkctrl(fe, col1l, col1r, y + 1, y + 11,
+                  TEXT("STATIC"), SS_LEFTNOWORDWRAP, 0, i->name, id++);
+           ctl = mkctrl(fe, col2l, col2r, y, y + 48,
+                        TEXT("COMBOBOX"), WS_BORDER | WS_TABSTOP |
+                        CBS_DROPDOWNLIST | CBS_HASSTRINGS,
+                        0, "", (j->ctlid = id++));
+           {
+               char c, *p, *q, *str;
+
+               p = i->sval;
+               c = *p++;
+               while (*p) {
+                   q = p;
+                   while (*q && *q != c) q++;
+                   str = snewn(q-p+1, char);
+                   strncpy(str, p, q-p);
+                   str[q-p] = '\0';
+                   {
+                       TCHAR ws[50];
+                       MultiByteToWideChar (CP_ACP, 0, str, -1, ws, 50);
+                       SendMessage(ctl, CB_ADDSTRING, 0, (LPARAM)ws);
+                   }
+                   
+                   sfree(str);
+                   if (*q) q++;
+                   p = q;
+               }
+           }
+           SendMessage(ctl, CB_SETCURSEL, i->ival, 0);
+           break;
+       }
+
+       y += 15;
+    }
+
+}
+#endif
+
+static int CALLBACK ConfigDlgProc(HWND hwnd, UINT msg,
+                                 WPARAM wParam, LPARAM lParam)
+{
+    frontend *fe = (frontend *)GetWindowLong(hwnd, GWL_USERDATA);
+    config_item *i;
+    struct cfg_aux *j;
+
+    switch (msg) {
+      case WM_INITDIALOG:
+#ifdef _WIN32_WCE
+       {
+            char *title;
+
+           fe = (frontend *) lParam;
+           SetWindowLong(hwnd, GWL_USERDATA, lParam);
+           fe->cfgbox = hwnd;
+
+            fe->cfg = frontend_get_config(fe, fe->cfg_which, &title);
+
+           make_dialog_full_screen(hwnd);
+
+           SetDlgItemTextA(hwnd, IDC_CONFIG_CAPTION, title);
+           SendDlgItemMessage(hwnd, IDC_CONFIG_CAPTION, WM_SETFONT,
+                              (WPARAM) dialog_title_font(), 0);
+
+           create_config_controls(fe);
+       }
+#endif
+       return TRUE;
+
+      case WM_COMMAND:
+       /*
+        * OK and Cancel are special cases.
+        */
+       if ((LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)) {
+           if (LOWORD(wParam) == IDOK) {
+               char *err = frontend_set_config(fe, fe->cfg_which, fe->cfg);
+
+               if (err) {
+                   MessageBox(hwnd, err, "Validation error",
+                              MB_ICONERROR | MB_OK);
+               } else {
+#ifdef _WIN32_WCE
+                   EndDialog(hwnd, 2);
+#else
+                   fe->dlg_done = 2;
+#endif
+               }
+           } else {
+#ifdef _WIN32_WCE
+               EndDialog(hwnd, 1);
+#else
+               fe->dlg_done = 1;
+#endif
+           }
+           return 0;
+       }
+
+       /*
+        * First find the control whose id this is.
+        */
+       for (i = fe->cfg, j = fe->cfgaux; i->type != C_END; i++, j++) {
+           if (j->ctlid == LOWORD(wParam))
+               break;
+       }
+       if (i->type == C_END)
+           return 0;                  /* not our problem */
+
+       if (i->type == C_STRING && HIWORD(wParam) == EN_CHANGE) {
+           char buffer[4096];
+#ifdef _WIN32_WCE
+           TCHAR wBuffer[4096];
+           GetDlgItemText(fe->cfgbox, j->ctlid, wBuffer, 4096);
+           WideCharToMultiByte(CP_ACP, 0, wBuffer, -1, buffer, 4096, NULL, NULL);
+#else
+           GetDlgItemText(fe->cfgbox, j->ctlid, buffer, lenof(buffer));
+#endif
+           buffer[lenof(buffer)-1] = '\0';
+           sfree(i->sval);
+           i->sval = dupstr(buffer);
+       } else if (i->type == C_BOOLEAN && 
+                  (HIWORD(wParam) == BN_CLICKED ||
+                   HIWORD(wParam) == BN_DBLCLK)) {
+           i->ival = IsDlgButtonChecked(fe->cfgbox, j->ctlid);
+       } else if (i->type == C_CHOICES &&
+                  HIWORD(wParam) == CBN_SELCHANGE) {
+           i->ival = SendDlgItemMessage(fe->cfgbox, j->ctlid,
+                                        CB_GETCURSEL, 0, 0);
+       }
+
+       return 0;
+
+      case WM_CLOSE:
+       fe->dlg_done = 1;
+       return 0;
+    }
+
+    return 0;
+}
+
+#ifndef _WIN32_WCE
+HWND mkctrl(frontend *fe, int x1, int x2, int y1, int y2,
+           char *wclass, int wstyle,
+           int exstyle, const char *wtext, int wid)
+{
+    HWND ret;
+    ret = CreateWindowEx(exstyle, wclass, wtext,
+                        wstyle | WS_CHILD | WS_VISIBLE, x1, y1, x2-x1, y2-y1,
+                        fe->cfgbox, (HMENU) wid, fe->inst, NULL);
+    SendMessage(ret, WM_SETFONT, (WPARAM)fe->cfgfont, MAKELPARAM(TRUE, 0));
+    return ret;
+}
+#endif
+
+static void about(frontend *fe)
+{
+#ifdef _WIN32_WCE
+    DialogBox(fe->inst, MAKEINTRESOURCE(IDD_ABOUT), fe->hwnd, AboutDlgProc);
+#else
+    int i;
+    WNDCLASS wc;
+    MSG msg;
+    TEXTMETRIC tm;
+    HDC hdc;
+    HFONT oldfont;
+    SIZE size;
+    int gm, id;
+    int winwidth, winheight, y;
+    int height, width, maxwid;
+    const char *strings[16];
+    int lengths[16];
+    int nstrings = 0;
+    char titlebuf[512];
+
+    sprintf(titlebuf, "About %.250s", fe->game->name);
+
+    strings[nstrings++] = fe->game->name;
+    strings[nstrings++] = "from Simon Tatham's Portable Puzzle Collection";
+    strings[nstrings++] = ver;
+
+    wc.style = CS_DBLCLKS | CS_SAVEBITS;
+    wc.lpfnWndProc = DefDlgProc;
+    wc.cbClsExtra = 0;
+    wc.cbWndExtra = DLGWINDOWEXTRA + 8;
+    wc.hInstance = fe->inst;
+    wc.hIcon = NULL;
+    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
+    wc.hbrBackground = (HBRUSH) (COLOR_BACKGROUND +1);
+    wc.lpszMenuName = NULL;
+    wc.lpszClassName = "GameAboutBox";
+    RegisterClass(&wc);
+
+    hdc = GetDC(fe->hwnd);
+    SetMapMode(hdc, MM_TEXT);
+
+    fe->dlg_done = FALSE;
+
+    fe->cfgfont = CreateFont(-MulDiv(8, GetDeviceCaps(hdc, LOGPIXELSY), 72),
+                            0, 0, 0, 0,
+                            FALSE, FALSE, FALSE, DEFAULT_CHARSET,
+                            OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
+                            DEFAULT_QUALITY,
+                            FF_SWISS,
+                            "MS Shell Dlg");
+
+    oldfont = SelectObject(hdc, fe->cfgfont);
+    if (GetTextMetrics(hdc, &tm)) {
+       height = tm.tmAscent + tm.tmDescent;
+       width = tm.tmAveCharWidth;
+    } else {
+       height = width = 30;
+    }
+
+    /*
+     * Figure out the layout of the About box by measuring the
+     * length of each piece of text.
+     */
+    maxwid = 0;
+    winheight = height/2;
+
+    for (i = 0; i < nstrings; i++) {
+       if (GetTextExtentPoint32(hdc, strings[i], strlen(strings[i]), &size))
+           lengths[i] = size.cx;
+       else
+           lengths[i] = 0;            /* *shrug* */
+       if (maxwid < lengths[i])
+           maxwid = lengths[i];
+       winheight += height * 3 / 2 + (height / 2);
+    }
+
+    winheight += height + height * 7 / 4;      /* OK button */
+    winwidth = maxwid + 4*width;
+
+    SelectObject(hdc, oldfont);
+    ReleaseDC(fe->hwnd, hdc);
+
+    /*
+     * Create the dialog, now that we know its size.
+     */
+    {
+       RECT r, r2;
+
+       r.left = r.top = 0;
+       r.right = winwidth;
+       r.bottom = winheight;
+
+       AdjustWindowRectEx(&r, (WS_OVERLAPPEDWINDOW /*|
+                               DS_MODALFRAME | WS_POPUP | WS_VISIBLE |
+                               WS_CAPTION | WS_SYSMENU*/) &~
+                          (WS_MAXIMIZEBOX | WS_OVERLAPPED),
+                          FALSE, 0);
+
+       /*
+        * Centre the dialog on its parent window.
+        */
+       r.right -= r.left;
+       r.bottom -= r.top;
+       GetWindowRect(fe->hwnd, &r2);
+       r.left = (r2.left + r2.right - r.right) / 2;
+       r.top = (r2.top + r2.bottom - r.bottom) / 2;
+       r.right += r.left;
+       r.bottom += r.top;
+
+       fe->cfgbox = CreateWindowEx(0, wc.lpszClassName, titlebuf,
+                                   DS_MODALFRAME | WS_POPUP | WS_VISIBLE |
+                                   WS_CAPTION | WS_SYSMENU,
+                                   r.left, r.top,
+                                   r.right-r.left, r.bottom-r.top,
+                                   fe->hwnd, NULL, fe->inst, NULL);
+    }
+
+    SendMessage(fe->cfgbox, WM_SETFONT, (WPARAM)fe->cfgfont, FALSE);
+
+    SetWindowLong(fe->cfgbox, GWL_USERDATA, (LONG)fe);
+    SetWindowLong(fe->cfgbox, DWL_DLGPROC, (LONG)AboutDlgProc);
+
+    id = 1000;
+    y = height/2;
+    for (i = 0; i < nstrings; i++) {
+       int border = width*2 + (maxwid - lengths[i]) / 2;
+       mkctrl(fe, border, border+lengths[i], y+height*1/8, y+height*9/8,
+              "Static", 0, 0, strings[i], id++);
+       y += height*3/2;
+
+       assert(y < winheight);
+       y += height/2;
+    }
+
+    y += height/2;                    /* extra space before OK */
+    mkctrl(fe, width*2, maxwid+width*2, y, y+height*7/4, "BUTTON",
+          BS_PUSHBUTTON | WS_TABSTOP | BS_DEFPUSHBUTTON, 0,
+          "OK", IDOK);
+
+    SendMessage(fe->cfgbox, WM_INITDIALOG, 0, 0);
+
+    EnableWindow(fe->hwnd, FALSE);
+    ShowWindow(fe->cfgbox, SW_SHOWNORMAL);
+    while ((gm=GetMessage(&msg, NULL, 0, 0)) > 0) {
+       if (!IsDialogMessage(fe->cfgbox, &msg))
+           DispatchMessage(&msg);
+       if (fe->dlg_done)
+           break;
+    }
+    EnableWindow(fe->hwnd, TRUE);
+    SetForegroundWindow(fe->hwnd);
+    DestroyWindow(fe->cfgbox);
+    DeleteObject(fe->cfgfont);
+#endif
+}
+
+static int get_config(frontend *fe, int which)
+{
+#ifdef _WIN32_WCE
+    fe->cfg_which = which;
+
+    return DialogBoxParam(fe->inst,
+                         MAKEINTRESOURCE(IDD_CONFIG),
+                         fe->hwnd, ConfigDlgProc,
+                         (LPARAM) fe) == 2;
+#else
+    config_item *i;
+    struct cfg_aux *j;
+    char *title;
+    WNDCLASS wc;
+    MSG msg;
+    TEXTMETRIC tm;
+    HDC hdc;
+    HFONT oldfont;
+    SIZE size;
+    HWND ctl;
+    int gm, id, nctrls;
+    int winwidth, winheight, col1l, col1r, col2l, col2r, y;
+    int height, width, maxlabel, maxcheckbox;
+
+    wc.style = CS_DBLCLKS | CS_SAVEBITS;
+    wc.lpfnWndProc = DefDlgProc;
+    wc.cbClsExtra = 0;
+    wc.cbWndExtra = DLGWINDOWEXTRA + 8;
+    wc.hInstance = fe->inst;
+    wc.hIcon = NULL;
+    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
+    wc.hbrBackground = (HBRUSH) (COLOR_BACKGROUND +1);
+    wc.lpszMenuName = NULL;
+    wc.lpszClassName = "GameConfigBox";
+    RegisterClass(&wc);
+
+    hdc = GetDC(fe->hwnd);
+    SetMapMode(hdc, MM_TEXT);
+
+    fe->dlg_done = FALSE;
+
+    fe->cfgfont = CreateFont(-MulDiv(8, GetDeviceCaps(hdc, LOGPIXELSY), 72),
+                            0, 0, 0, 0,
+                            FALSE, FALSE, FALSE, DEFAULT_CHARSET,
+                            OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
+                            DEFAULT_QUALITY,
+                            FF_SWISS,
+                            "MS Shell Dlg");
+
+    oldfont = SelectObject(hdc, fe->cfgfont);
+    if (GetTextMetrics(hdc, &tm)) {
+       height = tm.tmAscent + tm.tmDescent;
+       width = tm.tmAveCharWidth;
+    } else {
+       height = width = 30;
+    }
+
+    fe->cfg = frontend_get_config(fe, which, &title);
+    fe->cfg_which = which;
+
+    /*
+     * Figure out the layout of the config box by measuring the
+     * length of each piece of text.
+     */
+    maxlabel = maxcheckbox = 0;
+    winheight = height/2;
+
+    for (i = fe->cfg; i->type != C_END; i++) {
+       switch (i->type) {
+         case C_STRING:
+         case C_CHOICES:
+           /*
+            * Both these control types have a label filling only
+            * the left-hand column of the box.
+            */
+           if (GetTextExtentPoint32(hdc, i->name, strlen(i->name), &size) &&
+               maxlabel < size.cx)
+               maxlabel = size.cx;
+           winheight += height * 3 / 2 + (height / 2);
+           break;
+
+         case C_BOOLEAN:
+           /*
+            * Checkboxes take up the whole of the box width.
+            */
+           if (GetTextExtentPoint32(hdc, i->name, strlen(i->name), &size) &&
+               maxcheckbox < size.cx)
+               maxcheckbox = size.cx;
+           winheight += height + (height / 2);
+           break;
+       }
+    }
+
+    winheight += height + height * 7 / 4;      /* OK / Cancel buttons */
+
+    col1l = 2*width;
+    col1r = col1l + maxlabel;
+    col2l = col1r + 2*width;
+    col2r = col2l + 30*width;
+    if (col2r < col1l+2*height+maxcheckbox)
+       col2r = col1l+2*height+maxcheckbox;
+    winwidth = col2r + 2*width;
+
+    SelectObject(hdc, oldfont);
+    ReleaseDC(fe->hwnd, hdc);
+
+    /*
+     * Create the dialog, now that we know its size.
+     */
+    {
+       RECT r, r2;
+
+       r.left = r.top = 0;
+       r.right = winwidth;
+       r.bottom = winheight;
+
+       AdjustWindowRectEx(&r, (WS_OVERLAPPEDWINDOW /*|
+                               DS_MODALFRAME | WS_POPUP | WS_VISIBLE |
+                               WS_CAPTION | WS_SYSMENU*/) &~
+                          (WS_MAXIMIZEBOX | WS_OVERLAPPED),
+                          FALSE, 0);
+
+       /*
+        * Centre the dialog on its parent window.
+        */
+       r.right -= r.left;
+       r.bottom -= r.top;
+       GetWindowRect(fe->hwnd, &r2);
+       r.left = (r2.left + r2.right - r.right) / 2;
+       r.top = (r2.top + r2.bottom - r.bottom) / 2;
+       r.right += r.left;
+       r.bottom += r.top;
+
+       fe->cfgbox = CreateWindowEx(0, wc.lpszClassName, title,
+                                   DS_MODALFRAME | WS_POPUP | WS_VISIBLE |
+                                   WS_CAPTION | WS_SYSMENU,
+                                   r.left, r.top,
+                                   r.right-r.left, r.bottom-r.top,
+                                   fe->hwnd, NULL, fe->inst, NULL);
+       sfree(title);
+    }
+
+    SendMessage(fe->cfgbox, WM_SETFONT, (WPARAM)fe->cfgfont, FALSE);
+
+    SetWindowLong(fe->cfgbox, GWL_USERDATA, (LONG)fe);
+    SetWindowLong(fe->cfgbox, DWL_DLGPROC, (LONG)ConfigDlgProc);
+
+    /*
+     * Count the controls so we can allocate cfgaux.
+     */
+    for (nctrls = 0, i = fe->cfg; i->type != C_END; i++)
+       nctrls++;
+    fe->cfgaux = snewn(nctrls, struct cfg_aux);
+
+    id = 1000;
+    y = height/2;
+    for (i = fe->cfg, j = fe->cfgaux; i->type != C_END; i++, j++) {
+       switch (i->type) {
+         case C_STRING:
+           /*
+            * Edit box with a label beside it.
+            */
+           mkctrl(fe, col1l, col1r, y+height*1/8, y+height*9/8,
+                  "Static", 0, 0, i->name, id++);
+           ctl = mkctrl(fe, col2l, col2r, y, y+height*3/2,
+                        "EDIT", WS_TABSTOP | ES_AUTOHSCROLL,
+                        WS_EX_CLIENTEDGE, "", (j->ctlid = id++));
+           SetWindowText(ctl, i->sval);
+           y += height*3/2;
+           break;
+
+         case C_BOOLEAN:
+           /*
+            * Simple checkbox.
+            */
+           mkctrl(fe, col1l, col2r, y, y+height, "BUTTON",
+                  BS_NOTIFY | BS_AUTOCHECKBOX | WS_TABSTOP,
+                  0, i->name, (j->ctlid = id++));
+           CheckDlgButton(fe->cfgbox, j->ctlid, (i->ival != 0));
+           y += height;
+           break;
+
+         case C_CHOICES:
+           /*
+            * Drop-down list with a label beside it.
+            */
+           mkctrl(fe, col1l, col1r, y+height*1/8, y+height*9/8,
+                  "STATIC", 0, 0, i->name, id++);
+           ctl = mkctrl(fe, col2l, col2r, y, y+height*41/2,
+                        "COMBOBOX", WS_TABSTOP |
+                        CBS_DROPDOWNLIST | CBS_HASSTRINGS,
+                        WS_EX_CLIENTEDGE, "", (j->ctlid = id++));
+           {
+               char c, *p, *q, *str;
+
+               SendMessage(ctl, CB_RESETCONTENT, 0, 0);
+               p = i->sval;
+               c = *p++;
+               while (*p) {
+                   q = p;
+                   while (*q && *q != c) q++;
+                   str = snewn(q-p+1, char);
+                   strncpy(str, p, q-p);
+                   str[q-p] = '\0';
+                   SendMessage(ctl, CB_ADDSTRING, 0, (LPARAM)str);
+                   sfree(str);
+                   if (*q) q++;
+                   p = q;
+               }
+           }
+
+           SendMessage(ctl, CB_SETCURSEL, i->ival, 0);
+
+           y += height*3/2;
+           break;
+       }
+
+       assert(y < winheight);
+       y += height/2;
+    }
+
+    y += height/2;                    /* extra space before OK and Cancel */
+    mkctrl(fe, col1l, (col1l+col2r)/2-width, y, y+height*7/4, "BUTTON",
+          BS_PUSHBUTTON | WS_TABSTOP | BS_DEFPUSHBUTTON, 0,
+          "OK", IDOK);
+    mkctrl(fe, (col1l+col2r)/2+width, col2r, y, y+height*7/4, "BUTTON",
+          BS_PUSHBUTTON | WS_TABSTOP, 0, "Cancel", IDCANCEL);
+
+    SendMessage(fe->cfgbox, WM_INITDIALOG, 0, 0);
+
+    EnableWindow(fe->hwnd, FALSE);
+    ShowWindow(fe->cfgbox, SW_SHOWNORMAL);
+    while ((gm=GetMessage(&msg, NULL, 0, 0)) > 0) {
+       if (!IsDialogMessage(fe->cfgbox, &msg))
+           DispatchMessage(&msg);
+       if (fe->dlg_done)
+           break;
+    }
+    EnableWindow(fe->hwnd, TRUE);
+    SetForegroundWindow(fe->hwnd);
+    DestroyWindow(fe->cfgbox);
+    DeleteObject(fe->cfgfont);
+
+    free_cfg(fe->cfg);
+    sfree(fe->cfgaux);
+
+    return (fe->dlg_done == 2);
+#endif
+}
+
+#ifdef _WIN32_WCE
+static void calculate_bitmap_position(frontend *fe, int x, int y)
+{
+    /* Pocket PC - center the game in the full screen window */
+    int yMargin;
+    RECT rcClient;
+
+    GetClientRect(fe->hwnd, &rcClient);
+    fe->bitmapPosition.left = (rcClient.right  - x) / 2;
+    yMargin = rcClient.bottom - y;
+
+    if (fe->numpad != NULL) {
+       RECT rcPad;
+       GetWindowRect(fe->numpad, &rcPad);
+       yMargin -= rcPad.bottom - rcPad.top;
+    }
+
+    if (fe->statusbar != NULL) {
+       RECT rcStatus;
+       GetWindowRect(fe->statusbar, &rcStatus);
+       yMargin -= rcStatus.bottom - rcStatus.top;
+    }
+
+    fe->bitmapPosition.top = yMargin / 2;
+
+    fe->bitmapPosition.right  = fe->bitmapPosition.left + x;
+    fe->bitmapPosition.bottom = fe->bitmapPosition.top  + y;
+}
+#else
+static void calculate_bitmap_position(frontend *fe, int x, int y)
+{
+    /* Plain Windows - position the game in the upper-left corner */
+    fe->bitmapPosition.left = 0;
+    fe->bitmapPosition.top = 0;
+    fe->bitmapPosition.right  = fe->bitmapPosition.left + x;
+    fe->bitmapPosition.bottom = fe->bitmapPosition.top  + y;
+}
+#endif
+
+static void new_bitmap(frontend *fe, int x, int y)
+{
+    HDC hdc;
+
+    if (fe->bitmap) DeleteObject(fe->bitmap);
+
+    hdc = GetDC(fe->hwnd);
+    fe->bitmap = CreateCompatibleBitmap(hdc, x, y);
+    calculate_bitmap_position(fe, x, y);
+    ReleaseDC(fe->hwnd, hdc);
+}
+
+static void new_game_size(frontend *fe, float scale)
+{
+    RECT r, sr;
+    int x, y;
+
+    get_max_puzzle_size(fe, &x, &y);
+    midend_size(fe->me, &x, &y, FALSE);
+
+    if (scale != 1.0) {
+      x = (int)((float)x * fe->puzz_scale);
+      y = (int)((float)y * fe->puzz_scale);
+      midend_size(fe->me, &x, &y, TRUE);
+    }
+    fe->ymin = (fe->xmin * y) / x;
+
+    r.left = r.top = 0;
+    r.right = x;
+    r.bottom = y;
+    AdjustWindowRectEx(&r, WINFLAGS, TRUE, 0);
+
+    if (fe->statusbar != NULL) {
+       GetWindowRect(fe->statusbar, &sr);
+    } else {
+       sr.left = sr.right = sr.top = sr.bottom = 0;
+    }
+#ifndef _WIN32_WCE
+    SetWindowPos(fe->hwnd, NULL, 0, 0,
+                r.right - r.left,
+                r.bottom - r.top + sr.bottom - sr.top,
+                SWP_NOMOVE | SWP_NOZORDER);
+#endif
+
+    check_window_size(fe, &x, &y);
+
+#ifndef _WIN32_WCE
+    if (fe->statusbar != NULL)
+       SetWindowPos(fe->statusbar, NULL, 0, y, x,
+                    sr.bottom - sr.top, SWP_NOZORDER);
+#endif
+
+    new_bitmap(fe, x, y);
+
+#ifdef _WIN32_WCE
+    InvalidateRect(fe->hwnd, NULL, TRUE);
+#endif
+    midend_redraw(fe->me);
+}
+
+/*
+ * Given a proposed new window rect, work out the resulting
+ * difference in client size (from current), and use to try
+ * and resize the puzzle, returning (wx,wy) as the actual
+ * new window size.
+ */
+
+static void adjust_game_size(frontend *fe, RECT *proposed, int isedge,
+                             int *wx_r, int *wy_r)
+{
+    RECT cr, wr;
+    int nx, ny, xdiff, ydiff, wx, wy;
+
+    /* Work out the current window sizing, and thus the
+     * difference in size we're asking for. */
+    GetClientRect(fe->hwnd, &cr);
+    wr = cr;
+    AdjustWindowRectEx(&wr, WINFLAGS, TRUE, 0);
+
+    xdiff = (proposed->right - proposed->left) - (wr.right - wr.left);
+    ydiff = (proposed->bottom - proposed->top) - (wr.bottom - wr.top);
+
+    if (isedge) {
+      /* These next four lines work around the fact that midend_size
+       * is happy to shrink _but not grow_ if you change one dimension
+       * but not the other. */
+      if (xdiff > 0 && ydiff == 0)
+        ydiff = (xdiff * (wr.right - wr.left)) / (wr.bottom - wr.top);
+      if (xdiff == 0 && ydiff > 0)
+        xdiff = (ydiff * (wr.bottom - wr.top)) / (wr.right - wr.left);
+    }
+
+    if (check_window_resize(fe,
+                            (cr.right - cr.left) + xdiff,
+                            (cr.bottom - cr.top) + ydiff,
+                            &nx, &ny, &wx, &wy)) {
+        new_bitmap(fe, nx, ny);
+        midend_force_redraw(fe->me);
+    } else {
+        /* reset size to current window size */
+        wx = wr.right - wr.left;
+        wy = wr.bottom - wr.top;
+    }
+    /* Re-fetch rectangle; size limits mean we might not have
+     * taken it quite to the mouse drag positions. */
+    GetClientRect(fe->hwnd, &cr);
+    adjust_statusbar(fe, &cr);
+
+    *wx_r = wx; *wy_r = wy;
+}
+
+static void update_type_menu_tick(frontend *fe)
+{
+    int total, n, i;
+
+    if (fe->typemenu == INVALID_HANDLE_VALUE)
+       return;
+
+    total = GetMenuItemCount(fe->typemenu);
+    n = midend_which_preset(fe->me);
+    if (n < 0)
+       n = total - 1;                 /* "Custom" item */
+
+    for (i = 0; i < total; i++) {
+       int flag = (i == n ? MF_CHECKED : MF_UNCHECKED);
+       CheckMenuItem(fe->typemenu, i, MF_BYPOSITION | flag);
+    }
+
+    DrawMenuBar(fe->hwnd);
+}
+
+static void update_copy_menu_greying(frontend *fe)
+{
+    UINT enable = (midend_can_format_as_text_now(fe->me) ?
+                  MF_ENABLED : MF_GRAYED);
+    EnableMenuItem(fe->gamemenu, IDM_COPY, MF_BYCOMMAND | enable);
+}
+
+static void new_game_type(frontend *fe)
+{
+    midend_new_game(fe->me);
+    new_game_size(fe, 1.0);
+    update_type_menu_tick(fe);
+    update_copy_menu_greying(fe);
+}
+
+static int is_alt_pressed(void)
+{
+    BYTE keystate[256];
+    int r = GetKeyboardState(keystate);
+    if (!r)
+       return FALSE;
+    if (keystate[VK_MENU] & 0x80)
+       return TRUE;
+    if (keystate[VK_RMENU] & 0x80)
+       return TRUE;
+    return FALSE;
+}
+
+static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
+                               WPARAM wParam, LPARAM lParam)
+{
+    frontend *fe = (frontend *)GetWindowLong(hwnd, GWL_USERDATA);
+    int cmd;
+
+    switch (message) {
+      case WM_CLOSE:
+       DestroyWindow(hwnd);
+       return 0;
+      case WM_COMMAND:
+#ifdef _WIN32_WCE
+       /* Numeric pad sends WM_COMMAND messages */
+       if ((wParam >= IDM_KEYEMUL) && (wParam < IDM_KEYEMUL + 256))
+       {
+           midend_process_key(fe->me, 0, 0, wParam - IDM_KEYEMUL);
+       }
+#endif
+       cmd = wParam & ~0xF;           /* low 4 bits reserved to Windows */
+       switch (cmd) {
+         case IDM_NEW:
+           if (!midend_process_key(fe->me, 0, 0, 'n'))
+               PostQuitMessage(0);
+           break;
+         case IDM_RESTART:
+           midend_restart_game(fe->me);
+           break;
+         case IDM_UNDO:
+           if (!midend_process_key(fe->me, 0, 0, 'u'))
+               PostQuitMessage(0);
+           break;
+         case IDM_REDO:
+           if (!midend_process_key(fe->me, 0, 0, '\x12'))
+               PostQuitMessage(0);
+           break;
+         case IDM_COPY:
+           {
+               char *text = midend_text_format(fe->me);
+               if (text)
+                   write_clip(hwnd, text);
+               else
+                   MessageBeep(MB_ICONWARNING);
+               sfree(text);
+           }
+           break;
+         case IDM_SOLVE:
+           {
+               char *msg = midend_solve(fe->me);
+               if (msg)
+                   MessageBox(hwnd, msg, "Unable to solve",
+                              MB_ICONERROR | MB_OK);
+           }
+           break;
+         case IDM_QUIT:
+           if (!midend_process_key(fe->me, 0, 0, 'q'))
+               PostQuitMessage(0);
+           break;
+         case IDM_CONFIG:
+           if (get_config(fe, CFG_SETTINGS))
+               new_game_type(fe);
+           break;
+         case IDM_SEED:
+           if (get_config(fe, CFG_SEED))
+               new_game_type(fe);
+           break;
+         case IDM_DESC:
+           if (get_config(fe, CFG_DESC))
+               new_game_type(fe);
+           break;
+         case IDM_PRINT:
+           if (get_config(fe, CFG_PRINT))
+               print(fe);
+           break;
+          case IDM_ABOUT:
+           about(fe);
+            break;
+         case IDM_LOAD:
+         case IDM_SAVE:
+           {
+               OPENFILENAME of;
+               char filename[FILENAME_MAX];
+               int ret;
+
+               memset(&of, 0, sizeof(of));
+               of.hwndOwner = hwnd;
+               of.lpstrFilter = "All Files (*.*)\0*\0\0\0";
+               of.lpstrCustomFilter = NULL;
+               of.nFilterIndex = 1;
+               of.lpstrFile = filename;
+               filename[0] = '\0';
+               of.nMaxFile = lenof(filename);
+               of.lpstrFileTitle = NULL;
+               of.lpstrTitle = (cmd == IDM_SAVE ?
+                                "Enter name of game file to save" :
+                                "Enter name of saved game file to load");
+               of.Flags = 0;
+#ifdef OPENFILENAME_SIZE_VERSION_400
+               of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
+#else
+               of.lStructSize = sizeof(of);
+#endif
+               of.lpstrInitialDir = NULL;
+
+               if (cmd == IDM_SAVE)
+                   ret = GetSaveFileName(&of);
+               else
+                   ret = GetOpenFileName(&of);
+
+               if (ret) {
+                   if (cmd == IDM_SAVE) {
+                       FILE *fp;
+
+                       if ((fp = fopen(filename, "r")) != NULL) {
+                           char buf[256 + FILENAME_MAX];
+                           fclose(fp);
+                           /* file exists */
+
+                           sprintf(buf, "Are you sure you want to overwrite"
+                                   " the file \"%.*s\"?",
+                                   FILENAME_MAX, filename);
+                           if (MessageBox(hwnd, buf, "Question",
+                                          MB_YESNO | MB_ICONQUESTION)
+                               != IDYES)
+                               break;
+                       }
+
+                       fp = fopen(filename, "w");
+
+                       if (!fp) {
+                           MessageBox(hwnd, "Unable to open save file",
+                                      "Error", MB_ICONERROR | MB_OK);
+                           break;
+                       }
+
+                       midend_serialise(fe->me, savefile_write, fp);
+
+                       fclose(fp);
+                   } else {
+                       FILE *fp = fopen(filename, "r");
+                       char *err = NULL;
+                        midend *me = fe->me;
+#ifdef COMBINED
+                        char *id_name;
+#endif
+
+                       if (!fp) {
+                           MessageBox(hwnd, "Unable to open saved game file",
+                                      "Error", MB_ICONERROR | MB_OK);
+                           break;
+                       }
+
+#ifdef COMBINED
+                        /*
+                         * This save file might be from a different
+                         * game.
+                         */
+                        err = identify_game(&id_name, savefile_read, fp);
+                        if (!err) {
+                            int i;
+                            for (i = 0; i < gamecount; i++)
+                                if (!strcmp(id_name, gamelist[i]->name))
+                                    break;
+                            if (i == gamecount) {
+                                err = "Save file is for a game not "
+                                    "supported by this program";
+                            } else {
+                                me = midend_for_new_game(fe, gamelist[i], NULL,
+                                                         FALSE, FALSE, &err);
+                                rewind(fp); /* for the actual load */
+                            }
+                            sfree(id_name);
+                        }
+#endif
+                        if (!err)
+                            err = midend_deserialise(me, savefile_read, fp);
+
+                       fclose(fp);
+
+                       if (err) {
+                           MessageBox(hwnd, err, "Error", MB_ICONERROR|MB_OK);
+                           break;
+                       }
+
+                        if (fe->me != me)
+                            fe_set_midend(fe, me);
+                       new_game_size(fe, 1.0);
+                   }
+               }
+           }
+
+           break;
+#ifndef _WIN32_WCE
+          case IDM_HELPC:
+           start_help(fe, NULL);
+           break;
+          case IDM_GAMEHELP:
+            assert(help_type != NONE);
+           start_help(fe, help_type == CHM ?
+                       fe->game->htmlhelp_topic : fe->game->winhelp_topic);
+            break;
+#endif
+         default:
+#ifdef COMBINED
+            if (wParam >= IDM_GAMES && wParam < (IDM_GAMES + (WPARAM)gamecount)) {
+                int p = wParam - IDM_GAMES;
+                char *error = NULL;
+                fe_set_midend(fe, midend_for_new_game(fe, gamelist[p], NULL,
+                                                      FALSE, FALSE, &error));
+                sfree(error);
+            } else
+#endif
+           {
+               int p = ((wParam &~ 0xF) - IDM_PRESETS) / 0x10;
+
+               if (p >= 0 && p < fe->npresets) {
+                   midend_set_params(fe->me, fe->presets[p]);
+                   new_game_type(fe);
+               }
+           }
+           break;
+       }
+       break;
+      case WM_DESTROY:
+#ifndef _WIN32_WCE
+       stop_help(fe);
+#endif
+        frontend_free(fe);
+        PostQuitMessage(0);
+       return 0;
+      case WM_PAINT:
+       {
+           PAINTSTRUCT p;
+           HDC hdc, hdc2;
+           HBITMAP prevbm;
+           RECT rcDest;
+
+           hdc = BeginPaint(hwnd, &p);
+           hdc2 = CreateCompatibleDC(hdc);
+           prevbm = SelectObject(hdc2, fe->bitmap);
+#ifdef _WIN32_WCE
+           FillRect(hdc, &(p.rcPaint), (HBRUSH) GetStockObject(WHITE_BRUSH));
+#endif
+           IntersectRect(&rcDest, &(fe->bitmapPosition), &(p.rcPaint));
+           BitBlt(hdc,
+                  rcDest.left, rcDest.top,
+                  rcDest.right - rcDest.left,
+                  rcDest.bottom - rcDest.top,
+                  hdc2,
+                  rcDest.left - fe->bitmapPosition.left,
+                  rcDest.top - fe->bitmapPosition.top,
+                  SRCCOPY);
+           SelectObject(hdc2, prevbm);
+           DeleteDC(hdc2);
+           EndPaint(hwnd, &p);
+       }
+       return 0;
+      case WM_KEYDOWN:
+       {
+           int key = -1;
+            BYTE keystate[256];
+            int r = GetKeyboardState(keystate);
+            int shift = (r && (keystate[VK_SHIFT] & 0x80)) ? MOD_SHFT : 0;
+            int ctrl = (r && (keystate[VK_CONTROL] & 0x80)) ? MOD_CTRL : 0;
+
+           switch (wParam) {
+             case VK_LEFT:
+               if (!(lParam & 0x01000000))
+                   key = MOD_NUM_KEYPAD | '4';
+                else
+                   key = shift | ctrl | CURSOR_LEFT;
+               break;
+             case VK_RIGHT:
+               if (!(lParam & 0x01000000))
+                   key = MOD_NUM_KEYPAD | '6';
+                else
+                   key = shift | ctrl | CURSOR_RIGHT;
+               break;
+             case VK_UP:
+               if (!(lParam & 0x01000000))
+                   key = MOD_NUM_KEYPAD | '8';
+                else
+                   key = shift | ctrl | CURSOR_UP;
+               break;
+             case VK_DOWN:
+               if (!(lParam & 0x01000000))
+                   key = MOD_NUM_KEYPAD | '2';
+                else
+                   key = shift | ctrl | CURSOR_DOWN;
+               break;
+               /*
+                * Diagonal keys on the numeric keypad.
+                */
+             case VK_PRIOR:
+               if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '9';
+               break;
+             case VK_NEXT:
+               if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '3';
+               break;
+             case VK_HOME:
+               if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '7';
+               break;
+             case VK_END:
+               if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '1';
+               break;
+             case VK_INSERT:
+               if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '0';
+               break;
+             case VK_CLEAR:
+               if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '5';
+               break;
+               /*
+                * Numeric keypad keys with Num Lock on.
+                */
+             case VK_NUMPAD4: key = MOD_NUM_KEYPAD | '4'; break;
+             case VK_NUMPAD6: key = MOD_NUM_KEYPAD | '6'; break;
+             case VK_NUMPAD8: key = MOD_NUM_KEYPAD | '8'; break;
+             case VK_NUMPAD2: key = MOD_NUM_KEYPAD | '2'; break;
+             case VK_NUMPAD5: key = MOD_NUM_KEYPAD | '5'; break;
+             case VK_NUMPAD9: key = MOD_NUM_KEYPAD | '9'; break;
+             case VK_NUMPAD3: key = MOD_NUM_KEYPAD | '3'; break;
+             case VK_NUMPAD7: key = MOD_NUM_KEYPAD | '7'; break;
+             case VK_NUMPAD1: key = MOD_NUM_KEYPAD | '1'; break;
+             case VK_NUMPAD0: key = MOD_NUM_KEYPAD | '0'; break;
+           }
+
+           if (key != -1) {
+               if (!midend_process_key(fe->me, 0, 0, key))
+                   PostQuitMessage(0);
+           } else {
+               MSG m;
+               m.hwnd = hwnd;
+               m.message = WM_KEYDOWN;
+               m.wParam = wParam;
+               m.lParam = lParam & 0xdfff;
+               TranslateMessage(&m);
+           }
+       }
+       break;
+      case WM_LBUTTONDOWN:
+      case WM_RBUTTONDOWN:
+      case WM_MBUTTONDOWN:
+       {
+           int button;
+
+           /*
+            * Shift-clicks count as middle-clicks, since otherwise
+            * two-button Windows users won't have any kind of
+            * middle click to use.
+            */
+           if (message == WM_MBUTTONDOWN || (wParam & MK_SHIFT))
+               button = MIDDLE_BUTTON;
+           else if (message == WM_RBUTTONDOWN || is_alt_pressed())
+               button = RIGHT_BUTTON;
+           else
+#ifndef _WIN32_WCE
+               button = LEFT_BUTTON;
+#else
+               if ((fe->game->flags & REQUIRE_RBUTTON) == 0)
+                   button = LEFT_BUTTON;
+               else
+               {
+                   SHRGINFO shrgi;
+
+                   shrgi.cbSize     = sizeof(SHRGINFO);
+                   shrgi.hwndClient = hwnd;
+                   shrgi.ptDown.x   = (signed short)LOWORD(lParam);
+                   shrgi.ptDown.y   = (signed short)HIWORD(lParam);
+                   shrgi.dwFlags    = SHRG_RETURNCMD;
+
+                   if (GN_CONTEXTMENU == SHRecognizeGesture(&shrgi))
+                       button = RIGHT_BUTTON;
+                   else
+                       button = LEFT_BUTTON;
+               }
+#endif
+
+           if (!midend_process_key(fe->me,
+                                   (signed short)LOWORD(lParam) - fe->bitmapPosition.left,
+                                   (signed short)HIWORD(lParam) - fe->bitmapPosition.top,
+                                   button))
+               PostQuitMessage(0);
+
+           SetCapture(hwnd);
+       }
+       break;
+      case WM_LBUTTONUP:
+      case WM_RBUTTONUP:
+      case WM_MBUTTONUP:
+       {
+           int button;
+
+           /*
+            * Shift-clicks count as middle-clicks, since otherwise
+            * two-button Windows users won't have any kind of
+            * middle click to use.
+            */
+           if (message == WM_MBUTTONUP || (wParam & MK_SHIFT))
+               button = MIDDLE_RELEASE;
+           else if (message == WM_RBUTTONUP || is_alt_pressed())
+               button = RIGHT_RELEASE;
+           else
+               button = LEFT_RELEASE;
+
+           if (!midend_process_key(fe->me,
+                                   (signed short)LOWORD(lParam) - fe->bitmapPosition.left,
+                                   (signed short)HIWORD(lParam) - fe->bitmapPosition.top,
+                                   button))
+               PostQuitMessage(0);
+
+           ReleaseCapture();
+       }
+       break;
+      case WM_MOUSEMOVE:
+       {
+           int button;
+
+           if (wParam & (MK_MBUTTON | MK_SHIFT))
+               button = MIDDLE_DRAG;
+           else if (wParam & MK_RBUTTON || is_alt_pressed())
+               button = RIGHT_DRAG;
+           else
+               button = LEFT_DRAG;
+           
+           if (!midend_process_key(fe->me,
+                                   (signed short)LOWORD(lParam) - fe->bitmapPosition.left,
+                                   (signed short)HIWORD(lParam) - fe->bitmapPosition.top,
+                                   button))
+               PostQuitMessage(0);
+       }
+       break;
+      case WM_CHAR:
+       if (!midend_process_key(fe->me, 0, 0, (unsigned char)wParam))
+           PostQuitMessage(0);
+       return 0;
+      case WM_TIMER:
+       if (fe->timer) {
+           DWORD now = GetTickCount();
+           float elapsed = (float) (now - fe->timer_last_tickcount) * 0.001F;
+           midend_timer(fe->me, elapsed);
+           fe->timer_last_tickcount = now;
+       }
+       return 0;
+#ifndef _WIN32_WCE
+      case WM_SIZING:
+        {
+            RECT *sr = (RECT *)lParam;
+            int wx, wy, isedge = 0;
+
+            if (wParam == WMSZ_TOP ||
+                wParam == WMSZ_RIGHT ||
+                wParam == WMSZ_BOTTOM ||
+                wParam == WMSZ_LEFT) isedge = 1;
+            adjust_game_size(fe, sr, isedge, &wx, &wy);
+
+            /* Given the window size the puzzles constrain
+             * us to, work out which edge we should be moving. */
+            if (wParam == WMSZ_TOP ||
+                wParam == WMSZ_TOPLEFT ||
+                wParam == WMSZ_TOPRIGHT) {
+                sr->top = sr->bottom - wy;
+            } else {
+                sr->bottom = sr->top + wy;
+            }
+            if (wParam == WMSZ_LEFT ||
+                wParam == WMSZ_TOPLEFT ||
+                wParam == WMSZ_BOTTOMLEFT) {
+                sr->left = sr->right - wx;
+            } else {
+                sr->right = sr->left + wx;
+            }
+            return TRUE;
+        }
+        break;
+#endif
+    }
+
+    return DefWindowProc(hwnd, message, wParam, lParam);
+}
+
+#ifdef _WIN32_WCE
+static int FindPreviousInstance()
+{
+    /* Check if application is running. If it's running then focus on the window */
+    HWND hOtherWnd = NULL;
+
+    hOtherWnd = FindWindow (wGameName, wGameName);
+    if (hOtherWnd)
+    {
+        SetForegroundWindow (hOtherWnd);
+        return TRUE;
+    }
+
+    return FALSE;
+}
+#endif
+
+/*
+ * Split a complete command line into argc/argv, attempting to do it
+ * exactly the same way the Visual Studio C library would do it (so
+ * that our console utilities, which receive argc and argv already
+ * broken apart by the C library, will have their command lines
+ * processed in the same way as the GUI utilities which get a whole
+ * command line and must call this function).
+ * 
+ * Does not modify the input command line.
+ * 
+ * The final parameter (argstart) is used to return a second array
+ * of char * pointers, the same length as argv, each one pointing
+ * at the start of the corresponding element of argv in the
+ * original command line. So if you get half way through processing
+ * your command line in argc/argv form and then decide you want to
+ * treat the rest as a raw string, you can. If you don't want to,
+ * `argstart' can be safely left NULL.
+ */
+void split_into_argv(char *cmdline, int *argc, char ***argv,
+                    char ***argstart)
+{
+    char *p;
+    char *outputline, *q;
+    char **outputargv, **outputargstart;
+    int outputargc;
+
+    /*
+     * These argument-breaking rules apply to Visual Studio 7, which
+     * is currently the compiler expected to be used for the Windows
+     * port of my puzzles. Visual Studio 10 has different rules,
+     * lacking the curious mod 3 behaviour of consecutive quotes
+     * described below; I presume they fixed a bug. As and when we
+     * migrate to a newer compiler, we'll have to adjust this to
+     * match; however, for the moment we faithfully imitate in our GUI
+     * utilities what our CLI utilities can't be prevented from doing.
+     *
+     * When I investigated this, at first glance the rules appeared to
+     * be:
+     *
+     *  - Single quotes are not special characters.
+     *
+     *  - Double quotes are removed, but within them spaces cease
+     *    to be special.
+     *
+     *  - Backslashes are _only_ special when a sequence of them
+     *    appear just before a double quote. In this situation,
+     *    they are treated like C backslashes: so \" just gives a
+     *    literal quote, \\" gives a literal backslash and then
+     *    opens or closes a double-quoted segment, \\\" gives a
+     *    literal backslash and then a literal quote, \\\\" gives
+     *    two literal backslashes and then opens/closes a
+     *    double-quoted segment, and so forth. Note that this
+     *    behaviour is identical inside and outside double quotes.
+     *
+     *  - Two successive double quotes become one literal double
+     *    quote, but only _inside_ a double-quoted segment.
+     *    Outside, they just form an empty double-quoted segment
+     *    (which may cause an empty argument word).
+     *
+     *  - That only leaves the interesting question of what happens
+     *    when one or more backslashes precedes two or more double
+     *    quotes, starting inside a double-quoted string. And the
+     *    answer to that appears somewhat bizarre. Here I tabulate
+     *    number of backslashes (across the top) against number of
+     *    quotes (down the left), and indicate how many backslashes
+     *    are output, how many quotes are output, and whether a
+     *    quoted segment is open at the end of the sequence:
+     * 
+     *                      backslashes
+     * 
+     *               0         1      2      3      4
+     * 
+     *         0   0,0,y  |  1,0,y  2,0,y  3,0,y  4,0,y
+     *            --------+-----------------------------
+     *         1   0,0,n  |  0,1,y  1,0,n  1,1,y  2,0,n
+     *    q    2   0,1,n  |  0,1,n  1,1,n  1,1,n  2,1,n
+     *    u    3   0,1,y  |  0,2,n  1,1,y  1,2,n  2,1,y
+     *    o    4   0,1,n  |  0,2,y  1,1,n  1,2,y  2,1,n
+     *    t    5   0,2,n  |  0,2,n  1,2,n  1,2,n  2,2,n
+     *    e    6   0,2,y  |  0,3,n  1,2,y  1,3,n  2,2,y
+     *    s    7   0,2,n  |  0,3,y  1,2,n  1,3,y  2,2,n
+     *         8   0,3,n  |  0,3,n  1,3,n  1,3,n  2,3,n
+     *         9   0,3,y  |  0,4,n  1,3,y  1,4,n  2,3,y
+     *        10   0,3,n  |  0,4,y  1,3,n  1,4,y  2,3,n
+     *        11   0,4,n  |  0,4,n  1,4,n  1,4,n  2,4,n
+     * 
+     * 
+     *      [Test fragment was of the form "a\\\"""b c" d.]
+     * 
+     * There is very weird mod-3 behaviour going on here in the
+     * number of quotes, and it even applies when there aren't any
+     * backslashes! How ghastly.
+     * 
+     * With a bit of thought, this extremely odd diagram suddenly
+     * coalesced itself into a coherent, if still ghastly, model of
+     * how things work:
+     * 
+     *  - As before, backslashes are only special when one or more
+     *    of them appear contiguously before at least one double
+     *    quote. In this situation the backslashes do exactly what
+     *    you'd expect: each one quotes the next thing in front of
+     *    it, so you end up with n/2 literal backslashes (if n is
+     *    even) or (n-1)/2 literal backslashes and a literal quote
+     *    (if n is odd). In the latter case the double quote
+     *    character right after the backslashes is used up.
+     * 
+     *  - After that, any remaining double quotes are processed. A
+     *    string of contiguous unescaped double quotes has a mod-3
+     *    behaviour:
+     * 
+     *     * inside a quoted segment, a quote ends the segment.
+     *     * _immediately_ after ending a quoted segment, a quote
+     *       simply produces a literal quote.
+     *     * otherwise, outside a quoted segment, a quote begins a
+     *       quoted segment.
+     * 
+     *    So, for example, if we started inside a quoted segment
+     *    then two contiguous quotes would close the segment and
+     *    produce a literal quote; three would close the segment,
+     *    produce a literal quote, and open a new segment. If we
+     *    started outside a quoted segment, then two contiguous
+     *    quotes would open and then close a segment, producing no
+     *    output (but potentially creating a zero-length argument);
+     *    but three quotes would open and close a segment and then
+     *    produce a literal quote.
+     */
+
+    /*
+     * First deal with the simplest of all special cases: if there
+     * aren't any arguments, return 0,NULL,NULL.
+     */
+    while (*cmdline && isspace(*cmdline)) cmdline++;
+    if (!*cmdline) {
+       if (argc) *argc = 0;
+       if (argv) *argv = NULL;
+       if (argstart) *argstart = NULL;
+       return;
+    }
+
+    /*
+     * This will guaranteeably be big enough; we can realloc it
+     * down later.
+     */
+    outputline = snewn(1+strlen(cmdline), char);
+    outputargv = snewn(strlen(cmdline)+1 / 2, char *);
+    outputargstart = snewn(strlen(cmdline)+1 / 2, char *);
+
+    p = cmdline; q = outputline; outputargc = 0;
+
+    while (*p) {
+       int quote;
+
+       /* Skip whitespace searching for start of argument. */
+       while (*p && isspace(*p)) p++;
+       if (!*p) break;
+
+       /* We have an argument; start it. */
+       outputargv[outputargc] = q;
+       outputargstart[outputargc] = p;
+       outputargc++;
+       quote = 0;
+
+       /* Copy data into the argument until it's finished. */
+       while (*p) {
+           if (!quote && isspace(*p))
+               break;                 /* argument is finished */
+
+           if (*p == '"' || *p == '\\') {
+               /*
+                * We have a sequence of zero or more backslashes
+                * followed by a sequence of zero or more quotes.
+                * Count up how many of each, and then deal with
+                * them as appropriate.
+                */
+               int i, slashes = 0, quotes = 0;
+               while (*p == '\\') slashes++, p++;
+               while (*p == '"') quotes++, p++;
+
+               if (!quotes) {
+                   /*
+                    * Special case: if there are no quotes,
+                    * slashes are not special at all, so just copy
+                    * n slashes to the output string.
+                    */
+                   while (slashes--) *q++ = '\\';
+               } else {
+                   /* Slashes annihilate in pairs. */
+                   while (slashes >= 2) slashes -= 2, *q++ = '\\';
+
+                   /* One remaining slash takes out the first quote. */
+                   if (slashes) quotes--, *q++ = '"';
+
+                   if (quotes > 0) {
+                       /* Outside a quote segment, a quote starts one. */
+                       if (!quote) quotes--, quote = 1;
+
+                       /* Now we produce (n+1)/3 literal quotes... */
+                       for (i = 3; i <= quotes+1; i += 3) *q++ = '"';
+
+                       /* ... and end in a quote segment iff 3 divides n. */
+                       quote = (quotes % 3 == 0);
+                   }
+               }
+           } else {
+               *q++ = *p++;
+           }
+       }
+
+       /* At the end of an argument, just append a trailing NUL. */
+       *q++ = '\0';
+    }
+
+    outputargv = sresize(outputargv, outputargc, char *);
+    outputargstart = sresize(outputargstart, outputargc, char *);
+
+    if (argc) *argc = outputargc;
+    if (argv) *argv = outputargv; else sfree(outputargv);
+    if (argstart) *argstart = outputargstart; else sfree(outputargstart);
+}
+
+int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
+{
+    MSG msg;
+    char *error;
+    const game *gg;
+    frontend *fe;
+    midend *me;
+    int argc;
+    char **argv;
+
+    split_into_argv(cmdline, &argc, &argv, NULL);
+
+#ifdef _WIN32_WCE
+    MultiByteToWideChar (CP_ACP, 0, CLASSNAME, -1, wClassName, 256);
+    if (FindPreviousInstance ())
+        return 0;
+#endif
+
+    InitCommonControls();
+
+    if (!prev) {
+       WNDCLASS wndclass;
+
+       wndclass.style = 0;
+       wndclass.lpfnWndProc = WndProc;
+       wndclass.cbClsExtra = 0;
+       wndclass.cbWndExtra = 0;
+       wndclass.hInstance = inst;
+       wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(200));
+#ifndef _WIN32_WCE
+       if (!wndclass.hIcon)           /* in case resource file is absent */
+           wndclass.hIcon = LoadIcon(inst, IDI_APPLICATION);
+#endif
+       wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
+       wndclass.hbrBackground = NULL;
+       wndclass.lpszMenuName = NULL;
+#ifdef _WIN32_WCE
+       wndclass.lpszClassName = wClassName;
+#else
+       wndclass.lpszClassName = CLASSNAME;
+#endif
+
+       RegisterClass(&wndclass);
+    }
+
+    while (*cmdline && isspace((unsigned char)*cmdline))
+       cmdline++;
+
+    init_help();
+
+#ifdef COMBINED
+    gg = gamelist[0];
+    if (argc > 0) {
+        int i;
+        for (i = 0; i < gamecount; i++) {
+           const char *p = gamelist[i]->name;
+           char *q = argv[0];
+           while (*p && *q) {
+               if (isspace((unsigned char)*p)) {
+                   while (*q && isspace((unsigned char)*q))
+                       q++;
+               } else {
+                   if (tolower((unsigned char)*p) !=
+                       tolower((unsigned char)*q))
+                       break;
+                   q++;
+               }
+               p++;
+           }
+           if (!*p) {
+                gg = gamelist[i];
+                --argc;
+                ++argv;
+                break;
+            }
+        }
+    }
+#else
+    gg = &thegame;
+#endif
+
+    fe = frontend_new(inst);
+    me = midend_for_new_game(fe, gg, argc > 0 ? argv[0] : NULL,
+                             TRUE, TRUE, &error);
+    if (!me) {
+       char buf[128];
+#ifdef COMBINED
+       sprintf(buf, "Puzzles Error");
+#else
+       sprintf(buf, "%.100s Error", gg->name);
+#endif
+       MessageBox(NULL, error, buf, MB_OK|MB_ICONERROR);
+        sfree(error);
+       return 1;
+    }
+    fe_set_midend(fe, me);
+    show_window(fe);
+
+    while (GetMessage(&msg, NULL, 0, 0)) {
+       DispatchMessage(&msg);
+    }
+
+    DestroyWindow(fe->hwnd);
+    cleanup_help();
+
+    return msg.wParam;
+}
+/* vim: set shiftwidth=4 tabstop=8: */