summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/plugins/CATEGORIES40
-rw-r--r--apps/plugins/SOURCES4
-rw-r--r--apps/plugins/SUBDIRS1
-rw-r--r--apps/plugins/puzzles/Buildscr194
-rw-r--r--apps/plugins/puzzles/CHECKLST.txt70
-rw-r--r--apps/plugins/puzzles/LICENCE25
-rw-r--r--apps/plugins/puzzles/PuzzleApplet.java617
-rw-r--r--apps/plugins/puzzles/README54
-rw-r--r--apps/plugins/puzzles/Recipe157
-rw-r--r--apps/plugins/puzzles/SOURCES26
-rwxr-xr-xapps/plugins/puzzles/benchmark.pl197
-rwxr-xr-xapps/plugins/puzzles/benchmark.sh27
-rw-r--r--apps/plugins/puzzles/blackbox.R19
-rw-r--r--apps/plugins/puzzles/blackbox.c1543
-rw-r--r--apps/plugins/puzzles/bridges.R21
-rw-r--r--apps/plugins/puzzles/bridges.c3262
-rw-r--r--apps/plugins/puzzles/chm.but21
-rw-r--r--apps/plugins/puzzles/chm.css7
-rw-r--r--apps/plugins/puzzles/combi.c110
-rw-r--r--apps/plugins/puzzles/configure.ac85
-rw-r--r--apps/plugins/puzzles/cube.R19
-rw-r--r--apps/plugins/puzzles/cube.c1774
-rwxr-xr-xapps/plugins/puzzles/desktop.pl52
-rw-r--r--apps/plugins/puzzles/devel.but4777
-rw-r--r--apps/plugins/puzzles/divvy.c781
-rw-r--r--apps/plugins/puzzles/dominosa.R21
-rw-r--r--apps/plugins/puzzles/dominosa.c1748
-rw-r--r--apps/plugins/puzzles/drawing.c351
-rw-r--r--apps/plugins/puzzles/dsf.c192
-rw-r--r--apps/plugins/puzzles/emcc.c867
-rw-r--r--apps/plugins/puzzles/emcclib.js757
-rw-r--r--apps/plugins/puzzles/emccpre.js364
-rw-r--r--apps/plugins/puzzles/emccx.json29
-rw-r--r--apps/plugins/puzzles/fifteen.R22
-rw-r--r--apps/plugins/puzzles/fifteen.c1215
-rw-r--r--apps/plugins/puzzles/filling.R24
-rw-r--r--apps/plugins/puzzles/filling.c2179
-rw-r--r--apps/plugins/puzzles/findloop.c500
-rw-r--r--apps/plugins/puzzles/flip.R21
-rw-r--r--apps/plugins/puzzles/flip.c1349
-rw-r--r--apps/plugins/puzzles/flood.R19
-rw-r--r--apps/plugins/puzzles/flood.c1372
-rw-r--r--apps/plugins/puzzles/galaxies.R28
-rw-r--r--apps/plugins/puzzles/galaxies.c3995
-rw-r--r--apps/plugins/puzzles/grid.c2896
-rw-r--r--apps/plugins/puzzles/grid.h132
-rw-r--r--apps/plugins/puzzles/gtk.c3215
-rw-r--r--apps/plugins/puzzles/guess.R19
-rw-r--r--apps/plugins/puzzles/guess.c1518
-rw-r--r--apps/plugins/puzzles/html/blackbox.html16
-rw-r--r--apps/plugins/puzzles/html/bridges.html13
-rw-r--r--apps/plugins/puzzles/html/cube.html14
-rw-r--r--apps/plugins/puzzles/html/dominosa.html10
-rw-r--r--apps/plugins/puzzles/html/fifteen.html6
-rw-r--r--apps/plugins/puzzles/html/filling.html12
-rw-r--r--apps/plugins/puzzles/html/flip.html10
-rw-r--r--apps/plugins/puzzles/html/flood.html8
-rw-r--r--apps/plugins/puzzles/html/galaxies.html11
-rw-r--r--apps/plugins/puzzles/html/group.html52
-rw-r--r--apps/plugins/puzzles/html/guess.html12
-rw-r--r--apps/plugins/puzzles/html/inertia.html14
-rwxr-xr-xapps/plugins/puzzles/html/javapage.pl104
-rwxr-xr-xapps/plugins/puzzles/html/jspage.pl120
-rw-r--r--apps/plugins/puzzles/html/keen.html15
-rw-r--r--apps/plugins/puzzles/html/lightup.html10
-rw-r--r--apps/plugins/puzzles/html/loopy.html13
-rw-r--r--apps/plugins/puzzles/html/magnets.html17
-rw-r--r--apps/plugins/puzzles/html/map.html15
-rw-r--r--apps/plugins/puzzles/html/mines.html18
-rw-r--r--apps/plugins/puzzles/html/net.html17
-rw-r--r--apps/plugins/puzzles/html/netslide.html14
-rw-r--r--apps/plugins/puzzles/html/palisade.html11
-rw-r--r--apps/plugins/puzzles/html/pattern.html12
-rw-r--r--apps/plugins/puzzles/html/pearl.html13
-rw-r--r--apps/plugins/puzzles/html/pegs.html8
-rw-r--r--apps/plugins/puzzles/html/range.html21
-rw-r--r--apps/plugins/puzzles/html/rect.html10
-rw-r--r--apps/plugins/puzzles/html/samegame.html14
-rw-r--r--apps/plugins/puzzles/html/signpost.html14
-rw-r--r--apps/plugins/puzzles/html/singles.html11
-rw-r--r--apps/plugins/puzzles/html/sixteen.html8
-rw-r--r--apps/plugins/puzzles/html/slant.html9
-rw-r--r--apps/plugins/puzzles/html/solo.html20
-rw-r--r--apps/plugins/puzzles/html/tents.html20
-rw-r--r--apps/plugins/puzzles/html/towers.html22
-rw-r--r--apps/plugins/puzzles/html/tracks.html19
-rw-r--r--apps/plugins/puzzles/html/twiddle.html15
-rw-r--r--apps/plugins/puzzles/html/undead.html22
-rw-r--r--apps/plugins/puzzles/html/unequal.html14
-rw-r--r--apps/plugins/puzzles/html/unruly.html11
-rw-r--r--apps/plugins/puzzles/html/untangle.html5
-rw-r--r--apps/plugins/puzzles/icons/Makefile153
-rw-r--r--apps/plugins/puzzles/icons/blackbox.sav27
-rw-r--r--apps/plugins/puzzles/icons/bridges.sav72
-rwxr-xr-xapps/plugins/puzzles/icons/cicon.pl27
-rwxr-xr-xapps/plugins/puzzles/icons/crop.sh37
-rw-r--r--apps/plugins/puzzles/icons/cube.sav22
-rw-r--r--apps/plugins/puzzles/icons/dominosa.sav53
-rw-r--r--apps/plugins/puzzles/icons/fifteen.sav74
-rw-r--r--apps/plugins/puzzles/icons/filling.sav38
-rw-r--r--apps/plugins/puzzles/icons/flip.sav20
-rw-r--r--apps/plugins/puzzles/icons/flood.sav14
-rw-r--r--apps/plugins/puzzles/icons/galaxies.sav51
-rw-r--r--apps/plugins/puzzles/icons/guess.sav15
-rwxr-xr-xapps/plugins/puzzles/icons/icon.pl270
-rw-r--r--apps/plugins/puzzles/icons/inertia.sav30
-rw-r--r--apps/plugins/puzzles/icons/keen.sav62
-rw-r--r--apps/plugins/puzzles/icons/lightup.sav24
-rw-r--r--apps/plugins/puzzles/icons/loopy.sav120
-rw-r--r--apps/plugins/puzzles/icons/magnets.sav33
-rw-r--r--apps/plugins/puzzles/icons/map.sav27
-rw-r--r--apps/plugins/puzzles/icons/mines.sav67
-rw-r--r--apps/plugins/puzzles/icons/net.sav53
-rw-r--r--apps/plugins/puzzles/icons/netslide.sav47
-rw-r--r--apps/plugins/puzzles/icons/palisade.sav50
-rw-r--r--apps/plugins/puzzles/icons/pattern.sav29
-rw-r--r--apps/plugins/puzzles/icons/pearl.sav23
-rw-r--r--apps/plugins/puzzles/icons/pegs.sav16
-rw-r--r--apps/plugins/puzzles/icons/range.sav36
-rw-r--r--apps/plugins/puzzles/icons/rect.sav17
-rw-r--r--apps/plugins/puzzles/icons/samegame.sav34
-rwxr-xr-xapps/plugins/puzzles/icons/screenshot.sh25
-rw-r--r--apps/plugins/puzzles/icons/signpost.sav23
-rw-r--r--apps/plugins/puzzles/icons/singles.sav45
-rw-r--r--apps/plugins/puzzles/icons/sixteen.sav39
-rw-r--r--apps/plugins/puzzles/icons/slant.sav51
-rw-r--r--apps/plugins/puzzles/icons/solo.sav36
-rwxr-xr-xapps/plugins/puzzles/icons/square.pl95
-rw-r--r--apps/plugins/puzzles/icons/tents.sav32
-rw-r--r--apps/plugins/puzzles/icons/towers.sav26
-rw-r--r--apps/plugins/puzzles/icons/tracks.sav31
-rw-r--r--apps/plugins/puzzles/icons/twiddle.sav35
-rw-r--r--apps/plugins/puzzles/icons/undead.sav14
-rw-r--r--apps/plugins/puzzles/icons/unequal.sav25
-rw-r--r--apps/plugins/puzzles/icons/unruly.sav22
-rw-r--r--apps/plugins/puzzles/icons/untangle.sav16
-rw-r--r--apps/plugins/puzzles/icons/win16pal.xpm23
-rw-r--r--apps/plugins/puzzles/inertia.R19
-rw-r--r--apps/plugins/puzzles/inertia.c2249
-rw-r--r--apps/plugins/puzzles/keen.R25
-rw-r--r--apps/plugins/puzzles/keen.c2479
-rw-r--r--apps/plugins/puzzles/keymaps.h206
-rw-r--r--apps/plugins/puzzles/latin.c1436
-rw-r--r--apps/plugins/puzzles/latin.h122
-rw-r--r--apps/plugins/puzzles/laydomino.c291
-rw-r--r--apps/plugins/puzzles/lightup.R24
-rw-r--r--apps/plugins/puzzles/lightup.c2405
-rw-r--r--apps/plugins/puzzles/list.c55
-rw-r--r--apps/plugins/puzzles/loopgen.c536
-rw-r--r--apps/plugins/puzzles/loopgen.h35
-rw-r--r--apps/plugins/puzzles/loopy.R31
-rw-r--r--apps/plugins/puzzles/loopy.c3688
-rw-r--r--apps/plugins/puzzles/magnets.R24
-rw-r--r--apps/plugins/puzzles/magnets.c2641
-rwxr-xr-xapps/plugins/puzzles/makedist.sh47
-rw-r--r--apps/plugins/puzzles/malloc.c61
-rw-r--r--apps/plugins/puzzles/map.R24
-rw-r--r--apps/plugins/puzzles/map.c3340
-rw-r--r--apps/plugins/puzzles/maxflow.c461
-rw-r--r--apps/plugins/puzzles/maxflow.h95
-rw-r--r--apps/plugins/puzzles/midend.c2136
-rw-r--r--apps/plugins/puzzles/mines.R24
-rw-r--r--apps/plugins/puzzles/mines.c3250
-rw-r--r--apps/plugins/puzzles/misc.c363
-rwxr-xr-xapps/plugins/puzzles/mkauto.sh2
-rwxr-xr-xapps/plugins/puzzles/mkfiles.pl1807
-rw-r--r--apps/plugins/puzzles/nestedvm.c432
-rw-r--r--apps/plugins/puzzles/net.R23
-rw-r--r--apps/plugins/puzzles/net.c3210
-rw-r--r--apps/plugins/puzzles/netslide.R21
-rw-r--r--apps/plugins/puzzles/netslide.c1893
-rw-r--r--apps/plugins/puzzles/no-icon.c8
-rw-r--r--apps/plugins/puzzles/noicon.rc11
-rw-r--r--apps/plugins/puzzles/nullfe.c69
-rw-r--r--apps/plugins/puzzles/nullgame.R12
-rw-r--r--apps/plugins/puzzles/nullgame.c303
-rw-r--r--apps/plugins/puzzles/obfusc.c126
-rw-r--r--apps/plugins/puzzles/osx-help.but14
-rw-r--r--apps/plugins/puzzles/osx-info.plist34
-rw-r--r--apps/plugins/puzzles/osx.icnsbin0 -> 48589 bytes
-rw-r--r--apps/plugins/puzzles/osx.m1724
-rw-r--r--apps/plugins/puzzles/padtoolbar.bmpbin0 -> 1198 bytes
-rw-r--r--apps/plugins/puzzles/palisade.R21
-rw-r--r--apps/plugins/puzzles/palisade.c1383
-rw-r--r--apps/plugins/puzzles/pattern.R25
-rw-r--r--apps/plugins/puzzles/pattern.c2255
-rw-r--r--apps/plugins/puzzles/pearl.R23
-rw-r--r--apps/plugins/puzzles/pearl.c2772
-rw-r--r--apps/plugins/puzzles/pegs.R21
-rw-r--r--apps/plugins/puzzles/pegs.c1340
-rw-r--r--apps/plugins/puzzles/penrose.c629
-rw-r--r--apps/plugins/puzzles/penrose.h59
-rw-r--r--apps/plugins/puzzles/printing.c247
-rw-r--r--apps/plugins/puzzles/ps.c433
-rw-r--r--apps/plugins/puzzles/puzzles.but3401
-rw-r--r--apps/plugins/puzzles/puzzles.h636
-rw-r--r--apps/plugins/puzzles/puzzles.make113
-rw-r--r--apps/plugins/puzzles/puzzles.rc265
-rw-r--r--apps/plugins/puzzles/random.c350
-rw-r--r--apps/plugins/puzzles/range.R21
-rw-r--r--apps/plugins/puzzles/range.c1833
-rw-r--r--apps/plugins/puzzles/rbassert.h13
-rw-r--r--apps/plugins/puzzles/rbcompat.h62
-rw-r--r--apps/plugins/puzzles/rbwrappers.c2378
-rw-r--r--apps/plugins/puzzles/rect.R19
-rw-r--r--apps/plugins/puzzles/rect.c3000
-rw-r--r--apps/plugins/puzzles/resource.h20
-rw-r--r--apps/plugins/puzzles/rockbox.c1682
-rw-r--r--apps/plugins/puzzles/samegame.R19
-rw-r--r--apps/plugins/puzzles/samegame.c1679
-rw-r--r--apps/plugins/puzzles/signpost.R23
-rw-r--r--apps/plugins/puzzles/signpost.c2480
-rw-r--r--apps/plugins/puzzles/singles.R23
-rw-r--r--apps/plugins/puzzles/singles.c2004
-rw-r--r--apps/plugins/puzzles/sixteen.R19
-rw-r--r--apps/plugins/puzzles/sixteen.c1214
-rw-r--r--apps/plugins/puzzles/slant.R24
-rw-r--r--apps/plugins/puzzles/slant.c2278
-rw-r--r--apps/plugins/puzzles/solo.R24
-rw-r--r--apps/plugins/puzzles/solo.c5656
-rw-r--r--apps/plugins/puzzles/tdq.c88
-rw-r--r--apps/plugins/puzzles/tents.R24
-rw-r--r--apps/plugins/puzzles/tents.c2740
-rw-r--r--apps/plugins/puzzles/towers.R25
-rw-r--r--apps/plugins/puzzles/towers.c2104
-rw-r--r--apps/plugins/puzzles/tracks.R21
-rw-r--r--apps/plugins/puzzles/tracks.c2660
-rw-r--r--apps/plugins/puzzles/tree234.c2200
-rw-r--r--apps/plugins/puzzles/tree234.h202
-rw-r--r--apps/plugins/puzzles/twiddle.R19
-rw-r--r--apps/plugins/puzzles/twiddle.c1319
-rw-r--r--apps/plugins/puzzles/undead.R18
-rw-r--r--apps/plugins/puzzles/undead.c2738
-rw-r--r--apps/plugins/puzzles/unequal.R27
-rw-r--r--apps/plugins/puzzles/unequal.c2267
-rw-r--r--apps/plugins/puzzles/unfinished/README9
-rw-r--r--apps/plugins/puzzles/unfinished/group.R25
-rw-r--r--apps/plugins/puzzles/unfinished/group.c2198
-rw-r--r--apps/plugins/puzzles/unfinished/group.gap97
-rw-r--r--apps/plugins/puzzles/unfinished/numgame.c1290
-rw-r--r--apps/plugins/puzzles/unfinished/path.c786
-rw-r--r--apps/plugins/puzzles/unfinished/separate.R21
-rw-r--r--apps/plugins/puzzles/unfinished/separate.c859
-rw-r--r--apps/plugins/puzzles/unfinished/slide.R24
-rw-r--r--apps/plugins/puzzles/unfinished/slide.c2445
-rw-r--r--apps/plugins/puzzles/unfinished/sokoban.R19
-rw-r--r--apps/plugins/puzzles/unfinished/sokoban.c1479
-rw-r--r--apps/plugins/puzzles/unruly.R21
-rw-r--r--apps/plugins/puzzles/unruly.c2071
-rw-r--r--apps/plugins/puzzles/untangle.R21
-rw-r--r--apps/plugins/puzzles/untangle.c1491
-rw-r--r--apps/plugins/puzzles/version.c7
-rw-r--r--apps/plugins/puzzles/version.h11
-rw-r--r--apps/plugins/puzzles/wceinf.pl65
-rwxr-xr-xapps/plugins/puzzles/webpage.pl69
-rw-r--r--apps/plugins/puzzles/website.url2
-rw-r--r--apps/plugins/puzzles/windows.c3760
-rwxr-xr-xapps/plugins/puzzles/winiss.pl79
-rw-r--r--apps/plugins/puzzles/winwix.mc334
-rw-r--r--apps/plugins/sgt-puzzles.c33
-rw-r--r--docs/CREDITS1
-rw-r--r--manual/plugins/images/ss-puzzles-cube-128x128x16.pngbin0 -> 920 bytes
-rw-r--r--manual/plugins/images/ss-puzzles-cube-128x160x16.pngbin0 -> 1007 bytes
-rw-r--r--manual/plugins/images/ss-puzzles-cube-128x96x16.pngbin0 -> 549 bytes
-rw-r--r--manual/plugins/images/ss-puzzles-cube-132x80x16.pngbin0 -> 486 bytes
-rw-r--r--manual/plugins/images/ss-puzzles-cube-160x128x16.pngbin0 -> 843 bytes
-rw-r--r--manual/plugins/images/ss-puzzles-cube-176x132x16.pngbin0 -> 925 bytes
-rw-r--r--manual/plugins/images/ss-puzzles-cube-176x220x16.pngbin0 -> 1672 bytes
-rw-r--r--manual/plugins/images/ss-puzzles-cube-220x176x16.pngbin0 -> 1045 bytes
-rw-r--r--manual/plugins/images/ss-puzzles-cube-240x320x16.pngbin0 -> 2582 bytes
-rw-r--r--manual/plugins/images/ss-puzzles-cube-240x400x16.pngbin0 -> 2836 bytes
-rw-r--r--manual/plugins/images/ss-puzzles-cube-320x240x16.pngbin0 -> 2162 bytes
-rw-r--r--manual/plugins/images/ss-puzzles-cube-320x240x24.pngbin0 -> 2162 bytes
-rw-r--r--manual/plugins/images/ss-puzzles-cube-96x96x16.pngbin0 -> 565 bytes
-rw-r--r--manual/plugins/images/ss-puzzles-map-128x128x16.pngbin0 -> 1254 bytes
-rw-r--r--manual/plugins/images/ss-puzzles-map-128x160x16.pngbin0 -> 1342 bytes
-rw-r--r--manual/plugins/images/ss-puzzles-map-128x96x16.pngbin0 -> 1145 bytes
-rw-r--r--manual/plugins/images/ss-puzzles-map-132x80x16.pngbin0 -> 985 bytes
-rw-r--r--manual/plugins/images/ss-puzzles-map-160x128x16.pngbin0 -> 1438 bytes
-rw-r--r--manual/plugins/images/ss-puzzles-map-176x132x16.pngbin0 -> 1580 bytes
-rw-r--r--manual/plugins/images/ss-puzzles-map-176x220x16.pngbin0 -> 1833 bytes
-rw-r--r--manual/plugins/images/ss-puzzles-map-220x176x16.pngbin0 -> 2086 bytes
-rw-r--r--manual/plugins/images/ss-puzzles-map-240x320x16.pngbin0 -> 2659 bytes
-rw-r--r--manual/plugins/images/ss-puzzles-map-240x400x16.pngbin0 -> 2917 bytes
-rw-r--r--manual/plugins/images/ss-puzzles-map-320x240x16.pngbin0 -> 3103 bytes
-rw-r--r--manual/plugins/images/ss-puzzles-map-320x240x24.pngbin0 -> 3100 bytes
-rw-r--r--manual/plugins/images/ss-puzzles-map-96x96x16.pngbin0 -> 956 bytes
-rw-r--r--manual/plugins/main.tex2
-rw-r--r--manual/plugins/puzzles.tex7
289 files changed, 147273 insertions, 0 deletions
diff --git a/apps/plugins/CATEGORIES b/apps/plugins/CATEGORIES
index 28248802b7..4b1b8d635c 100644
--- a/apps/plugins/CATEGORIES
+++ b/apps/plugins/CATEGORIES
@@ -78,6 +78,7 @@ gif,viewers
pong,games
ppm,viewers
properties,viewers
+puzzles,games
random_folder_advance_config,apps
remote_control,apps
resistor,apps
@@ -92,6 +93,45 @@ rockpaint,apps
search,viewers
searchengine,viewers
settings_dumper,apps
+sgt-blackbox,games
+sgt-bridges,games
+sgt-cube,games
+sgt-dominosa,games
+sgt-fifteen,games
+sgt-filling,games
+sgt-flip,games
+sgt-flood,games
+sgt-galaxies,games
+sgt-guess,games
+sgt-inertia,games
+sgt-keen,games
+sgt-lightup,games
+sgt-loopy,games
+sgt-magnets,games
+sgt-map,games
+sgt-mines,games
+sgt-net,games
+sgt-netslide,games
+sgt-palisade,games
+sgt-pattern,games
+sgt-pearl,games
+sgt-pegs,games
+sgt-range,games
+sgt-rect,games
+sgt-samegame,games
+sgt-signpost,games
+sgt-singles,games
+sgt-sixteen,games
+sgt-slant,games
+sgt-solo,games
+sgt-tents,games
+sgt-towers,games
+sgt-tracks,games
+sgt-twiddle,games
+sgt-undead,games
+sgt-unequal,games
+sgt-unruly,games
+sgt-untangle,games
shopper,viewers
shortcuts_append,viewers
shortcuts_view,viewers
diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES
index c7a8cb69f7..98e727dbd4 100644
--- a/apps/plugins/SOURCES
+++ b/apps/plugins/SOURCES
@@ -73,6 +73,10 @@ iriverify.c
#if (CONFIG_PLATFORM & PLATFORM_NATIVE) /* those plugins only run on hardware */
/* Overlays loaders */
+
+/* use this if you want a monolithic 'puzzles' */
+/*sgt-puzzles.c*/
+
#if PLUGIN_BUFFER_SIZE <= 0x20000 && defined(HAVE_LCD_BITMAP)
#if CONFIG_KEYPAD != ONDIO_PAD && CONFIG_KEYPAD != SANSA_M200_PAD \
diff --git a/apps/plugins/SUBDIRS b/apps/plugins/SUBDIRS
index d02073e29d..56ac665e40 100644
--- a/apps/plugins/SUBDIRS
+++ b/apps/plugins/SUBDIRS
@@ -16,6 +16,7 @@ clock
#if defined(HAVE_LCD_COLOR) && \
(!defined(LCD_STRIDEFORMAT) || (LCD_STRIDEFORMAT != VERTICAL_STRIDE))
xworld
+puzzles
#endif
#if (CONFIG_KEYPAD != ONDIO_PAD) /* not enough buttons */ \
diff --git a/apps/plugins/puzzles/Buildscr b/apps/plugins/puzzles/Buildscr
new file mode 100644
index 0000000000..910981f079
--- /dev/null
+++ b/apps/plugins/puzzles/Buildscr
@@ -0,0 +1,194 @@
+# -*- sh -*-
+# Build script to build Puzzles.
+
+module puzzles
+
+set Version $(!builddate).$(vcsid)
+
+# Start by substituting the right version number in configure.ac.
+in puzzles do perl -i~ -pe 's/6.66/$(Version)/' configure.ac
+in puzzles do rm configure.ac~
+
+# And put it into the documentation as a versionid.
+# use perl to avoid inconsistent behaviour of echo '\v'
+in puzzles do perl -e 'print "\n\\versionid Simon Tatham'\''s Portable Puzzle Collection, version $$ARGV[0]\n"' $(Version) >> puzzles.but
+in puzzles do perl -e 'print "\n\\versionid Simon Tatham'\''s Portable Puzzle Collection, version $$ARGV[0]\n"' $(Version) >> devel.but
+
+# Write out a version.h that contains the real version number.
+in puzzles do echo '/* Generated by automated build script */' > version.h
+in puzzles do echo '$#define VER "Version $(Version)"' >> version.h
+
+# And do the same substitution in the OS X metadata. (This is a bit
+# icky in principle because it presumes that my version numbers don't
+# need XML escaping, but frankly, if they ever do then I should fix
+# them!)
+in puzzles do perl -i -pe 's/Unidentified build/$(Version)/' osx-info.plist
+
+# First build some local binaries, to run the icon build.
+in puzzles do perl mkfiles.pl -U
+in puzzles do make
+
+# Now build the screenshots and icons.
+in puzzles/icons do xvfb-run -s "-screen 0 1024x768x24" make web winicons gtkicons
+
+# Destroy the local binaries and autoconf detritus, mostly to avoid
+# wasting network bandwidth by transferring them to the delegate
+# servers.
+in puzzles do make distclean
+
+# Re-run mkfiles.pl now that it knows the icons are there.
+in puzzles do perl mkfiles.pl
+
+# Rebuild the configure script.
+in puzzles do ./mkauto.sh
+
+# Build the OS X .dmg archive.
+delegate osx
+ in puzzles do make -f Makefile.osx clean
+ in puzzles do make -f Makefile.osx release VER=-DVER=$(Version)
+ return puzzles/Puzzles.dmg
+enddelegate
+
+# Build the Windows binaries and installer, and the CHM file.
+in puzzles do make -f Makefile.doc clean
+in puzzles do make -f Makefile.doc chm
+in puzzles do make -f Makefile.doc # build help file for installer
+in puzzles do mason.pl --args '{"version":"$(Version)","descfile":"gamedesc.txt"}' winwix.mc > puzzles.wxs
+in puzzles do perl winiss.pl $(Version) gamedesc.txt > puzzles.iss
+delegate windows
+ # Ignore the poorly controlled return value from HHC, and instead
+ # just test that the output file was generated.
+ in puzzles with htmlhelp do/win hhc puzzles.hhp & type puzzles.chm >nul
+ # FIXME: Cygwin alternative?
+ in puzzles with visualstudio do/win nmake -f Makefile.vc clean
+ in puzzles with visualstudio do/win nmake -f Makefile.vc VER=-DVER=$(Version)
+ # Code-sign the binaries, if the local bob config provides a script
+ # to do so. We assume here that the script accepts an -i option to
+ # provide a 'more info' URL, and an optional -n option to provide a
+ # program name, and that it can take multiple .exe filename
+ # arguments and sign them all in place.
+ ifneq "$(winsigncode)" "" in puzzles do $(winsigncode) -i http://www.chiark.greenend.org.uk/~sgtatham/puzzles/ *.exe
+ # Build installers.
+ in puzzles with wix do/win candle puzzles.wxs && light -ext WixUIExtension -sval puzzles.wixobj
+ in puzzles with innosetup do/win iscc puzzles.iss
+ ifneq "$(winsigncode)" "" in puzzles do $(winsigncode) -i http://www.chiark.greenend.org.uk/~sgtatham/puzzles/ -n "Simon Tatham's Portable Puzzle Collection Installer" puzzles.msi Output/installer.exe
+ return puzzles/puzzles.chm
+ return puzzles/*.exe
+ return puzzles/Output/installer.exe
+ return puzzles/puzzles.msi
+enddelegate
+in puzzles do chmod +x *.exe
+
+# Build the Pocket PC binaries and CAB.
+#
+# NOTE: This part of the build script requires the Windows delegate
+# server to have the cabwiz program on its PATH. This will
+# typically be at
+#
+# C:\Program Files\Windows CE Tools\WCE420\POCKET PC 2003\Tools
+#
+# but it might not be if you've installed it somewhere else, or
+# have a different version.
+#
+# NOTE ALSO: This part of the build is commented out, for the
+# moment, because cabwiz does unhelpful things when run from within
+# a bob delegate process (or, more generally, when run from any
+# terminal-based remote login to a Windows machine, including
+# Cygwin opensshd and Windows Telnet). The symptom is that cabwiz
+# just beeps and sits there. Until I figure out how to build the
+# .cab from an automated process (and I'm willing to consider silly
+# approaches such as a third-party CAB generator), I don't think I
+# can sensibly enable this build.
+
+#in puzzles do perl wceinf.pl gamedesc.txt > puzzles.inf
+#delegate windows
+# in puzzles do cmd /c 'wcearmv4 & nmake -f Makefile.wce clean'
+# in puzzles do cmd /c 'wcearmv4 & nmake -f Makefile.wce VER=-DVER=$(Version)'
+# # Nasty piece of sh here which saves the return code from cabwiz,
+# # outputs its errors and/or warnings, and then propagates the
+# # return code back to bob. If only cabwiz could output to
+# # standard error LIKE EVERY OTHER COMMAND-LINE UTILITY IN THE
+# # WORLD, I wouldn't have to do this.
+# in puzzles do cat puzzles.inf
+# in puzzles do cmd /c 'wcearmv4 & bash -c cabwiz puzzles.inf /err cabwiz.err /cpu ARMV4'; a=$$?; cat cabwiz.err; exit $$a
+# return puzzles/puzzles.armv4.cab
+#enddelegate
+
+# Build the help file and the HTML docs.
+in puzzles do make -f Makefile.doc clean # remove CHM-target HTML
+in puzzles do make -f Makefile.doc # and rebuild help file...
+in puzzles do mkdir doc
+in puzzles do mkdir devel
+in puzzles/doc do halibut --html -Chtml-contents-filename:index.html -Chtml-index-filename:indexpage.html -Chtml-template-filename:%k.html -Chtml-template-fragment:%k ../puzzles.but
+in puzzles/devel do halibut --html -Chtml-contents-filename:index.html -Chtml-index-filename:indexpage.html -Chtml-template-filename:%k.html -Chtml-template-fragment:%k ../devel.but
+
+# Move the deliver-worthy Windows binaries (those specified in
+# gamedesc.txt, which is generated by mkfiles.pl and helpfully
+# excludes the command-line auxiliary utilities such as solosolver,
+# and nullgame.exe) into a subdirectory for easy access.
+in puzzles do mkdir winbin
+in puzzles do mv `cut -f2 -d: gamedesc.txt` winbin
+
+# Make a zip file of the Windows binaries and help files.
+in puzzles do zip -j puzzles.zip winbin/*.exe puzzles.chm puzzles.hlp puzzles.cnt
+
+# Create the source archive. (That writes the archive into the
+# _parent_ directory, so be careful when we deliver it.)
+in puzzles do ./makedist.sh $(Version)
+
+# Build the autogenerated pieces of the main web page.
+in puzzles do perl webpage.pl
+
+ifneq "$(JAVA_UNFINISHED)" "" in puzzles do perl -i~ -pe 'print "!srcdir unfinished/\n" if /!srcdir icons/' Recipe
+ifneq "$(JAVA_UNFINISHED)" "" in puzzles do ln -s unfinished/group.R .
+ifneq "$(JAVA_UNFINISHED)" "" in puzzles do perl mkfiles.pl
+
+# Build the Java applets.
+delegate nestedvm
+ in puzzles do make -f Makefile.nestedvm NESTEDVM="$$NESTEDVM" VER=-DVER=$(Version)
+ return puzzles/*.jar
+enddelegate
+
+# Build the Javascript applets. Since my master build machine doesn't
+# have the right dependencies installed for Emscripten, I do this by a
+# delegation.
+in puzzles do mkdir js # so we can tell output .js files from emcc*.js
+delegate emscripten
+ in puzzles do make -f Makefile.emcc OUTPREFIX=js/ clean
+ in puzzles do make -f Makefile.emcc OUTPREFIX=js/
+ return puzzles/js/*.js
+enddelegate
+
+# Set up .htaccess containing a redirect for the archive filename.
+in puzzles do echo "AddType application/octet-stream .chm" > .htaccess
+in puzzles do echo "AddType application/octet-stream .hlp" >> .htaccess
+in puzzles do echo "AddType application/octet-stream .cnt" >> .htaccess
+in . do set -- puzzles*.tar.gz; echo RedirectMatch temp '(.*/)'puzzles.tar.gz '$$1'"$$1" >> puzzles/.htaccess
+in puzzles do echo RedirectMatch temp '(.*/)'puzzles-installer.msi '$$1'puzzles-$(Version)-installer.msi >> .htaccess
+in puzzles do echo RedirectMatch temp '(.*/)'puzzles-installer.exe '$$1'puzzles-$(Version)-installer.exe >> .htaccess
+
+# Phew, we're done. Deliver everything!
+deliver puzzles/icons/*-web.png $@
+deliver puzzles/winbin/*.exe $@
+deliver puzzles/.htaccess $@
+deliver puzzles/doc/*.html doc/$@
+deliver puzzles/devel/*.html devel/$@
+deliver puzzles/Puzzles.dmg $@
+deliver puzzles/puzzles.chm $@
+deliver puzzles/puzzles.hlp $@
+deliver puzzles/puzzles.cnt $@
+deliver puzzles/puzzles.zip $@
+deliver puzzles/puzzles.msi puzzles-$(Version)-installer.msi
+deliver puzzles/Output/installer.exe puzzles-$(Version)-installer.exe
+deliver puzzles/*.jar java/$@
+deliver puzzles/js/*.js js/$@
+deliver puzzles/html/*.html html/$@
+deliver puzzles/html/*.pl html/$@
+deliver puzzles/wwwspans.html $@
+deliver puzzles/wwwlinks.html $@
+
+# deliver puzzles/puzzles.armv4.cab $@ # (not built at the moment)
+
+# This one isn't in the puzzles subdir, because makedist.sh left it
+# one level up.
+deliver puzzles*.tar.gz $@
diff --git a/apps/plugins/puzzles/CHECKLST.txt b/apps/plugins/puzzles/CHECKLST.txt
new file mode 100644
index 0000000000..2bef909e14
--- /dev/null
+++ b/apps/plugins/puzzles/CHECKLST.txt
@@ -0,0 +1,70 @@
+Useful checklists
+=================
+
+Things to remember when adding a new puzzle
+-------------------------------------------
+
+Write the source file for the new puzzle (duhh).
+
+Create a .R file for it which:
+ - defines a <puzzle>_EXTRA symbol for it if it requires auxiliary
+ object files (make sure that symbol doesn't contain the icon)
+ - adds it to the `ALL' definition, to ensure it is compiled into
+ the OS X binary
+ - adds it as a GTK build target, with the optional GTK icon
+ - adds it as a Windows build target, with the optional resource
+ file
+ - adds auxiliary solver binaries if any
+ - adds it to $(GAMES) in both the automake and GTK makefiles, for
+ `make install'
+ - adds it to list.c for the OS X binary
+ - adds it to gamedesc.txt, with its Windows executable name, display
+ name, and slightly longer description.
+
+If the puzzle is by a new author, modify the copyright notice in
+LICENCE and in puzzles.but. (Also in index.html, but that's listed
+below under website changes.)
+
+Double-check that the game structure name in the source file has
+been renamed from `nullgame', so that it'll work on OS X. Actually
+compiling it on OS X would be a good way to check this, if
+convenient.
+
+Add a documentation section in puzzles.but.
+
+Make sure there's a Windows help topic name defined in puzzles.but,
+and that it's referenced by the help topic field in the game
+structure in the source file.
+
+Check that REQUIRE_RBUTTON and/or REQUIRE_NUMPAD are set as
+appropriate.
+
+Add the new Unix binary name, and the names of any auxiliary solver
+binaries, to .gitignore.
+
+Write an instructions fragment for the webified puzzle pages, as
+html/<puzzlename>.html .
+
+Make a screenshot:
+ - create an appropriate save file in `icons'
+ - add the puzzle name to icons/Makefile
+ - set up a REDO property in icons/Makefile if the screenshot wants
+ to display a move halfway through an animation
+ - set up a CROP property in icons/Makefile if the icon wants to be
+ a sub-rectangle of the whole screenshot
+
+Don't forget to `git add' the new source file, the new .R file and the
+save file in `icons', the new .html file, and any other new files that
+might have been involved.
+
+Check in!
+
+Put the puzzle on the web:
+ - run puzzlesnap.sh
+ - adjust the copyright in index-mid.html if the puzzle is by a new
+ author
+ - check that the new puzzle has appeared on the staging web page
+ - test both Windows binary links, the docs link, the Javascript
+ version and the Java version
+ - run webupdate
+ - test all those things once more on the live website
diff --git a/apps/plugins/puzzles/LICENCE b/apps/plugins/puzzles/LICENCE
new file mode 100644
index 0000000000..4235005ea7
--- /dev/null
+++ b/apps/plugins/puzzles/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/apps/plugins/puzzles/PuzzleApplet.java b/apps/plugins/puzzles/PuzzleApplet.java
new file mode 100644
index 0000000000..0b0648ce9b
--- /dev/null
+++ b/apps/plugins/puzzles/PuzzleApplet.java
@@ -0,0 +1,617 @@
+/*
+ * PuzzleApplet.java: NestedVM applet for the puzzle collection
+ */
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.image.BufferedImage;
+import java.util.*;
+import javax.swing.*;
+import javax.swing.border.BevelBorder;
+import javax.swing.Timer;
+import java.util.List;
+
+import org.ibex.nestedvm.Runtime;
+
+public class PuzzleApplet extends JApplet implements Runtime.CallJavaCB {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final int CFG_SETTINGS = 0, CFG_SEED = 1, CFG_DESC = 2,
+ LEFT_BUTTON = 0x0200, MIDDLE_BUTTON = 0x201, RIGHT_BUTTON = 0x202,
+ LEFT_DRAG = 0x203, MIDDLE_DRAG = 0x204, RIGHT_DRAG = 0x205,
+ LEFT_RELEASE = 0x206, CURSOR_UP = 0x209, CURSOR_DOWN = 0x20a,
+ CURSOR_LEFT = 0x20b, CURSOR_RIGHT = 0x20c, MOD_CTRL = 0x1000,
+ MOD_SHFT = 0x2000, MOD_NUM_KEYPAD = 0x4000, ALIGN_VCENTRE = 0x100,
+ ALIGN_HCENTRE = 0x001, ALIGN_HRIGHT = 0x002, C_STRING = 0,
+ C_CHOICES = 1, C_BOOLEAN = 2;
+
+ private JFrame mainWindow;
+
+ private JMenu typeMenu;
+ private JMenuItem solveCommand;
+ private Color[] colors;
+ private JLabel statusBar;
+ private PuzzlePanel pp;
+ private Runtime runtime;
+ private String[] puzzle_args;
+ private Graphics2D gg;
+ private Timer timer;
+ private int xarg1, xarg2, xarg3;
+ private int[] xPoints, yPoints;
+ private BufferedImage[] blitters = new BufferedImage[512];
+ private ConfigDialog dlg;
+
+ static {
+ try {
+ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ }
+
+ public void init() {
+ try {
+ Container cp = getContentPane();
+ cp.setLayout(new BorderLayout());
+ runtime = (Runtime) Class.forName("PuzzleEngine").newInstance();
+ runtime.setCallJavaCB(this);
+ JMenuBar menubar = new JMenuBar();
+ JMenu jm;
+ menubar.add(jm = new JMenu("Game"));
+ addMenuItemWithKey(jm, "New", 'n');
+ addMenuItemCallback(jm, "Restart", "jcallback_restart_event");
+ addMenuItemCallback(jm, "Specific...", "jcallback_config_event", CFG_DESC);
+ addMenuItemCallback(jm, "Random Seed...", "jcallback_config_event", CFG_SEED);
+ jm.addSeparator();
+ addMenuItemWithKey(jm, "Undo", 'u');
+ addMenuItemWithKey(jm, "Redo", 'r');
+ jm.addSeparator();
+ solveCommand = addMenuItemCallback(jm, "Solve", "jcallback_solve_event");
+ solveCommand.setEnabled(false);
+ if (mainWindow != null) {
+ jm.addSeparator();
+ addMenuItemWithKey(jm, "Exit", 'q');
+ }
+ menubar.add(typeMenu = new JMenu("Type"));
+ typeMenu.setVisible(false);
+ menubar.add(jm = new JMenu("Help"));
+ addMenuItemCallback(jm, "About", "jcallback_about_event");
+ setJMenuBar(menubar);
+ cp.add(pp = new PuzzlePanel(), BorderLayout.CENTER);
+ pp.addKeyListener(new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ int key = -1;
+ int shift = e.isShiftDown() ? MOD_SHFT : 0;
+ int ctrl = e.isControlDown() ? MOD_CTRL : 0;
+ switch (e.getKeyCode()) {
+ case KeyEvent.VK_LEFT:
+ case KeyEvent.VK_KP_LEFT:
+ key = shift | ctrl | CURSOR_LEFT;
+ break;
+ case KeyEvent.VK_RIGHT:
+ case KeyEvent.VK_KP_RIGHT:
+ key = shift | ctrl | CURSOR_RIGHT;
+ break;
+ case KeyEvent.VK_UP:
+ case KeyEvent.VK_KP_UP:
+ key = shift | ctrl | CURSOR_UP;
+ break;
+ case KeyEvent.VK_DOWN:
+ case KeyEvent.VK_KP_DOWN:
+ key = shift | ctrl | CURSOR_DOWN;
+ break;
+ case KeyEvent.VK_PAGE_UP:
+ key = shift | ctrl | MOD_NUM_KEYPAD | '9';
+ break;
+ case KeyEvent.VK_PAGE_DOWN:
+ key = shift | ctrl | MOD_NUM_KEYPAD | '3';
+ break;
+ case KeyEvent.VK_HOME:
+ key = shift | ctrl | MOD_NUM_KEYPAD | '7';
+ break;
+ case KeyEvent.VK_END:
+ key = shift | ctrl | MOD_NUM_KEYPAD | '1';
+ break;
+ default:
+ if (e.getKeyCode() >= KeyEvent.VK_NUMPAD0 && e.getKeyCode() <=KeyEvent.VK_NUMPAD9) {
+ key = MOD_NUM_KEYPAD | (e.getKeyCode() - KeyEvent.VK_NUMPAD0+'0');
+ }
+ break;
+ }
+ if (key != -1) {
+ runtimeCall("jcallback_key_event", new int[] {0, 0, key});
+ }
+ }
+ public void keyTyped(KeyEvent e) {
+ runtimeCall("jcallback_key_event", new int[] {0, 0, e.getKeyChar()});
+ }
+ });
+ pp.addMouseListener(new MouseAdapter() {
+ public void mouseReleased(MouseEvent e) {
+ mousePressedReleased(e, true);
+ }
+ public void mousePressed(MouseEvent e) {
+ pp.requestFocus();
+ mousePressedReleased(e, false);
+ }
+ private void mousePressedReleased(MouseEvent e, boolean released) {
+ int button;
+ if ((e.getModifiers() & (InputEvent.BUTTON2_MASK | InputEvent.SHIFT_MASK)) != 0)
+ button = MIDDLE_BUTTON;
+ else if ((e.getModifiers() & (InputEvent.BUTTON3_MASK | InputEvent.ALT_MASK)) != 0)
+ button = RIGHT_BUTTON;
+ else if ((e.getModifiers() & (InputEvent.BUTTON1_MASK)) != 0)
+ button = LEFT_BUTTON;
+ else
+ return;
+ if (released)
+ button += LEFT_RELEASE - LEFT_BUTTON;
+ runtimeCall("jcallback_key_event", new int[] {e.getX(), e.getY(), button});
+ }
+ });
+ pp.addMouseMotionListener(new MouseMotionAdapter() {
+ public void mouseDragged(MouseEvent e) {
+ int button;
+ if ((e.getModifiers() & (InputEvent.BUTTON2_MASK | InputEvent.SHIFT_MASK)) != 0)
+ button = MIDDLE_DRAG;
+ else if ((e.getModifiers() & (InputEvent.BUTTON3_MASK | InputEvent.ALT_MASK)) != 0)
+ button = RIGHT_DRAG;
+ else
+ button = LEFT_DRAG;
+ runtimeCall("jcallback_key_event", new int[] {e.getX(), e.getY(), button});
+ }
+ });
+ pp.addComponentListener(new ComponentAdapter() {
+ public void componentResized(ComponentEvent e) {
+ handleResized();
+ }
+ });
+ pp.setFocusable(true);
+ pp.requestFocus();
+ timer = new Timer(20, new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ runtimeCall("jcallback_timer_func", new int[0]);
+ }
+ });
+ String gameid;
+ try {
+ gameid = getParameter("game_id");
+ } catch (java.lang.NullPointerException ex) {
+ gameid = null;
+ }
+ if (gameid == null) {
+ puzzle_args = null;
+ } else {
+ puzzle_args = new String[2];
+ puzzle_args[0] = "puzzle";
+ puzzle_args[1] = gameid;
+ }
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ runtime.start(puzzle_args);
+ runtime.execute();
+ }
+ });
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ }
+
+ public void destroy() {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ runtime.execute();
+ if (mainWindow != null) {
+ mainWindow.dispose();
+ System.exit(0);
+ }
+ }
+ });
+ }
+
+ protected void handleResized() {
+ pp.createBackBuffer(pp.getWidth(), pp.getHeight(), colors[0]);
+ runtimeCall("jcallback_resize", new int[] {pp.getWidth(), pp.getHeight()});
+ }
+
+ private void addMenuItemWithKey(JMenu jm, String name, int key) {
+ addMenuItemCallback(jm, name, "jcallback_menu_key_event", key);
+ }
+
+ private JMenuItem addMenuItemCallback(JMenu jm, String name, final String callback, final int arg) {
+ return addMenuItemCallback(jm, name, callback, new int[] {arg});
+ }
+
+ private JMenuItem addMenuItemCallback(JMenu jm, String name, final String callback) {
+ return addMenuItemCallback(jm, name, callback, new int[0]);
+ }
+
+ private JMenuItem addMenuItemCallback(JMenu jm, String name, final String callback, final int[] args) {
+ JMenuItem jmi;
+ if (jm == typeMenu)
+ typeMenu.add(jmi = new JCheckBoxMenuItem(name));
+ else
+ jm.add(jmi = new JMenuItem(name));
+ jmi.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ runtimeCall(callback, args);
+ }
+ });
+ return jmi;
+ }
+
+ protected void runtimeCall(String func, int[] args) {
+ if (runtimeCallWithResult(func, args) == 42 && mainWindow != null) {
+ destroy();
+ }
+ }
+
+ protected int runtimeCallWithResult(String func, int[] args) {
+ try {
+ return runtime.call(func, args);
+ } catch (Runtime.CallException ex) {
+ ex.printStackTrace();
+ return 42;
+ }
+ }
+
+ private void buildConfigureMenuItem() {
+ if (typeMenu.isVisible()) {
+ typeMenu.addSeparator();
+ } else {
+ typeMenu.setVisible(true);
+ }
+ addMenuItemCallback(typeMenu, "Custom...", "jcallback_config_event", CFG_SETTINGS);
+ }
+
+ private void addTypeItem(String name, final int ptrGameParams) {
+ typeMenu.setVisible(true);
+ addMenuItemCallback(typeMenu, name, "jcallback_preset_event", ptrGameParams);
+ }
+
+ public int call(int cmd, int arg1, int arg2, int arg3) {
+ try {
+ switch(cmd) {
+ case 0: // initialize
+ if (mainWindow != null) mainWindow.setTitle(runtime.cstring(arg1));
+ if ((arg2 & 1) != 0) buildConfigureMenuItem();
+ if ((arg2 & 2) != 0) addStatusBar();
+ if ((arg2 & 4) != 0) solveCommand.setEnabled(true);
+ colors = new Color[arg3];
+ return 0;
+ case 1: // Type menu item
+ addTypeItem(runtime.cstring(arg1), arg2);
+ return 0;
+ case 2: // MessageBox
+ JOptionPane.showMessageDialog(this, runtime.cstring(arg2), runtime.cstring(arg1), arg3 == 0 ? JOptionPane.INFORMATION_MESSAGE : JOptionPane.ERROR_MESSAGE);
+ return 0;
+ case 3: // Resize
+ pp.setPreferredSize(new Dimension(arg1, arg2));
+ if (mainWindow != null) mainWindow.pack();
+ handleResized();
+ if (mainWindow != null) mainWindow.setVisible(true);
+ return 0;
+ case 4: // drawing tasks
+ switch(arg1) {
+ case 0:
+ String text = runtime.cstring(arg2);
+ if (text.equals("")) text = " ";
+ statusBar.setText(text);
+ break;
+ case 1:
+ gg = pp.backBuffer.createGraphics();
+ if (arg2 != 0 || arg3 != 0 ||
+ arg2 + xarg2 != getWidth() ||
+ arg3 + xarg3 != getHeight()) {
+ int left = arg2, right = arg2 + xarg2;
+ int top = arg3, bottom = arg3 + xarg3;
+ int width = getWidth(), height = getHeight();
+ gg.setColor(colors != null ? colors[0] : Color.black);
+ gg.fillRect(0, 0, left, height);
+ gg.fillRect(right, 0, width-right, height);
+ gg.fillRect(0, 0, width, top);
+ gg.fillRect(0, bottom, width, height-bottom);
+ gg.setClip(left, top, right-left, bottom-top);
+ }
+ break;
+ case 2: gg.dispose(); pp.repaint(); break;
+ case 3: gg.setClip(arg2, arg3, xarg1, xarg2); break;
+ case 4:
+ if (arg2 == 0 && arg3 == 0) {
+ gg.setClip(0, 0, getWidth(), getHeight());
+ } else {
+ gg.setClip(arg2, arg3, getWidth()-2*arg2, getHeight()-2*arg3);
+ }
+ break;
+ case 5:
+ gg.setColor(colors[xarg3]);
+ gg.fillRect(arg2, arg3, xarg1, xarg2);
+ break;
+ case 6:
+ gg.setColor(colors[xarg3]);
+ gg.drawLine(arg2, arg3, xarg1, xarg2);
+ break;
+ case 7:
+ xPoints = new int[arg2];
+ yPoints = new int[arg2];
+ break;
+ case 8:
+ if (arg3 != -1) {
+ gg.setColor(colors[arg3]);
+ gg.fillPolygon(xPoints, yPoints, xPoints.length);
+ }
+ gg.setColor(colors[arg2]);
+ gg.drawPolygon(xPoints, yPoints, xPoints.length);
+ break;
+ case 9:
+ if (arg3 != -1) {
+ gg.setColor(colors[arg3]);
+ gg.fillOval(xarg1-xarg3, xarg2-xarg3, xarg3*2, xarg3*2);
+ }
+ gg.setColor(colors[arg2]);
+ gg.drawOval(xarg1-xarg3, xarg2-xarg3, xarg3*2, xarg3*2);
+ break;
+ case 10:
+ for(int i=0; i<blitters.length; i++) {
+ if (blitters[i] == null) {
+ blitters[i] = new BufferedImage(arg2, arg3, BufferedImage.TYPE_3BYTE_BGR);
+ return i;
+ }
+ }
+ throw new RuntimeException("No free blitter found!");
+ case 11: blitters[arg2] = null; break;
+ case 12:
+ timer.start(); break;
+ case 13:
+ timer.stop(); break;
+ }
+ return 0;
+ case 5: // more arguments
+ xarg1 = arg1;
+ xarg2 = arg2;
+ xarg3 = arg3;
+ return 0;
+ case 6: // polygon vertex
+ xPoints[arg1]=arg2;
+ yPoints[arg1]=arg3;
+ return 0;
+ case 7: // string
+ gg.setColor(colors[arg2]);
+ {
+ String text = runtime.utfstring(arg3);
+ Font ft = new Font((xarg3 & 0x10) != 0 ? "Monospaced" : "Dialog",
+ Font.PLAIN, 100);
+ int height100 = this.getFontMetrics(ft).getHeight();
+ ft = ft.deriveFont(arg1 * 100 / (float)height100);
+ FontMetrics fm = this.getFontMetrics(ft);
+ int asc = fm.getAscent(), desc = fm.getDescent();
+ if ((xarg3 & ALIGN_VCENTRE) != 0)
+ xarg2 += asc - (asc+desc)/2;
+ int wid = fm.stringWidth(text);
+ if ((xarg3 & ALIGN_HCENTRE) != 0)
+ xarg1 -= wid / 2;
+ else if ((xarg3 & ALIGN_HRIGHT) != 0)
+ xarg1 -= wid;
+ gg.setFont(ft);
+ gg.drawString(text, xarg1, xarg2);
+ }
+ return 0;
+ case 8: // blitter_save
+ Graphics g2 = blitters[arg1].createGraphics();
+ g2.drawImage(pp.backBuffer, 0, 0, blitters[arg1].getWidth(), blitters[arg1].getHeight(),
+ arg2, arg3, arg2 + blitters[arg1].getWidth(), arg3 + blitters[arg1].getHeight(), this);
+ g2.dispose();
+ return 0;
+ case 9: // blitter_load
+ gg.drawImage(blitters[arg1], arg2, arg3, this);
+ return 0;
+ case 10: // dialog_init
+ dlg= new ConfigDialog(this, runtime.cstring(arg1));
+ return 0;
+ case 11: // dialog_add_control
+ {
+ int sval_ptr = arg1;
+ int ival = arg2;
+ int ptr = xarg1;
+ int type=xarg2;
+ String name = runtime.cstring(xarg3);
+ switch(type) {
+ case C_STRING:
+ dlg.addTextBox(ptr, name, runtime.cstring(sval_ptr));
+ break;
+ case C_BOOLEAN:
+ dlg.addCheckBox(ptr, name, ival != 0);
+ break;
+ case C_CHOICES:
+ dlg.addComboBox(ptr, name, runtime.cstring(sval_ptr), ival);
+ }
+ }
+ return 0;
+ case 12:
+ dlg.finish();
+ dlg = null;
+ return 0;
+ case 13: // tick a menu item
+ if (arg1 < 0) arg1 = typeMenu.getItemCount() - 1;
+ for (int i = 0; i < typeMenu.getItemCount(); i++) {
+ if (typeMenu.getMenuComponent(i) instanceof JCheckBoxMenuItem) {
+ ((JCheckBoxMenuItem)typeMenu.getMenuComponent(i)).setSelected(arg1 == i);
+ }
+ }
+ return 0;
+ default:
+ if (cmd >= 1024 && cmd < 2048) { // palette
+ colors[cmd-1024] = new Color(arg1, arg2, arg3);
+ }
+ if (cmd == 1024) {
+ pp.setBackground(colors[0]);
+ if (statusBar != null) statusBar.setBackground(colors[0]);
+ this.setBackground(colors[0]);
+ }
+ return 0;
+ }
+ } catch (Throwable ex) {
+ ex.printStackTrace();
+ System.exit(-1);
+ return 0;
+ }
+ }
+
+ private void addStatusBar() {
+ statusBar = new JLabel("test");
+ statusBar.setBorder(new BevelBorder(BevelBorder.LOWERED));
+ getContentPane().add(BorderLayout.SOUTH,statusBar);
+ }
+
+ // Standalone runner
+ public static void main(String[] args) {
+ final PuzzleApplet a = new PuzzleApplet();
+ JFrame jf = new JFrame("Loading...");
+ jf.getContentPane().setLayout(new BorderLayout());
+ jf.getContentPane().add(a, BorderLayout.CENTER);
+ a.mainWindow=jf;
+ a.init();
+ a.start();
+ jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ jf.addWindowListener(new WindowAdapter() {
+ public void windowClosing(WindowEvent e) {
+ a.stop();
+ a.destroy();
+ }
+ });
+ jf.setVisible(true);
+ }
+
+ public static class PuzzlePanel extends JPanel {
+
+ private static final long serialVersionUID = 1L;
+ protected BufferedImage backBuffer;
+
+ public PuzzlePanel() {
+ setPreferredSize(new Dimension(100,100));
+ createBackBuffer(100,100, Color.black);
+ }
+
+ public void createBackBuffer(int w, int h, Color bg) {
+ if (w > 0 && h > 0) {
+ backBuffer = new BufferedImage(w,h, BufferedImage.TYPE_3BYTE_BGR);
+ Graphics g = backBuffer.createGraphics();
+ g.setColor(bg);
+ g.fillRect(0, 0, w, h);
+ g.dispose();
+ }
+ }
+
+ protected void paintComponent(Graphics g) {
+ g.drawImage(backBuffer, 0, 0, this);
+ }
+ }
+
+ public static class ConfigComponent {
+ public int type;
+ public int configItemPointer;
+ public JComponent component;
+
+ public ConfigComponent(int type, int configItemPointer, JComponent component) {
+ this.type = type;
+ this.configItemPointer = configItemPointer;
+ this.component = component;
+ }
+ }
+
+ public class ConfigDialog extends JDialog {
+
+ private GridBagConstraints gbcLeft = new GridBagConstraints(
+ GridBagConstraints.RELATIVE, GridBagConstraints.RELATIVE, 1, 1,
+ 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE,
+ new Insets(0, 0, 0, 0), 0, 0);
+ private GridBagConstraints gbcRight = new GridBagConstraints(
+ GridBagConstraints.RELATIVE, GridBagConstraints.RELATIVE,
+ GridBagConstraints.REMAINDER, 1, 1.0, 0,
+ GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,
+ new Insets(5, 5, 5, 5), 0, 0);
+ private GridBagConstraints gbcBottom = new GridBagConstraints(
+ GridBagConstraints.RELATIVE, GridBagConstraints.RELATIVE,
+ GridBagConstraints.REMAINDER, GridBagConstraints.REMAINDER,
+ 1.0, 1.0, GridBagConstraints.CENTER,
+ GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0);
+
+ private static final long serialVersionUID = 1L;
+ private List components = new ArrayList();
+
+ public ConfigDialog(JApplet parent, String title) {
+ super(JOptionPane.getFrameForComponent(parent), title, true);
+ getContentPane().setLayout(new GridBagLayout());
+ }
+
+ public void addTextBox(int ptr, String name, String value) {
+ getContentPane().add(new JLabel(name), gbcLeft);
+ JComponent c = new JTextField(value, 25);
+ getContentPane().add(c, gbcRight);
+ components.add(new ConfigComponent(C_STRING, ptr, c));
+ }
+
+
+ public void addCheckBox(int ptr, String name, boolean selected) {
+ JComponent c = new JCheckBox(name, selected);
+ getContentPane().add(c, gbcRight);
+ components.add(new ConfigComponent(C_BOOLEAN, ptr, c));
+ }
+
+ public void addComboBox(int ptr, String name, String values, int selected) {
+ getContentPane().add(new JLabel(name), gbcLeft);
+ StringTokenizer st = new StringTokenizer(values.substring(1), values.substring(0,1));
+ JComboBox c = new JComboBox();
+ c.setEditable(false);
+ while(st.hasMoreTokens())
+ c.addItem(st.nextToken());
+ c.setSelectedIndex(selected);
+ getContentPane().add(c, gbcRight);
+ components.add(new ConfigComponent(C_CHOICES, ptr, c));
+ }
+
+ public void finish() {
+ JPanel buttons = new JPanel(new GridLayout(1, 2, 5, 5));
+ getContentPane().add(buttons, gbcBottom);
+ JButton b;
+ buttons.add(b=new JButton("OK"));
+ b.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ save();
+ dispose();
+ }
+ });
+ getRootPane().setDefaultButton(b);
+ buttons.add(b=new JButton("Cancel"));
+ b.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ dispose();
+ }
+ });
+ setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+ pack();
+ setLocationRelativeTo(null);
+ setVisible(true);
+ }
+ private void save() {
+ for (int i = 0; i < components.size(); i++) {
+ ConfigComponent cc = (ConfigComponent) components.get(i);
+ switch(cc.type) {
+ case C_STRING:
+ JTextField jtf = (JTextField)cc.component;
+ runtimeCall("jcallback_config_set_string", new int[] {cc.configItemPointer, runtime.strdup(jtf.getText())});
+ break;
+ case C_BOOLEAN:
+ JCheckBox jcb = (JCheckBox)cc.component;
+ runtimeCall("jcallback_config_set_boolean", new int[] {cc.configItemPointer, jcb.isSelected()?1:0});
+ break;
+ case C_CHOICES:
+ JComboBox jcm = (JComboBox)cc.component;
+ runtimeCall("jcallback_config_set_choice", new int[] {cc.configItemPointer, jcm.getSelectedIndex()});
+ break;
+ }
+ }
+ runtimeCall("jcallback_config_ok", new int[0]);
+ }
+ }
+}
diff --git a/apps/plugins/puzzles/README b/apps/plugins/puzzles/README
new file mode 100644
index 0000000000..890db56771
--- /dev/null
+++ b/apps/plugins/puzzles/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/apps/plugins/puzzles/Recipe b/apps/plugins/puzzles/Recipe
new file mode 100644
index 0000000000..ba8317f51a
--- /dev/null
+++ b/apps/plugins/puzzles/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/apps/plugins/puzzles/SOURCES b/apps/plugins/puzzles/SOURCES
new file mode 100644
index 0000000000..9c41a00358
--- /dev/null
+++ b/apps/plugins/puzzles/SOURCES
@@ -0,0 +1,26 @@
+rockbox.c
+rbwrappers.c
+
+combi.c
+divvy.c
+drawing.c
+dsf.c
+findloop.c
+grid.c
+latin.c
+laydomino.c
+loopgen.c
+malloc.c
+maxflow.c
+midend.c
+misc.c
+penrose.c
+printing.c
+random.c
+tdq.c
+tree234.c
+version.c
+
+#ifdef COMBINED
+list.c
+#endif
diff --git a/apps/plugins/puzzles/benchmark.pl b/apps/plugins/puzzles/benchmark.pl
new file mode 100755
index 0000000000..98763859e8
--- /dev/null
+++ b/apps/plugins/puzzles/benchmark.pl
@@ -0,0 +1,197 @@
+#!/usr/bin/perl
+
+# Process the raw output from benchmark.sh into Javascript-ified HTML.
+
+use strict;
+use warnings;
+
+my @presets = ();
+my %presets = ();
+my $maxval = 0;
+
+while (<>) {
+ chomp;
+ if (/^(.*)(#.*): ([\d\.]+)$/) {
+ push @presets, $1 unless defined $presets{$1};
+ push @{$presets{$1}}, $3;
+ $maxval = $3 if $maxval < $3;
+ }
+}
+
+print <<EOF;
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=ASCII" />
+<title>Puzzle generation-time benchmarks</title>
+<script type="text/javascript">
+//<![CDATA[
+function choose_scale_ticks(scale) {
+ var nscale = 1, j = 0, factors = [2,2.5,2];
+ while (scale / nscale > 20) {
+ nscale *= factors[j];
+ j = (j+1) % factors.length;
+ }
+ return nscale;
+}
+function initPlots() {
+ var canvases = document.getElementsByTagName('canvas');
+ for (var i = 0; i < canvases.length; i++) {
+ var canvas = canvases[i];
+ var scale = eval(canvas.getAttribute("data-scale"));
+ var add = 20.5, mult = (canvas.width - 2*add) / scale;
+ var data = eval(canvas.getAttribute("data-points"));
+ var ctx = canvas.getContext('2d');
+ ctx.lineWidth = '1px';
+ ctx.lineCap = 'round';
+ ctx.lineJoin = 'round';
+ ctx.strokeStyle = ctx.fillStyle = '#000000';
+ if (data === "scale") {
+ // Draw scale.
+ ctx.font = "16px sans-serif";
+ ctx.textAlign = "center";
+ ctx.textBaseline = "alphabetic";
+ var nscale = choose_scale_ticks(scale);
+ for (var x = 0; x <= scale; x += nscale) {
+ ctx.beginPath();
+ ctx.moveTo(add+mult*x, canvas.height);
+ ctx.lineTo(add+mult*x, canvas.height - 3);
+ ctx.stroke();
+ ctx.fillText(x + "s", add+mult*x, canvas.height - 6);
+ }
+ } else {
+ // Draw a box plot.
+ function quantile(x) {
+ var n = (data.length * x) | 0;
+ return (data[n-1] + data[n]) / 2;
+ }
+
+ var q1 = quantile(0.25), q2 = quantile(0.5), q3 = quantile(0.75);
+ var iqr = q3 - q1;
+ var top = 0.5, bot = canvas.height - 1.5, mid = (top+bot)/2;
+ var wlo = null, whi = null; // whisker ends
+
+ ctx.strokeStyle = '#bbbbbb';
+ var nscale = choose_scale_ticks(scale);
+ for (var x = 0; x <= scale; x += nscale) {
+ ctx.beginPath();
+ ctx.moveTo(add+mult*x, 0);
+ ctx.lineTo(add+mult*x, canvas.height);
+ ctx.stroke();
+ }
+ ctx.strokeStyle = '#000000';
+
+ for (var j in data) {
+ var x = data[j];
+ if (x >= q1 - 1.5 * iqr && x <= q3 + 1.5 * iqr) {
+ if (wlo === null || wlo > x)
+ wlo = x;
+ if (whi === null || whi < x)
+ whi = x;
+ } else {
+ ctx.beginPath();
+ ctx.arc(add+mult*x, mid, 2, 0, 2*Math.PI);
+ ctx.stroke();
+ if (x >= q1 - 3 * iqr && x <= q3 + 3 * iqr)
+ ctx.fill();
+ }
+ }
+
+ ctx.beginPath();
+
+ // Box
+ ctx.moveTo(add+mult*q1, top);
+ ctx.lineTo(add+mult*q3, top);
+ ctx.lineTo(add+mult*q3, bot);
+ ctx.lineTo(add+mult*q1, bot);
+ ctx.closePath();
+
+ // Line at median
+ ctx.moveTo(add+mult*q2, top);
+ ctx.lineTo(add+mult*q2, bot);
+
+ // Lower whisker
+ ctx.moveTo(add+mult*q1, mid);
+ ctx.lineTo(add+mult*wlo, mid);
+ ctx.moveTo(add+mult*wlo, top);
+ ctx.lineTo(add+mult*wlo, bot);
+
+ // Upper whisker
+ ctx.moveTo(add+mult*q3, mid);
+ ctx.lineTo(add+mult*whi, mid);
+ ctx.moveTo(add+mult*whi, top);
+ ctx.lineTo(add+mult*whi, bot);
+
+ ctx.stroke();
+ }
+ }
+ document.getElementById('sort_orig').onclick = function() {
+ sort(function(e) {
+ return parseFloat(e.getAttribute("data-index"));
+ });
+ };
+ document.getElementById('sort_median').onclick = function() {
+ sort(function(e) {
+ return -parseFloat(e.getAttribute("data-median"));
+ });
+ };
+ document.getElementById('sort_mean').onclick = function() {
+ sort(function(e) {
+ return -parseFloat(e.getAttribute("data-mean"));
+ });
+ };
+}
+function sort(keyfn) {
+ var rows = document.getElementsByTagName("tr");
+ var trs = [];
+ for (var i = 0; i < rows.length; i++)
+ trs.push(rows[i]);
+ trs.sort(function(a,b) {
+ var akey = keyfn(a);
+ var bkey = keyfn(b);
+ return akey < bkey ? -1 : akey > bkey ? +1 : 0;
+ });
+ var parent = trs[0].parentElement;
+ for (var i = 0; i < trs.length; i++)
+ parent.removeChild(trs[i]);
+ for (var i = 0; i < trs.length; i++)
+ parent.appendChild(trs[i]);
+}
+//]]>
+</script>
+</head>
+<body onLoad="initPlots();">
+<h1 align=center>Puzzle generation-time benchmarks</h1>
+<p>Sort order:
+<button id="sort_orig">Original</button>
+<button id="sort_median">Median</button>
+<button id="sort_mean">Mean</button>
+<table>
+<tr><th>Preset</th><td><canvas width=700 height=30 data-points='"scale"' data-scale="$maxval"></td></tr>
+EOF
+
+my $index = 0;
+for my $preset (@presets) {
+ my @data = sort { $a <=> $b } @{$presets{$preset}};
+ my $median = ($#data % 2 ?
+ ($data[($#data-1)/2]+$data[($#data+1)/2])/2 :
+ $data[$#data/2]);
+ my $mean = 0; map { $mean += $_ } @data; $mean /= @data;
+ print "<tr data-index=\"$index\" data-mean=\"$mean\" data-median=\"$median\"><td>", &escape($preset), "</td><td><canvas width=700 height=15 data-points=\"[";
+ print join ",", @data;
+ print "]\" data-scale=\"$maxval\"></td></tr>\n";
+ $index++;
+}
+
+print <<EOF;
+</body>
+</html>
+EOF
+
+sub escape {
+ my ($text) = @_;
+ $text =~ s/&/&amp;/g;
+ $text =~ s/</&lt;/g;
+ $text =~ s/>/&gt;/g;
+ return $text;
+}
diff --git a/apps/plugins/puzzles/benchmark.sh b/apps/plugins/puzzles/benchmark.sh
new file mode 100755
index 0000000000..b3af27765e
--- /dev/null
+++ b/apps/plugins/puzzles/benchmark.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+# Run every puzzle in benchmarking mode, and generate a file of raw
+# data that benchmark.pl will format into a web page.
+
+# If any arguments are provided, use those as the list of games to
+# benchmark. Otherwise, read the full list from gamedesc.txt.
+if test $# = 0; then
+ set -- $(cut -f1 -d: < gamedesc.txt)
+fi
+
+failures=false
+
+for game in "$@"; do
+ # Use 'env -i' to suppress any environment variables that might
+ # change the preset list for a puzzle (e.g. user-defined extras)
+ presets=$(env -i ./$game --list-presets | cut -f1 -d' ')
+ for preset in $presets; do
+ if ! env -i ./$game --test-solve --time-generation \
+ --generate 100 $preset;
+ then
+ echo "${game} ${preset} failed to generate" >&2
+ fi
+ done
+done
+
+if $failures; then exit 1; fi
diff --git a/apps/plugins/puzzles/blackbox.R b/apps/plugins/puzzles/blackbox.R
new file mode 100644
index 0000000000..116225206d
--- /dev/null
+++ b/apps/plugins/puzzles/blackbox.R
@@ -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/apps/plugins/puzzles/blackbox.c b/apps/plugins/puzzles/blackbox.c
new file mode 100644
index 0000000000..a4875e49d7
--- /dev/null
+++ b/apps/plugins/puzzles/blackbox.c
@@ -0,0 +1,1543 @@
+/*
+ * blackbox.c: implementation of 'Black Box'.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "rbassert.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/apps/plugins/puzzles/bridges.R b/apps/plugins/puzzles/bridges.R
new file mode 100644
index 0000000000..75df309152
--- /dev/null
+++ b/apps/plugins/puzzles/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/apps/plugins/puzzles/bridges.c b/apps/plugins/puzzles/bridges.c
new file mode 100644
index 0000000000..05a9b16823
--- /dev/null
+++ b/apps/plugins/puzzles/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 "rbassert.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/apps/plugins/puzzles/chm.but b/apps/plugins/puzzles/chm.but
new file mode 100644
index 0000000000..e0237044e4
--- /dev/null
+++ b/apps/plugins/puzzles/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/apps/plugins/puzzles/chm.css b/apps/plugins/puzzles/chm.css
new file mode 100644
index 0000000000..d8c316bfc6
--- /dev/null
+++ b/apps/plugins/puzzles/chm.css
@@ -0,0 +1,7 @@
+/* Stylesheet for a Windows .CHM help file */
+
+body { font-size: 75%; font-family: Verdana, Arial, Helvetica, Sans-Serif; }
+
+h1 { font-weight: bold; font-size: 150%; }
+h2 { font-weight: bold; font-size: 130%; }
+h3 { font-weight: bold; font-size: 120%; }
diff --git a/apps/plugins/puzzles/combi.c b/apps/plugins/puzzles/combi.c
new file mode 100644
index 0000000000..d39e298405
--- /dev/null
+++ b/apps/plugins/puzzles/combi.c
@@ -0,0 +1,110 @@
+#include "rbassert.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/apps/plugins/puzzles/configure.ac b/apps/plugins/puzzles/configure.ac
new file mode 100644
index 0000000000..3a38c95602
--- /dev/null
+++ b/apps/plugins/puzzles/configure.ac
@@ -0,0 +1,85 @@
+dnl Configure script for the Unix GTK build of puzzles.
+
+AC_INIT([puzzles], [6.66], [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/apps/plugins/puzzles/cube.R b/apps/plugins/puzzles/cube.R
new file mode 100644
index 0000000000..85b081ec76
--- /dev/null
+++ b/apps/plugins/puzzles/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/apps/plugins/puzzles/cube.c b/apps/plugins/puzzles/cube.c
new file mode 100644
index 0000000000..5a09648226
--- /dev/null
+++ b/apps/plugins/puzzles/cube.c
@@ -0,0 +1,1774 @@
+/*
+ * cube.c: Cube game.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "rbassert.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);
+ //float theight = 0.8660254037844386467;
+
+ 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 */
+};
diff --git a/apps/plugins/puzzles/desktop.pl b/apps/plugins/puzzles/desktop.pl
new file mode 100755
index 0000000000..204c0ce262
--- /dev/null
+++ b/apps/plugins/puzzles/desktop.pl
@@ -0,0 +1,52 @@
+#!/usr/bin/perl
+
+# Make .desktop files for the puzzles.
+#
+# At present, this script is intended for developer usage: if you're
+# working on the puzzles and want to play your bleeding-edge locally
+# modified and compiled versions, run this script and it will create a
+# collection of desktop files in ~/.local/share/applications where
+# XFCE can pick them up and add them to its main menu. (Be sure to run
+# 'xfdesktop --reload' after running this.)
+#
+# (If you don't use XFCE, patches to support other desktop
+# environments are welcome :-)
+
+use strict;
+use warnings;
+use Cwd 'abs_path';
+
+die "usage: desktop.pl [<outdir> [<bindir> <icondir>]]\n"
+ unless @ARGV == 0 or @ARGV == 1 or @ARGV == 3;
+
+my ($outdir, $bindir, $icondir) = @ARGV;
+$outdir = $ENV{'HOME'}."/.local/share/applications" unless defined $outdir;
+$bindir = "." unless defined $bindir;
+$icondir = "./icons" unless defined $icondir;
+$bindir = abs_path($bindir);
+$icondir = abs_path($icondir);
+
+open my $desc, "<", "gamedesc.txt"
+ or die "gamedesc.txt: open: $!\n";
+
+while (<$desc>) {
+ chomp;
+ my ($id, $win, $displayname, $description, $summary) = split /:/, $_;
+
+ open my $desktop, ">", "$outdir/$id.desktop"
+ or die "$outdir/$id.desktop: open: $!\n";
+
+ print $desktop "[Desktop Entry]\n";
+ print $desktop "Version=1.0\n";
+ print $desktop "Type=Application\n";
+ print $desktop "Name=$displayname\n";
+ print $desktop "Comment=$description\n";
+ print $desktop "Exec=$bindir/$id\n";
+ print $desktop "Icon=$icondir/$id-48d24.png\n";
+ print $desktop "StartupNotify=false\n";
+ print $desktop "Categories=Game;\n";
+ print $desktop "Terminal=false\n";
+
+ close $desktop
+ or die "$outdir/$id.desktop: close: $!\n";
+}
diff --git a/apps/plugins/puzzles/devel.but b/apps/plugins/puzzles/devel.but
new file mode 100644
index 0000000000..9befcadcb7
--- /dev/null
+++ b/apps/plugins/puzzles/devel.but
@@ -0,0 +1,4777 @@
+\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.
diff --git a/apps/plugins/puzzles/divvy.c b/apps/plugins/puzzles/divvy.c
new file mode 100644
index 0000000000..dfd409c9e0
--- /dev/null
+++ b/apps/plugins/puzzles/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 "rbassert.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/apps/plugins/puzzles/dominosa.R b/apps/plugins/puzzles/dominosa.R
new file mode 100644
index 0000000000..99218366e6
--- /dev/null
+++ b/apps/plugins/puzzles/dominosa.R
@@ -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/apps/plugins/puzzles/dominosa.c b/apps/plugins/puzzles/dominosa.c
new file mode 100644
index 0000000000..a2dd69ba86
--- /dev/null
+++ b/apps/plugins/puzzles/dominosa.c
@@ -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 "rbassert.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